diff --git a/.gqlgenc.yml b/.gqlgenc.yml new file mode 100644 index 000000000..9d10817b5 --- /dev/null +++ b/.gqlgenc.yml @@ -0,0 +1,13 @@ +model: + filename: ./pkg/scraper/stashbox/graphql/generated_models.go +client: + filename: ./pkg/scraper/stashbox/graphql/generated_client.go +models: + Date: + model: github.com/99designs/gqlgen/graphql.String +endpoint: + # This points to stashdb.org currently, but can be directed at any stash-box + # instance. It is used for generation only. + url: https://stashdb.org/graphql +query: + - "./graphql/stash-box/*.graphql" diff --git a/.travis.yml b/.travis.yml index 02505c726..f4839e07d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ git: depth: false language: go go: -- 1.11.x +- 1.13.x services: - docker env: @@ -23,7 +23,7 @@ script: #- make lint - make fmt-check vet it after_success: -- docker pull stashapp/compiler:3 +- docker pull stashapp/compiler:4 - sh ./scripts/cross-compile.sh - 'if [ "$TRAVIS_PULL_REQUEST" != "false" ]; then sh ./scripts/upload-pull-request.sh; fi' before_deploy: @@ -32,7 +32,7 @@ before_deploy: - export RELEASE_DATE=$(date +'%Y-%m-%d %H:%M:%S %Z') - export STASH_VERSION=$(git describe --tags --exclude latest_develop) # set TRAVIS_TAG explcitly to the version so that it doesn't pick up latest_develop -- if [ "$TRAVIS_BRANCH" = "master"]; then export TRAVIS_TAG=${STASH_VERSION}; fi +- if [ "$TRAVIS_BRANCH" = "master" ]; then export TRAVIS_TAG=${STASH_VERSION}; fi deploy: # latest develop release - provider: releases diff --git a/Makefile b/Makefile index c3e46b17f..03d86daeb 100644 --- a/Makefile +++ b/Makefile @@ -41,13 +41,16 @@ endif build: pre-build $(eval LDFLAGS := $(LDFLAGS) -X 'github.com/stashapp/stash/pkg/api.version=$(STASH_VERSION)' -X 'github.com/stashapp/stash/pkg/api.buildstamp=$(BUILD_DATE)' -X 'github.com/stashapp/stash/pkg/api.githash=$(GITHASH)') - $(SET) CGO_ENABLED=1 $(SEPARATOR) go build $(OUTPUT) -mod=vendor -v -ldflags "$(LDFLAGS) $(EXTRA_LDFLAGS)" + $(SET) CGO_ENABLED=1 $(SEPARATOR) go build $(OUTPUT) -mod=vendor -v -tags "sqlite_omit_load_extension osusergo netgo" -ldflags "$(LDFLAGS) $(EXTRA_LDFLAGS)" # strips debug symbols from the release build # consider -trimpath in go build if we move to go 1.13+ build-release: EXTRA_LDFLAGS := -s -w build-release: build +build-release-static: EXTRA_LDFLAGS := -extldflags=-static -s -w +build-release-static: build + install: packr2 install @@ -60,6 +63,11 @@ generate: go generate -mod=vendor cd ui/v2.5 && yarn run gqlgen +# Regenerates stash-box client files +.PHONY: generate-stash-box-client +generate-stash-box-client: + go run -mod=vendor github.com/Yamashou/gqlgenc + # Runs gofmt -w on the project's source code, modifying any files that do not match its style. .PHONY: fmt fmt: @@ -89,6 +97,11 @@ test: it: go test -mod=vendor -tags=integration ./... +# generates test mocks +.PHONY: generate-test-mocks +generate-test-mocks: + go run -mod=vendor github.com/vektra/mockery/v2 --dir ./pkg/models --name '.*ReaderWriter' --outpkg mocks --output ./pkg/models/mocks + # installs UI dependencies. Run when first cloning repository, or if UI # dependencies have changed .PHONY: pre-ui diff --git a/docker/build/x86_64/Dockerfile b/docker/build/x86_64/Dockerfile index 54fed78f9..6eabf1105 100644 --- a/docker/build/x86_64/Dockerfile +++ b/docker/build/x86_64/Dockerfile @@ -2,10 +2,10 @@ # ie from top=level stash: # docker build -t stash/build -f docker/build/x86_64/Dockerfile . -FROM golang:1.11.13 as compiler +FROM golang:1.13.15 as compiler RUN apt-get update && apt-get install -y apt-transport-https -RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - +RUN curl -sL https://deb.nodesource.com/setup_lts.x | bash - # prevent caching of the key ADD https://dl.yarnpkg.com/debian/pubkey.gpg yarn.gpg @@ -48,7 +48,7 @@ RUN make generate RUN make ui RUN make build -FROM ubuntu:19.10 as app +FROM ubuntu:20.04 as app RUN apt-get update && apt-get -y install ca-certificates COPY --from=compiler /stash/stash /ffmpeg/ffmpeg /ffmpeg/ffprobe /usr/bin/ diff --git a/docker/ci/x86_64/Dockerfile b/docker/ci/x86_64/Dockerfile index 54d1bec4f..2cfe763fe 100644 --- a/docker/ci/x86_64/Dockerfile +++ b/docker/ci/x86_64/Dockerfile @@ -1,7 +1,7 @@ # must be built from /dist directory -FROM ubuntu:18.04 as prep +FROM ubuntu:20.04 as prep LABEL MAINTAINER="https://discord.gg/Uz29ny" RUN apt-get update && \ @@ -16,7 +16,7 @@ RUN curl --http1.1 -o /ffmpeg.tar.xz https://johnvansickle.com/ffmpeg/releases/f rm ffmpeg.tar.xz && \ mv /ffmpeg*/ /ffmpeg/ -FROM ubuntu:18.04 as app +FROM ubuntu:20.04 as app RUN apt-get update && apt-get -y install ca-certificates COPY --from=prep /ffmpeg/ffmpeg /ffmpeg/ffprobe /usr/bin/ COPY /stash-linux /usr/bin/stash diff --git a/docker/compiler/Dockerfile b/docker/compiler/Dockerfile index 09c1ffd08..5f3f4b4c6 100644 --- a/docker/compiler/Dockerfile +++ b/docker/compiler/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.11.5 +FROM golang:1.13.15 LABEL maintainer="stashappdev@gmail.com" @@ -9,7 +9,7 @@ ENV PACKR2_DOWNLOAD_URL=https://github.com/gobuffalo/packr/releases/download/v${ # Install tools RUN apt-get update && apt-get install -y apt-transport-https -RUN curl -sL https://deb.nodesource.com/setup_10.x | bash - +RUN curl -sL https://deb.nodesource.com/setup_lts.x | bash - # prevent caching of the key ADD https://dl.yarnpkg.com/debian/pubkey.gpg yarn.gpg @@ -21,7 +21,7 @@ RUN apt-get update && \ apt-get install -y automake autogen \ libtool libxml2-dev uuid-dev libssl-dev bash \ patch make tar xz-utils bzip2 gzip sed cpio \ - gcc-6-multilib g++-6-multilib gcc-mingw-w64 g++-mingw-w64 clang llvm-dev \ + gcc-8-multilib gcc-mingw-w64 g++-mingw-w64 clang llvm-dev \ gcc-arm-linux-gnueabi libc-dev-armel-cross linux-libc-dev-armel-cross \ gcc-arm-linux-gnueabihf libc-dev-armhf-cross \ gcc-aarch64-linux-gnu libc-dev-arm64-cross \ diff --git a/docker/compiler/Makefile b/docker/compiler/Makefile index c705b1b6f..6f444e73d 100644 --- a/docker/compiler/Makefile +++ b/docker/compiler/Makefile @@ -1,6 +1,6 @@ user=stashapp repo=compiler -version=3 +version=4 latest: docker build -t ${user}/${repo}:latest . diff --git a/docker/compiler/README.md b/docker/compiler/README.md index 437dae408..abe4060b5 100644 --- a/docker/compiler/README.md +++ b/docker/compiler/README.md @@ -1 +1,3 @@ -Modified from https://github.com/bep/dockerfiles/tree/master/ci-goreleaser \ No newline at end of file +Modified from https://github.com/bep/dockerfiles/tree/master/ci-goreleaser + +When the dockerfile is changed, the version number should be incremented in the Makefile and the new version tag should be pushed to docker hub. The `scripts/cross-compile.sh` script should also be updated to use the new version number tag, and `.travis.yml` needs to be updated to pull the correct image tag. \ No newline at end of file diff --git a/docker/develop/x86_64/Dockerfile b/docker/develop/x86_64/Dockerfile index 94624c7b0..cca25aafd 100644 --- a/docker/develop/x86_64/Dockerfile +++ b/docker/develop/x86_64/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:18.04 as prep +FROM ubuntu:20.04 as prep LABEL MAINTAINER="https://discord.gg/Uz29ny" RUN apt-get update && \ @@ -17,7 +17,7 @@ RUN curl --http1.1 -o /ffmpeg.tar.xz https://johnvansickle.com/ffmpeg/releases/f rm ffmpeg.tar.xz && \ mv /ffmpeg*/ /ffmpeg/ -FROM ubuntu:18.04 as app +FROM ubuntu:20.04 as app RUN apt-get update && apt-get -y install ca-certificates COPY --from=prep /stash /ffmpeg/ffmpeg /ffmpeg/ffprobe /usr/bin/ EXPOSE 9999 diff --git a/docker/production/x86_64/Dockerfile b/docker/production/x86_64/Dockerfile index 41285d760..bc152161c 100644 --- a/docker/production/x86_64/Dockerfile +++ b/docker/production/x86_64/Dockerfile @@ -1,4 +1,4 @@ -FROM ubuntu:18.04 as prep +FROM ubuntu:20.04 as prep LABEL MAINTAINER="leopere [at] nixc [dot] us" RUN apt-get update && \ @@ -17,7 +17,7 @@ RUN curl --http1.1 -o /ffmpeg.tar.xz https://johnvansickle.com/ffmpeg/releases/f rm ffmpeg.tar.xz && \ mv /ffmpeg*/ /ffmpeg/ -FROM ubuntu:18.04 as app +FROM ubuntu:20.04 as app RUN apt-get update && apt-get -y install ca-certificates COPY --from=prep /stash /ffmpeg/ffmpeg /ffmpeg/ffprobe /usr/bin/ EXPOSE 9999 diff --git a/go.mod b/go.mod index a822751e1..08c335f7e 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,8 @@ module github.com/stashapp/stash require ( - github.com/99designs/gqlgen v0.9.0 + github.com/99designs/gqlgen v0.12.2 + github.com/Yamashou/gqlgenc v0.0.0-20200902035953-4dbef3551953 github.com/antchfx/htmlquery v1.2.3 github.com/bmatcuk/doublestar/v2 v2.0.1 github.com/chromedp/cdproto v0.0.0-20200608134039-8a80cdaf865c @@ -12,9 +13,8 @@ require ( github.com/golang-migrate/migrate/v4 v4.3.1 github.com/gorilla/securecookie v1.1.1 github.com/gorilla/sessions v1.2.0 - github.com/gorilla/websocket v1.4.0 + github.com/gorilla/websocket v1.4.2 github.com/h2non/filetype v1.0.8 - github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a github.com/jmoiron/sqlx v1.2.0 github.com/json-iterator/go v1.1.9 @@ -24,16 +24,18 @@ require ( github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f github.com/sirupsen/logrus v1.4.2 github.com/spf13/pflag v1.0.3 - github.com/spf13/viper v1.4.0 + github.com/spf13/viper v1.7.0 github.com/stretchr/testify v1.5.1 github.com/tidwall/gjson v1.6.0 - github.com/vektah/gqlparser v1.1.2 - golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 - golang.org/x/image v0.0.0-20190118043309-183bebdce1b2 - golang.org/x/net v0.0.0-20200602114024-627f9648deb9 - gopkg.in/yaml.v2 v2.2.2 + github.com/vektah/gqlparser/v2 v2.0.1 + github.com/vektra/mockery/v2 v2.2.1 + golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 + golang.org/x/image v0.0.0-20190802002840-cff245a6509b + golang.org/x/net v0.0.0-20200822124328-c89045814202 + golang.org/x/tools v0.0.0-20200915031644-64986481280e // indirect + gopkg.in/yaml.v2 v2.3.0 ) replace git.apache.org/thrift.git => github.com/apache/thrift v0.0.0-20180902110319-2566ecd5d999 -go 1.11 +go 1.13 diff --git a/go.sum b/go.sum index d1da3efc4..7a51679a7 100644 --- a/go.sum +++ b/go.sum @@ -3,23 +3,41 @@ cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.36.0/go.mod h1:RUoy9p/M4ge0HzT8L+SDZ8jg+Q6fth0CiBuhFJpSV40= cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= -github.com/99designs/gqlgen v0.9.0 h1:g1arBPML74Vqv0L3Q+TqIhGXLspV+2MYtRLkBxuZrlE= -github.com/99designs/gqlgen v0.9.0/go.mod h1:HrrG7ic9EgLPsULxsZh/Ti+p0HNWgR3XRuvnD0pb5KY= +github.com/99designs/gqlgen v0.12.2 h1:aOdpsiCycFtCnAv8CAI1exnKrIDHMqtMzQoXeTziY4o= +github.com/99designs/gqlgen v0.12.2/go.mod h1:7zdGo6ry9u1YBp/qlb2uxSU5Mt2jQKLcBETQiKk+Bxo= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/Yamashou/gqlgenc v0.0.0-20200902035953-4dbef3551953 h1:+iPJDL28FxZhEdtJ9qykrMt/oDiOvlzTa0zV06nUcFM= +github.com/Yamashou/gqlgenc v0.0.0-20200902035953-4dbef3551953/go.mod h1:kaTsk10p2hJWwrB2t7vMsk1lXj9KAHaDYRtJQiB+Ick= github.com/agnivade/levenshtein v1.0.1 h1:3oJU7J3FGFmyhn8KHjmVaZCN5hxTr7GxgRue+sxIXdQ= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= +github.com/agnivade/levenshtein v1.0.3 h1:M5ZnqLOoZR8ygVq0FfkXsNOKzMCk0xRiow0R5+5VkQ0= +github.com/agnivade/levenshtein v1.0.3/go.mod h1:4SFRZbbXWLF4MU1T9Qg0pGgH3Pjs+t6ie5efyrwRJXs= +github.com/agnivade/levenshtein v1.1.0 h1:n6qGwyHG61v3ABce1rPVZklEYRT8NFpCMrpZdBUbYGM= +github.com/agnivade/levenshtein v1.1.0/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= github.com/ajg/form v0.0.0-20160822230020-523a5da1a92f/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= @@ -32,11 +50,18 @@ github.com/antchfx/xpath v1.1.6 h1:6sVh6hB5T6phw1pFpHRQ+C4bd8sNI+O58flqtg7h0R0= github.com/antchfx/xpath v1.1.6/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk= github.com/apache/thrift v0.0.0-20180902110319-2566ecd5d999/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= +github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/aws/aws-sdk-go v1.17.7/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bmatcuk/doublestar/v2 v2.0.1 h1:EFT91DmIMRcrUEcYUW7AqSAwKvNzP5+CoDmNVBbcQOU= github.com/bmatcuk/doublestar/v2 v2.0.1/go.mod h1:QMmcs3H2AUQICWhfzLXz+IYln8lRQmTZRptLie8RgRw= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= @@ -53,11 +78,16 @@ github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c/go.mod h1 github.com/codegangsta/negroni v1.0.0/go.mod h1:v0y3T5G7Y1UlFfyxFn/QLRU4a2EuNau2iZY63YTKWo0= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cznic/b v0.0.0-20180115125044-35e9bbe41f07/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8= github.com/cznic/fileutil v0.0.0-20180108211300-6a051e75936f/go.mod h1:8S58EK26zhXSxzv7NQFpnliaOQsmDUxvoQO3rt154Vg= github.com/cznic/golex v0.0.0-20170803123110-4ab7c5e190e4/go.mod h1:+bmmJDNmKlhWNG+gwWCkaBoTy39Fs+bzRxVBzoTQbIc= @@ -73,6 +103,9 @@ 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/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= +github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g= +github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= github.com/dhui/dktest v0.3.0/go.mod h1:cyzIUfGsBEbZ6BT7tnXqAShHSXCZhSNmFl70sZ7c1yc= github.com/disintegration/imaging v1.6.0 h1:nVPXRUUQ36Z7MNf0O77UzgnOb1mkMMor7lmJMJXc/mA= github.com/disintegration/imaging v1.6.0/go.mod h1:xuIt+sRxDFrHS0drzXUlCJthkJ8k7lkkUojDSR247MQ= @@ -100,6 +133,7 @@ github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aev github.com/go-chi/chi v3.3.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/go-chi/chi v4.0.2+incompatible h1:maB6vn6FqCxrpz4FqWdh4+lwpyZIQS7YEAUcHlgXVRs= github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= @@ -338,9 +372,11 @@ github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4er github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.1.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.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -348,14 +384,21 @@ github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 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.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= @@ -369,10 +412,10 @@ github.com/gorilla/sessions v1.1.2/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= github.com/gorilla/sessions v1.2.0 h1:S7P+1Hm5V/AT9cjEcUD5uDaQSX0OE577aCXgoaKpYbQ= github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= -github.com/gorilla/websocket v1.2.0 h1:VJtLvh6VQym50czpZzx07z/kw9EgAxI3x1ZB8taTMQQ= -github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= @@ -381,16 +424,31 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t github.com/h2non/filetype v1.0.8 h1:le8gpf+FQA0/DlDABbtisA1KiTS0Xi+YSC/E8yY3Y14= github.com/h2non/filetype v1.0.8/go.mod h1:isekKqOuhMj+s/7r3rIeTErIRy4Rub5uBWHfvMusLMU= github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= @@ -407,9 +465,12 @@ github.com/joho/godotenv v1.2.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqx github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= github.com/karrick/godirwalk v1.7.5/go.mod h1:2c9FRhkDxdIbgkOnCEvnSWs71Bhugbl46shStcFDJ34= @@ -436,8 +497,11 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kshvakov/clickhouse v1.3.5/go.mod h1:DMzX7FxRymoNkVgizH0DWAL8Cur7wHLgx3MUnGwJqpE= github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= github.com/mailru/easyjson v0.7.1 h1:mdxE1MF9o53iCb2Ghj1VfWvh7ZOwHpnVG/xwXrV90U8= github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= @@ -462,8 +526,14 @@ github.com/markbates/safe v1.0.1 h1:yjZkbvRM6IzKj9tlu/zMJLS0n/V351OZWRnF3QfaUxI= github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0= github.com/markbates/sigtx v1.0.0/go.mod h1:QF1Hv6Ic6Ca6W+T+DL0Y/ypborFKyvUY9HmuCD4VeTc= github.com/markbates/willie v1.0.9/go.mod h1:fsrFVWl91+gXpx/6dv715j7i11fYPfZ9ZGfH0DQzY7w= +github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007 h1:reVOUXwnhsYv/8UqjvhrMOu5CNT9UapHFLbQ2JcXsmg= +github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.10.0 h1:jbhqpg7tQe4SupckyijYiy0mJJ/pRyHvXf7JdWK860o= @@ -473,15 +543,27 @@ github.com/mattn/go-sqlite3 v1.13.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsO github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mongodb/mongo-go-driver v0.3.0/go.mod h1:NK/HWDIIZkaYsnYa0hmtP443T5ELr0KDecmIioVuuyU= github.com/monoculum/formam v0.0.0-20180901015400-4e68be1d79ba/go.mod h1:RKgILGEJq24YyJ2ban8EO0RUVSJlF1pGsEvoLEACr/Q= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= @@ -503,14 +585,18 @@ github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKw github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= @@ -534,13 +620,27 @@ github.com/rogpeppe/go-internal v1.1.0 h1:g0fH8RicVgNl+zVZDCDfbdWxAWoAEJyI7I3TZY github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.2.2 h1:J7U/N7eRtzjhs26d6GqMh2HBuXP8/Z64Densiiieafo= github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/cors v1.6.0 h1:G9tHG9lebljV9mfp9SNPDL36nCDxmo3zTlAf1YgvzmI= github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.18.0 h1:CbAm3kP2Tptby1i9sYy2MGRg0uxIN9cyDb59Ys7W8z8= +github.com/rs/zerolog v1.18.0/go.mod h1:9nvC1axdVrAHcu/s9taAVfBuIdTZLVQmKQyvrUjF5+I= +github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= @@ -566,6 +666,8 @@ github.com/shurcooL/octicon v0.0.0-20180602230221-c42b0e3b24d9/go.mod h1:eWdoE5J github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ= github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk= github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= @@ -578,6 +680,10 @@ github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= @@ -590,6 +696,8 @@ github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= +github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= @@ -599,6 +707,8 @@ github.com/spf13/viper v1.2.1/go.mod h1:P4AexN0a+C9tGAnUFNwDMYYZv3pjFuvmeiMyKRaN github.com/spf13/viper v1.3.1/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU= github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -606,8 +716,11 @@ github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 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.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/tidwall/gjson v1.6.0 h1:9VEQWz6LLMUsUl6PueE49ir4Ka6CzLymOAZDxpFsTDc= github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= @@ -621,22 +734,31 @@ github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGr github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/unrolled/secure v0.0.0-20180918153822-f340ee86eb8b/go.mod h1:mnPT77IAdsi/kV7+Es7y+pXALeV3h7G6dQF6mNYjcLA= github.com/unrolled/secure v0.0.0-20181005190816-ff9db2ff917f/go.mod h1:mnPT77IAdsi/kV7+Es7y+pXALeV3h7G6dQF6mNYjcLA= -github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= -github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k= +github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e h1:+w0Zm/9gaWpEAyDlU1eKOuk5twTjAjuevXqcJJw8hrg= github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U= -github.com/vektah/gqlparser v1.1.2 h1:ZsyLGn7/7jDNI+y4SEhI4yAxRChlv15pUHMjijT+e68= -github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= +github.com/vektah/gqlparser v1.3.1 h1:8b0IcD3qZKWJQHSzynbDlrtP3IxVydZ2DZepCGofqfU= +github.com/vektah/gqlparser v1.3.1/go.mod h1:bkVf0FX+Stjg/MHnm8mEyubuaArhNEqfQhF+OTiAL74= +github.com/vektah/gqlparser/v2 v2.0.1 h1:xgl5abVnsd4hkN9rk65OJID9bfcLSMuTaTcZj777q1o= +github.com/vektah/gqlparser/v2 v2.0.1/go.mod h1:SyUiHgLATUR8BiYURfTirrTcGpcE+4XkV2se04Px1Ms= +github.com/vektra/mockery/v2 v2.2.1 h1:EYgPvxyYkm/0JKs62qlVc9pO+ljb8biPbDWabk5/PmI= +github.com/vektra/mockery/v2 v2.2.1/go.mod h1:rBZUbbhMbiSX1WlCGsOgAi6xjuJGxB7KKbnoL0XNYW8= github.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs= github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= @@ -650,6 +772,7 @@ golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20181024171144-74cb1d3d52f4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181025113841-85e1b3f9139a/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181106171534-e4dc69e5b2fd/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -662,17 +785,38 @@ golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 h1:p/H982KKEjUnLJkM3tt/LemDnOc1GiZL5FCVlORJ5zo= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 h1:HuIa8hRrWRSrqYzx1qI49NNxhdi2PrY7gxVSq1JjLDc= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= -golang.org/x/image v0.0.0-20190118043309-183bebdce1b2 h1:FNSSV4jv1PrPsiM2iKGpqLPPgYACqh9Muav7Pollk1k= -golang.org/x/image v0.0.0-20190118043309-183bebdce1b2/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/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-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180816102801-aaf60122140d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -688,6 +832,7 @@ golang.org/x/net v0.0.0-20181102091132-c10e9556a7bc/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20181106065722-10aee1819953/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181207154023-610586996380/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e h1:bRhVy7zSSasaqNksaRZiA5EEI+Ei4I1nO5Jh72wfHlg= @@ -697,18 +842,26 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r 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-20190424112056-4829fb13d2c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200421231249-e086a090c8fd h1:QPwSajcTUrFriMF1nJ3XzgoqakqQEsnZf9LdXdi2nkI= golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM= -golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -717,7 +870,11 @@ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180816055513-1c9583448a9c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/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-20180906133057-8cf3aee42992/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -742,10 +899,16 @@ golang.org/x/sys v0.0.0-20190102155601-82a175fd1598/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20190116161447-11f53e031339 h1:g/Jesu8+QLnA0CPzF3E1pURg0Byr7i6jLoX5sqjcAh0= golang.org/x/sys v0.0.0-20190116161447-11f53e031339/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190426135247-a129542de9ae h1:mQLHiymj/JXKnnjc62tb7nD5pZLs940/sXJu+Xp3DBA= golang.org/x/sys v0.0.0-20190426135247-a129542de9ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/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= @@ -794,16 +957,51 @@ golang.org/x/tools v0.0.0-20190219185102-9394956cfdc5/go.mod h1:E6PF97AdD6v0s+fP golang.org/x/tools v0.0.0-20190221204921-83362c3779f5/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190425222832-ad9eeb80039a/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd h1:oMEQDWVXVNpceQoVd1JN3CQ7LYJJzs5qWqZIUcxXHHw= golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589 h1:rjUrONFu4kLchcZTfp3/96bR8bW8dIa8uz3cR5n0cgM= +golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200323144430-8dcfad9e016e h1:ssd5ulOvVWlh4kDSUF2SqzmMeWfjmwDXM+uGw/aQjRE= +golang.org/x/tools v0.0.0-20200323144430-8dcfad9e016e/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200827163409-021d7c6f1ec3 h1:OjYQxZBKJFs+sJbHkvSGIKNMkZXDJQ9JsMpebGhkafI= +golang.org/x/tools v0.0.0-20200827163409-021d7c6f1ec3/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200915031644-64986481280e h1:tfSNPIxC48Azhz4nLSPskz/yE9R6ftFRK8pfgfqWUAc= +golang.org/x/tools v0.0.0-20200915031644-64986481280e/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +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-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +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/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.3.2/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= @@ -811,6 +1009,8 @@ google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO50 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -818,24 +1018,35 @@ google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk google.golang.org/genproto v0.0.0-20190201180003-4b09977fb922/go.mod h1:L3J43x8/uS+qIUoksaLKe6OS3nUKxOKuIFz1sl2/jx4= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= 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-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/mail.v2 v2.0.0-20180731213649-a0242b2233b4/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= @@ -843,11 +1054,19 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/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= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= sourcegraph.com/sourcegraph/appdash v0.0.0-20180110180208-2cc67fd64755/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67/go.mod h1:L5q+DGLGOQFpo1snNEkLOJT2d1YTW66rWNzatr3He1k= sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= diff --git a/gqlgen.yml b/gqlgen.yml index 0d1a780e5..2b2402034 100644 --- a/gqlgen.yml +++ b/gqlgen.yml @@ -16,6 +16,10 @@ struct_tag: gqlgen models: Gallery: model: github.com/stashapp/stash/pkg/models.Gallery + Image: + model: github.com/stashapp/stash/pkg/models.Image + ImageFileType: + model: github.com/stashapp/stash/pkg/models.ImageFileType Performer: model: github.com/stashapp/stash/pkg/models.Performer Scene: @@ -48,3 +52,5 @@ models: model: github.com/stashapp/stash/pkg/models.ScrapedMovie ScrapedMovieStudio: model: github.com/stashapp/stash/pkg/models.ScrapedMovieStudio + StashID: + model: github.com/stashapp/stash/pkg/models.StashID diff --git a/graphql/documents/data/config.graphql b/graphql/documents/data/config.graphql index ada4e99fb..663788299 100644 --- a/graphql/documents/data/config.graphql +++ b/graphql/documents/data/config.graphql @@ -1,5 +1,9 @@ fragment ConfigGeneralData on ConfigGeneralResult { - stashes + stashes { + path + excludeVideo + excludeImage + } databasePath generatedPath cachePath @@ -19,9 +23,19 @@ fragment ConfigGeneralData on ConfigGeneralResult { logOut logLevel logAccess + createGalleriesFromFolders + videoExtensions + imageExtensions + galleryExtensions excludes + imageExcludes scraperUserAgent scraperCDPPath + stashBoxes { + name + endpoint + api_key + } } fragment ConfigInterfaceData on ConfigInterfaceResult { diff --git a/graphql/documents/data/gallery-slim.graphql b/graphql/documents/data/gallery-slim.graphql new file mode 100644 index 000000000..1f99cc201 --- /dev/null +++ b/graphql/documents/data/gallery-slim.graphql @@ -0,0 +1,28 @@ +fragment GallerySlimData on Gallery { + id + checksum + path + title + date + url + details + rating + image_count + cover { + ...SlimImageData + } + studio { + ...StudioData + } + tags { + ...TagData + } + performers { + ...PerformerData + } + scene { + id + title + path + } +} diff --git a/graphql/documents/data/gallery.graphql b/graphql/documents/data/gallery.graphql index 2f77518b4..af99c77f7 100644 --- a/graphql/documents/data/gallery.graphql +++ b/graphql/documents/data/gallery.graphql @@ -3,10 +3,25 @@ fragment GalleryData on Gallery { checksum path title - files { - index - name - path + date + url + details + rating + images { + ...SlimImageData + } + cover { + ...SlimImageData + } + studio { + ...StudioData + } + tags { + ...TagData + } + + performers { + ...PerformerData } scene { id diff --git a/graphql/documents/data/image-slim.graphql b/graphql/documents/data/image-slim.graphql new file mode 100644 index 000000000..41789f5f5 --- /dev/null +++ b/graphql/documents/data/image-slim.graphql @@ -0,0 +1,43 @@ +fragment SlimImageData on Image { + id + checksum + title + rating + o_counter + path + + file { + size + width + height + } + + paths { + thumbnail + image + } + + galleries { + id + path + title + } + + studio { + id + name + image_path + } + + tags { + id + name + } + + performers { + id + name + favorite + image_path + } +} diff --git a/graphql/documents/data/image.graphql b/graphql/documents/data/image.graphql new file mode 100644 index 000000000..9bd94b633 --- /dev/null +++ b/graphql/documents/data/image.graphql @@ -0,0 +1,35 @@ +fragment ImageData on Image { + id + checksum + title + rating + o_counter + path + + file { + size + width + height + } + + paths { + thumbnail + image + } + + galleries { + ...GalleryData + } + + studio { + ...StudioData + } + + tags { + ...TagData + } + + performers { + ...PerformerData + } +} diff --git a/graphql/documents/data/performer-slim.graphql b/graphql/documents/data/performer-slim.graphql index c2abc6023..09aeb5c16 100644 --- a/graphql/documents/data/performer-slim.graphql +++ b/graphql/documents/data/performer-slim.graphql @@ -3,4 +3,8 @@ fragment SlimPerformerData on Performer { name gender image_path + stash_ids { + endpoint + stash_id + } } diff --git a/graphql/documents/data/performer.graphql b/graphql/documents/data/performer.graphql index cc5e6d2f1..24ce512ad 100644 --- a/graphql/documents/data/performer.graphql +++ b/graphql/documents/data/performer.graphql @@ -20,4 +20,8 @@ fragment PerformerData on Performer { favorite image_path scene_count + stash_ids { + stash_id + endpoint + } } diff --git a/graphql/documents/data/scene-slim.graphql b/graphql/documents/data/scene-slim.graphql index 535d79c87..732c19280 100644 --- a/graphql/documents/data/scene-slim.graphql +++ b/graphql/documents/data/scene-slim.graphql @@ -68,4 +68,9 @@ fragment SlimSceneData on Scene { favorite image_path } + + stash_ids { + endpoint + stash_id + } } diff --git a/graphql/documents/data/scene.graphql b/graphql/documents/data/scene.graphql index aa02db41a..43161efcb 100644 --- a/graphql/documents/data/scene.graphql +++ b/graphql/documents/data/scene.graphql @@ -56,4 +56,9 @@ fragment SceneData on Scene { performers { ...PerformerData } + + stash_ids { + endpoint + stash_id + } } diff --git a/graphql/documents/data/scrapers.graphql b/graphql/documents/data/scrapers.graphql index eb10e6a5c..fe2bf7f7b 100644 --- a/graphql/documents/data/scrapers.graphql +++ b/graphql/documents/data/scrapers.graphql @@ -36,6 +36,8 @@ fragment ScrapedScenePerformerData on ScrapedScenePerformer { tattoos piercings aliases + remote_site_id + images } fragment ScrapedMovieStudioData on ScrapedMovieStudio { @@ -77,6 +79,7 @@ fragment ScrapedSceneStudioData on ScrapedSceneStudio { stored_id name url + remote_site_id } fragment ScrapedSceneTagData on ScrapedSceneTag { @@ -118,3 +121,65 @@ fragment ScrapedSceneData on ScrapedScene { ...ScrapedSceneMovieData } } + +fragment ScrapedGalleryData on ScrapedGallery { + title + details + url + date + + studio { + ...ScrapedSceneStudioData + } + + tags { + ...ScrapedSceneTagData + } + + performers { + ...ScrapedScenePerformerData + } +} + +fragment ScrapedStashBoxSceneData on ScrapedScene { + title + details + url + date + image + remote_site_id + duration + + file { + size + duration + video_codec + audio_codec + width + height + framerate + bitrate + } + + fingerprints { + hash + algorithm + duration + } + + studio { + ...ScrapedSceneStudioData + } + + tags { + ...ScrapedSceneTagData + } + + performers { + ...ScrapedScenePerformerData + } + + movies { + ...ScrapedSceneMovieData + } +} diff --git a/graphql/documents/data/studio-slim.graphql b/graphql/documents/data/studio-slim.graphql index 0ce2ec675..a247b4e34 100644 --- a/graphql/documents/data/studio-slim.graphql +++ b/graphql/documents/data/studio-slim.graphql @@ -2,4 +2,11 @@ fragment SlimStudioData on Studio { id name image_path -} \ No newline at end of file + stash_ids { + endpoint + stash_id + } + parent_studio { + id + } +} diff --git a/graphql/documents/data/studio.graphql b/graphql/documents/data/studio.graphql index 890f9c43f..d2f60a44b 100644 --- a/graphql/documents/data/studio.graphql +++ b/graphql/documents/data/studio.graphql @@ -21,4 +21,8 @@ fragment StudioData on Studio { } image_path scene_count + stash_ids { + stash_id + endpoint + } } diff --git a/graphql/documents/mutations/gallery.graphql b/graphql/documents/mutations/gallery.graphql new file mode 100644 index 000000000..8cb5f82b8 --- /dev/null +++ b/graphql/documents/mutations/gallery.graphql @@ -0,0 +1,97 @@ +mutation GalleryCreate( + $title: String!, + $details: String, + $url: String, + $date: String, + $rating: Int, + $scene_id: ID, + $studio_id: ID, + $performer_ids: [ID!] = [], + $tag_ids: [ID!] = []) { + + galleryCreate(input: { + title: $title, + details: $details, + url: $url, + date: $date, + rating: $rating, + scene_id: $scene_id, + studio_id: $studio_id, + tag_ids: $tag_ids, + performer_ids: $performer_ids + }) { + ...GalleryData + } +} + +mutation GalleryUpdate( + $id: ID!, + $title: String, + $details: String, + $url: String, + $date: String, + $rating: Int, + $scene_id: ID, + $studio_id: ID, + $performer_ids: [ID!] = [], + $tag_ids: [ID!] = []) { + + galleryUpdate(input: { + id: $id, + title: $title, + details: $details, + url: $url, + date: $date, + rating: $rating, + scene_id: $scene_id, + studio_id: $studio_id, + tag_ids: $tag_ids, + performer_ids: $performer_ids + }) { + ...GalleryData + } +} + +mutation BulkGalleryUpdate( + $ids: [ID!] = [], + $url: String, + $date: String, + $details: String, + $rating: Int, + $scene_id: ID, + $studio_id: ID, + $tag_ids: BulkUpdateIds, + $performer_ids: BulkUpdateIds) { + + bulkGalleryUpdate(input: { + ids: $ids, + details: $details, + url: $url, + date: $date, + rating: $rating, + scene_id: $scene_id, + studio_id: $studio_id, + tag_ids: $tag_ids, + performer_ids: $performer_ids + }) { + ...GalleryData + } +} + +mutation GalleriesUpdate($input : [GalleryUpdateInput!]!) { + galleriesUpdate(input: $input) { + ...GalleryData + } +} + +mutation GalleryDestroy($ids: [ID!]!, $delete_file: Boolean, $delete_generated : Boolean) { + galleryDestroy(input: {ids: $ids, delete_file: $delete_file, delete_generated: $delete_generated}) +} + +mutation AddGalleryImages($gallery_id: ID!, $image_ids: [ID!]!) { + addGalleryImages(input: {gallery_id: $gallery_id, image_ids: $image_ids}) +} + +mutation RemoveGalleryImages($gallery_id: ID!, $image_ids: [ID!]!) { + removeGalleryImages(input: {gallery_id: $gallery_id, image_ids: $image_ids}) +} diff --git a/graphql/documents/mutations/image.graphql b/graphql/documents/mutations/image.graphql new file mode 100644 index 000000000..963c33f5c --- /dev/null +++ b/graphql/documents/mutations/image.graphql @@ -0,0 +1,69 @@ +mutation ImageUpdate( + $id: ID!, + $title: String, + $rating: Int, + $studio_id: ID, + $gallery_ids: [ID!] = [], + $performer_ids: [ID!] = [], + $tag_ids: [ID!] = []) { + + imageUpdate(input: { + id: $id, + title: $title, + rating: $rating, + studio_id: $studio_id, + gallery_ids: $gallery_ids, + performer_ids: $performer_ids, + tag_ids: $tag_ids + }) { + ...SlimImageData + } +} + +mutation BulkImageUpdate( + $ids: [ID!] = [], + $title: String, + $rating: Int, + $studio_id: ID, + $gallery_ids: BulkUpdateIds, + $performer_ids: BulkUpdateIds, + $tag_ids: BulkUpdateIds) { + + bulkImageUpdate(input: { + ids: $ids, + title: $title, + rating: $rating, + studio_id: $studio_id, + gallery_ids: $gallery_ids, + performer_ids: $performer_ids, + tag_ids: $tag_ids + }) { + ...SlimImageData + } +} + +mutation ImagesUpdate($input : [ImageUpdateInput!]!) { + imagesUpdate(input: $input) { + ...SlimImageData + } +} + +mutation ImageIncrementO($id: ID!) { + imageIncrementO(id: $id) +} + +mutation ImageDecrementO($id: ID!) { + imageDecrementO(id: $id) +} + +mutation ImageResetO($id: ID!) { + imageResetO(id: $id) +} + +mutation ImageDestroy($id: ID!, $delete_file: Boolean, $delete_generated : Boolean) { + imageDestroy(input: {id: $id, delete_file: $delete_file, delete_generated: $delete_generated}) +} + +mutation ImagesDestroy($ids: [ID!]!, $delete_file: Boolean, $delete_generated : Boolean) { + imagesDestroy(input: {ids: $ids, delete_file: $delete_file, delete_generated: $delete_generated}) +} diff --git a/graphql/documents/mutations/metadata.graphql b/graphql/documents/mutations/metadata.graphql index 925fd5158..0b94728af 100644 --- a/graphql/documents/mutations/metadata.graphql +++ b/graphql/documents/mutations/metadata.graphql @@ -6,6 +6,14 @@ mutation MetadataExport { metadataExport } +mutation ExportObjects($input: ExportObjectsInput!) { + exportObjects(input: $input) +} + +mutation ImportObjects($input: ImportObjectsInput!) { + importObjects(input: $input) +} + mutation MetadataScan($input: ScanMetadataInput!) { metadataScan(input: $input) } diff --git a/graphql/documents/mutations/performer.graphql b/graphql/documents/mutations/performer.graphql index ae0b5e17f..48b490f9d 100644 --- a/graphql/documents/mutations/performer.graphql +++ b/graphql/documents/mutations/performer.graphql @@ -1,5 +1,5 @@ mutation PerformerCreate( - $name: String, + $name: String!, $url: String, $gender: GenderEnum, $birthdate: String, @@ -16,6 +16,7 @@ mutation PerformerCreate( $twitter: String, $instagram: String, $favorite: Boolean, + $stash_ids: [StashIDInput!], $image: String) { performerCreate(input: { @@ -36,6 +37,7 @@ mutation PerformerCreate( twitter: $twitter, instagram: $instagram, favorite: $favorite, + stash_ids: $stash_ids, image: $image }) { ...PerformerData @@ -61,6 +63,7 @@ mutation PerformerUpdate( $twitter: String, $instagram: String, $favorite: Boolean, + $stash_ids: [StashIDInput!], $image: String) { performerUpdate(input: { @@ -82,6 +85,7 @@ mutation PerformerUpdate( twitter: $twitter, instagram: $instagram, favorite: $favorite, + stash_ids: $stash_ids, image: $image }) { ...PerformerData @@ -90,4 +94,4 @@ mutation PerformerUpdate( mutation PerformerDestroy($id: ID!) { performerDestroy(input: { id: $id }) -} \ No newline at end of file +} diff --git a/graphql/documents/mutations/scene.graphql b/graphql/documents/mutations/scene.graphql index 0915b48cd..98c0b8a08 100644 --- a/graphql/documents/mutations/scene.graphql +++ b/graphql/documents/mutations/scene.graphql @@ -10,6 +10,7 @@ mutation SceneUpdate( $performer_ids: [ID!] = [], $movies: [SceneMovieInput!] = [], $tag_ids: [ID!] = [], + $stash_ids: [StashIDInput!], $cover_image: String) { sceneUpdate(input: { @@ -24,6 +25,7 @@ mutation SceneUpdate( performer_ids: $performer_ids, movies: $movies, tag_ids: $tag_ids, + stash_ids: $stash_ids, cover_image: $cover_image }) { ...SceneData diff --git a/graphql/documents/mutations/stash-box.graphql b/graphql/documents/mutations/stash-box.graphql new file mode 100644 index 000000000..24a9dc169 --- /dev/null +++ b/graphql/documents/mutations/stash-box.graphql @@ -0,0 +1,3 @@ +mutation SubmitStashBoxFingerprints($input: StashBoxFingerprintSubmissionInput!) { + submitStashBoxFingerprints(input: $input) +} diff --git a/graphql/documents/mutations/studio.graphql b/graphql/documents/mutations/studio.graphql index 539b96358..9e4a473b5 100644 --- a/graphql/documents/mutations/studio.graphql +++ b/graphql/documents/mutations/studio.graphql @@ -1,10 +1,11 @@ mutation StudioCreate( $name: String!, $url: String, - $image: String + $image: String, + $stash_ids: [StashIDInput!], $parent_id: ID) { - studioCreate(input: { name: $name, url: $url, image: $image, parent_id: $parent_id }) { + studioCreate(input: { name: $name, url: $url, image: $image, stash_ids: $stash_ids, parent_id: $parent_id }) { ...StudioData } } @@ -13,14 +14,15 @@ mutation StudioUpdate( $id: ID! $name: String, $url: String, - $image: String + $image: String, + $stash_ids: [StashIDInput!], $parent_id: ID) { - studioUpdate(input: { id: $id, name: $name, url: $url, image: $image, parent_id: $parent_id }) { + studioUpdate(input: { id: $id, name: $name, url: $url, image: $image, stash_ids: $stash_ids, parent_id: $parent_id }) { ...StudioData } } mutation StudioDestroy($id: ID!) { studioDestroy(input: { id: $id }) -} \ No newline at end of file +} diff --git a/graphql/documents/queries/gallery.graphql b/graphql/documents/queries/gallery.graphql index 205f661bc..c289d9758 100644 --- a/graphql/documents/queries/gallery.graphql +++ b/graphql/documents/queries/gallery.graphql @@ -2,7 +2,7 @@ query FindGalleries($filter: FindFilterType, $gallery_filter: GalleryFilterType) findGalleries(gallery_filter: $gallery_filter, filter: $filter) { count galleries { - ...GalleryData + ...GallerySlimData } } } @@ -11,4 +11,4 @@ query FindGallery($id: ID!) { findGallery(id: $id) { ...GalleryData } -} \ No newline at end of file +} diff --git a/graphql/documents/queries/image.graphql b/graphql/documents/queries/image.graphql new file mode 100644 index 000000000..4d35bc69b --- /dev/null +++ b/graphql/documents/queries/image.graphql @@ -0,0 +1,14 @@ +query FindImages($filter: FindFilterType, $image_filter: ImageFilterType, $image_ids: [Int!]) { + findImages(filter: $filter, image_filter: $image_filter, image_ids: $image_ids) { + count + images { + ...SlimImageData + } + } +} + +query FindImage($id: ID!, $checksum: String) { + findImage(id: $id, checksum: $checksum) { + ...ImageData + } +} diff --git a/graphql/documents/queries/misc.graphql b/graphql/documents/queries/misc.graphql index fe9e85da7..aad4dcfea 100644 --- a/graphql/documents/queries/misc.graphql +++ b/graphql/documents/queries/misc.graphql @@ -40,13 +40,16 @@ query ValidGalleriesForScene($scene_id: ID!) { validGalleriesForScene(scene_id: $scene_id) { id path + title } } query Stats { stats { scene_count, - scene_size_count, + scenes_size, + image_count, + images_size, gallery_count, performer_count, studio_count, diff --git a/graphql/documents/queries/performer.graphql b/graphql/documents/queries/performer.graphql index ead379f75..dec46bd2d 100644 --- a/graphql/documents/queries/performer.graphql +++ b/graphql/documents/queries/performer.graphql @@ -11,4 +11,4 @@ query FindPerformer($id: ID!) { findPerformer(id: $id) { ...PerformerData } -} \ No newline at end of file +} diff --git a/graphql/documents/queries/scrapers/scrapers.graphql b/graphql/documents/queries/scrapers/scrapers.graphql index 9904f31f5..bb9d99284 100644 --- a/graphql/documents/queries/scrapers/scrapers.graphql +++ b/graphql/documents/queries/scrapers/scrapers.graphql @@ -20,6 +20,17 @@ query ListSceneScrapers { } } +query ListGalleryScrapers { + listGalleryScrapers { + id + name + gallery { + urls + supported_scrapes + } + } +} + query ListMovieScrapers { listMovieScrapers { id @@ -61,8 +72,26 @@ query ScrapeSceneURL($url: String!) { } } +query ScrapeGallery($scraper_id: ID!, $gallery: GalleryUpdateInput!) { + scrapeGallery(scraper_id: $scraper_id, gallery: $gallery) { + ...ScrapedGalleryData + } +} + +query ScrapeGalleryURL($url: String!) { + scrapeGalleryURL(url: $url) { + ...ScrapedGalleryData + } +} + query ScrapeMovieURL($url: String!) { scrapeMovieURL(url: $url) { ...ScrapedMovieData } } + +query QueryStashBoxScene($input: StashBoxQueryInput!) { + queryStashBoxScene(input: $input) { + ...ScrapedStashBoxSceneData + } +} diff --git a/graphql/documents/queries/studio.graphql b/graphql/documents/queries/studio.graphql index f18f67e93..d999343d2 100644 --- a/graphql/documents/queries/studio.graphql +++ b/graphql/documents/queries/studio.graphql @@ -11,4 +11,4 @@ query FindStudio($id: ID!) { findStudio(id: $id) { ...StudioData } -} \ No newline at end of file +} diff --git a/graphql/schema/schema.graphql b/graphql/schema/schema.graphql index e2ebf0280..7ae7a95aa 100644 --- a/graphql/schema/schema.graphql +++ b/graphql/schema/schema.graphql @@ -17,6 +17,11 @@ type Query { """A function which queries SceneMarker objects""" findSceneMarkers(scene_marker_filter: SceneMarkerFilterType filter: FindFilterType): FindSceneMarkersResultType! + findImage(id: ID, checksum: String): Image + + """A function which queries Scene objects""" + findImages(image_filter: ImageFilterType, image_ids: [Int!], filter: FindFilterType): FindImagesResultType! + """Find a performer by ID""" findPerformer(id: ID!): Performer """A function which queries Performer objects""" @@ -59,6 +64,7 @@ type Query { """List available scrapers""" listPerformerScrapers: [Scraper!]! listSceneScrapers: [Scraper!]! + listGalleryScrapers: [Scraper!]! listMovieScrapers: [Scraper!]! """Scrape a list of performers based on name""" @@ -71,6 +77,10 @@ type Query { scrapeScene(scraper_id: ID!, scene: SceneUpdateInput!): ScrapedScene """Scrapes a complete performer record based on a URL""" scrapeSceneURL(url: String!): ScrapedScene + """Scrapes a complete gallery record based on an existing gallery""" + scrapeGallery(scraper_id: ID!, gallery: GalleryUpdateInput!): ScrapedGallery + """Scrapes a complete gallery record based on a URL""" + scrapeGalleryURL(url: String!): ScrapedGallery """Scrapes a complete movie record based on a URL""" scrapeMovieURL(url: String!): ScrapedMovie @@ -79,13 +89,15 @@ type Query { """Scrape a list of performers from a query""" scrapeFreeonesPerformerList(query: String!): [String!]! + """Query StashBox for scenes""" + queryStashBoxScene(input: StashBoxQueryInput!): [ScrapedScene!]! + # Plugins """List loaded plugins""" plugins: [Plugin!] """List available plugin operations""" pluginTasks: [PluginTask!] - # Config """Returns the current, complete configuration""" configuration: ConfigResult! @@ -138,6 +150,28 @@ type Mutation { sceneMarkerUpdate(input: SceneMarkerUpdateInput!): SceneMarker sceneMarkerDestroy(id: ID!): Boolean! + imageUpdate(input: ImageUpdateInput!): Image + bulkImageUpdate(input: BulkImageUpdateInput!): [Image!] + imageDestroy(input: ImageDestroyInput!): Boolean! + imagesDestroy(input: ImagesDestroyInput!): Boolean! + imagesUpdate(input: [ImageUpdateInput!]!): [Image] + + """Increments the o-counter for an image. Returns the new value""" + imageIncrementO(id: ID!): Int! + """Decrements the o-counter for an image. Returns the new value""" + imageDecrementO(id: ID!): Int! + """Resets the o-counter for a image to 0. Returns the new value""" + imageResetO(id: ID!): Int! + + galleryCreate(input: GalleryCreateInput!): Gallery + galleryUpdate(input: GalleryUpdateInput!): Gallery + bulkGalleryUpdate(input: BulkGalleryUpdateInput!): [Gallery!] + galleryDestroy(input: GalleryDestroyInput!): Boolean! + galleriesUpdate(input: [GalleryUpdateInput!]!): [Gallery] + + addGalleryImages(input: GalleryAddInput!): Boolean! + removeGalleryImages(input: GalleryRemoveInput!): Boolean! + performerCreate(input: PerformerCreateInput!): Performer performerUpdate(input: PerformerUpdateInput!): Performer performerDestroy(input: PerformerDestroyInput!): Boolean! @@ -158,9 +192,15 @@ type Mutation { configureGeneral(input: ConfigGeneralInput!): ConfigGeneralResult! configureInterface(input: ConfigInterfaceInput!): ConfigInterfaceResult! - """Start an import. Returns the job ID""" + """Returns a link to download the result""" + exportObjects(input: ExportObjectsInput!): String + + """Performs an incremental import. Returns the job ID""" + importObjects(input: ImportObjectsInput!): String! + + """Start an full import. Completely wipes the database and imports from the metadata directory. Returns the job ID""" metadataImport: String! - """Start an export. Returns the job ID""" + """Start a full export. Outputs to the metadata directory. Returns the job ID""" metadataExport: String! """Start a scan. Returns the job ID""" metadataScan(input: ScanMetadataInput!): String! @@ -181,6 +221,9 @@ type Mutation { reloadPlugins: Boolean! stopJob: Boolean! + + """ Submit fingerprints to stash-box instance """ + submitStashBoxFingerprints(input: StashBoxFingerprintSubmissionInput!): Boolean! } type Subscription { diff --git a/graphql/schema/types/config.graphql b/graphql/schema/types/config.graphql index 0803ca9d1..819977d24 100644 --- a/graphql/schema/types/config.graphql +++ b/graphql/schema/types/config.graphql @@ -24,7 +24,7 @@ enum HashAlgorithm { input ConfigGeneralInput { """Array of file paths to content""" - stashes: [String!] + stashes: [StashConfigInput!] """Path to the SQLite database""" databasePath: String """Path to generated files""" @@ -63,17 +63,29 @@ input ConfigGeneralInput { logLevel: String! """Whether to log http access""" logAccess: Boolean! - """Array of file regexp to exclude from Scan""" + """True if galleries should be created from folders with images""" + createGalleriesFromFolders: Boolean! + """Array of video file extensions""" + videoExtensions: [String!] + """Array of image file extensions""" + imageExtensions: [String!] + """Array of gallery zip file extensions""" + galleryExtensions: [String!] + """Array of file regexp to exclude from Video Scans""" excludes: [String!] + """Array of file regexp to exclude from Image Scans""" + imageExcludes: [String!] """Scraper user agent string""" scraperUserAgent: String """Scraper CDP path. Path to chrome executable or remote address""" scraperCDPPath: String + """Stash-box instances used for tagging""" + stashBoxes: [StashBoxInput!]! } type ConfigGeneralResult { """Array of file paths to content""" - stashes: [String!]! + stashes: [StashConfig!]! """Path to the SQLite database""" databasePath: String! """Path to generated files""" @@ -112,12 +124,24 @@ type ConfigGeneralResult { logLevel: String! """Whether to log http access""" logAccess: Boolean! - """Array of file regexp to exclude from Scan""" + """Array of video file extensions""" + videoExtensions: [String!]! + """Array of image file extensions""" + imageExtensions: [String!]! + """Array of gallery zip file extensions""" + galleryExtensions: [String!]! + """True if galleries should be created from folders with images""" + createGalleriesFromFolders: Boolean! + """Array of file regexp to exclude from Video Scans""" excludes: [String!]! + """Array of file regexp to exclude from Image Scans""" + imageExcludes: [String!]! """Scraper user agent string""" scraperUserAgent: String """Scraper CDP path. Path to chrome executable or remote address""" scraperCDPPath: String + """Stash-box instances used for tagging""" + stashBoxes: [StashBox!]! } input ConfigInterfaceInput { @@ -171,3 +195,16 @@ type Directory { parent: String directories: [String!]! } + +"""Stash configuration details""" +input StashConfigInput { + path: String! + excludeVideo: Boolean! + excludeImage: Boolean! +} + +type StashConfig { + path: String! + excludeVideo: Boolean! + excludeImage: Boolean! +} diff --git a/graphql/schema/types/filters.graphql b/graphql/schema/types/filters.graphql index cda7adb6d..5b911d784 100644 --- a/graphql/schema/types/filters.graphql +++ b/graphql/schema/types/filters.graphql @@ -50,6 +50,8 @@ input PerformerFilterType { gender: GenderCriterionInput """Filter to only include performers missing this property""" is_missing: String + """Filter by StashID""" + stash_id: String } input SceneMarkerFilterType { @@ -64,6 +66,8 @@ input SceneMarkerFilterType { } input SceneFilterType { + """Filter by path""" + path: StringCriterionInput """Filter by rating""" rating: IntCriterionInput """Filter by o-counter""" @@ -84,6 +88,8 @@ input SceneFilterType { tags: MultiCriterionInput """Filter to only include scenes with these performers""" performers: MultiCriterionInput + """Filter by StashID""" + stash_id: String } input MovieFilterType { @@ -96,13 +102,31 @@ input MovieFilterType { input StudioFilterType { """Filter to only include studios with this parent studio""" parents: MultiCriterionInput + """Filter by StashID""" + stash_id: String """Filter to only include studios missing this property""" is_missing: String } input GalleryFilterType { + """Filter by path""" + path: StringCriterionInput """Filter to only include galleries missing this property""" is_missing: String + """Filter to include/exclude galleries that were created from zip""" + is_zip: Boolean + """Filter by rating""" + rating: IntCriterionInput + """Filter by average image resolution""" + average_resolution: ResolutionEnum + """Filter to only include scenes with this studio""" + studios: MultiCriterionInput + """Filter to only include scenes with these tags""" + tags: MultiCriterionInput + """Filter to only include scenes with these performers""" + performers: MultiCriterionInput + """Filter by number of images in this gallery""" + image_count: IntCriterionInput } input TagFilterType { @@ -116,6 +140,27 @@ input TagFilterType { marker_count: IntCriterionInput } +input ImageFilterType { + """Filter by path""" + path: StringCriterionInput + """Filter by rating""" + rating: IntCriterionInput + """Filter by o-counter""" + o_counter: IntCriterionInput + """Filter by resolution""" + resolution: ResolutionEnum + """Filter to only include images missing this property""" + is_missing: String + """Filter to only include images with this studio""" + studios: MultiCriterionInput + """Filter to only include images with these tags""" + tags: MultiCriterionInput + """Filter to only include images with these performers""" + performers: MultiCriterionInput + """Filter to only include images with these galleries""" + galleries: MultiCriterionInput +} + enum CriterionModifier { """=""" EQUALS, @@ -153,4 +198,4 @@ input MultiCriterionInput { input GenderCriterionInput { value: GenderEnum modifier: CriterionModifier! -} \ No newline at end of file +} diff --git a/graphql/schema/types/gallery.graphql b/graphql/schema/types/gallery.graphql index e94be7be1..c39654a7e 100644 --- a/graphql/schema/types/gallery.graphql +++ b/graphql/schema/types/gallery.graphql @@ -2,12 +2,21 @@ type Gallery { id: ID! checksum: String! - path: String! + path: String title: String + url: String + date: String + details: String + rating: Int scene: Scene + studio: Studio + image_count: Int! + tags: [Tag!]! + performers: [Performer!]! - """The files in the gallery""" - files: [GalleryFilesType!]! # Resolver + """The images in the gallery""" + images: [Image!]! # Resolver + cover: Image } type GalleryFilesType { @@ -16,7 +25,62 @@ type GalleryFilesType { path: String } +input GalleryCreateInput { + title: String! + url: String + date: String + details: String + rating: Int + scene_id: ID + studio_id: ID + tag_ids: [ID!] + performer_ids: [ID!] +} + +input GalleryUpdateInput { + clientMutationId: String + id: ID! + title: String + url: String + date: String + details: String + rating: Int + scene_id: ID + studio_id: ID + tag_ids: [ID!] + performer_ids: [ID!] +} + +input BulkGalleryUpdateInput { + clientMutationId: String + ids: [ID!] + url: String + date: String + details: String + rating: Int + scene_id: ID + studio_id: ID + tag_ids: BulkUpdateIds + performer_ids: BulkUpdateIds +} + +input GalleryDestroyInput { + ids: [ID!]! + delete_file: Boolean + delete_generated: Boolean +} + type FindGalleriesResultType { count: Int! galleries: [Gallery!]! -} \ No newline at end of file +} + +input GalleryAddInput { + gallery_id: ID! + image_ids: [ID!]! +} + +input GalleryRemoveInput { + gallery_id: ID! + image_ids: [ID!]! +} diff --git a/graphql/schema/types/image.graphql b/graphql/schema/types/image.graphql new file mode 100644 index 000000000..efb90ffaf --- /dev/null +++ b/graphql/schema/types/image.graphql @@ -0,0 +1,68 @@ +type Image { + id: ID! + checksum: String + title: String + rating: Int + o_counter: Int + path: String! + + file: ImageFileType! # Resolver + paths: ImagePathsType! # Resolver + + galleries: [Gallery!]! + studio: Studio + tags: [Tag!]! + performers: [Performer!]! +} + +type ImageFileType { + size: Int + width: Int + height: Int +} + +type ImagePathsType { + thumbnail: String # Resolver + image: String # Resolver +} + +input ImageUpdateInput { + clientMutationId: String + id: ID! + title: String + rating: Int + + studio_id: ID + performer_ids: [ID!] + tag_ids: [ID!] + gallery_ids: [ID!] +} + +input BulkImageUpdateInput { + clientMutationId: String + ids: [ID!] + title: String + rating: Int + + studio_id: ID + performer_ids: BulkUpdateIds + tag_ids: BulkUpdateIds + gallery_ids: BulkUpdateIds +} + +input ImageDestroyInput { + id: ID! + delete_file: Boolean + delete_generated: Boolean +} + +input ImagesDestroyInput { + ids: [ID!]! + delete_file: Boolean + delete_generated: Boolean +} + +type FindImagesResultType { + count: Int! + images: [Image!]! +} \ No newline at end of file diff --git a/graphql/schema/types/metadata.graphql b/graphql/schema/types/metadata.graphql index 25a5b66f7..76d11de11 100644 --- a/graphql/schema/types/metadata.graphql +++ b/graphql/schema/types/metadata.graphql @@ -1,3 +1,5 @@ +scalar Upload + input GenerateMetadataInput { sprites: Boolean! previews: Boolean! @@ -5,15 +7,11 @@ input GenerateMetadataInput { previewOptions: GeneratePreviewOptionsInput markers: Boolean! transcodes: Boolean! - """gallery thumbnails for cache usage""" - thumbnails: Boolean! """scene ids to generate for""" sceneIDs: [ID!] """marker ids to generate for""" markerIDs: [ID!] - """gallery ids to generate for""" - galleryIDs: [ID!] """overwrite existing media""" overwrite: Boolean @@ -33,6 +31,7 @@ input GeneratePreviewOptionsInput { } input ScanMetadataInput { + paths: [String!] useFileMetadata: Boolean! } @@ -50,3 +49,37 @@ type MetadataUpdateStatus { status: String! message: String! } + +input ExportObjectTypeInput { + ids: [String!] + all: Boolean +} + +input ExportObjectsInput { + scenes: ExportObjectTypeInput + images: ExportObjectTypeInput + studios: ExportObjectTypeInput + performers: ExportObjectTypeInput + tags: ExportObjectTypeInput + movies: ExportObjectTypeInput + galleries: ExportObjectTypeInput + includeDependencies: Boolean +} + +enum ImportDuplicateEnum { + IGNORE + OVERWRITE + FAIL +} + +enum ImportMissingRefEnum { + IGNORE + FAIL + CREATE +} + +input ImportObjectsInput { + file: Upload! + duplicateBehaviour: ImportDuplicateEnum! + missingRefBehaviour: ImportMissingRefEnum! +} diff --git a/graphql/schema/types/performer.graphql b/graphql/schema/types/performer.graphql index d10b95b69..fd5c08fc4 100644 --- a/graphql/schema/types/performer.graphql +++ b/graphql/schema/types/performer.graphql @@ -31,10 +31,11 @@ type Performer { image_path: String # Resolver scene_count: Int # Resolver scenes: [Scene!]! + stash_ids: [StashID!]! } input PerformerCreateInput { - name: String + name: String! url: String gender: GenderEnum birthdate: String @@ -53,6 +54,7 @@ input PerformerCreateInput { favorite: Boolean """This should be base64 encoded""" image: String + stash_ids: [StashIDInput!] } input PerformerUpdateInput { @@ -76,6 +78,7 @@ input PerformerUpdateInput { favorite: Boolean """This should be base64 encoded""" image: String + stash_ids: [StashIDInput!] } input PerformerDestroyInput { @@ -85,4 +88,4 @@ input PerformerDestroyInput { type FindPerformersResultType { count: Int! performers: [Performer!]! -} \ No newline at end of file +} diff --git a/graphql/schema/types/scene.graphql b/graphql/schema/types/scene.graphql index 3844c6a8e..23bacf926 100644 --- a/graphql/schema/types/scene.graphql +++ b/graphql/schema/types/scene.graphql @@ -44,6 +44,7 @@ type Scene { movies: [SceneMovie!]! tags: [Tag!]! performers: [Performer!]! + stash_ids: [StashID!]! } input SceneMovieInput { @@ -66,6 +67,7 @@ input SceneUpdateInput { tag_ids: [ID!] """This should be base64 encoded""" cover_image: String + stash_ids: [StashIDInput!] } enum BulkUpdateIdMode { diff --git a/graphql/schema/types/scraper.graphql b/graphql/schema/types/scraper.graphql index 6a7dcaa0b..7066ac4b4 100644 --- a/graphql/schema/types/scraper.graphql +++ b/graphql/schema/types/scraper.graphql @@ -20,11 +20,12 @@ type Scraper { performer: ScraperSpec """Details for scene scraper""" scene: ScraperSpec + """Details for gallery scraper""" + gallery: ScraperSpec """Details for movie scraper""" movie: ScraperSpec } - type ScrapedScenePerformer { """Set if performer matched""" stored_id: ID @@ -44,6 +45,9 @@ type ScrapedScenePerformer { tattoos: String piercings: String aliases: String + + remote_site_id: String + images: [String!] } type ScrapedSceneMovie { @@ -64,6 +68,8 @@ type ScrapedSceneStudio { stored_id: ID name: String! url: String + + remote_site_id: String } type ScrapedSceneTag { @@ -87,4 +93,34 @@ type ScrapedScene { tags: [ScrapedSceneTag!] performers: [ScrapedScenePerformer!] movies: [ScrapedSceneMovie!] + + remote_site_id: String + duration: Int + fingerprints: [StashBoxFingerprint!] +} + +type ScrapedGallery { + title: String + details: String + url: String + date: String + + studio: ScrapedSceneStudio + tags: [ScrapedSceneTag!] + performers: [ScrapedScenePerformer!] +} + +input StashBoxQueryInput { + """Index of the configured stash-box instance to use""" + stash_box_index: Int! + """Instructs query by scene fingerprints""" + scene_ids: [ID!] + """Query by query string""" + q: String +} + +type StashBoxFingerprint { + algorithm: String! + hash: String! + duration: Int! } diff --git a/graphql/schema/types/stash-box.graphql b/graphql/schema/types/stash-box.graphql new file mode 100644 index 000000000..471db19b1 --- /dev/null +++ b/graphql/schema/types/stash-box.graphql @@ -0,0 +1,26 @@ +type StashBox { + endpoint: String! + api_key: String! + name: String! +} + +input StashBoxInput { + endpoint: String! + api_key: String! + name: String! +} + +type StashID { + endpoint: String! + stash_id: String! +} + +input StashIDInput { + endpoint: String! + stash_id: String! +} + +input StashBoxFingerprintSubmissionInput { + scene_ids: [String!]! + stash_box_index: Int! +} diff --git a/graphql/schema/types/stats.graphql b/graphql/schema/types/stats.graphql index d94086308..2ce9b6a76 100644 --- a/graphql/schema/types/stats.graphql +++ b/graphql/schema/types/stats.graphql @@ -1,6 +1,8 @@ type StatsResultType { scene_count: Int! - scene_size_count: String! + scenes_size: Int! + image_count: Int! + images_size: Int! gallery_count: Int! performer_count: Int! studio_count: Int! diff --git a/graphql/schema/types/studio.graphql b/graphql/schema/types/studio.graphql index 90309b8c5..051776e03 100644 --- a/graphql/schema/types/studio.graphql +++ b/graphql/schema/types/studio.graphql @@ -5,8 +5,10 @@ type Studio { url: String parent_studio: Studio child_studios: [Studio!]! + image_path: String # Resolver scene_count: Int # Resolver + stash_ids: [StashID!]! } input StudioCreateInput { @@ -15,6 +17,7 @@ input StudioCreateInput { parent_id: ID """This should be base64 encoded""" image: String + stash_ids: [StashIDInput!] } input StudioUpdateInput { @@ -24,6 +27,7 @@ input StudioUpdateInput { parent_id: ID, """This should be base64 encoded""" image: String + stash_ids: [StashIDInput!] } input StudioDestroyInput { @@ -33,4 +37,4 @@ input StudioDestroyInput { type FindStudiosResultType { count: Int! studios: [Studio!]! -} \ No newline at end of file +} diff --git a/graphql/stash-box/query.graphql b/graphql/stash-box/query.graphql new file mode 100644 index 000000000..4424ce8db --- /dev/null +++ b/graphql/stash-box/query.graphql @@ -0,0 +1,139 @@ +fragment URLFragment on URL { + url + type +} + +fragment ImageFragment on Image { + id + url + width + height +} + +fragment StudioFragment on Studio { + name + id + urls { + ...URLFragment + } + images { + ...ImageFragment + } +} + +fragment TagFragment on Tag { + name + id +} + +fragment FuzzyDateFragment on FuzzyDate { + date + accuracy +} + +fragment MeasurementsFragment on Measurements { + band_size + cup_size + waist + hip +} + +fragment BodyModificationFragment on BodyModification { + location + description +} + +fragment PerformerFragment on Performer { + id + name + disambiguation + aliases + gender + urls { + ...URLFragment + } + images { + ...ImageFragment + } + birthdate { + ...FuzzyDateFragment + } + ethnicity + country + eye_color + hair_color + height + measurements { + ...MeasurementsFragment + } + breast_type + career_start_year + career_end_year + tattoos { + ...BodyModificationFragment + } + piercings { + ...BodyModificationFragment + } +} + +fragment PerformerAppearanceFragment on PerformerAppearance { + as + performer { + ...PerformerFragment + } +} + +fragment FingerprintFragment on Fingerprint { + algorithm + hash + duration +} + +fragment SceneFragment on Scene { + id + title + details + duration + date + urls { + ...URLFragment + } + images { + ...ImageFragment + } + studio { + ...StudioFragment + } + tags { + ...TagFragment + } + performers { + ...PerformerAppearanceFragment + } + fingerprints { + ...FingerprintFragment + } +} + +query FindSceneByFingerprint($fingerprint: FingerprintQueryInput!) { + findSceneByFingerprint(fingerprint: $fingerprint) { + ...SceneFragment + } +} + +query FindScenesByFingerprints($fingerprints: [String!]!) { + findScenesByFingerprints(fingerprints: $fingerprints) { + ...SceneFragment + } +} + +query SearchScene($term: String!) { + searchScene(term: $term) { + ...SceneFragment + } +} + +mutation SubmitFingerprint($input: FingerprintSubmission!) { + submitFingerprint(input: $input) +} diff --git a/pkg/api/cache_thumbs.go b/pkg/api/cache_thumbs.go deleted file mode 100644 index 0bcbd616c..000000000 --- a/pkg/api/cache_thumbs.go +++ /dev/null @@ -1,72 +0,0 @@ -package api - -import ( - "github.com/stashapp/stash/pkg/logger" - "github.com/stashapp/stash/pkg/manager/paths" - "github.com/stashapp/stash/pkg/models" - "github.com/stashapp/stash/pkg/utils" - "io/ioutil" -) - -type thumbBuffer struct { - path string - dir string - data []byte -} - -func newCacheThumb(dir string, path string, data []byte) *thumbBuffer { - t := thumbBuffer{dir: dir, path: path, data: data} - return &t -} - -var writeChan chan *thumbBuffer -var touchChan chan *string - -func startThumbCache() { // TODO add extra wait, close chan code if/when stash gets a stop mode - - writeChan = make(chan *thumbBuffer, 20) - go thumbnailCacheWriter() -} - -//serialize file writes to avoid race conditions -func thumbnailCacheWriter() { - - for thumb := range writeChan { - exists, _ := utils.FileExists(thumb.path) - if !exists { - err := utils.WriteFile(thumb.path, thumb.data) - if err != nil { - logger.Errorf("Write error for thumbnail %s: %s ", thumb.path, err) - } - } - } - -} - -// get thumbnail from cache, otherwise create it and store to cache -func cacheGthumb(gallery *models.Gallery, index int, width int) []byte { - thumbPath := paths.GetGthumbPath(gallery.Checksum, index, width) - exists, _ := utils.FileExists(thumbPath) - if exists { // if thumbnail exists in cache return that - content, err := ioutil.ReadFile(thumbPath) - if err == nil { - return content - } else { - logger.Errorf("Read Error for file %s : %s", thumbPath, err) - } - - } - data := gallery.GetThumbnail(index, width) - thumbDir := paths.GetGthumbDir(gallery.Checksum) - t := newCacheThumb(thumbDir, thumbPath, data) - writeChan <- t // write the file to cache - return data -} - -// create all thumbs for a given gallery -func CreateGthumbs(gallery *models.Gallery) { - count := gallery.ImageCount() - for i := 0; i < count; i++ { - cacheGthumb(gallery, i, models.DefaultGthumbWidth) - } -} diff --git a/pkg/api/context_keys.go b/pkg/api/context_keys.go index 5b0581bef..95eb0fd6a 100644 --- a/pkg/api/context_keys.go +++ b/pkg/api/context_keys.go @@ -12,4 +12,6 @@ const ( movieKey key = 4 ContextUser key = 5 tagKey key = 6 + downloadKey key = 7 + imageKey key = 8 ) diff --git a/pkg/api/resolver.go b/pkg/api/resolver.go index 63ef59e87..0ef308a33 100644 --- a/pkg/api/resolver.go +++ b/pkg/api/resolver.go @@ -27,6 +27,9 @@ func (r *Resolver) Query() models.QueryResolver { func (r *Resolver) Scene() models.SceneResolver { return &sceneResolver{r} } +func (r *Resolver) Image() models.ImageResolver { + return &imageResolver{r} +} func (r *Resolver) SceneMarker() models.SceneMarkerResolver { return &sceneMarkerResolver{r} } @@ -67,6 +70,7 @@ type galleryResolver struct{ *Resolver } type performerResolver struct{ *Resolver } type sceneResolver struct{ *Resolver } type sceneMarkerResolver struct{ *Resolver } +type imageResolver struct{ *Resolver } type studioResolver struct{ *Resolver } type movieResolver struct{ *Resolver } type tagResolver struct{ *Resolver } @@ -113,7 +117,10 @@ func (r *queryResolver) ValidGalleriesForScene(ctx context.Context, scene_id *st func (r *queryResolver) Stats(ctx context.Context) (*models.StatsResultType, error) { scenesQB := models.NewSceneQueryBuilder() scenesCount, _ := scenesQB.Count() - scenesSizeCount, _ := scenesQB.SizeCount() + scenesSize, _ := scenesQB.Size() + imageQB := models.NewImageQueryBuilder() + imageCount, _ := imageQB.Count() + imageSize, _ := imageQB.Size() galleryQB := models.NewGalleryQueryBuilder() galleryCount, _ := galleryQB.Count() performersQB := models.NewPerformerQueryBuilder() @@ -126,7 +133,9 @@ func (r *queryResolver) Stats(ctx context.Context) (*models.StatsResultType, err tagsCount, _ := tagsQB.Count() return &models.StatsResultType{ SceneCount: scenesCount, - SceneSizeCount: scenesSizeCount, + ScenesSize: int(scenesSize), + ImageCount: imageCount, + ImagesSize: int(imageSize), GalleryCount: galleryCount, PerformerCount: performersCount, StudioCount: studiosCount, diff --git a/pkg/api/resolver_model_gallery.go b/pkg/api/resolver_model_gallery.go index ebee45420..0d73f8219 100644 --- a/pkg/api/resolver_model_gallery.go +++ b/pkg/api/resolver_model_gallery.go @@ -3,16 +3,82 @@ package api import ( "context" + "github.com/stashapp/stash/pkg/image" "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/utils" ) -func (r *galleryResolver) Title(ctx context.Context, obj *models.Gallery) (*string, error) { - return nil, nil // TODO remove this from schema +func (r *galleryResolver) Path(ctx context.Context, obj *models.Gallery) (*string, error) { + if obj.Path.Valid { + return &obj.Path.String, nil + } + return nil, nil } -func (r *galleryResolver) Files(ctx context.Context, obj *models.Gallery) ([]*models.GalleryFilesType, error) { - baseURL, _ := ctx.Value(BaseURLCtxKey).(string) - return obj.GetFiles(baseURL), nil +func (r *galleryResolver) Title(ctx context.Context, obj *models.Gallery) (*string, error) { + if obj.Title.Valid { + return &obj.Title.String, nil + } + return nil, nil +} + +func (r *galleryResolver) Images(ctx context.Context, obj *models.Gallery) ([]*models.Image, error) { + qb := models.NewImageQueryBuilder() + + return qb.FindByGalleryID(obj.ID) +} + +func (r *galleryResolver) Cover(ctx context.Context, obj *models.Gallery) (*models.Image, error) { + qb := models.NewImageQueryBuilder() + + imgs, err := qb.FindByGalleryID(obj.ID) + if err != nil { + return nil, err + } + + var ret *models.Image + if len(imgs) > 0 { + ret = imgs[0] + } + + for _, img := range imgs { + if image.IsCover(img) { + ret = img + break + } + } + + return ret, nil +} + +func (r *galleryResolver) Date(ctx context.Context, obj *models.Gallery) (*string, error) { + if obj.Date.Valid { + result := utils.GetYMDFromDatabaseDate(obj.Date.String) + return &result, nil + } + return nil, nil +} + +func (r *galleryResolver) URL(ctx context.Context, obj *models.Gallery) (*string, error) { + if obj.URL.Valid { + return &obj.URL.String, nil + } + return nil, nil +} + +func (r *galleryResolver) Details(ctx context.Context, obj *models.Gallery) (*string, error) { + if obj.Details.Valid { + return &obj.Details.String, nil + } + return nil, nil +} + +func (r *galleryResolver) Rating(ctx context.Context, obj *models.Gallery) (*int, error) { + if obj.Rating.Valid { + rating := int(obj.Rating.Int64) + return &rating, nil + } + return nil, nil } func (r *galleryResolver) Scene(ctx context.Context, obj *models.Gallery) (*models.Scene, error) { @@ -23,3 +89,27 @@ func (r *galleryResolver) Scene(ctx context.Context, obj *models.Gallery) (*mode qb := models.NewSceneQueryBuilder() return qb.Find(int(obj.SceneID.Int64)) } + +func (r *galleryResolver) Studio(ctx context.Context, obj *models.Gallery) (*models.Studio, error) { + if !obj.StudioID.Valid { + return nil, nil + } + + qb := models.NewStudioQueryBuilder() + return qb.Find(int(obj.StudioID.Int64), nil) +} + +func (r *galleryResolver) Tags(ctx context.Context, obj *models.Gallery) ([]*models.Tag, error) { + qb := models.NewTagQueryBuilder() + return qb.FindByGalleryID(obj.ID, nil) +} + +func (r *galleryResolver) Performers(ctx context.Context, obj *models.Gallery) ([]*models.Performer, error) { + qb := models.NewPerformerQueryBuilder() + return qb.FindByGalleryID(obj.ID, nil) +} + +func (r *galleryResolver) ImageCount(ctx context.Context, obj *models.Gallery) (int, error) { + qb := models.NewImageQueryBuilder() + return qb.CountByGalleryID(obj.ID) +} diff --git a/pkg/api/resolver_model_image.go b/pkg/api/resolver_model_image.go new file mode 100644 index 000000000..6ca679f89 --- /dev/null +++ b/pkg/api/resolver_model_image.go @@ -0,0 +1,68 @@ +package api + +import ( + "context" + + "github.com/stashapp/stash/pkg/api/urlbuilders" + "github.com/stashapp/stash/pkg/image" + "github.com/stashapp/stash/pkg/models" +) + +func (r *imageResolver) Title(ctx context.Context, obj *models.Image) (*string, error) { + ret := image.GetTitle(obj) + return &ret, nil +} + +func (r *imageResolver) Rating(ctx context.Context, obj *models.Image) (*int, error) { + if obj.Rating.Valid { + rating := int(obj.Rating.Int64) + return &rating, nil + } + return nil, nil +} + +func (r *imageResolver) File(ctx context.Context, obj *models.Image) (*models.ImageFileType, error) { + width := int(obj.Width.Int64) + height := int(obj.Height.Int64) + size := int(obj.Size.Int64) + return &models.ImageFileType{ + Size: &size, + Width: &width, + Height: &height, + }, nil +} + +func (r *imageResolver) Paths(ctx context.Context, obj *models.Image) (*models.ImagePathsType, error) { + baseURL, _ := ctx.Value(BaseURLCtxKey).(string) + builder := urlbuilders.NewImageURLBuilder(baseURL, obj.ID) + thumbnailPath := builder.GetThumbnailURL() + imagePath := builder.GetImageURL() + return &models.ImagePathsType{ + Image: &imagePath, + Thumbnail: &thumbnailPath, + }, nil +} + +func (r *imageResolver) Galleries(ctx context.Context, obj *models.Image) ([]*models.Gallery, error) { + qb := models.NewGalleryQueryBuilder() + return qb.FindByImageID(obj.ID, nil) +} + +func (r *imageResolver) Studio(ctx context.Context, obj *models.Image) (*models.Studio, error) { + if !obj.StudioID.Valid { + return nil, nil + } + + qb := models.NewStudioQueryBuilder() + return qb.Find(int(obj.StudioID.Int64), nil) +} + +func (r *imageResolver) Tags(ctx context.Context, obj *models.Image) ([]*models.Tag, error) { + qb := models.NewTagQueryBuilder() + return qb.FindByImageID(obj.ID, nil) +} + +func (r *imageResolver) Performers(ctx context.Context, obj *models.Image) ([]*models.Performer, error) { + qb := models.NewPerformerQueryBuilder() + return qb.FindByImageID(obj.ID, nil) +} diff --git a/pkg/api/resolver_model_performer.go b/pkg/api/resolver_model_performer.go index 29a4d2d90..c322e4943 100644 --- a/pkg/api/resolver_model_performer.go +++ b/pkg/api/resolver_model_performer.go @@ -148,3 +148,8 @@ func (r *performerResolver) Scenes(ctx context.Context, obj *models.Performer) ( qb := models.NewSceneQueryBuilder() return qb.FindByPerformerID(obj.ID) } + +func (r *performerResolver) StashIds(ctx context.Context, obj *models.Performer) ([]*models.StashID, error) { + qb := models.NewJoinsQueryBuilder() + return qb.GetPerformerStashIDs(obj.ID) +} diff --git a/pkg/api/resolver_model_scene.go b/pkg/api/resolver_model_scene.go index 9d2c26e3b..656e2ef66 100644 --- a/pkg/api/resolver_model_scene.go +++ b/pkg/api/resolver_model_scene.go @@ -150,3 +150,8 @@ func (r *sceneResolver) Performers(ctx context.Context, obj *models.Scene) ([]*m qb := models.NewPerformerQueryBuilder() return qb.FindBySceneID(obj.ID, nil) } + +func (r *sceneResolver) StashIds(ctx context.Context, obj *models.Scene) ([]*models.StashID, error) { + qb := models.NewJoinsQueryBuilder() + return qb.GetSceneStashIDs(obj.ID) +} diff --git a/pkg/api/resolver_model_studio.go b/pkg/api/resolver_model_studio.go index f068fa12c..cfe7d5cc5 100644 --- a/pkg/api/resolver_model_studio.go +++ b/pkg/api/resolver_model_studio.go @@ -46,3 +46,8 @@ func (r *studioResolver) ChildStudios(ctx context.Context, obj *models.Studio) ( qb := models.NewStudioQueryBuilder() return qb.FindChildren(obj.ID, nil) } + +func (r *studioResolver) StashIds(ctx context.Context, obj *models.Studio) ([]*models.StashID, error) { + qb := models.NewJoinsQueryBuilder() + return qb.GetStudioStashIDs(obj.ID) +} diff --git a/pkg/api/resolver_mutation_configure.go b/pkg/api/resolver_mutation_configure.go index edeb7f011..c95eb17ef 100644 --- a/pkg/api/resolver_mutation_configure.go +++ b/pkg/api/resolver_mutation_configure.go @@ -15,8 +15,8 @@ import ( func (r *mutationResolver) ConfigureGeneral(ctx context.Context, input models.ConfigGeneralInput) (*models.ConfigGeneralResult, error) { if len(input.Stashes) > 0 { - for _, stashPath := range input.Stashes { - exists, err := utils.DirExists(stashPath) + for _, s := range input.Stashes { + exists, err := utils.DirExists(s.Path) if !exists { return makeConfigGeneralResult(), err } @@ -119,6 +119,24 @@ func (r *mutationResolver) ConfigureGeneral(ctx context.Context, input models.Co config.Set(config.Exclude, input.Excludes) } + if input.ImageExcludes != nil { + config.Set(config.ImageExclude, input.ImageExcludes) + } + + if input.VideoExtensions != nil { + config.Set(config.VideoExtensions, input.VideoExtensions) + } + + if input.ImageExtensions != nil { + config.Set(config.ImageExtensions, input.ImageExtensions) + } + + if input.GalleryExtensions != nil { + config.Set(config.GalleryExtensions, input.GalleryExtensions) + } + + config.Set(config.CreateGalleriesFromFolders, input.CreateGalleriesFromFolders) + refreshScraperCache := false if input.ScraperUserAgent != nil { config.Set(config.ScraperUserAgent, input.ScraperUserAgent) @@ -130,6 +148,13 @@ func (r *mutationResolver) ConfigureGeneral(ctx context.Context, input models.Co refreshScraperCache = true } + if input.StashBoxes != nil { + if err := config.ValidateStashBoxes(input.StashBoxes); err != nil { + return nil, err + } + config.Set(config.StashBoxes, input.StashBoxes) + } + if err := config.Write(); err != nil { return makeConfigGeneralResult(), err } diff --git a/pkg/api/resolver_mutation_gallery.go b/pkg/api/resolver_mutation_gallery.go new file mode 100644 index 000000000..77b48d09d --- /dev/null +++ b/pkg/api/resolver_mutation_gallery.go @@ -0,0 +1,576 @@ +package api + +import ( + "context" + "database/sql" + "errors" + "strconv" + "time" + + "github.com/jmoiron/sqlx" + "github.com/stashapp/stash/pkg/database" + "github.com/stashapp/stash/pkg/manager" + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/utils" +) + +func (r *mutationResolver) GalleryCreate(ctx context.Context, input models.GalleryCreateInput) (*models.Gallery, error) { + // name must be provided + if input.Title == "" { + return nil, errors.New("title must not be empty") + } + + // for manually created galleries, generate checksum from title + checksum := utils.MD5FromString(input.Title) + + // Populate a new performer from the input + currentTime := time.Now() + newGallery := models.Gallery{ + Title: sql.NullString{ + String: input.Title, + Valid: true, + }, + Checksum: checksum, + CreatedAt: models.SQLiteTimestamp{Timestamp: currentTime}, + UpdatedAt: models.SQLiteTimestamp{Timestamp: currentTime}, + } + if input.URL != nil { + newGallery.URL = sql.NullString{String: *input.URL, Valid: true} + } + if input.Details != nil { + newGallery.Details = sql.NullString{String: *input.Details, Valid: true} + } + if input.URL != nil { + newGallery.URL = sql.NullString{String: *input.URL, Valid: true} + } + if input.Date != nil { + newGallery.Date = models.SQLiteDate{String: *input.Date, Valid: true} + } + if input.Rating != nil { + newGallery.Rating = sql.NullInt64{Int64: int64(*input.Rating), Valid: true} + } else { + // rating must be nullable + newGallery.Rating = sql.NullInt64{Valid: false} + } + + if input.StudioID != nil { + studioID, _ := strconv.ParseInt(*input.StudioID, 10, 64) + newGallery.StudioID = sql.NullInt64{Int64: studioID, Valid: true} + } else { + // studio must be nullable + newGallery.StudioID = sql.NullInt64{Valid: false} + } + + if input.SceneID != nil { + sceneID, _ := strconv.ParseInt(*input.SceneID, 10, 64) + newGallery.SceneID = sql.NullInt64{Int64: sceneID, Valid: true} + } else { + // studio must be nullable + newGallery.SceneID = sql.NullInt64{Valid: false} + } + + // Start the transaction and save the performer + tx := database.DB.MustBeginTx(ctx, nil) + qb := models.NewGalleryQueryBuilder() + jqb := models.NewJoinsQueryBuilder() + gallery, err := qb.Create(newGallery, tx) + if err != nil { + _ = tx.Rollback() + return nil, err + } + + // Save the performers + var performerJoins []models.PerformersGalleries + for _, pid := range input.PerformerIds { + performerID, _ := strconv.Atoi(pid) + performerJoin := models.PerformersGalleries{ + PerformerID: performerID, + GalleryID: gallery.ID, + } + performerJoins = append(performerJoins, performerJoin) + } + if err := jqb.UpdatePerformersGalleries(gallery.ID, performerJoins, tx); err != nil { + return nil, err + } + + // Save the tags + var tagJoins []models.GalleriesTags + for _, tid := range input.TagIds { + tagID, _ := strconv.Atoi(tid) + tagJoin := models.GalleriesTags{ + GalleryID: gallery.ID, + TagID: tagID, + } + tagJoins = append(tagJoins, tagJoin) + } + if err := jqb.UpdateGalleriesTags(gallery.ID, tagJoins, tx); err != nil { + return nil, err + } + + // Commit + if err := tx.Commit(); err != nil { + return nil, err + } + + return gallery, nil +} + +func (r *mutationResolver) GalleryUpdate(ctx context.Context, input models.GalleryUpdateInput) (*models.Gallery, error) { + // Start the transaction and save the gallery + tx := database.DB.MustBeginTx(ctx, nil) + + ret, err := r.galleryUpdate(input, tx) + + if err != nil { + _ = tx.Rollback() + return nil, err + } + + // Commit + if err := tx.Commit(); err != nil { + return nil, err + } + + return ret, nil +} + +func (r *mutationResolver) GalleriesUpdate(ctx context.Context, input []*models.GalleryUpdateInput) ([]*models.Gallery, error) { + // Start the transaction and save the gallery + tx := database.DB.MustBeginTx(ctx, nil) + + var ret []*models.Gallery + + for _, gallery := range input { + thisGallery, err := r.galleryUpdate(*gallery, tx) + ret = append(ret, thisGallery) + + if err != nil { + _ = tx.Rollback() + return nil, err + } + } + + // Commit + if err := tx.Commit(); err != nil { + return nil, err + } + + return ret, nil +} + +func (r *mutationResolver) galleryUpdate(input models.GalleryUpdateInput, tx *sqlx.Tx) (*models.Gallery, error) { + qb := models.NewGalleryQueryBuilder() + // Populate gallery from the input + galleryID, _ := strconv.Atoi(input.ID) + originalGallery, err := qb.Find(galleryID, nil) + if err != nil { + return nil, err + } + + if originalGallery == nil { + return nil, errors.New("not found") + } + + updatedTime := time.Now() + updatedGallery := models.GalleryPartial{ + ID: galleryID, + UpdatedAt: &models.SQLiteTimestamp{Timestamp: updatedTime}, + } + if input.Title != nil { + // ensure title is not empty + if *input.Title == "" { + return nil, errors.New("title must not be empty") + } + + // if gallery is not zip-based, then generate the checksum from the title + if !originalGallery.Path.Valid { + checksum := utils.MD5FromString(*input.Title) + updatedGallery.Checksum = &checksum + } + + updatedGallery.Title = &sql.NullString{String: *input.Title, Valid: true} + } + if input.Details != nil { + updatedGallery.Details = &sql.NullString{String: *input.Details, Valid: true} + } + if input.URL != nil { + updatedGallery.URL = &sql.NullString{String: *input.URL, Valid: true} + } + if input.Date != nil { + updatedGallery.Date = &models.SQLiteDate{String: *input.Date, Valid: true} + } + + if input.Rating != nil { + updatedGallery.Rating = &sql.NullInt64{Int64: int64(*input.Rating), Valid: true} + } else { + // rating must be nullable + updatedGallery.Rating = &sql.NullInt64{Valid: false} + } + + if input.StudioID != nil { + studioID, _ := strconv.ParseInt(*input.StudioID, 10, 64) + updatedGallery.StudioID = &sql.NullInt64{Int64: studioID, Valid: true} + } else { + // studio must be nullable + updatedGallery.StudioID = &sql.NullInt64{Valid: false} + } + + // gallery scene is set from the scene only + + jqb := models.NewJoinsQueryBuilder() + gallery, err := qb.UpdatePartial(updatedGallery, tx) + if err != nil { + return nil, err + } + + // Save the performers + var performerJoins []models.PerformersGalleries + for _, pid := range input.PerformerIds { + performerID, _ := strconv.Atoi(pid) + performerJoin := models.PerformersGalleries{ + PerformerID: performerID, + GalleryID: galleryID, + } + performerJoins = append(performerJoins, performerJoin) + } + if err := jqb.UpdatePerformersGalleries(galleryID, performerJoins, tx); err != nil { + return nil, err + } + + // Save the tags + var tagJoins []models.GalleriesTags + for _, tid := range input.TagIds { + tagID, _ := strconv.Atoi(tid) + tagJoin := models.GalleriesTags{ + GalleryID: galleryID, + TagID: tagID, + } + tagJoins = append(tagJoins, tagJoin) + } + if err := jqb.UpdateGalleriesTags(galleryID, tagJoins, tx); err != nil { + return nil, err + } + + return gallery, nil +} + +func (r *mutationResolver) BulkGalleryUpdate(ctx context.Context, input models.BulkGalleryUpdateInput) ([]*models.Gallery, error) { + // Populate gallery from the input + updatedTime := time.Now() + + // Start the transaction and save the gallery marker + tx := database.DB.MustBeginTx(ctx, nil) + qb := models.NewGalleryQueryBuilder() + jqb := models.NewJoinsQueryBuilder() + + updatedGallery := models.GalleryPartial{ + UpdatedAt: &models.SQLiteTimestamp{Timestamp: updatedTime}, + } + if input.Details != nil { + updatedGallery.Details = &sql.NullString{String: *input.Details, Valid: true} + } + if input.URL != nil { + updatedGallery.URL = &sql.NullString{String: *input.URL, Valid: true} + } + if input.Date != nil { + updatedGallery.Date = &models.SQLiteDate{String: *input.Date, Valid: true} + } + if input.Rating != nil { + // a rating of 0 means unset the rating + if *input.Rating == 0 { + updatedGallery.Rating = &sql.NullInt64{Int64: 0, Valid: false} + } else { + updatedGallery.Rating = &sql.NullInt64{Int64: int64(*input.Rating), Valid: true} + } + } + if input.StudioID != nil { + // empty string means unset the studio + if *input.StudioID == "" { + updatedGallery.StudioID = &sql.NullInt64{Int64: 0, Valid: false} + } else { + studioID, _ := strconv.ParseInt(*input.StudioID, 10, 64) + updatedGallery.StudioID = &sql.NullInt64{Int64: studioID, Valid: true} + } + } + if input.SceneID != nil { + // empty string means unset the studio + if *input.SceneID == "" { + updatedGallery.SceneID = &sql.NullInt64{Int64: 0, Valid: false} + } else { + sceneID, _ := strconv.ParseInt(*input.SceneID, 10, 64) + updatedGallery.SceneID = &sql.NullInt64{Int64: sceneID, Valid: true} + } + } + + ret := []*models.Gallery{} + + for _, galleryIDStr := range input.Ids { + galleryID, _ := strconv.Atoi(galleryIDStr) + updatedGallery.ID = galleryID + + gallery, err := qb.UpdatePartial(updatedGallery, tx) + if err != nil { + _ = tx.Rollback() + return nil, err + } + + ret = append(ret, gallery) + + // Save the performers + if wasFieldIncluded(ctx, "performer_ids") { + performerIDs, err := adjustGalleryPerformerIDs(tx, galleryID, *input.PerformerIds) + if err != nil { + _ = tx.Rollback() + return nil, err + } + + var performerJoins []models.PerformersGalleries + for _, performerID := range performerIDs { + performerJoin := models.PerformersGalleries{ + PerformerID: performerID, + GalleryID: galleryID, + } + performerJoins = append(performerJoins, performerJoin) + } + if err := jqb.UpdatePerformersGalleries(galleryID, performerJoins, tx); err != nil { + _ = tx.Rollback() + return nil, err + } + } + + // Save the tags + if wasFieldIncluded(ctx, "tag_ids") { + tagIDs, err := adjustGalleryTagIDs(tx, galleryID, *input.TagIds) + if err != nil { + _ = tx.Rollback() + return nil, err + } + + var tagJoins []models.GalleriesTags + for _, tagID := range tagIDs { + tagJoin := models.GalleriesTags{ + GalleryID: galleryID, + TagID: tagID, + } + tagJoins = append(tagJoins, tagJoin) + } + if err := jqb.UpdateGalleriesTags(galleryID, tagJoins, tx); err != nil { + _ = tx.Rollback() + return nil, err + } + } + } + + // Commit + if err := tx.Commit(); err != nil { + return nil, err + } + + return ret, nil +} + +func adjustGalleryPerformerIDs(tx *sqlx.Tx, galleryID int, ids models.BulkUpdateIds) ([]int, error) { + var ret []int + + jqb := models.NewJoinsQueryBuilder() + if ids.Mode == models.BulkUpdateIDModeAdd || ids.Mode == models.BulkUpdateIDModeRemove { + // adding to the joins + performerJoins, err := jqb.GetGalleryPerformers(galleryID, tx) + + if err != nil { + return nil, err + } + + for _, join := range performerJoins { + ret = append(ret, join.PerformerID) + } + } + + return adjustIDs(ret, ids), nil +} + +func adjustGalleryTagIDs(tx *sqlx.Tx, galleryID int, ids models.BulkUpdateIds) ([]int, error) { + var ret []int + + jqb := models.NewJoinsQueryBuilder() + if ids.Mode == models.BulkUpdateIDModeAdd || ids.Mode == models.BulkUpdateIDModeRemove { + // adding to the joins + tagJoins, err := jqb.GetGalleryTags(galleryID, tx) + + if err != nil { + return nil, err + } + + for _, join := range tagJoins { + ret = append(ret, join.TagID) + } + } + + return adjustIDs(ret, ids), nil +} + +func (r *mutationResolver) GalleryDestroy(ctx context.Context, input models.GalleryDestroyInput) (bool, error) { + qb := models.NewGalleryQueryBuilder() + iqb := models.NewImageQueryBuilder() + tx := database.DB.MustBeginTx(ctx, nil) + + var galleries []*models.Gallery + var imgsToPostProcess []*models.Image + var imgsToDelete []*models.Image + + for _, id := range input.Ids { + galleryID, _ := strconv.Atoi(id) + + gallery, err := qb.Find(galleryID, tx) + if gallery != nil { + galleries = append(galleries, gallery) + } + err = qb.Destroy(galleryID, tx) + + if err != nil { + tx.Rollback() + return false, err + } + + // if this is a zip-based gallery, delete the images as well + if gallery.Zip { + imgs, err := iqb.FindByGalleryID(galleryID) + if err != nil { + tx.Rollback() + return false, err + } + + for _, img := range imgs { + err = iqb.Destroy(img.ID, tx) + if err != nil { + tx.Rollback() + return false, err + } + + imgsToPostProcess = append(imgsToPostProcess, img) + } + } else if input.DeleteFile != nil && *input.DeleteFile { + // Delete image if it is only attached to this gallery + imgs, err := iqb.FindByGalleryID(galleryID) + if err != nil { + tx.Rollback() + return false, err + } + + for _, img := range imgs { + imgGalleries, err := qb.FindByImageID(img.ID, tx) + if err != nil { + tx.Rollback() + return false, err + } + + if len(imgGalleries) == 0 { + err = iqb.Destroy(img.ID, tx) + if err != nil { + tx.Rollback() + return false, err + } + + imgsToDelete = append(imgsToDelete, img) + imgsToPostProcess = append(imgsToPostProcess, img) + } + } + } + } + + if err := tx.Commit(); err != nil { + return false, err + } + + // if delete file is true, then delete the file as well + // if it fails, just log a message + if input.DeleteFile != nil && *input.DeleteFile { + for _, gallery := range galleries { + manager.DeleteGalleryFile(gallery) + } + + for _, img := range imgsToDelete { + manager.DeleteImageFile(img) + } + } + + // if delete generated is true, then delete the generated files + // for the gallery + if input.DeleteGenerated != nil && *input.DeleteGenerated { + for _, img := range imgsToPostProcess { + manager.DeleteGeneratedImageFiles(img) + } + } + + return true, nil +} + +func (r *mutationResolver) AddGalleryImages(ctx context.Context, input models.GalleryAddInput) (bool, error) { + galleryID, _ := strconv.Atoi(input.GalleryID) + qb := models.NewGalleryQueryBuilder() + gallery, err := qb.Find(galleryID, nil) + if err != nil { + return false, err + } + + if gallery == nil { + return false, errors.New("gallery not found") + } + + if gallery.Zip { + return false, errors.New("cannot modify zip gallery images") + } + + jqb := models.NewJoinsQueryBuilder() + tx := database.DB.MustBeginTx(ctx, nil) + + for _, id := range input.ImageIds { + imageID, _ := strconv.Atoi(id) + _, err := jqb.AddImageGallery(imageID, galleryID, tx) + if err != nil { + tx.Rollback() + return false, err + } + } + + if err := tx.Commit(); err != nil { + return false, err + } + + return true, nil +} + +func (r *mutationResolver) RemoveGalleryImages(ctx context.Context, input models.GalleryRemoveInput) (bool, error) { + galleryID, _ := strconv.Atoi(input.GalleryID) + qb := models.NewGalleryQueryBuilder() + gallery, err := qb.Find(galleryID, nil) + if err != nil { + return false, err + } + + if gallery == nil { + return false, errors.New("gallery not found") + } + + if gallery.Zip { + return false, errors.New("cannot modify zip gallery images") + } + + jqb := models.NewJoinsQueryBuilder() + tx := database.DB.MustBeginTx(ctx, nil) + + for _, id := range input.ImageIds { + imageID, _ := strconv.Atoi(id) + _, err := jqb.RemoveImageGallery(imageID, galleryID, tx) + if err != nil { + tx.Rollback() + return false, err + } + } + + if err := tx.Commit(); err != nil { + return false, err + } + + return true, nil +} diff --git a/pkg/api/resolver_mutation_image.go b/pkg/api/resolver_mutation_image.go new file mode 100644 index 000000000..a71c617f1 --- /dev/null +++ b/pkg/api/resolver_mutation_image.go @@ -0,0 +1,439 @@ +package api + +import ( + "context" + "database/sql" + "strconv" + "time" + + "github.com/jmoiron/sqlx" + + "github.com/stashapp/stash/pkg/database" + "github.com/stashapp/stash/pkg/manager" + "github.com/stashapp/stash/pkg/models" +) + +func (r *mutationResolver) ImageUpdate(ctx context.Context, input models.ImageUpdateInput) (*models.Image, error) { + // Start the transaction and save the image + tx := database.DB.MustBeginTx(ctx, nil) + + ret, err := r.imageUpdate(input, tx) + + if err != nil { + _ = tx.Rollback() + return nil, err + } + + // Commit + if err := tx.Commit(); err != nil { + return nil, err + } + + return ret, nil +} + +func (r *mutationResolver) ImagesUpdate(ctx context.Context, input []*models.ImageUpdateInput) ([]*models.Image, error) { + // Start the transaction and save the image + tx := database.DB.MustBeginTx(ctx, nil) + + var ret []*models.Image + + for _, image := range input { + thisImage, err := r.imageUpdate(*image, tx) + ret = append(ret, thisImage) + + if err != nil { + _ = tx.Rollback() + return nil, err + } + } + + // Commit + if err := tx.Commit(); err != nil { + return nil, err + } + + return ret, nil +} + +func (r *mutationResolver) imageUpdate(input models.ImageUpdateInput, tx *sqlx.Tx) (*models.Image, error) { + // Populate image from the input + imageID, _ := strconv.Atoi(input.ID) + + updatedTime := time.Now() + updatedImage := models.ImagePartial{ + ID: imageID, + UpdatedAt: &models.SQLiteTimestamp{Timestamp: updatedTime}, + } + if input.Title != nil { + updatedImage.Title = &sql.NullString{String: *input.Title, Valid: true} + } + + if input.Rating != nil { + updatedImage.Rating = &sql.NullInt64{Int64: int64(*input.Rating), Valid: true} + } else { + // rating must be nullable + updatedImage.Rating = &sql.NullInt64{Valid: false} + } + + if input.StudioID != nil { + studioID, _ := strconv.ParseInt(*input.StudioID, 10, 64) + updatedImage.StudioID = &sql.NullInt64{Int64: studioID, Valid: true} + } else { + // studio must be nullable + updatedImage.StudioID = &sql.NullInt64{Valid: false} + } + + qb := models.NewImageQueryBuilder() + jqb := models.NewJoinsQueryBuilder() + image, err := qb.Update(updatedImage, tx) + if err != nil { + return nil, err + } + + // don't set the galleries directly. Use add/remove gallery images interface instead + + // Save the performers + var performerJoins []models.PerformersImages + for _, pid := range input.PerformerIds { + performerID, _ := strconv.Atoi(pid) + performerJoin := models.PerformersImages{ + PerformerID: performerID, + ImageID: imageID, + } + performerJoins = append(performerJoins, performerJoin) + } + if err := jqb.UpdatePerformersImages(imageID, performerJoins, tx); err != nil { + return nil, err + } + + // Save the tags + var tagJoins []models.ImagesTags + for _, tid := range input.TagIds { + tagID, _ := strconv.Atoi(tid) + tagJoin := models.ImagesTags{ + ImageID: imageID, + TagID: tagID, + } + tagJoins = append(tagJoins, tagJoin) + } + if err := jqb.UpdateImagesTags(imageID, tagJoins, tx); err != nil { + return nil, err + } + + return image, nil +} + +func (r *mutationResolver) BulkImageUpdate(ctx context.Context, input models.BulkImageUpdateInput) ([]*models.Image, error) { + // Populate image from the input + updatedTime := time.Now() + + // Start the transaction and save the image marker + tx := database.DB.MustBeginTx(ctx, nil) + qb := models.NewImageQueryBuilder() + jqb := models.NewJoinsQueryBuilder() + + updatedImage := models.ImagePartial{ + UpdatedAt: &models.SQLiteTimestamp{Timestamp: updatedTime}, + } + if input.Title != nil { + updatedImage.Title = &sql.NullString{String: *input.Title, Valid: true} + } + if input.Rating != nil { + // a rating of 0 means unset the rating + if *input.Rating == 0 { + updatedImage.Rating = &sql.NullInt64{Int64: 0, Valid: false} + } else { + updatedImage.Rating = &sql.NullInt64{Int64: int64(*input.Rating), Valid: true} + } + } + if input.StudioID != nil { + // empty string means unset the studio + if *input.StudioID == "" { + updatedImage.StudioID = &sql.NullInt64{Int64: 0, Valid: false} + } else { + studioID, _ := strconv.ParseInt(*input.StudioID, 10, 64) + updatedImage.StudioID = &sql.NullInt64{Int64: studioID, Valid: true} + } + } + + ret := []*models.Image{} + + for _, imageIDStr := range input.Ids { + imageID, _ := strconv.Atoi(imageIDStr) + updatedImage.ID = imageID + + image, err := qb.Update(updatedImage, tx) + if err != nil { + _ = tx.Rollback() + return nil, err + } + + ret = append(ret, image) + + // Save the galleries + if wasFieldIncluded(ctx, "gallery_ids") { + galleryIDs, err := adjustImageGalleryIDs(tx, imageID, *input.GalleryIds) + if err != nil { + _ = tx.Rollback() + return nil, err + } + + var galleryJoins []models.GalleriesImages + for _, gid := range galleryIDs { + galleryJoin := models.GalleriesImages{ + GalleryID: gid, + ImageID: imageID, + } + galleryJoins = append(galleryJoins, galleryJoin) + } + if err := jqb.UpdateGalleriesImages(imageID, galleryJoins, tx); err != nil { + return nil, err + } + } + + // Save the performers + if wasFieldIncluded(ctx, "performer_ids") { + performerIDs, err := adjustImagePerformerIDs(tx, imageID, *input.PerformerIds) + if err != nil { + _ = tx.Rollback() + return nil, err + } + + var performerJoins []models.PerformersImages + for _, performerID := range performerIDs { + performerJoin := models.PerformersImages{ + PerformerID: performerID, + ImageID: imageID, + } + performerJoins = append(performerJoins, performerJoin) + } + if err := jqb.UpdatePerformersImages(imageID, performerJoins, tx); err != nil { + _ = tx.Rollback() + return nil, err + } + } + + // Save the tags + if wasFieldIncluded(ctx, "tag_ids") { + tagIDs, err := adjustImageTagIDs(tx, imageID, *input.TagIds) + if err != nil { + _ = tx.Rollback() + return nil, err + } + + var tagJoins []models.ImagesTags + for _, tagID := range tagIDs { + tagJoin := models.ImagesTags{ + ImageID: imageID, + TagID: tagID, + } + tagJoins = append(tagJoins, tagJoin) + } + if err := jqb.UpdateImagesTags(imageID, tagJoins, tx); err != nil { + _ = tx.Rollback() + return nil, err + } + } + } + + // Commit + if err := tx.Commit(); err != nil { + return nil, err + } + + return ret, nil +} + +func adjustImageGalleryIDs(tx *sqlx.Tx, imageID int, ids models.BulkUpdateIds) ([]int, error) { + var ret []int + + jqb := models.NewJoinsQueryBuilder() + if ids.Mode == models.BulkUpdateIDModeAdd || ids.Mode == models.BulkUpdateIDModeRemove { + // adding to the joins + galleryJoins, err := jqb.GetImageGalleries(imageID, tx) + + if err != nil { + return nil, err + } + + for _, join := range galleryJoins { + ret = append(ret, join.GalleryID) + } + } + + return adjustIDs(ret, ids), nil +} + +func adjustImagePerformerIDs(tx *sqlx.Tx, imageID int, ids models.BulkUpdateIds) ([]int, error) { + var ret []int + + jqb := models.NewJoinsQueryBuilder() + if ids.Mode == models.BulkUpdateIDModeAdd || ids.Mode == models.BulkUpdateIDModeRemove { + // adding to the joins + performerJoins, err := jqb.GetImagePerformers(imageID, tx) + + if err != nil { + return nil, err + } + + for _, join := range performerJoins { + ret = append(ret, join.PerformerID) + } + } + + return adjustIDs(ret, ids), nil +} + +func adjustImageTagIDs(tx *sqlx.Tx, imageID int, ids models.BulkUpdateIds) ([]int, error) { + var ret []int + + jqb := models.NewJoinsQueryBuilder() + if ids.Mode == models.BulkUpdateIDModeAdd || ids.Mode == models.BulkUpdateIDModeRemove { + // adding to the joins + tagJoins, err := jqb.GetImageTags(imageID, tx) + + if err != nil { + return nil, err + } + + for _, join := range tagJoins { + ret = append(ret, join.TagID) + } + } + + return adjustIDs(ret, ids), nil +} + +func (r *mutationResolver) ImageDestroy(ctx context.Context, input models.ImageDestroyInput) (bool, error) { + qb := models.NewImageQueryBuilder() + tx := database.DB.MustBeginTx(ctx, nil) + + imageID, _ := strconv.Atoi(input.ID) + image, err := qb.Find(imageID) + err = qb.Destroy(imageID, tx) + + if err != nil { + tx.Rollback() + return false, err + } + + if err := tx.Commit(); err != nil { + return false, err + } + + // if delete generated is true, then delete the generated files + // for the image + if input.DeleteGenerated != nil && *input.DeleteGenerated { + manager.DeleteGeneratedImageFiles(image) + } + + // if delete file is true, then delete the file as well + // if it fails, just log a message + if input.DeleteFile != nil && *input.DeleteFile { + manager.DeleteImageFile(image) + } + + return true, nil +} + +func (r *mutationResolver) ImagesDestroy(ctx context.Context, input models.ImagesDestroyInput) (bool, error) { + qb := models.NewImageQueryBuilder() + tx := database.DB.MustBeginTx(ctx, nil) + + var images []*models.Image + for _, id := range input.Ids { + imageID, _ := strconv.Atoi(id) + + image, err := qb.Find(imageID) + if image != nil { + images = append(images, image) + } + err = qb.Destroy(imageID, tx) + + if err != nil { + tx.Rollback() + return false, err + } + } + + if err := tx.Commit(); err != nil { + return false, err + } + + for _, image := range images { + // if delete generated is true, then delete the generated files + // for the image + if input.DeleteGenerated != nil && *input.DeleteGenerated { + manager.DeleteGeneratedImageFiles(image) + } + + // if delete file is true, then delete the file as well + // if it fails, just log a message + if input.DeleteFile != nil && *input.DeleteFile { + manager.DeleteImageFile(image) + } + } + + return true, nil +} + +func (r *mutationResolver) ImageIncrementO(ctx context.Context, id string) (int, error) { + imageID, _ := strconv.Atoi(id) + + tx := database.DB.MustBeginTx(ctx, nil) + qb := models.NewImageQueryBuilder() + + newVal, err := qb.IncrementOCounter(imageID, tx) + if err != nil { + _ = tx.Rollback() + return 0, err + } + + // Commit + if err := tx.Commit(); err != nil { + return 0, err + } + + return newVal, nil +} + +func (r *mutationResolver) ImageDecrementO(ctx context.Context, id string) (int, error) { + imageID, _ := strconv.Atoi(id) + + tx := database.DB.MustBeginTx(ctx, nil) + qb := models.NewImageQueryBuilder() + + newVal, err := qb.DecrementOCounter(imageID, tx) + if err != nil { + _ = tx.Rollback() + return 0, err + } + + // Commit + if err := tx.Commit(); err != nil { + return 0, err + } + + return newVal, nil +} + +func (r *mutationResolver) ImageResetO(ctx context.Context, id string) (int, error) { + imageID, _ := strconv.Atoi(id) + + tx := database.DB.MustBeginTx(ctx, nil) + qb := models.NewImageQueryBuilder() + + newVal, err := qb.ResetOCounter(imageID, tx) + if err != nil { + _ = tx.Rollback() + return 0, err + } + + // Commit + if err := tx.Commit(); err != nil { + return 0, err + } + + return newVal, nil +} diff --git a/pkg/api/resolver_mutation_metadata.go b/pkg/api/resolver_mutation_metadata.go index 13f4da1ef..5423289e2 100644 --- a/pkg/api/resolver_mutation_metadata.go +++ b/pkg/api/resolver_mutation_metadata.go @@ -2,13 +2,15 @@ package api import ( "context" + "time" "github.com/stashapp/stash/pkg/manager" + "github.com/stashapp/stash/pkg/manager/config" "github.com/stashapp/stash/pkg/models" ) func (r *mutationResolver) MetadataScan(ctx context.Context, input models.ScanMetadataInput) (string, error) { - manager.GetInstance().Scan(input.UseFileMetadata) + manager.GetInstance().Scan(input) return "todo", nil } @@ -17,11 +19,42 @@ func (r *mutationResolver) MetadataImport(ctx context.Context) (string, error) { return "todo", nil } +func (r *mutationResolver) ImportObjects(ctx context.Context, input models.ImportObjectsInput) (string, error) { + t := manager.CreateImportTask(config.GetVideoFileNamingAlgorithm(), input) + _, err := manager.GetInstance().RunSingleTask(t) + if err != nil { + return "", err + } + + return "todo", nil +} + func (r *mutationResolver) MetadataExport(ctx context.Context) (string, error) { manager.GetInstance().Export() return "todo", nil } +func (r *mutationResolver) ExportObjects(ctx context.Context, input models.ExportObjectsInput) (*string, error) { + t := manager.CreateExportTask(config.GetVideoFileNamingAlgorithm(), input) + wg, err := manager.GetInstance().RunSingleTask(t) + if err != nil { + return nil, err + } + + wg.Wait() + + if t.DownloadHash != "" { + baseURL, _ := ctx.Value(BaseURLCtxKey).(string) + + // generate timestamp + suffix := time.Now().Format("20060102-150405") + ret := baseURL + "/downloads/" + t.DownloadHash + "/export" + suffix + ".zip" + return &ret, nil + } + + return nil, nil +} + func (r *mutationResolver) MetadataGenerate(ctx context.Context, input models.GenerateMetadataInput) (string, error) { manager.GetInstance().Generate(input) return "todo", nil diff --git a/pkg/api/resolver_mutation_performer.go b/pkg/api/resolver_mutation_performer.go index a00d568a6..0ef185dc0 100644 --- a/pkg/api/resolver_mutation_performer.go +++ b/pkg/api/resolver_mutation_performer.go @@ -13,7 +13,7 @@ import ( func (r *mutationResolver) PerformerCreate(ctx context.Context, input models.PerformerCreateInput) (*models.Performer, error) { // generate checksum from performer name rather than image - checksum := utils.MD5FromString(*input.Name) + checksum := utils.MD5FromString(input.Name) var imageData []byte var err error @@ -33,9 +33,7 @@ func (r *mutationResolver) PerformerCreate(ctx context.Context, input models.Per CreatedAt: models.SQLiteTimestamp{Timestamp: currentTime}, UpdatedAt: models.SQLiteTimestamp{Timestamp: currentTime}, } - if input.Name != nil { - newPerformer.Name = sql.NullString{String: *input.Name, Valid: true} - } + newPerformer.Name = sql.NullString{String: input.Name, Valid: true} if input.URL != nil { newPerformer.URL = sql.NullString{String: *input.URL, Valid: true} } @@ -90,6 +88,8 @@ func (r *mutationResolver) PerformerCreate(ctx context.Context, input models.Per // Start the transaction and save the performer tx := database.DB.MustBeginTx(ctx, nil) qb := models.NewPerformerQueryBuilder() + jqb := models.NewJoinsQueryBuilder() + performer, err := qb.Create(newPerformer, tx) if err != nil { _ = tx.Rollback() @@ -104,6 +104,21 @@ func (r *mutationResolver) PerformerCreate(ctx context.Context, input models.Per } } + // Save the stash_ids + if input.StashIds != nil { + var stashIDJoins []models.StashID + for _, stashID := range input.StashIds { + newJoin := models.StashID{ + StashID: stashID.StashID, + Endpoint: stashID.Endpoint, + } + stashIDJoins = append(stashIDJoins, newJoin) + } + if err := jqb.UpdatePerformerStashIDs(performer.ID, stashIDJoins, tx); err != nil { + return nil, err + } + } + // Commit if err := tx.Commit(); err != nil { return nil, err @@ -189,6 +204,8 @@ func (r *mutationResolver) PerformerUpdate(ctx context.Context, input models.Per // Start the transaction and save the performer tx := database.DB.MustBeginTx(ctx, nil) qb := models.NewPerformerQueryBuilder() + jqb := models.NewJoinsQueryBuilder() + performer, err := qb.Update(updatedPerformer, tx) if err != nil { tx.Rollback() @@ -209,6 +226,21 @@ func (r *mutationResolver) PerformerUpdate(ctx context.Context, input models.Per } } + // Save the stash_ids + if input.StashIds != nil { + var stashIDJoins []models.StashID + for _, stashID := range input.StashIds { + newJoin := models.StashID{ + StashID: stashID.StashID, + Endpoint: stashID.Endpoint, + } + stashIDJoins = append(stashIDJoins, newJoin) + } + if err := jqb.UpdatePerformerStashIDs(performerID, stashIDJoins, tx); err != nil { + return nil, err + } + } + // Commit if err := tx.Commit(); err != nil { return nil, err diff --git a/pkg/api/resolver_mutation_scene.go b/pkg/api/resolver_mutation_scene.go index 1e4d78b1e..ff2d77a04 100644 --- a/pkg/api/resolver_mutation_scene.go +++ b/pkg/api/resolver_mutation_scene.go @@ -204,6 +204,21 @@ func (r *mutationResolver) sceneUpdate(input models.SceneUpdateInput, tx *sqlx.T } } + // Save the stash_ids + if input.StashIds != nil { + var stashIDJoins []models.StashID + for _, stashID := range input.StashIds { + newJoin := models.StashID{ + StashID: stashID.StashID, + Endpoint: stashID.Endpoint, + } + stashIDJoins = append(stashIDJoins, newJoin) + } + if err := jqb.UpdateSceneStashIDs(sceneID, stashIDJoins, tx); err != nil { + return nil, err + } + } + return scene, nil } diff --git a/pkg/api/resolver_mutation_stash_box.go b/pkg/api/resolver_mutation_stash_box.go new file mode 100644 index 000000000..5dd1acfde --- /dev/null +++ b/pkg/api/resolver_mutation_stash_box.go @@ -0,0 +1,22 @@ +package api + +import ( + "context" + "fmt" + + "github.com/stashapp/stash/pkg/manager/config" + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/scraper/stashbox" +) + +func (r *mutationResolver) SubmitStashBoxFingerprints(ctx context.Context, input models.StashBoxFingerprintSubmissionInput) (bool, error) { + boxes := config.GetStashBoxes() + + if input.StashBoxIndex < 0 || input.StashBoxIndex >= len(boxes) { + return false, fmt.Errorf("invalid stash_box_index %d", input.StashBoxIndex) + } + + client := stashbox.NewClient(*boxes[input.StashBoxIndex]) + + return client.SubmitStashBoxFingerprints(input.SceneIds, boxes[input.StashBoxIndex].Endpoint) +} diff --git a/pkg/api/resolver_mutation_studio.go b/pkg/api/resolver_mutation_studio.go index 1069137f6..855dfd7a5 100644 --- a/pkg/api/resolver_mutation_studio.go +++ b/pkg/api/resolver_mutation_studio.go @@ -19,14 +19,12 @@ func (r *mutationResolver) StudioCreate(ctx context.Context, input models.Studio var imageData []byte var err error - if input.Image == nil { - input.Image = &models.DefaultStudioImage - } - // Process the base 64 encoded image string - _, imageData, err = utils.ProcessBase64Image(*input.Image) - if err != nil { - return nil, err + if input.Image != nil { + _, imageData, err = utils.ProcessBase64Image(*input.Image) + if err != nil { + return nil, err + } } // Populate a new studio from the input @@ -48,6 +46,8 @@ func (r *mutationResolver) StudioCreate(ctx context.Context, input models.Studio // Start the transaction and save the studio tx := database.DB.MustBeginTx(ctx, nil) qb := models.NewStudioQueryBuilder() + jqb := models.NewJoinsQueryBuilder() + studio, err := qb.Create(newStudio, tx) if err != nil { _ = tx.Rollback() @@ -62,6 +62,21 @@ func (r *mutationResolver) StudioCreate(ctx context.Context, input models.Studio } } + // Save the stash_ids + if input.StashIds != nil { + var stashIDJoins []models.StashID + for _, stashID := range input.StashIds { + newJoin := models.StashID{ + StashID: stashID.StashID, + Endpoint: stashID.Endpoint, + } + stashIDJoins = append(stashIDJoins, newJoin) + } + if err := jqb.UpdateStudioStashIDs(studio.ID, stashIDJoins, tx); err != nil { + return nil, err + } + } + // Commit if err := tx.Commit(); err != nil { return nil, err @@ -109,6 +124,7 @@ func (r *mutationResolver) StudioUpdate(ctx context.Context, input models.Studio // Start the transaction and save the studio tx := database.DB.MustBeginTx(ctx, nil) qb := models.NewStudioQueryBuilder() + jqb := models.NewJoinsQueryBuilder() if err := manager.ValidateModifyStudio(updatedStudio, tx); err != nil { tx.Rollback() @@ -135,6 +151,21 @@ func (r *mutationResolver) StudioUpdate(ctx context.Context, input models.Studio } } + // Save the stash_ids + if input.StashIds != nil { + var stashIDJoins []models.StashID + for _, stashID := range input.StashIds { + newJoin := models.StashID{ + StashID: stashID.StashID, + Endpoint: stashID.Endpoint, + } + stashIDJoins = append(stashIDJoins, newJoin) + } + if err := jqb.UpdateStudioStashIDs(studioID, stashIDJoins, tx); err != nil { + return nil, err + } + } + // Commit if err := tx.Commit(); err != nil { return nil, err diff --git a/pkg/api/resolver_query_configuration.go b/pkg/api/resolver_query_configuration.go index d8e0bbeaf..6f1bc4741 100644 --- a/pkg/api/resolver_query_configuration.go +++ b/pkg/api/resolver_query_configuration.go @@ -43,29 +43,35 @@ func makeConfigGeneralResult() *models.ConfigGeneralResult { scraperCDPPath := config.GetScraperCDPPath() return &models.ConfigGeneralResult{ - Stashes: config.GetStashPaths(), - DatabasePath: config.GetDatabasePath(), - GeneratedPath: config.GetGeneratedPath(), - CachePath: config.GetCachePath(), - CalculateMd5: config.IsCalculateMD5(), - VideoFileNamingAlgorithm: config.GetVideoFileNamingAlgorithm(), - PreviewSegments: config.GetPreviewSegments(), - PreviewSegmentDuration: config.GetPreviewSegmentDuration(), - PreviewExcludeStart: config.GetPreviewExcludeStart(), - PreviewExcludeEnd: config.GetPreviewExcludeEnd(), - PreviewPreset: config.GetPreviewPreset(), - MaxTranscodeSize: &maxTranscodeSize, - MaxStreamingTranscodeSize: &maxStreamingTranscodeSize, - Username: config.GetUsername(), - Password: config.GetPasswordHash(), - MaxSessionAge: config.GetMaxSessionAge(), - LogFile: &logFile, - LogOut: config.GetLogOut(), - LogLevel: config.GetLogLevel(), - LogAccess: config.GetLogAccess(), - Excludes: config.GetExcludes(), - ScraperUserAgent: &scraperUserAgent, - ScraperCDPPath: &scraperCDPPath, + Stashes: config.GetStashPaths(), + DatabasePath: config.GetDatabasePath(), + GeneratedPath: config.GetGeneratedPath(), + CachePath: config.GetCachePath(), + CalculateMd5: config.IsCalculateMD5(), + VideoFileNamingAlgorithm: config.GetVideoFileNamingAlgorithm(), + PreviewSegments: config.GetPreviewSegments(), + PreviewSegmentDuration: config.GetPreviewSegmentDuration(), + PreviewExcludeStart: config.GetPreviewExcludeStart(), + PreviewExcludeEnd: config.GetPreviewExcludeEnd(), + PreviewPreset: config.GetPreviewPreset(), + MaxTranscodeSize: &maxTranscodeSize, + MaxStreamingTranscodeSize: &maxStreamingTranscodeSize, + Username: config.GetUsername(), + Password: config.GetPasswordHash(), + MaxSessionAge: config.GetMaxSessionAge(), + LogFile: &logFile, + LogOut: config.GetLogOut(), + LogLevel: config.GetLogLevel(), + LogAccess: config.GetLogAccess(), + VideoExtensions: config.GetVideoExtensions(), + ImageExtensions: config.GetImageExtensions(), + GalleryExtensions: config.GetGalleryExtensions(), + CreateGalleriesFromFolders: config.GetCreateGalleriesFromFolders(), + Excludes: config.GetExcludes(), + ImageExcludes: config.GetImageExcludes(), + ScraperUserAgent: &scraperUserAgent, + ScraperCDPPath: &scraperCDPPath, + StashBoxes: config.GetStashBoxes(), } } diff --git a/pkg/api/resolver_query_find_gallery.go b/pkg/api/resolver_query_find_gallery.go index 9468b73bc..de5437efe 100644 --- a/pkg/api/resolver_query_find_gallery.go +++ b/pkg/api/resolver_query_find_gallery.go @@ -10,7 +10,7 @@ import ( func (r *queryResolver) FindGallery(ctx context.Context, id string) (*models.Gallery, error) { qb := models.NewGalleryQueryBuilder() idInt, _ := strconv.Atoi(id) - return qb.Find(idInt) + return qb.Find(idInt, nil) } func (r *queryResolver) FindGalleries(ctx context.Context, galleryFilter *models.GalleryFilterType, filter *models.FindFilterType) (*models.FindGalleriesResultType, error) { diff --git a/pkg/api/resolver_query_find_image.go b/pkg/api/resolver_query_find_image.go new file mode 100644 index 000000000..471e80853 --- /dev/null +++ b/pkg/api/resolver_query_find_image.go @@ -0,0 +1,30 @@ +package api + +import ( + "context" + "strconv" + + "github.com/stashapp/stash/pkg/models" +) + +func (r *queryResolver) FindImage(ctx context.Context, id *string, checksum *string) (*models.Image, error) { + qb := models.NewImageQueryBuilder() + var image *models.Image + var err error + if id != nil { + idInt, _ := strconv.Atoi(*id) + image, err = qb.Find(idInt) + } else if checksum != nil { + image, err = qb.FindByChecksum(*checksum) + } + return image, err +} + +func (r *queryResolver) FindImages(ctx context.Context, imageFilter *models.ImageFilterType, imageIds []int, filter *models.FindFilterType) (*models.FindImagesResultType, error) { + qb := models.NewImageQueryBuilder() + images, total := qb.Query(imageFilter, filter) + return &models.FindImagesResultType{ + Count: total, + Images: images, + }, nil +} diff --git a/pkg/api/resolver_query_scraper.go b/pkg/api/resolver_query_scraper.go index c66f2ebc6..04be0d9ee 100644 --- a/pkg/api/resolver_query_scraper.go +++ b/pkg/api/resolver_query_scraper.go @@ -2,10 +2,13 @@ package api import ( "context" + "fmt" "github.com/stashapp/stash/pkg/manager" + "github.com/stashapp/stash/pkg/manager/config" "github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/scraper" + "github.com/stashapp/stash/pkg/scraper/stashbox" ) // deprecated @@ -41,6 +44,10 @@ func (r *queryResolver) ListSceneScrapers(ctx context.Context) ([]*models.Scrape return manager.GetInstance().ScraperCache.ListSceneScrapers(), nil } +func (r *queryResolver) ListGalleryScrapers(ctx context.Context) ([]*models.Scraper, error) { + return manager.GetInstance().ScraperCache.ListGalleryScrapers(), nil +} + func (r *queryResolver) ListMovieScrapers(ctx context.Context) ([]*models.Scraper, error) { return manager.GetInstance().ScraperCache.ListMovieScrapers(), nil } @@ -69,6 +76,34 @@ func (r *queryResolver) ScrapeSceneURL(ctx context.Context, url string) (*models return manager.GetInstance().ScraperCache.ScrapeSceneURL(url) } +func (r *queryResolver) ScrapeGallery(ctx context.Context, scraperID string, gallery models.GalleryUpdateInput) (*models.ScrapedGallery, error) { + return manager.GetInstance().ScraperCache.ScrapeGallery(scraperID, gallery) +} + +func (r *queryResolver) ScrapeGalleryURL(ctx context.Context, url string) (*models.ScrapedGallery, error) { + return manager.GetInstance().ScraperCache.ScrapeGalleryURL(url) +} + func (r *queryResolver) ScrapeMovieURL(ctx context.Context, url string) (*models.ScrapedMovie, error) { return manager.GetInstance().ScraperCache.ScrapeMovieURL(url) } + +func (r *queryResolver) QueryStashBoxScene(ctx context.Context, input models.StashBoxQueryInput) ([]*models.ScrapedScene, error) { + boxes := config.GetStashBoxes() + + if input.StashBoxIndex < 0 || input.StashBoxIndex >= len(boxes) { + return nil, fmt.Errorf("invalid stash_box_index %d", input.StashBoxIndex) + } + + client := stashbox.NewClient(*boxes[input.StashBoxIndex]) + + if len(input.SceneIds) > 0 { + return client.FindStashBoxScenesByFingerprints(input.SceneIds) + } + + if input.Q != nil { + return client.QueryStashBoxScene(*input.Q) + } + + return nil, nil +} diff --git a/pkg/api/routes_downloads.go b/pkg/api/routes_downloads.go new file mode 100644 index 000000000..dbe138fe7 --- /dev/null +++ b/pkg/api/routes_downloads.go @@ -0,0 +1,41 @@ +package api + +import ( + "context" + "net/http" + + "github.com/go-chi/chi" + "github.com/stashapp/stash/pkg/manager" +) + +type downloadsRoutes struct{} + +func (rs downloadsRoutes) Routes() chi.Router { + r := chi.NewRouter() + + r.Route("/{downloadHash}", func(r chi.Router) { + r.Use(downloadCtx) + r.Get("/{filename}", rs.file) + }) + + return r +} + +func (rs downloadsRoutes) file(w http.ResponseWriter, r *http.Request) { + hash := r.Context().Value(downloadKey).(string) + if hash == "" { + http.Error(w, http.StatusText(404), 404) + return + } + + manager.GetInstance().DownloadStore.Serve(hash, w, r) +} + +func downloadCtx(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + downloadHash := chi.URLParam(r, "downloadHash") + + ctx := context.WithValue(r.Context(), downloadKey, downloadHash) + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} diff --git a/pkg/api/routes_gallery.go b/pkg/api/routes_gallery.go deleted file mode 100644 index 4b5a7f4cd..000000000 --- a/pkg/api/routes_gallery.go +++ /dev/null @@ -1,65 +0,0 @@ -package api - -import ( - "context" - "github.com/go-chi/chi" - "github.com/stashapp/stash/pkg/models" - "net/http" - "strconv" -) - -type galleryRoutes struct{} - -func (rs galleryRoutes) Routes() chi.Router { - r := chi.NewRouter() - - r.Route("/{galleryId}", func(r chi.Router) { - r.Use(GalleryCtx) - r.Get("/{fileIndex}", rs.File) - }) - - return r -} - -func (rs galleryRoutes) File(w http.ResponseWriter, r *http.Request) { - gallery := r.Context().Value(galleryKey).(*models.Gallery) - if gallery == nil { - http.Error(w, http.StatusText(404), 404) - return - } - fileIndex, _ := strconv.Atoi(chi.URLParam(r, "fileIndex")) - thumb := r.URL.Query().Get("thumb") - w.Header().Add("Cache-Control", "max-age=604800000") // 1 Week - if thumb == "true" { - _, _ = w.Write(cacheGthumb(gallery, fileIndex, models.DefaultGthumbWidth)) - } else if thumb == "" { - _, _ = w.Write(gallery.GetImage(fileIndex)) - } else { - width, err := strconv.ParseInt(thumb, 0, 64) - if err != nil { - http.Error(w, http.StatusText(400), 400) - return - } - _, _ = w.Write(cacheGthumb(gallery, fileIndex, int(width))) - } -} - -func GalleryCtx(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - galleryID, err := strconv.Atoi(chi.URLParam(r, "galleryId")) - if err != nil { - http.Error(w, http.StatusText(404), 404) - return - } - - qb := models.NewGalleryQueryBuilder() - gallery, err := qb.Find(galleryID) - if err != nil { - http.Error(w, http.StatusText(404), 404) - return - } - - ctx := context.WithValue(r.Context(), galleryKey, gallery) - next.ServeHTTP(w, r.WithContext(ctx)) - }) -} diff --git a/pkg/api/routes_image.go b/pkg/api/routes_image.go new file mode 100644 index 000000000..cc182fb0a --- /dev/null +++ b/pkg/api/routes_image.go @@ -0,0 +1,75 @@ +package api + +import ( + "context" + "net/http" + "strconv" + + "github.com/go-chi/chi" + "github.com/stashapp/stash/pkg/image" + "github.com/stashapp/stash/pkg/manager" + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/utils" +) + +type imageRoutes struct{} + +func (rs imageRoutes) Routes() chi.Router { + r := chi.NewRouter() + + r.Route("/{imageId}", func(r chi.Router) { + r.Use(ImageCtx) + + r.Get("/image", rs.Image) + r.Get("/thumbnail", rs.Thumbnail) + }) + + return r +} + +// region Handlers + +func (rs imageRoutes) Thumbnail(w http.ResponseWriter, r *http.Request) { + image := r.Context().Value(imageKey).(*models.Image) + filepath := manager.GetInstance().Paths.Generated.GetThumbnailPath(image.Checksum, models.DefaultGthumbWidth) + + // if the thumbnail doesn't exist, fall back to the original file + exists, _ := utils.FileExists(filepath) + if exists { + http.ServeFile(w, r, filepath) + } else { + rs.Image(w, r) + } +} + +func (rs imageRoutes) Image(w http.ResponseWriter, r *http.Request) { + i := r.Context().Value(imageKey).(*models.Image) + + // if image is in a zip file, we need to serve it specifically + image.Serve(w, r, i.Path) +} + +// endregion + +func ImageCtx(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + imageIdentifierQueryParam := chi.URLParam(r, "imageId") + imageID, _ := strconv.Atoi(imageIdentifierQueryParam) + + var image *models.Image + qb := models.NewImageQueryBuilder() + if imageID == 0 { + image, _ = qb.FindByChecksum(imageIdentifierQueryParam) + } else { + image, _ = qb.Find(imageID) + } + + if image == nil { + http.Error(w, http.StatusText(404), 404) + return + } + + ctx := context.WithValue(r.Context(), imageKey, image) + next.ServeHTTP(w, r.WithContext(ctx)) + }) +} diff --git a/pkg/api/routes_scene.go b/pkg/api/routes_scene.go index 8f2cdf9c6..046ac09f9 100644 --- a/pkg/api/routes_scene.go +++ b/pkg/api/routes_scene.go @@ -145,6 +145,7 @@ func (rs sceneRoutes) streamTranscode(w http.ResponseWriter, r *http.Request, vi // start stream based on query param, if provided r.ParseForm() startTime := r.Form.Get("start") + requestedSize := r.Form.Get("resolution") var stream *ffmpeg.Stream @@ -156,6 +157,9 @@ func (rs sceneRoutes) streamTranscode(w http.ResponseWriter, r *http.Request, vi options := ffmpeg.GetTranscodeStreamOptions(*videoFile, videoCodec, audioCodec) options.StartTime = startTime options.MaxTranscodeSize = config.GetMaxStreamingTranscodeSize() + if requestedSize != "" { + options.MaxTranscodeSize = models.StreamingResolutionEnum(requestedSize) + } encoder := ffmpeg.NewEncoder(manager.GetInstance().FFMPEGPath) stream, err = encoder.GetTranscodeStream(options) diff --git a/pkg/api/server.go b/pkg/api/server.go index aef48be8d..ab34e8353 100644 --- a/pkg/api/server.go +++ b/pkg/api/server.go @@ -129,16 +129,12 @@ func Start() { message := fmt.Sprintf("Internal system error. Error <%v>", err) return errors.New(message) }) - requestMiddleware := handler.RequestMiddleware(func(ctx context.Context, next func(ctx context.Context) []byte) []byte { - //api.GetRequestContext(ctx).Variables[] - return next(ctx) - }) websocketUpgrader := handler.WebsocketUpgrader(websocket.Upgrader{ CheckOrigin: func(r *http.Request) bool { return true }, }) - gqlHandler := handler.GraphQL(models.NewExecutableSchema(models.Config{Resolvers: &Resolver{}}), recoverFunc, requestMiddleware, websocketUpgrader) + gqlHandler := handler.GraphQL(models.NewExecutableSchema(models.Config{Resolvers: &Resolver{}}), recoverFunc, websocketUpgrader) r.Handle("/graphql", gqlHandler) r.Handle("/playground", handler.Playground("GraphQL playground", "/graphql")) @@ -149,12 +145,13 @@ func Start() { r.Get(loginEndPoint, getLoginHandler) - r.Mount("/gallery", galleryRoutes{}.Routes()) r.Mount("/performer", performerRoutes{}.Routes()) r.Mount("/scene", sceneRoutes{}.Routes()) + r.Mount("/image", imageRoutes{}.Routes()) r.Mount("/studio", studioRoutes{}.Routes()) r.Mount("/movie", movieRoutes{}.Routes()) r.Mount("/tag", tagRoutes{}.Routes()) + r.Mount("/downloads", downloadsRoutes{}.Routes()) r.HandleFunc("/css", func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/css") @@ -251,8 +248,6 @@ func Start() { http.Redirect(w, r, "/", 301) }) - startThumbCache() - // Serve static folders customServedFolders := config.GetCustomServedFolders() if customServedFolders != nil { @@ -405,7 +400,7 @@ func ConfigCheckMiddleware(next http.Handler) http.Handler { if !config.IsValid() && shouldRedirect { // #539 - don't redirect if loading login page if !strings.HasPrefix(r.URL.Path, setupEndPoint) && !strings.HasPrefix(r.URL.Path, loginEndPoint) { - http.Redirect(w, r, setupEndPoint, 301) + http.Redirect(w, r, setupEndPoint, http.StatusFound) return } } @@ -421,7 +416,7 @@ func DatabaseCheckMiddleware(next http.Handler) http.Handler { // #451 - don't redirect if loading login page // #539 - or setup page if !strings.HasPrefix(r.URL.Path, migrateEndPoint) && !strings.HasPrefix(r.URL.Path, loginEndPoint) && !strings.HasPrefix(r.URL.Path, setupEndPoint) { - http.Redirect(w, r, migrateEndPoint, 301) + http.Redirect(w, r, migrateEndPoint, http.StatusFound) return } } diff --git a/pkg/api/urlbuilders/image.go b/pkg/api/urlbuilders/image.go new file mode 100644 index 000000000..e81dd446e --- /dev/null +++ b/pkg/api/urlbuilders/image.go @@ -0,0 +1,25 @@ +package urlbuilders + +import ( + "strconv" +) + +type ImageURLBuilder struct { + BaseURL string + ImageID string +} + +func NewImageURLBuilder(baseURL string, imageID int) ImageURLBuilder { + return ImageURLBuilder{ + BaseURL: baseURL, + ImageID: strconv.Itoa(imageID), + } +} + +func (b ImageURLBuilder) GetImageURL() string { + return b.BaseURL + "/image/" + b.ImageID + "/image" +} + +func (b ImageURLBuilder) GetThumbnailURL() string { + return b.BaseURL + "/image/" + b.ImageID + "/thumbnail" +} diff --git a/pkg/database/database.go b/pkg/database/database.go index e02aaca91..6d685bf8b 100644 --- a/pkg/database/database.go +++ b/pkg/database/database.go @@ -19,7 +19,7 @@ import ( var DB *sqlx.DB var dbPath string -var appSchemaVersion uint = 12 +var appSchemaVersion uint = 15 var databaseSchemaVersion uint const sqlite3Driver = "sqlite3ex" diff --git a/pkg/database/migrations/13_images.up.sql b/pkg/database/migrations/13_images.up.sql new file mode 100644 index 000000000..23aef81cd --- /dev/null +++ b/pkg/database/migrations/13_images.up.sql @@ -0,0 +1,117 @@ +CREATE TABLE `images` ( + `id` integer not null primary key autoincrement, + `path` varchar(510) not null, + `checksum` varchar(255) not null, + `title` varchar(255), + `rating` tinyint, + `size` integer, + `width` tinyint, + `height` tinyint, + `studio_id` integer, + `o_counter` tinyint not null default 0, + `created_at` datetime not null, + `updated_at` datetime not null, + foreign key(`studio_id`) references `studios`(`id`) on delete SET NULL +); + +CREATE INDEX `index_images_on_studio_id` on `images` (`studio_id`); + +CREATE TABLE `performers_images` ( + `performer_id` integer, + `image_id` integer, + foreign key(`performer_id`) references `performers`(`id`) on delete CASCADE, + foreign key(`image_id`) references `images`(`id`) on delete CASCADE +); + +CREATE INDEX `index_performers_images_on_image_id` on `performers_images` (`image_id`); +CREATE INDEX `index_performers_images_on_performer_id` on `performers_images` (`performer_id`); + +CREATE TABLE `images_tags` ( + `image_id` integer, + `tag_id` integer, + foreign key(`image_id`) references `images`(`id`) on delete CASCADE, + foreign key(`tag_id`) references `tags`(`id`) on delete CASCADE +); + +CREATE INDEX `index_images_tags_on_tag_id` on `images_tags` (`tag_id`); +CREATE INDEX `index_images_tags_on_image_id` on `images_tags` (`image_id`); + +-- need to recreate galleries to add foreign key +ALTER TABLE `galleries` rename to `_galleries_old`; + +CREATE TABLE `galleries` ( + `id` integer not null primary key autoincrement, + `path` varchar(510), + `checksum` varchar(255) not null, + `zip` boolean not null default '0', + `title` varchar(255), + `url` varchar(255), + `date` date, + `details` text, + `studio_id` integer, + `rating` tinyint, + `scene_id` integer, + `created_at` datetime not null, + `updated_at` datetime not null, + foreign key(`scene_id`) references `scenes`(`id`) on delete SET NULL, + foreign key(`studio_id`) references `studios`(`id`) on delete SET NULL +); + +DROP INDEX IF EXISTS `index_galleries_on_scene_id`; +DROP INDEX IF EXISTS `galleries_path_unique`; +DROP INDEX IF EXISTS `galleries_checksum_unique`; + +CREATE INDEX `index_galleries_on_scene_id` on `galleries` (`scene_id`); +CREATE UNIQUE INDEX `galleries_path_unique` on `galleries` (`path`); +CREATE UNIQUE INDEX `galleries_checksum_unique` on `galleries` (`checksum`); +CREATE INDEX `index_galleries_on_studio_id` on `galleries` (`studio_id`); + +CREATE TABLE `galleries_images` ( + `gallery_id` integer, + `image_id` integer, + foreign key(`gallery_id`) references `galleries`(`id`) on delete CASCADE, + foreign key(`image_id`) references `images`(`id`) on delete CASCADE +); + +CREATE INDEX `index_galleries_images_on_image_id` on `galleries_images` (`image_id`); +CREATE INDEX `index_galleries_images_on_gallery_id` on `galleries_images` (`gallery_id`); + +CREATE TABLE `performers_galleries` ( + `performer_id` integer, + `gallery_id` integer, + foreign key(`performer_id`) references `performers`(`id`) on delete CASCADE, + foreign key(`gallery_id`) references `galleries`(`id`) on delete CASCADE +); + +CREATE INDEX `index_performers_galleries_on_gallery_id` on `performers_galleries` (`gallery_id`); +CREATE INDEX `index_performers_galleries_on_performer_id` on `performers_galleries` (`performer_id`); + +CREATE TABLE `galleries_tags` ( + `gallery_id` integer, + `tag_id` integer, + foreign key(`gallery_id`) references `galleries`(`id`) on delete CASCADE, + foreign key(`tag_id`) references `tags`(`id`) on delete CASCADE +); + +CREATE INDEX `index_galleries_tags_on_tag_id` on `galleries_tags` (`tag_id`); +CREATE INDEX `index_galleries_tags_on_gallery_id` on `galleries_tags` (`gallery_id`); + +INSERT INTO `galleries` + ( + `id`, + `path`, + `checksum`, + `scene_id`, + `created_at`, + `updated_at` + ) + SELECT + `id`, + `path`, + `checksum`, + `scene_id`, + `created_at`, + `updated_at` + FROM `_galleries_old`; + +DROP TABLE `_galleries_old`; diff --git a/pkg/database/migrations/14_stash_box_ids.up.sql b/pkg/database/migrations/14_stash_box_ids.up.sql new file mode 100644 index 000000000..f652e92b3 --- /dev/null +++ b/pkg/database/migrations/14_stash_box_ids.up.sql @@ -0,0 +1,20 @@ +CREATE TABLE `scene_stash_ids` ( + `scene_id` integer, + `endpoint` varchar(255), + `stash_id` varchar(36), + foreign key(`scene_id`) references `scenes`(`id`) on delete CASCADE +); + +CREATE TABLE `performer_stash_ids` ( + `performer_id` integer, + `endpoint` varchar(255), + `stash_id` varchar(36), + foreign key(`performer_id`) references `performers`(`id`) on delete CASCADE +); + +CREATE TABLE `studio_stash_ids` ( + `studio_id` integer, + `endpoint` varchar(255), + `stash_id` varchar(36), + foreign key(`studio_id`) references `studios`(`id`) on delete CASCADE +); diff --git a/pkg/database/migrations/15_file_mod_time.up.sql b/pkg/database/migrations/15_file_mod_time.up.sql new file mode 100644 index 000000000..9015d30a1 --- /dev/null +++ b/pkg/database/migrations/15_file_mod_time.up.sql @@ -0,0 +1,3 @@ +ALTER TABLE `scenes` ADD COLUMN `file_mod_time` datetime; +ALTER TABLE `images` ADD COLUMN `file_mod_time` datetime; +ALTER TABLE `galleries` ADD COLUMN `file_mod_time` datetime; diff --git a/pkg/database/transaction.go b/pkg/database/transaction.go new file mode 100644 index 000000000..c3ef9a85f --- /dev/null +++ b/pkg/database/transaction.go @@ -0,0 +1,33 @@ +package database + +import ( + "context" + + "github.com/jmoiron/sqlx" +) + +// WithTxn executes the provided function within a transaction. It rolls back +// the transaction if the function returns an error, otherwise the transaction +// is committed. +func WithTxn(fn func(tx *sqlx.Tx) error) error { + ctx := context.TODO() + tx := DB.MustBeginTx(ctx, nil) + + var err error + defer func() { + if p := recover(); p != nil { + // a panic occurred, rollback and repanic + tx.Rollback() + panic(p) + } else if err != nil { + // something went wrong, rollback + tx.Rollback() + } else { + // all good, commit + err = tx.Commit() + } + }() + + err = fn(tx) + return err +} diff --git a/pkg/ffmpeg/downloader.go b/pkg/ffmpeg/downloader.go index a00195a41..37ed444c7 100644 --- a/pkg/ffmpeg/downloader.go +++ b/pkg/ffmpeg/downloader.go @@ -39,14 +39,24 @@ func GetPaths(configDirectory string) (string, string) { } func Download(configDirectory string) error { - url := getFFMPEGURL() + for _, url := range getFFMPEGURL() { + err := DownloadSingle(configDirectory, url) + if err != nil { + return err + } + } + return nil +} + +func DownloadSingle(configDirectory, url string) error { if url == "" { return fmt.Errorf("no ffmpeg url for this platform") } // Configure where we want to download the archive urlExt := path.Ext(url) - archivePath := filepath.Join(configDirectory, "ffmpeg"+urlExt) + urlBase := path.Base(url) + archivePath := filepath.Join(configDirectory, urlBase) _ = os.Remove(archivePath) // remove archive if it already exists out, err := os.Create(archivePath) if err != nil { @@ -76,6 +86,22 @@ func Download(configDirectory string) error { if err := unzip(archivePath, configDirectory); err != nil { return err } + + // On OSX or Linux set downloaded files permissions + if runtime.GOOS == "darwin" || runtime.GOOS == "linux" { + if err := os.Chmod(filepath.Join(configDirectory, "ffmpeg"), 0755); err != nil { + return err + } + + if err := os.Chmod(filepath.Join(configDirectory, "ffprobe"), 0755); err != nil { + return err + } + + // TODO: In future possible clear xattr to allow running on osx without user intervention + // TODO: this however may not be required. + // xattr -c /path/to/binary -- xattr.Remove(path, "com.apple.quarantine") + } + } else { return fmt.Errorf("ffmpeg was downloaded to %s", archivePath) } @@ -83,19 +109,21 @@ func Download(configDirectory string) error { return nil } -func getFFMPEGURL() string { +func getFFMPEGURL() []string { + urls := []string{""} switch runtime.GOOS { case "darwin": - return "https://ffmpeg.zeranoe.com/builds/macos64/static/ffmpeg-4.1-macos64-static.zip" + urls = []string{"https://evermeet.cx/ffmpeg/ffmpeg-4.3.1.zip", "https://evermeet.cx/ffmpeg/ffprobe-4.3.1.zip"} case "linux": - // TODO: untar this - //return "https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz" - return "" + // TODO: get appropriate arch (arm,arm64,amd64) and xz untar from https://johnvansickle.com/ffmpeg/ + // or get the ffmpeg,ffprobe zip repackaged ones from https://ffbinaries.com/downloads + urls = []string{""} case "windows": - return "https://ffmpeg.zeranoe.com/builds/win64/static/ffmpeg-4.1-win64-static.zip" + urls = []string{"https://www.gyan.dev/ffmpeg/builds/ffmpeg-release-essentials.zip"} default: - return "" + urls = []string{""} } + return urls } func getFFMPEGFilename() string { diff --git a/pkg/ffmpeg/ffprobe.go b/pkg/ffmpeg/ffprobe.go index bc590771b..d05ad2fd9 100644 --- a/pkg/ffmpeg/ffprobe.go +++ b/pkg/ffmpeg/ffprobe.go @@ -236,7 +236,7 @@ func NewVideoFile(ffprobePath string, videoPath string) (*VideoFile, error) { probeJSON := &FFProbeJSON{} if err := json.Unmarshal(out, probeJSON); err != nil { - return nil, err + return nil, fmt.Errorf("Error unmarshalling video data for <%s>: %s", videoPath, err.Error()) } return parse(videoPath, probeJSON) @@ -244,7 +244,7 @@ func NewVideoFile(ffprobePath string, videoPath string) (*VideoFile, error) { func parse(filePath string, probeJSON *FFProbeJSON) (*VideoFile, error) { if probeJSON == nil { - return nil, fmt.Errorf("failed to get ffprobe json") + return nil, fmt.Errorf("failed to get ffprobe json for <%s>", filePath) } result := &VideoFile{} @@ -273,7 +273,7 @@ func parse(filePath string, probeJSON *FFProbeJSON) (*VideoFile, error) { result.Duration = math.Round(duration*100) / 100 fileStat, err := os.Stat(filePath) if err != nil { - logger.Errorf("Error statting file: %v", err) + logger.Errorf("Error statting file <%s>: %s", filePath, err.Error()) return nil, err } result.Size = fileStat.Size() diff --git a/pkg/ffmpeg/stream.go b/pkg/ffmpeg/stream.go index 6af646500..420667680 100644 --- a/pkg/ffmpeg/stream.go +++ b/pkg/ffmpeg/stream.go @@ -67,7 +67,7 @@ var CodecH264 = Codec{ format: "mp4", MimeType: MimeMp4, extraArgs: []string{ - "-movflags", "frag_keyframe", + "-movflags", "frag_keyframe+empty_moov", "-pix_fmt", "yuv420p", "-preset", "veryfast", "-crf", "25", diff --git a/pkg/gallery/export.go b/pkg/gallery/export.go new file mode 100644 index 000000000..fd6999081 --- /dev/null +++ b/pkg/gallery/export.go @@ -0,0 +1,74 @@ +package gallery + +import ( + "github.com/stashapp/stash/pkg/manager/jsonschema" + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/utils" +) + +// ToBasicJSON converts a gallery object into its JSON object equivalent. It +// does not convert the relationships to other objects. +func ToBasicJSON(gallery *models.Gallery) (*jsonschema.Gallery, error) { + newGalleryJSON := jsonschema.Gallery{ + Checksum: gallery.Checksum, + Zip: gallery.Zip, + CreatedAt: models.JSONTime{Time: gallery.CreatedAt.Timestamp}, + UpdatedAt: models.JSONTime{Time: gallery.UpdatedAt.Timestamp}, + } + + if gallery.Path.Valid { + newGalleryJSON.Path = gallery.Path.String + } + + if gallery.FileModTime.Valid { + newGalleryJSON.FileModTime = models.JSONTime{Time: gallery.FileModTime.Timestamp} + } + + if gallery.Title.Valid { + newGalleryJSON.Title = gallery.Title.String + } + + if gallery.URL.Valid { + newGalleryJSON.URL = gallery.URL.String + } + + if gallery.Date.Valid { + newGalleryJSON.Date = utils.GetYMDFromDatabaseDate(gallery.Date.String) + } + + if gallery.Rating.Valid { + newGalleryJSON.Rating = int(gallery.Rating.Int64) + } + + if gallery.Details.Valid { + newGalleryJSON.Details = gallery.Details.String + } + + return &newGalleryJSON, nil +} + +// GetStudioName returns the name of the provided gallery's studio. It returns an +// empty string if there is no studio assigned to the gallery. +func GetStudioName(reader models.StudioReader, gallery *models.Gallery) (string, error) { + if gallery.StudioID.Valid { + studio, err := reader.Find(int(gallery.StudioID.Int64)) + if err != nil { + return "", err + } + + if studio != nil { + return studio.Name.String, nil + } + } + + return "", nil +} + +func GetIDs(galleries []*models.Gallery) []int { + var results []int + for _, gallery := range galleries { + results = append(results, gallery.ID) + } + + return results +} diff --git a/pkg/gallery/export_test.go b/pkg/gallery/export_test.go new file mode 100644 index 000000000..439a116de --- /dev/null +++ b/pkg/gallery/export_test.go @@ -0,0 +1,199 @@ +package gallery + +import ( + "errors" + + "github.com/stashapp/stash/pkg/manager/jsonschema" + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/models/mocks" + "github.com/stashapp/stash/pkg/models/modelstest" + "github.com/stretchr/testify/assert" + + "testing" + "time" +) + +const ( + galleryID = 1 + + studioID = 4 + missingStudioID = 5 + errStudioID = 6 + + noTagsID = 11 + errTagsID = 12 +) + +const ( + path = "path" + zip = true + url = "url" + checksum = "checksum" + title = "title" + date = "2001-01-01" + rating = 5 + details = "details" +) + +const ( + studioName = "studioName" +) + +var names = []string{ + "name1", + "name2", +} + +var createTime time.Time = time.Date(2001, 01, 01, 0, 0, 0, 0, time.UTC) +var updateTime time.Time = time.Date(2002, 01, 01, 0, 0, 0, 0, time.UTC) + +func createFullGallery(id int) models.Gallery { + return models.Gallery{ + ID: id, + Path: modelstest.NullString(path), + Zip: zip, + Title: modelstest.NullString(title), + Checksum: checksum, + Date: models.SQLiteDate{ + String: date, + Valid: true, + }, + Details: modelstest.NullString(details), + Rating: modelstest.NullInt64(rating), + URL: modelstest.NullString(url), + CreatedAt: models.SQLiteTimestamp{ + Timestamp: createTime, + }, + UpdatedAt: models.SQLiteTimestamp{ + Timestamp: updateTime, + }, + } +} + +func createEmptyGallery(id int) models.Gallery { + return models.Gallery{ + ID: id, + CreatedAt: models.SQLiteTimestamp{ + Timestamp: createTime, + }, + UpdatedAt: models.SQLiteTimestamp{ + Timestamp: updateTime, + }, + } +} + +func createFullJSONGallery() *jsonschema.Gallery { + return &jsonschema.Gallery{ + Title: title, + Path: path, + Zip: zip, + Checksum: checksum, + Date: date, + Details: details, + Rating: rating, + URL: url, + CreatedAt: models.JSONTime{ + Time: createTime, + }, + UpdatedAt: models.JSONTime{ + Time: updateTime, + }, + } +} + +func createEmptyJSONGallery() *jsonschema.Gallery { + return &jsonschema.Gallery{ + CreatedAt: models.JSONTime{ + Time: createTime, + }, + UpdatedAt: models.JSONTime{ + Time: updateTime, + }, + } +} + +type basicTestScenario struct { + input models.Gallery + expected *jsonschema.Gallery + err bool +} + +var scenarios = []basicTestScenario{ + { + createFullGallery(galleryID), + createFullJSONGallery(), + false, + }, +} + +func TestToJSON(t *testing.T) { + for i, s := range scenarios { + gallery := s.input + json, err := ToBasicJSON(&gallery) + + if !s.err && err != nil { + t.Errorf("[%d] unexpected error: %s", i, err.Error()) + } else if s.err && err == nil { + t.Errorf("[%d] expected error not returned", i) + } else { + assert.Equal(t, s.expected, json, "[%d]", i) + } + } +} + +func createStudioGallery(studioID int) models.Gallery { + return models.Gallery{ + StudioID: modelstest.NullInt64(int64(studioID)), + } +} + +type stringTestScenario struct { + input models.Gallery + expected string + err bool +} + +var getStudioScenarios = []stringTestScenario{ + { + createStudioGallery(studioID), + studioName, + false, + }, + { + createStudioGallery(missingStudioID), + "", + false, + }, + { + createStudioGallery(errStudioID), + "", + true, + }, +} + +func TestGetStudioName(t *testing.T) { + mockStudioReader := &mocks.StudioReaderWriter{} + + studioErr := errors.New("error getting image") + + mockStudioReader.On("Find", studioID).Return(&models.Studio{ + Name: modelstest.NullString(studioName), + }, nil).Once() + mockStudioReader.On("Find", missingStudioID).Return(nil, nil).Once() + mockStudioReader.On("Find", errStudioID).Return(nil, studioErr).Once() + + for i, s := range getStudioScenarios { + gallery := s.input + json, err := GetStudioName(mockStudioReader, &gallery) + + if !s.err && err != nil { + t.Errorf("[%d] unexpected error: %s", i, err.Error()) + } else if s.err && err == nil { + t.Errorf("[%d] expected error not returned", i) + } else { + assert.Equal(t, s.expected, json, "[%d]", i) + } + } + + mockStudioReader.AssertExpectations(t) +} diff --git a/pkg/gallery/images.go b/pkg/gallery/images.go new file mode 100644 index 000000000..5487c797e --- /dev/null +++ b/pkg/gallery/images.go @@ -0,0 +1,30 @@ +package gallery + +import ( + "github.com/stashapp/stash/pkg/api/urlbuilders" + "github.com/stashapp/stash/pkg/models" +) + +func GetFiles(g *models.Gallery, baseURL string) []*models.GalleryFilesType { + var galleryFiles []*models.GalleryFilesType + + qb := models.NewImageQueryBuilder() + images, err := qb.FindByGalleryID(g.ID) + if err != nil { + return nil + } + + for i, img := range images { + builder := urlbuilders.NewImageURLBuilder(baseURL, img.ID) + imageURL := builder.GetImageURL() + + galleryFile := models.GalleryFilesType{ + Index: i, + Name: &img.Title.String, + Path: &imageURL, + } + galleryFiles = append(galleryFiles, &galleryFile) + } + + return galleryFiles +} diff --git a/pkg/gallery/import.go b/pkg/gallery/import.go new file mode 100644 index 000000000..3643a2346 --- /dev/null +++ b/pkg/gallery/import.go @@ -0,0 +1,306 @@ +package gallery + +import ( + "database/sql" + "fmt" + "strings" + + "github.com/stashapp/stash/pkg/manager/jsonschema" + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/utils" +) + +type Importer struct { + ReaderWriter models.GalleryReaderWriter + StudioWriter models.StudioReaderWriter + PerformerWriter models.PerformerReaderWriter + TagWriter models.TagReaderWriter + JoinWriter models.JoinReaderWriter + Input jsonschema.Gallery + MissingRefBehaviour models.ImportMissingRefEnum + + gallery models.Gallery + performers []*models.Performer + tags []*models.Tag +} + +func (i *Importer) PreImport() error { + i.gallery = i.galleryJSONToGallery(i.Input) + + if err := i.populateStudio(); err != nil { + return err + } + + if err := i.populatePerformers(); err != nil { + return err + } + + if err := i.populateTags(); err != nil { + return err + } + + return nil +} + +func (i *Importer) galleryJSONToGallery(galleryJSON jsonschema.Gallery) models.Gallery { + newGallery := models.Gallery{ + Checksum: galleryJSON.Checksum, + Zip: galleryJSON.Zip, + } + + if galleryJSON.Path != "" { + newGallery.Path = sql.NullString{String: galleryJSON.Path, Valid: true} + } + + if galleryJSON.Title != "" { + newGallery.Title = sql.NullString{String: galleryJSON.Title, Valid: true} + } + if galleryJSON.Details != "" { + newGallery.Details = sql.NullString{String: galleryJSON.Details, Valid: true} + } + if galleryJSON.URL != "" { + newGallery.URL = sql.NullString{String: galleryJSON.URL, Valid: true} + } + if galleryJSON.Date != "" { + newGallery.Date = models.SQLiteDate{String: galleryJSON.Date, Valid: true} + } + if galleryJSON.Rating != 0 { + newGallery.Rating = sql.NullInt64{Int64: int64(galleryJSON.Rating), Valid: true} + } + + newGallery.CreatedAt = models.SQLiteTimestamp{Timestamp: galleryJSON.CreatedAt.GetTime()} + newGallery.UpdatedAt = models.SQLiteTimestamp{Timestamp: galleryJSON.UpdatedAt.GetTime()} + + return newGallery +} + +func (i *Importer) populateStudio() error { + if i.Input.Studio != "" { + studio, err := i.StudioWriter.FindByName(i.Input.Studio, false) + if err != nil { + return fmt.Errorf("error finding studio by name: %s", err.Error()) + } + + if studio == nil { + if i.MissingRefBehaviour == models.ImportMissingRefEnumFail { + return fmt.Errorf("gallery studio '%s' not found", i.Input.Studio) + } + + if i.MissingRefBehaviour == models.ImportMissingRefEnumIgnore { + return nil + } + + if i.MissingRefBehaviour == models.ImportMissingRefEnumCreate { + studioID, err := i.createStudio(i.Input.Studio) + if err != nil { + return err + } + i.gallery.StudioID = sql.NullInt64{ + Int64: int64(studioID), + Valid: true, + } + } + } else { + i.gallery.StudioID = sql.NullInt64{Int64: int64(studio.ID), Valid: true} + } + } + + return nil +} + +func (i *Importer) createStudio(name string) (int, error) { + newStudio := *models.NewStudio(name) + + created, err := i.StudioWriter.Create(newStudio) + if err != nil { + return 0, err + } + + return created.ID, nil +} + +func (i *Importer) populatePerformers() error { + if len(i.Input.Performers) > 0 { + names := i.Input.Performers + performers, err := i.PerformerWriter.FindByNames(names, false) + if err != nil { + return err + } + + var pluckedNames []string + for _, performer := range performers { + if !performer.Name.Valid { + continue + } + pluckedNames = append(pluckedNames, performer.Name.String) + } + + missingPerformers := utils.StrFilter(names, func(name string) bool { + return !utils.StrInclude(pluckedNames, name) + }) + + if len(missingPerformers) > 0 { + if i.MissingRefBehaviour == models.ImportMissingRefEnumFail { + return fmt.Errorf("gallery performers [%s] not found", strings.Join(missingPerformers, ", ")) + } + + if i.MissingRefBehaviour == models.ImportMissingRefEnumCreate { + createdPerformers, err := i.createPerformers(missingPerformers) + if err != nil { + return fmt.Errorf("error creating gallery performers: %s", err.Error()) + } + + performers = append(performers, createdPerformers...) + } + + // ignore if MissingRefBehaviour set to Ignore + } + + i.performers = performers + } + + return nil +} + +func (i *Importer) createPerformers(names []string) ([]*models.Performer, error) { + var ret []*models.Performer + for _, name := range names { + newPerformer := *models.NewPerformer(name) + + created, err := i.PerformerWriter.Create(newPerformer) + if err != nil { + return nil, err + } + + ret = append(ret, created) + } + + return ret, nil +} + +func (i *Importer) populateTags() error { + if len(i.Input.Tags) > 0 { + names := i.Input.Tags + tags, err := i.TagWriter.FindByNames(names, false) + if err != nil { + return err + } + + var pluckedNames []string + for _, tag := range tags { + pluckedNames = append(pluckedNames, tag.Name) + } + + missingTags := utils.StrFilter(names, func(name string) bool { + return !utils.StrInclude(pluckedNames, name) + }) + + if len(missingTags) > 0 { + if i.MissingRefBehaviour == models.ImportMissingRefEnumFail { + return fmt.Errorf("gallery tags [%s] not found", strings.Join(missingTags, ", ")) + } + + if i.MissingRefBehaviour == models.ImportMissingRefEnumCreate { + createdTags, err := i.createTags(missingTags) + if err != nil { + return fmt.Errorf("error creating gallery tags: %s", err.Error()) + } + + tags = append(tags, createdTags...) + } + + // ignore if MissingRefBehaviour set to Ignore + } + + i.tags = tags + } + + return nil +} + +func (i *Importer) createTags(names []string) ([]*models.Tag, error) { + var ret []*models.Tag + for _, name := range names { + newTag := *models.NewTag(name) + + created, err := i.TagWriter.Create(newTag) + if err != nil { + return nil, err + } + + ret = append(ret, created) + } + + return ret, nil +} + +func (i *Importer) PostImport(id int) error { + if len(i.performers) > 0 { + var performerJoins []models.PerformersGalleries + for _, performer := range i.performers { + join := models.PerformersGalleries{ + PerformerID: performer.ID, + GalleryID: id, + } + performerJoins = append(performerJoins, join) + } + if err := i.JoinWriter.UpdatePerformersGalleries(id, performerJoins); err != nil { + return fmt.Errorf("failed to associate performers: %s", err.Error()) + } + } + + if len(i.tags) > 0 { + var tagJoins []models.GalleriesTags + for _, tag := range i.tags { + join := models.GalleriesTags{ + GalleryID: id, + TagID: tag.ID, + } + tagJoins = append(tagJoins, join) + } + if err := i.JoinWriter.UpdateGalleriesTags(id, tagJoins); err != nil { + return fmt.Errorf("failed to associate tags: %s", err.Error()) + } + } + + return nil +} + +func (i *Importer) Name() string { + return i.Input.Path +} + +func (i *Importer) FindExistingID() (*int, error) { + existing, err := i.ReaderWriter.FindByChecksum(i.Input.Checksum) + if err != nil { + return nil, err + } + + if existing != nil { + id := existing.ID + return &id, nil + } + + return nil, nil +} + +func (i *Importer) Create() (*int, error) { + created, err := i.ReaderWriter.Create(i.gallery) + if err != nil { + return nil, fmt.Errorf("error creating gallery: %s", err.Error()) + } + + id := created.ID + return &id, nil +} + +func (i *Importer) Update(id int) error { + gallery := i.gallery + gallery.ID = id + _, err := i.ReaderWriter.Update(gallery) + if err != nil { + return fmt.Errorf("error updating existing gallery: %s", err.Error()) + } + + return nil +} diff --git a/pkg/gallery/import_test.go b/pkg/gallery/import_test.go new file mode 100644 index 000000000..6cbcbc32a --- /dev/null +++ b/pkg/gallery/import_test.go @@ -0,0 +1,505 @@ +package gallery + +import ( + "errors" + "testing" + "time" + + "github.com/stashapp/stash/pkg/manager/jsonschema" + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/models/mocks" + "github.com/stashapp/stash/pkg/models/modelstest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +const ( + galleryNameErr = "galleryNameErr" + existingGalleryName = "existingGalleryName" + + existingGalleryID = 100 + existingStudioID = 101 + existingPerformerID = 103 + existingTagID = 105 + + existingStudioName = "existingStudioName" + existingStudioErr = "existingStudioErr" + missingStudioName = "missingStudioName" + + existingPerformerName = "existingPerformerName" + existingPerformerErr = "existingPerformerErr" + missingPerformerName = "missingPerformerName" + + existingTagName = "existingTagName" + existingTagErr = "existingTagErr" + missingTagName = "missingTagName" + + errPerformersID = 200 + + missingChecksum = "missingChecksum" + errChecksum = "errChecksum" +) + +var createdAt time.Time = time.Date(2001, time.January, 2, 1, 2, 3, 4, time.Local) +var updatedAt time.Time = time.Date(2002, time.January, 2, 1, 2, 3, 4, time.Local) + +func TestImporterName(t *testing.T) { + i := Importer{ + Input: jsonschema.Gallery{ + Path: path, + }, + } + + assert.Equal(t, path, i.Name()) +} + +func TestImporterPreImport(t *testing.T) { + i := Importer{ + Input: jsonschema.Gallery{ + Path: path, + Checksum: checksum, + Title: title, + Date: date, + Details: details, + Rating: rating, + URL: url, + CreatedAt: models.JSONTime{ + Time: createdAt, + }, + UpdatedAt: models.JSONTime{ + Time: updatedAt, + }, + }, + } + + err := i.PreImport() + assert.Nil(t, err) + + expectedGallery := models.Gallery{ + Path: modelstest.NullString(path), + Checksum: checksum, + Title: modelstest.NullString(title), + Date: models.SQLiteDate{ + String: date, + Valid: true, + }, + Details: modelstest.NullString(details), + Rating: modelstest.NullInt64(rating), + URL: modelstest.NullString(url), + CreatedAt: models.SQLiteTimestamp{ + Timestamp: createdAt, + }, + UpdatedAt: models.SQLiteTimestamp{ + Timestamp: updatedAt, + }, + } + + assert.Equal(t, expectedGallery, i.gallery) +} + +func TestImporterPreImportWithStudio(t *testing.T) { + studioReaderWriter := &mocks.StudioReaderWriter{} + + i := Importer{ + StudioWriter: studioReaderWriter, + Input: jsonschema.Gallery{ + Studio: existingStudioName, + Path: path, + }, + } + + studioReaderWriter.On("FindByName", existingStudioName, false).Return(&models.Studio{ + ID: existingStudioID, + }, nil).Once() + studioReaderWriter.On("FindByName", existingStudioErr, false).Return(nil, errors.New("FindByName error")).Once() + + err := i.PreImport() + assert.Nil(t, err) + assert.Equal(t, int64(existingStudioID), i.gallery.StudioID.Int64) + + i.Input.Studio = existingStudioErr + err = i.PreImport() + assert.NotNil(t, err) + + studioReaderWriter.AssertExpectations(t) +} + +func TestImporterPreImportWithMissingStudio(t *testing.T) { + studioReaderWriter := &mocks.StudioReaderWriter{} + + i := Importer{ + StudioWriter: studioReaderWriter, + Input: jsonschema.Gallery{ + Path: path, + Studio: missingStudioName, + }, + MissingRefBehaviour: models.ImportMissingRefEnumFail, + } + + studioReaderWriter.On("FindByName", missingStudioName, false).Return(nil, nil).Times(3) + studioReaderWriter.On("Create", mock.AnythingOfType("models.Studio")).Return(&models.Studio{ + ID: existingStudioID, + }, nil) + + err := i.PreImport() + assert.NotNil(t, err) + + i.MissingRefBehaviour = models.ImportMissingRefEnumIgnore + err = i.PreImport() + assert.Nil(t, err) + + i.MissingRefBehaviour = models.ImportMissingRefEnumCreate + err = i.PreImport() + assert.Nil(t, err) + assert.Equal(t, int64(existingStudioID), i.gallery.StudioID.Int64) + + studioReaderWriter.AssertExpectations(t) +} + +func TestImporterPreImportWithMissingStudioCreateErr(t *testing.T) { + studioReaderWriter := &mocks.StudioReaderWriter{} + + i := Importer{ + StudioWriter: studioReaderWriter, + Input: jsonschema.Gallery{ + Path: path, + Studio: missingStudioName, + }, + MissingRefBehaviour: models.ImportMissingRefEnumCreate, + } + + studioReaderWriter.On("FindByName", missingStudioName, false).Return(nil, nil).Once() + studioReaderWriter.On("Create", mock.AnythingOfType("models.Studio")).Return(nil, errors.New("Create error")) + + err := i.PreImport() + assert.NotNil(t, err) +} + +func TestImporterPreImportWithPerformer(t *testing.T) { + performerReaderWriter := &mocks.PerformerReaderWriter{} + + i := Importer{ + PerformerWriter: performerReaderWriter, + MissingRefBehaviour: models.ImportMissingRefEnumFail, + Input: jsonschema.Gallery{ + Path: path, + Performers: []string{ + existingPerformerName, + }, + }, + } + + performerReaderWriter.On("FindByNames", []string{existingPerformerName}, false).Return([]*models.Performer{ + { + ID: existingPerformerID, + Name: modelstest.NullString(existingPerformerName), + }, + }, nil).Once() + performerReaderWriter.On("FindByNames", []string{existingPerformerErr}, false).Return(nil, errors.New("FindByNames error")).Once() + + err := i.PreImport() + assert.Nil(t, err) + assert.Equal(t, existingPerformerID, i.performers[0].ID) + + i.Input.Performers = []string{existingPerformerErr} + err = i.PreImport() + assert.NotNil(t, err) + + performerReaderWriter.AssertExpectations(t) +} + +func TestImporterPreImportWithMissingPerformer(t *testing.T) { + performerReaderWriter := &mocks.PerformerReaderWriter{} + + i := Importer{ + PerformerWriter: performerReaderWriter, + Input: jsonschema.Gallery{ + Path: path, + Performers: []string{ + missingPerformerName, + }, + }, + MissingRefBehaviour: models.ImportMissingRefEnumFail, + } + + performerReaderWriter.On("FindByNames", []string{missingPerformerName}, false).Return(nil, nil).Times(3) + performerReaderWriter.On("Create", mock.AnythingOfType("models.Performer")).Return(&models.Performer{ + ID: existingPerformerID, + }, nil) + + err := i.PreImport() + assert.NotNil(t, err) + + i.MissingRefBehaviour = models.ImportMissingRefEnumIgnore + err = i.PreImport() + assert.Nil(t, err) + + i.MissingRefBehaviour = models.ImportMissingRefEnumCreate + err = i.PreImport() + assert.Nil(t, err) + assert.Equal(t, existingPerformerID, i.performers[0].ID) + + performerReaderWriter.AssertExpectations(t) +} + +func TestImporterPreImportWithMissingPerformerCreateErr(t *testing.T) { + performerReaderWriter := &mocks.PerformerReaderWriter{} + + i := Importer{ + PerformerWriter: performerReaderWriter, + Input: jsonschema.Gallery{ + Path: path, + Performers: []string{ + missingPerformerName, + }, + }, + MissingRefBehaviour: models.ImportMissingRefEnumCreate, + } + + performerReaderWriter.On("FindByNames", []string{missingPerformerName}, false).Return(nil, nil).Once() + performerReaderWriter.On("Create", mock.AnythingOfType("models.Performer")).Return(nil, errors.New("Create error")) + + err := i.PreImport() + assert.NotNil(t, err) +} + +func TestImporterPreImportWithTag(t *testing.T) { + tagReaderWriter := &mocks.TagReaderWriter{} + + i := Importer{ + TagWriter: tagReaderWriter, + MissingRefBehaviour: models.ImportMissingRefEnumFail, + Input: jsonschema.Gallery{ + Path: path, + Tags: []string{ + existingTagName, + }, + }, + } + + tagReaderWriter.On("FindByNames", []string{existingTagName}, false).Return([]*models.Tag{ + { + ID: existingTagID, + Name: existingTagName, + }, + }, nil).Once() + tagReaderWriter.On("FindByNames", []string{existingTagErr}, false).Return(nil, errors.New("FindByNames error")).Once() + + err := i.PreImport() + assert.Nil(t, err) + assert.Equal(t, existingTagID, i.tags[0].ID) + + i.Input.Tags = []string{existingTagErr} + err = i.PreImport() + assert.NotNil(t, err) + + tagReaderWriter.AssertExpectations(t) +} + +func TestImporterPreImportWithMissingTag(t *testing.T) { + tagReaderWriter := &mocks.TagReaderWriter{} + + i := Importer{ + TagWriter: tagReaderWriter, + Input: jsonschema.Gallery{ + Path: path, + Tags: []string{ + missingTagName, + }, + }, + MissingRefBehaviour: models.ImportMissingRefEnumFail, + } + + tagReaderWriter.On("FindByNames", []string{missingTagName}, false).Return(nil, nil).Times(3) + tagReaderWriter.On("Create", mock.AnythingOfType("models.Tag")).Return(&models.Tag{ + ID: existingTagID, + }, nil) + + err := i.PreImport() + assert.NotNil(t, err) + + i.MissingRefBehaviour = models.ImportMissingRefEnumIgnore + err = i.PreImport() + assert.Nil(t, err) + + i.MissingRefBehaviour = models.ImportMissingRefEnumCreate + err = i.PreImport() + assert.Nil(t, err) + assert.Equal(t, existingTagID, i.tags[0].ID) + + tagReaderWriter.AssertExpectations(t) +} + +func TestImporterPreImportWithMissingTagCreateErr(t *testing.T) { + tagReaderWriter := &mocks.TagReaderWriter{} + + i := Importer{ + TagWriter: tagReaderWriter, + Input: jsonschema.Gallery{ + Path: path, + Tags: []string{ + missingTagName, + }, + }, + MissingRefBehaviour: models.ImportMissingRefEnumCreate, + } + + tagReaderWriter.On("FindByNames", []string{missingTagName}, false).Return(nil, nil).Once() + tagReaderWriter.On("Create", mock.AnythingOfType("models.Tag")).Return(nil, errors.New("Create error")) + + err := i.PreImport() + assert.NotNil(t, err) +} + +func TestImporterPostImportUpdatePerformers(t *testing.T) { + joinReaderWriter := &mocks.JoinReaderWriter{} + + i := Importer{ + JoinWriter: joinReaderWriter, + performers: []*models.Performer{ + { + ID: existingPerformerID, + }, + }, + } + + updateErr := errors.New("UpdatePerformersGalleries error") + + joinReaderWriter.On("UpdatePerformersGalleries", galleryID, []models.PerformersGalleries{ + { + PerformerID: existingPerformerID, + GalleryID: galleryID, + }, + }).Return(nil).Once() + joinReaderWriter.On("UpdatePerformersGalleries", errPerformersID, mock.AnythingOfType("[]models.PerformersGalleries")).Return(updateErr).Once() + + err := i.PostImport(galleryID) + assert.Nil(t, err) + + err = i.PostImport(errPerformersID) + assert.NotNil(t, err) + + joinReaderWriter.AssertExpectations(t) +} + +func TestImporterPostImportUpdateTags(t *testing.T) { + joinReaderWriter := &mocks.JoinReaderWriter{} + + i := Importer{ + JoinWriter: joinReaderWriter, + tags: []*models.Tag{ + { + ID: existingTagID, + }, + }, + } + + updateErr := errors.New("UpdateGalleriesTags error") + + joinReaderWriter.On("UpdateGalleriesTags", galleryID, []models.GalleriesTags{ + { + TagID: existingTagID, + GalleryID: galleryID, + }, + }).Return(nil).Once() + joinReaderWriter.On("UpdateGalleriesTags", errTagsID, mock.AnythingOfType("[]models.GalleriesTags")).Return(updateErr).Once() + + err := i.PostImport(galleryID) + assert.Nil(t, err) + + err = i.PostImport(errTagsID) + assert.NotNil(t, err) + + joinReaderWriter.AssertExpectations(t) +} + +func TestImporterFindExistingID(t *testing.T) { + readerWriter := &mocks.GalleryReaderWriter{} + + i := Importer{ + ReaderWriter: readerWriter, + Input: jsonschema.Gallery{ + Path: path, + Checksum: missingChecksum, + }, + } + + expectedErr := errors.New("FindBy* error") + readerWriter.On("FindByChecksum", missingChecksum).Return(nil, nil).Once() + readerWriter.On("FindByChecksum", checksum).Return(&models.Gallery{ + ID: existingGalleryID, + }, nil).Once() + readerWriter.On("FindByChecksum", errChecksum).Return(nil, expectedErr).Once() + + id, err := i.FindExistingID() + assert.Nil(t, id) + assert.Nil(t, err) + + i.Input.Checksum = checksum + id, err = i.FindExistingID() + assert.Equal(t, existingGalleryID, *id) + assert.Nil(t, err) + + i.Input.Checksum = errChecksum + id, err = i.FindExistingID() + assert.Nil(t, id) + assert.NotNil(t, err) + + readerWriter.AssertExpectations(t) +} + +func TestCreate(t *testing.T) { + readerWriter := &mocks.GalleryReaderWriter{} + + gallery := models.Gallery{ + Title: modelstest.NullString(title), + } + + galleryErr := models.Gallery{ + Title: modelstest.NullString(galleryNameErr), + } + + i := Importer{ + ReaderWriter: readerWriter, + gallery: gallery, + } + + errCreate := errors.New("Create error") + readerWriter.On("Create", gallery).Return(&models.Gallery{ + ID: galleryID, + }, nil).Once() + readerWriter.On("Create", galleryErr).Return(nil, errCreate).Once() + + id, err := i.Create() + assert.Equal(t, galleryID, *id) + assert.Nil(t, err) + + i.gallery = galleryErr + id, err = i.Create() + assert.Nil(t, id) + assert.NotNil(t, err) + + readerWriter.AssertExpectations(t) +} + +func TestUpdate(t *testing.T) { + readerWriter := &mocks.GalleryReaderWriter{} + + gallery := models.Gallery{ + Title: modelstest.NullString(title), + } + + i := Importer{ + ReaderWriter: readerWriter, + gallery: gallery, + } + + // id needs to be set for the mock input + gallery.ID = galleryID + readerWriter.On("Update", gallery).Return(nil, nil).Once() + + err := i.Update(galleryID) + assert.Nil(t, err) + + readerWriter.AssertExpectations(t) +} diff --git a/pkg/image/export.go b/pkg/image/export.go new file mode 100644 index 000000000..f75da1832 --- /dev/null +++ b/pkg/image/export.go @@ -0,0 +1,85 @@ +package image + +import ( + "github.com/stashapp/stash/pkg/manager/jsonschema" + "github.com/stashapp/stash/pkg/models" +) + +// ToBasicJSON converts a image object into its JSON object equivalent. It +// does not convert the relationships to other objects, with the exception +// of cover image. +func ToBasicJSON(image *models.Image) *jsonschema.Image { + newImageJSON := jsonschema.Image{ + Checksum: image.Checksum, + CreatedAt: models.JSONTime{Time: image.CreatedAt.Timestamp}, + UpdatedAt: models.JSONTime{Time: image.UpdatedAt.Timestamp}, + } + + if image.Title.Valid { + newImageJSON.Title = image.Title.String + } + + if image.Rating.Valid { + newImageJSON.Rating = int(image.Rating.Int64) + } + + newImageJSON.OCounter = image.OCounter + + newImageJSON.File = getImageFileJSON(image) + + return &newImageJSON +} + +func getImageFileJSON(image *models.Image) *jsonschema.ImageFile { + ret := &jsonschema.ImageFile{} + + if image.FileModTime.Valid { + ret.ModTime = models.JSONTime{Time: image.FileModTime.Timestamp} + } + + if image.Size.Valid { + ret.Size = int(image.Size.Int64) + } + + if image.Width.Valid { + ret.Width = int(image.Width.Int64) + } + + if image.Height.Valid { + ret.Height = int(image.Height.Int64) + } + + return ret +} + +// GetStudioName returns the name of the provided image's studio. It returns an +// empty string if there is no studio assigned to the image. +func GetStudioName(reader models.StudioReader, image *models.Image) (string, error) { + if image.StudioID.Valid { + studio, err := reader.Find(int(image.StudioID.Int64)) + if err != nil { + return "", err + } + + if studio != nil { + return studio.Name.String, nil + } + } + + return "", nil +} + +// GetGalleryChecksum returns the checksum of the provided image. It returns an +// empty string if there is no gallery assigned to the image. +// func GetGalleryChecksum(reader models.GalleryReader, image *models.Image) (string, error) { +// gallery, err := reader.FindByImageID(image.ID) +// if err != nil { +// return "", fmt.Errorf("error getting image gallery: %s", err.Error()) +// } + +// if gallery != nil { +// return gallery.Checksum, nil +// } + +// return "", nil +// } diff --git a/pkg/image/export_test.go b/pkg/image/export_test.go new file mode 100644 index 000000000..8bbc198b0 --- /dev/null +++ b/pkg/image/export_test.go @@ -0,0 +1,248 @@ +package image + +import ( + "errors" + + "github.com/stashapp/stash/pkg/manager/jsonschema" + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/models/mocks" + "github.com/stashapp/stash/pkg/models/modelstest" + "github.com/stretchr/testify/assert" + + "testing" + "time" +) + +const ( + imageID = 1 + noImageID = 2 + errImageID = 3 + + studioID = 4 + missingStudioID = 5 + errStudioID = 6 + + // noGalleryID = 7 + // errGalleryID = 8 + + noTagsID = 11 + errTagsID = 12 + + noMoviesID = 13 + errMoviesID = 14 + errFindMovieID = 15 + + noMarkersID = 16 + errMarkersID = 17 + errFindPrimaryTagID = 18 + errFindByMarkerID = 19 +) + +const ( + checksum = "checksum" + title = "title" + rating = 5 + ocounter = 2 + size = 123 + width = 100 + height = 100 +) + +const ( + studioName = "studioName" + //galleryChecksum = "galleryChecksum" +) + +var names = []string{ + "name1", + "name2", +} + +var createTime time.Time = time.Date(2001, 01, 01, 0, 0, 0, 0, time.UTC) +var updateTime time.Time = time.Date(2002, 01, 01, 0, 0, 0, 0, time.UTC) + +func createFullImage(id int) models.Image { + return models.Image{ + ID: id, + Title: modelstest.NullString(title), + Checksum: checksum, + Height: modelstest.NullInt64(height), + OCounter: ocounter, + Rating: modelstest.NullInt64(rating), + Size: modelstest.NullInt64(int64(size)), + Width: modelstest.NullInt64(width), + CreatedAt: models.SQLiteTimestamp{ + Timestamp: createTime, + }, + UpdatedAt: models.SQLiteTimestamp{ + Timestamp: updateTime, + }, + } +} + +func createEmptyImage(id int) models.Image { + return models.Image{ + ID: id, + CreatedAt: models.SQLiteTimestamp{ + Timestamp: createTime, + }, + UpdatedAt: models.SQLiteTimestamp{ + Timestamp: updateTime, + }, + } +} + +func createFullJSONImage() *jsonschema.Image { + return &jsonschema.Image{ + Title: title, + Checksum: checksum, + OCounter: ocounter, + Rating: rating, + File: &jsonschema.ImageFile{ + Height: height, + Size: size, + Width: width, + }, + CreatedAt: models.JSONTime{ + Time: createTime, + }, + UpdatedAt: models.JSONTime{ + Time: updateTime, + }, + } +} + +func createEmptyJSONImage() *jsonschema.Image { + return &jsonschema.Image{ + File: &jsonschema.ImageFile{}, + CreatedAt: models.JSONTime{ + Time: createTime, + }, + UpdatedAt: models.JSONTime{ + Time: updateTime, + }, + } +} + +type basicTestScenario struct { + input models.Image + expected *jsonschema.Image +} + +var scenarios = []basicTestScenario{ + { + createFullImage(imageID), + createFullJSONImage(), + }, +} + +func TestToJSON(t *testing.T) { + for i, s := range scenarios { + image := s.input + json := ToBasicJSON(&image) + + assert.Equal(t, s.expected, json, "[%d]", i) + } +} + +func createStudioImage(studioID int) models.Image { + return models.Image{ + StudioID: modelstest.NullInt64(int64(studioID)), + } +} + +type stringTestScenario struct { + input models.Image + expected string + err bool +} + +var getStudioScenarios = []stringTestScenario{ + { + createStudioImage(studioID), + studioName, + false, + }, + { + createStudioImage(missingStudioID), + "", + false, + }, + { + createStudioImage(errStudioID), + "", + true, + }, +} + +func TestGetStudioName(t *testing.T) { + mockStudioReader := &mocks.StudioReaderWriter{} + + studioErr := errors.New("error getting image") + + mockStudioReader.On("Find", studioID).Return(&models.Studio{ + Name: modelstest.NullString(studioName), + }, nil).Once() + mockStudioReader.On("Find", missingStudioID).Return(nil, nil).Once() + mockStudioReader.On("Find", errStudioID).Return(nil, studioErr).Once() + + for i, s := range getStudioScenarios { + image := s.input + json, err := GetStudioName(mockStudioReader, &image) + + if !s.err && err != nil { + t.Errorf("[%d] unexpected error: %s", i, err.Error()) + } else if s.err && err == nil { + t.Errorf("[%d] expected error not returned", i) + } else { + assert.Equal(t, s.expected, json, "[%d]", i) + } + } + + mockStudioReader.AssertExpectations(t) +} + +// var getGalleryChecksumScenarios = []stringTestScenario{ +// { +// createEmptyImage(imageID), +// galleryChecksum, +// false, +// }, +// { +// createEmptyImage(noGalleryID), +// "", +// false, +// }, +// { +// createEmptyImage(errGalleryID), +// "", +// true, +// }, +// } + +// func TestGetGalleryChecksum(t *testing.T) { +// mockGalleryReader := &mocks.GalleryReaderWriter{} + +// galleryErr := errors.New("error getting gallery") + +// mockGalleryReader.On("FindByImageID", imageID).Return(&models.Gallery{ +// Checksum: galleryChecksum, +// }, nil).Once() +// mockGalleryReader.On("FindByImageID", noGalleryID).Return(nil, nil).Once() +// mockGalleryReader.On("FindByImageID", errGalleryID).Return(nil, galleryErr).Once() + +// for i, s := range getGalleryChecksumScenarios { +// image := s.input +// json, err := GetGalleryChecksum(mockGalleryReader, &image) + +// if !s.err && err != nil { +// t.Errorf("[%d] unexpected error: %s", i, err.Error()) +// } else if s.err && err == nil { +// t.Errorf("[%d] expected error not returned", i) +// } else { +// assert.Equal(t, s.expected, json, "[%d]", i) +// } +// } + +// mockGalleryReader.AssertExpectations(t) +// } diff --git a/pkg/image/image.go b/pkg/image/image.go new file mode 100644 index 000000000..ec781afb7 --- /dev/null +++ b/pkg/image/image.go @@ -0,0 +1,252 @@ +package image + +import ( + "archive/zip" + "database/sql" + "fmt" + "image" + "io" + "io/ioutil" + "net/http" + "os" + "path/filepath" + "strings" + "time" + + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/utils" + _ "golang.org/x/image/webp" +) + +const zipSeparator = "\x00" + +func GetSourceImage(i *models.Image) (image.Image, error) { + f, err := openSourceImage(i.Path) + if err != nil { + return nil, err + } + defer f.Close() + + srcImage, _, err := image.Decode(f) + if err != nil { + return nil, err + } + + return srcImage, nil +} + +func CalculateMD5(path string) (string, error) { + f, err := openSourceImage(path) + if err != nil { + return "", err + } + defer f.Close() + + return utils.MD5FromReader(f) +} + +func FileExists(path string) bool { + f, err := openSourceImage(path) + if err != nil { + return false + } + defer f.Close() + + return true +} + +func ZipFilename(zipFilename, filenameInZip string) string { + return zipFilename + zipSeparator + filenameInZip +} + +type imageReadCloser struct { + src io.ReadCloser + zrc *zip.ReadCloser +} + +func (i *imageReadCloser) Read(p []byte) (n int, err error) { + return i.src.Read(p) +} + +func (i *imageReadCloser) Close() error { + err := i.src.Close() + var err2 error + if i.zrc != nil { + err2 = i.zrc.Close() + } + + if err != nil { + return err + } + return err2 +} + +func openSourceImage(path string) (io.ReadCloser, error) { + // may need to read from a zip file + zipFilename, filename := getFilePath(path) + if zipFilename != "" { + r, err := zip.OpenReader(zipFilename) + if err != nil { + return nil, err + } + + // defer closing of zip to the calling function, unless an error + // is returned, in which case it should be closed immediately + + // find the file matching the filename + for _, f := range r.File { + if f.Name == filename { + src, err := f.Open() + if err != nil { + r.Close() + return nil, err + } + return &imageReadCloser{ + src: src, + zrc: r, + }, nil + } + } + + r.Close() + return nil, fmt.Errorf("file with name '%s' not found in zip file '%s'", filename, zipFilename) + } + + return os.Open(filename) +} + +func getFilePath(path string) (zipFilename, filename string) { + nullIndex := strings.Index(path, zipSeparator) + if nullIndex != -1 { + zipFilename = path[0:nullIndex] + filename = path[nullIndex+1:] + } else { + filename = path + } + return +} + +// GetFileDetails returns a pointer to an Image object with the +// width, height and size populated. +func GetFileDetails(path string) (*models.Image, error) { + i := &models.Image{ + Path: path, + } + + err := SetFileDetails(i) + if err != nil { + return nil, err + } + + return i, nil +} + +func SetFileDetails(i *models.Image) error { + f, err := stat(i.Path) + if err != nil { + return err + } + + src, _ := GetSourceImage(i) + + if src != nil { + i.Width = sql.NullInt64{ + Int64: int64(src.Bounds().Max.X), + Valid: true, + } + i.Height = sql.NullInt64{ + Int64: int64(src.Bounds().Max.Y), + Valid: true, + } + } + + i.Size = sql.NullInt64{ + Int64: int64(f.Size()), + Valid: true, + } + + return nil +} + +// GetFileModTime gets the file modification time, handling files in zip files. +func GetFileModTime(path string) (time.Time, error) { + fi, err := stat(path) + if err != nil { + return time.Time{}, fmt.Errorf("error performing stat on %s: %s", path, err.Error()) + } + + ret := fi.ModTime() + // truncate to seconds, since we don't store beyond that in the database + ret = ret.Truncate(time.Second) + + return ret, nil +} + +func stat(path string) (os.FileInfo, error) { + // may need to read from a zip file + zipFilename, filename := getFilePath(path) + if zipFilename != "" { + r, err := zip.OpenReader(zipFilename) + if err != nil { + return nil, err + } + defer r.Close() + + // find the file matching the filename + for _, f := range r.File { + if f.Name == filename { + return f.FileInfo(), nil + } + } + + return nil, fmt.Errorf("file with name '%s' not found in zip file '%s'", filename, zipFilename) + } + + return os.Stat(filename) +} + +// PathDisplayName converts an image path for display. It translates the zip +// file separator character into '/', since this character is also used for +// path separators within zip files. It returns the original provided path +// if it does not contain the zip file separator character. +func PathDisplayName(path string) string { + return strings.Replace(path, zipSeparator, "/", -1) +} + +func Serve(w http.ResponseWriter, r *http.Request, path string) { + zipFilename, _ := getFilePath(path) + w.Header().Add("Cache-Control", "max-age=604800000") // 1 Week + if zipFilename == "" { + http.ServeFile(w, r, path) + } else { + rc, err := openSourceImage(path) + if err != nil { + // assume not found + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + return + } + defer rc.Close() + + data, err := ioutil.ReadAll(rc) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Write(data) + } +} + +func IsCover(img *models.Image) bool { + _, fn := getFilePath(img.Path) + return fn == "cover.jpg" +} + +func GetTitle(s *models.Image) string { + if s.Title.String != "" { + return s.Title.String + } + + _, fn := getFilePath(s.Path) + return filepath.Base(fn) +} diff --git a/pkg/image/import.go b/pkg/image/import.go new file mode 100644 index 000000000..b970be83d --- /dev/null +++ b/pkg/image/import.go @@ -0,0 +1,366 @@ +package image + +import ( + "database/sql" + "fmt" + "strings" + + "github.com/stashapp/stash/pkg/manager/jsonschema" + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/utils" +) + +type Importer struct { + ReaderWriter models.ImageReaderWriter + StudioWriter models.StudioReaderWriter + GalleryWriter models.GalleryReaderWriter + PerformerWriter models.PerformerReaderWriter + TagWriter models.TagReaderWriter + JoinWriter models.JoinReaderWriter + Input jsonschema.Image + Path string + MissingRefBehaviour models.ImportMissingRefEnum + + ID int + image models.Image + galleries []*models.Gallery + performers []*models.Performer + tags []*models.Tag +} + +func (i *Importer) PreImport() error { + i.image = i.imageJSONToImage(i.Input) + + if err := i.populateStudio(); err != nil { + return err + } + + if err := i.populateGalleries(); err != nil { + return err + } + + if err := i.populatePerformers(); err != nil { + return err + } + + if err := i.populateTags(); err != nil { + return err + } + + return nil +} + +func (i *Importer) imageJSONToImage(imageJSON jsonschema.Image) models.Image { + newImage := models.Image{ + Checksum: imageJSON.Checksum, + Path: i.Path, + } + + if imageJSON.Title != "" { + newImage.Title = sql.NullString{String: imageJSON.Title, Valid: true} + } + if imageJSON.Rating != 0 { + newImage.Rating = sql.NullInt64{Int64: int64(imageJSON.Rating), Valid: true} + } + + newImage.OCounter = imageJSON.OCounter + newImage.CreatedAt = models.SQLiteTimestamp{Timestamp: imageJSON.CreatedAt.GetTime()} + newImage.UpdatedAt = models.SQLiteTimestamp{Timestamp: imageJSON.UpdatedAt.GetTime()} + + if imageJSON.File != nil { + if imageJSON.File.Size != 0 { + newImage.Size = sql.NullInt64{Int64: int64(imageJSON.File.Size), Valid: true} + } + if imageJSON.File.Width != 0 { + newImage.Width = sql.NullInt64{Int64: int64(imageJSON.File.Width), Valid: true} + } + if imageJSON.File.Height != 0 { + newImage.Height = sql.NullInt64{Int64: int64(imageJSON.File.Height), Valid: true} + } + } + + return newImage +} + +func (i *Importer) populateStudio() error { + if i.Input.Studio != "" { + studio, err := i.StudioWriter.FindByName(i.Input.Studio, false) + if err != nil { + return fmt.Errorf("error finding studio by name: %s", err.Error()) + } + + if studio == nil { + if i.MissingRefBehaviour == models.ImportMissingRefEnumFail { + return fmt.Errorf("image studio '%s' not found", i.Input.Studio) + } + + if i.MissingRefBehaviour == models.ImportMissingRefEnumIgnore { + return nil + } + + if i.MissingRefBehaviour == models.ImportMissingRefEnumCreate { + studioID, err := i.createStudio(i.Input.Studio) + if err != nil { + return err + } + i.image.StudioID = sql.NullInt64{ + Int64: int64(studioID), + Valid: true, + } + } + } else { + i.image.StudioID = sql.NullInt64{Int64: int64(studio.ID), Valid: true} + } + } + + return nil +} + +func (i *Importer) createStudio(name string) (int, error) { + newStudio := *models.NewStudio(name) + + created, err := i.StudioWriter.Create(newStudio) + if err != nil { + return 0, err + } + + return created.ID, nil +} + +func (i *Importer) populateGalleries() error { + for _, checksum := range i.Input.Galleries { + gallery, err := i.GalleryWriter.FindByChecksum(checksum) + if err != nil { + return fmt.Errorf("error finding gallery: %s", err.Error()) + } + + if gallery == nil { + if i.MissingRefBehaviour == models.ImportMissingRefEnumFail { + return fmt.Errorf("image gallery '%s' not found", i.Input.Studio) + } + + // we don't create galleries - just ignore + if i.MissingRefBehaviour == models.ImportMissingRefEnumIgnore || i.MissingRefBehaviour == models.ImportMissingRefEnumCreate { + continue + } + } else { + i.galleries = append(i.galleries, gallery) + } + } + + return nil +} + +func (i *Importer) populatePerformers() error { + if len(i.Input.Performers) > 0 { + names := i.Input.Performers + performers, err := i.PerformerWriter.FindByNames(names, false) + if err != nil { + return err + } + + var pluckedNames []string + for _, performer := range performers { + if !performer.Name.Valid { + continue + } + pluckedNames = append(pluckedNames, performer.Name.String) + } + + missingPerformers := utils.StrFilter(names, func(name string) bool { + return !utils.StrInclude(pluckedNames, name) + }) + + if len(missingPerformers) > 0 { + if i.MissingRefBehaviour == models.ImportMissingRefEnumFail { + return fmt.Errorf("image performers [%s] not found", strings.Join(missingPerformers, ", ")) + } + + if i.MissingRefBehaviour == models.ImportMissingRefEnumCreate { + createdPerformers, err := i.createPerformers(missingPerformers) + if err != nil { + return fmt.Errorf("error creating image performers: %s", err.Error()) + } + + performers = append(performers, createdPerformers...) + } + + // ignore if MissingRefBehaviour set to Ignore + } + + i.performers = performers + } + + return nil +} + +func (i *Importer) createPerformers(names []string) ([]*models.Performer, error) { + var ret []*models.Performer + for _, name := range names { + newPerformer := *models.NewPerformer(name) + + created, err := i.PerformerWriter.Create(newPerformer) + if err != nil { + return nil, err + } + + ret = append(ret, created) + } + + return ret, nil +} + +func (i *Importer) populateTags() error { + if len(i.Input.Tags) > 0 { + + tags, err := importTags(i.TagWriter, i.Input.Tags, i.MissingRefBehaviour) + if err != nil { + return err + } + + i.tags = tags + } + + return nil +} + +func (i *Importer) PostImport(id int) error { + if len(i.galleries) > 0 { + var galleryJoins []models.GalleriesImages + for _, gallery := range i.galleries { + join := models.GalleriesImages{ + GalleryID: gallery.ID, + ImageID: id, + } + galleryJoins = append(galleryJoins, join) + } + if err := i.JoinWriter.UpdateGalleriesImages(id, galleryJoins); err != nil { + return fmt.Errorf("failed to associate galleries: %s", err.Error()) + } + } + + if len(i.performers) > 0 { + var performerJoins []models.PerformersImages + for _, performer := range i.performers { + join := models.PerformersImages{ + PerformerID: performer.ID, + ImageID: id, + } + performerJoins = append(performerJoins, join) + } + if err := i.JoinWriter.UpdatePerformersImages(id, performerJoins); err != nil { + return fmt.Errorf("failed to associate performers: %s", err.Error()) + } + } + + if len(i.tags) > 0 { + var tagJoins []models.ImagesTags + for _, tag := range i.tags { + join := models.ImagesTags{ + ImageID: id, + TagID: tag.ID, + } + tagJoins = append(tagJoins, join) + } + if err := i.JoinWriter.UpdateImagesTags(id, tagJoins); err != nil { + return fmt.Errorf("failed to associate tags: %s", err.Error()) + } + } + + return nil +} + +func (i *Importer) Name() string { + return i.Path +} + +func (i *Importer) FindExistingID() (*int, error) { + var existing *models.Image + var err error + existing, err = i.ReaderWriter.FindByChecksum(i.Input.Checksum) + + if err != nil { + return nil, err + } + + if existing != nil { + id := existing.ID + return &id, nil + } + + return nil, nil +} + +func (i *Importer) Create() (*int, error) { + created, err := i.ReaderWriter.Create(i.image) + if err != nil { + return nil, fmt.Errorf("error creating image: %s", err.Error()) + } + + id := created.ID + i.ID = id + return &id, nil +} + +func (i *Importer) Update(id int) error { + image := i.image + image.ID = id + i.ID = id + _, err := i.ReaderWriter.UpdateFull(image) + if err != nil { + return fmt.Errorf("error updating existing image: %s", err.Error()) + } + + return nil +} + +func importTags(tagWriter models.TagReaderWriter, names []string, missingRefBehaviour models.ImportMissingRefEnum) ([]*models.Tag, error) { + tags, err := tagWriter.FindByNames(names, false) + if err != nil { + return nil, err + } + + var pluckedNames []string + for _, tag := range tags { + pluckedNames = append(pluckedNames, tag.Name) + } + + missingTags := utils.StrFilter(names, func(name string) bool { + return !utils.StrInclude(pluckedNames, name) + }) + + if len(missingTags) > 0 { + if missingRefBehaviour == models.ImportMissingRefEnumFail { + return nil, fmt.Errorf("tags [%s] not found", strings.Join(missingTags, ", ")) + } + + if missingRefBehaviour == models.ImportMissingRefEnumCreate { + createdTags, err := createTags(tagWriter, missingTags) + if err != nil { + return nil, fmt.Errorf("error creating tags: %s", err.Error()) + } + + tags = append(tags, createdTags...) + } + + // ignore if MissingRefBehaviour set to Ignore + } + + return tags, nil +} + +func createTags(tagWriter models.TagWriter, names []string) ([]*models.Tag, error) { + var ret []*models.Tag + for _, name := range names { + newTag := *models.NewTag(name) + + created, err := tagWriter.Create(newTag) + if err != nil { + return nil, err + } + + ret = append(ret, created) + } + + return ret, nil +} diff --git a/pkg/image/import_test.go b/pkg/image/import_test.go new file mode 100644 index 000000000..53414666e --- /dev/null +++ b/pkg/image/import_test.go @@ -0,0 +1,588 @@ +package image + +import ( + "errors" + "testing" + + "github.com/stashapp/stash/pkg/manager/jsonschema" + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/models/mocks" + "github.com/stashapp/stash/pkg/models/modelstest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +const invalidImage = "aW1hZ2VCeXRlcw&&" + +const ( + path = "path" + + imageNameErr = "imageNameErr" + existingImageName = "existingImageName" + + existingImageID = 100 + existingStudioID = 101 + existingGalleryID = 102 + existingPerformerID = 103 + existingMovieID = 104 + existingTagID = 105 + + existingStudioName = "existingStudioName" + existingStudioErr = "existingStudioErr" + missingStudioName = "missingStudioName" + + existingGalleryChecksum = "existingGalleryChecksum" + existingGalleryErr = "existingGalleryErr" + missingGalleryChecksum = "missingGalleryChecksum" + + existingPerformerName = "existingPerformerName" + existingPerformerErr = "existingPerformerErr" + missingPerformerName = "missingPerformerName" + + existingTagName = "existingTagName" + existingTagErr = "existingTagErr" + missingTagName = "missingTagName" + + errPerformersID = 200 + errGalleriesID = 201 + + missingChecksum = "missingChecksum" + errChecksum = "errChecksum" +) + +func TestImporterName(t *testing.T) { + i := Importer{ + Path: path, + Input: jsonschema.Image{}, + } + + assert.Equal(t, path, i.Name()) +} + +func TestImporterPreImport(t *testing.T) { + i := Importer{ + Path: path, + } + + err := i.PreImport() + assert.Nil(t, err) +} + +func TestImporterPreImportWithStudio(t *testing.T) { + studioReaderWriter := &mocks.StudioReaderWriter{} + + i := Importer{ + StudioWriter: studioReaderWriter, + Path: path, + Input: jsonschema.Image{ + Studio: existingStudioName, + }, + } + + studioReaderWriter.On("FindByName", existingStudioName, false).Return(&models.Studio{ + ID: existingStudioID, + }, nil).Once() + studioReaderWriter.On("FindByName", existingStudioErr, false).Return(nil, errors.New("FindByName error")).Once() + + err := i.PreImport() + assert.Nil(t, err) + assert.Equal(t, int64(existingStudioID), i.image.StudioID.Int64) + + i.Input.Studio = existingStudioErr + err = i.PreImport() + assert.NotNil(t, err) + + studioReaderWriter.AssertExpectations(t) +} + +func TestImporterPreImportWithMissingStudio(t *testing.T) { + studioReaderWriter := &mocks.StudioReaderWriter{} + + i := Importer{ + Path: path, + StudioWriter: studioReaderWriter, + Input: jsonschema.Image{ + Studio: missingStudioName, + }, + MissingRefBehaviour: models.ImportMissingRefEnumFail, + } + + studioReaderWriter.On("FindByName", missingStudioName, false).Return(nil, nil).Times(3) + studioReaderWriter.On("Create", mock.AnythingOfType("models.Studio")).Return(&models.Studio{ + ID: existingStudioID, + }, nil) + + err := i.PreImport() + assert.NotNil(t, err) + + i.MissingRefBehaviour = models.ImportMissingRefEnumIgnore + err = i.PreImport() + assert.Nil(t, err) + + i.MissingRefBehaviour = models.ImportMissingRefEnumCreate + err = i.PreImport() + assert.Nil(t, err) + assert.Equal(t, int64(existingStudioID), i.image.StudioID.Int64) + + studioReaderWriter.AssertExpectations(t) +} + +func TestImporterPreImportWithMissingStudioCreateErr(t *testing.T) { + studioReaderWriter := &mocks.StudioReaderWriter{} + + i := Importer{ + StudioWriter: studioReaderWriter, + Path: path, + Input: jsonschema.Image{ + Studio: missingStudioName, + }, + MissingRefBehaviour: models.ImportMissingRefEnumCreate, + } + + studioReaderWriter.On("FindByName", missingStudioName, false).Return(nil, nil).Once() + studioReaderWriter.On("Create", mock.AnythingOfType("models.Studio")).Return(nil, errors.New("Create error")) + + err := i.PreImport() + assert.NotNil(t, err) +} + +func TestImporterPreImportWithGallery(t *testing.T) { + galleryReaderWriter := &mocks.GalleryReaderWriter{} + + i := Importer{ + GalleryWriter: galleryReaderWriter, + Path: path, + Input: jsonschema.Image{ + Galleries: []string{ + existingGalleryChecksum, + }, + }, + } + + galleryReaderWriter.On("FindByChecksum", existingGalleryChecksum).Return(&models.Gallery{ + ID: existingGalleryID, + }, nil).Once() + galleryReaderWriter.On("FindByChecksum", existingGalleryErr).Return(nil, errors.New("FindByChecksum error")).Once() + + err := i.PreImport() + assert.Nil(t, err) + assert.Equal(t, existingGalleryID, i.galleries[0].ID) + + i.Input.Galleries = []string{ + existingGalleryErr, + } + + err = i.PreImport() + assert.NotNil(t, err) + + galleryReaderWriter.AssertExpectations(t) +} + +func TestImporterPreImportWithMissingGallery(t *testing.T) { + galleryReaderWriter := &mocks.GalleryReaderWriter{} + + i := Importer{ + Path: path, + GalleryWriter: galleryReaderWriter, + Input: jsonschema.Image{ + Galleries: []string{ + missingGalleryChecksum, + }, + }, + MissingRefBehaviour: models.ImportMissingRefEnumFail, + } + + galleryReaderWriter.On("FindByChecksum", missingGalleryChecksum).Return(nil, nil).Times(3) + + err := i.PreImport() + assert.NotNil(t, err) + + i.MissingRefBehaviour = models.ImportMissingRefEnumIgnore + err = i.PreImport() + assert.Nil(t, err) + assert.Nil(t, i.galleries) + + i.MissingRefBehaviour = models.ImportMissingRefEnumCreate + err = i.PreImport() + assert.Nil(t, err) + assert.Nil(t, i.galleries) + + galleryReaderWriter.AssertExpectations(t) +} + +func TestImporterPreImportWithPerformer(t *testing.T) { + performerReaderWriter := &mocks.PerformerReaderWriter{} + + i := Importer{ + PerformerWriter: performerReaderWriter, + Path: path, + MissingRefBehaviour: models.ImportMissingRefEnumFail, + Input: jsonschema.Image{ + Performers: []string{ + existingPerformerName, + }, + }, + } + + performerReaderWriter.On("FindByNames", []string{existingPerformerName}, false).Return([]*models.Performer{ + { + ID: existingPerformerID, + Name: modelstest.NullString(existingPerformerName), + }, + }, nil).Once() + performerReaderWriter.On("FindByNames", []string{existingPerformerErr}, false).Return(nil, errors.New("FindByNames error")).Once() + + err := i.PreImport() + assert.Nil(t, err) + assert.Equal(t, existingPerformerID, i.performers[0].ID) + + i.Input.Performers = []string{existingPerformerErr} + err = i.PreImport() + assert.NotNil(t, err) + + performerReaderWriter.AssertExpectations(t) +} + +func TestImporterPreImportWithMissingPerformer(t *testing.T) { + performerReaderWriter := &mocks.PerformerReaderWriter{} + + i := Importer{ + Path: path, + PerformerWriter: performerReaderWriter, + Input: jsonschema.Image{ + Performers: []string{ + missingPerformerName, + }, + }, + MissingRefBehaviour: models.ImportMissingRefEnumFail, + } + + performerReaderWriter.On("FindByNames", []string{missingPerformerName}, false).Return(nil, nil).Times(3) + performerReaderWriter.On("Create", mock.AnythingOfType("models.Performer")).Return(&models.Performer{ + ID: existingPerformerID, + }, nil) + + err := i.PreImport() + assert.NotNil(t, err) + + i.MissingRefBehaviour = models.ImportMissingRefEnumIgnore + err = i.PreImport() + assert.Nil(t, err) + + i.MissingRefBehaviour = models.ImportMissingRefEnumCreate + err = i.PreImport() + assert.Nil(t, err) + assert.Equal(t, existingPerformerID, i.performers[0].ID) + + performerReaderWriter.AssertExpectations(t) +} + +func TestImporterPreImportWithMissingPerformerCreateErr(t *testing.T) { + performerReaderWriter := &mocks.PerformerReaderWriter{} + + i := Importer{ + PerformerWriter: performerReaderWriter, + Path: path, + Input: jsonschema.Image{ + Performers: []string{ + missingPerformerName, + }, + }, + MissingRefBehaviour: models.ImportMissingRefEnumCreate, + } + + performerReaderWriter.On("FindByNames", []string{missingPerformerName}, false).Return(nil, nil).Once() + performerReaderWriter.On("Create", mock.AnythingOfType("models.Performer")).Return(nil, errors.New("Create error")) + + err := i.PreImport() + assert.NotNil(t, err) +} + +func TestImporterPreImportWithTag(t *testing.T) { + tagReaderWriter := &mocks.TagReaderWriter{} + + i := Importer{ + TagWriter: tagReaderWriter, + Path: path, + MissingRefBehaviour: models.ImportMissingRefEnumFail, + Input: jsonschema.Image{ + Tags: []string{ + existingTagName, + }, + }, + } + + tagReaderWriter.On("FindByNames", []string{existingTagName}, false).Return([]*models.Tag{ + { + ID: existingTagID, + Name: existingTagName, + }, + }, nil).Once() + tagReaderWriter.On("FindByNames", []string{existingTagErr}, false).Return(nil, errors.New("FindByNames error")).Once() + + err := i.PreImport() + assert.Nil(t, err) + assert.Equal(t, existingTagID, i.tags[0].ID) + + i.Input.Tags = []string{existingTagErr} + err = i.PreImport() + assert.NotNil(t, err) + + tagReaderWriter.AssertExpectations(t) +} + +func TestImporterPreImportWithMissingTag(t *testing.T) { + tagReaderWriter := &mocks.TagReaderWriter{} + + i := Importer{ + Path: path, + TagWriter: tagReaderWriter, + Input: jsonschema.Image{ + Tags: []string{ + missingTagName, + }, + }, + MissingRefBehaviour: models.ImportMissingRefEnumFail, + } + + tagReaderWriter.On("FindByNames", []string{missingTagName}, false).Return(nil, nil).Times(3) + tagReaderWriter.On("Create", mock.AnythingOfType("models.Tag")).Return(&models.Tag{ + ID: existingTagID, + }, nil) + + err := i.PreImport() + assert.NotNil(t, err) + + i.MissingRefBehaviour = models.ImportMissingRefEnumIgnore + err = i.PreImport() + assert.Nil(t, err) + + i.MissingRefBehaviour = models.ImportMissingRefEnumCreate + err = i.PreImport() + assert.Nil(t, err) + assert.Equal(t, existingTagID, i.tags[0].ID) + + tagReaderWriter.AssertExpectations(t) +} + +func TestImporterPreImportWithMissingTagCreateErr(t *testing.T) { + tagReaderWriter := &mocks.TagReaderWriter{} + + i := Importer{ + TagWriter: tagReaderWriter, + Path: path, + Input: jsonschema.Image{ + Tags: []string{ + missingTagName, + }, + }, + MissingRefBehaviour: models.ImportMissingRefEnumCreate, + } + + tagReaderWriter.On("FindByNames", []string{missingTagName}, false).Return(nil, nil).Once() + tagReaderWriter.On("Create", mock.AnythingOfType("models.Tag")).Return(nil, errors.New("Create error")) + + err := i.PreImport() + assert.NotNil(t, err) +} + +func TestImporterPostImportUpdateGallery(t *testing.T) { + joinReaderWriter := &mocks.JoinReaderWriter{} + + i := Importer{ + JoinWriter: joinReaderWriter, + galleries: []*models.Gallery{ + { + ID: existingGalleryID, + }, + }, + } + + updateErr := errors.New("UpdateGalleriesImages error") + + joinReaderWriter.On("UpdateGalleriesImages", imageID, []models.GalleriesImages{ + { + GalleryID: existingGalleryID, + ImageID: imageID, + }, + }).Return(nil).Once() + joinReaderWriter.On("UpdateGalleriesImages", errGalleriesID, mock.AnythingOfType("[]models.GalleriesImages")).Return(updateErr).Once() + + err := i.PostImport(imageID) + assert.Nil(t, err) + + err = i.PostImport(errGalleriesID) + assert.NotNil(t, err) + + joinReaderWriter.AssertExpectations(t) +} + +func TestImporterPostImportUpdatePerformers(t *testing.T) { + joinReaderWriter := &mocks.JoinReaderWriter{} + + i := Importer{ + JoinWriter: joinReaderWriter, + performers: []*models.Performer{ + { + ID: existingPerformerID, + }, + }, + } + + updateErr := errors.New("UpdatePerformersImages error") + + joinReaderWriter.On("UpdatePerformersImages", imageID, []models.PerformersImages{ + { + PerformerID: existingPerformerID, + ImageID: imageID, + }, + }).Return(nil).Once() + joinReaderWriter.On("UpdatePerformersImages", errPerformersID, mock.AnythingOfType("[]models.PerformersImages")).Return(updateErr).Once() + + err := i.PostImport(imageID) + assert.Nil(t, err) + + err = i.PostImport(errPerformersID) + assert.NotNil(t, err) + + joinReaderWriter.AssertExpectations(t) +} + +func TestImporterPostImportUpdateTags(t *testing.T) { + joinReaderWriter := &mocks.JoinReaderWriter{} + + i := Importer{ + JoinWriter: joinReaderWriter, + tags: []*models.Tag{ + { + ID: existingTagID, + }, + }, + } + + updateErr := errors.New("UpdateImagesTags error") + + joinReaderWriter.On("UpdateImagesTags", imageID, []models.ImagesTags{ + { + TagID: existingTagID, + ImageID: imageID, + }, + }).Return(nil).Once() + joinReaderWriter.On("UpdateImagesTags", errTagsID, mock.AnythingOfType("[]models.ImagesTags")).Return(updateErr).Once() + + err := i.PostImport(imageID) + assert.Nil(t, err) + + err = i.PostImport(errTagsID) + assert.NotNil(t, err) + + joinReaderWriter.AssertExpectations(t) +} + +func TestImporterFindExistingID(t *testing.T) { + readerWriter := &mocks.ImageReaderWriter{} + + i := Importer{ + ReaderWriter: readerWriter, + Path: path, + Input: jsonschema.Image{ + Checksum: missingChecksum, + }, + } + + expectedErr := errors.New("FindBy* error") + readerWriter.On("FindByChecksum", missingChecksum).Return(nil, nil).Once() + readerWriter.On("FindByChecksum", checksum).Return(&models.Image{ + ID: existingImageID, + }, nil).Once() + readerWriter.On("FindByChecksum", errChecksum).Return(nil, expectedErr).Once() + + id, err := i.FindExistingID() + assert.Nil(t, id) + assert.Nil(t, err) + + i.Input.Checksum = checksum + id, err = i.FindExistingID() + assert.Equal(t, existingImageID, *id) + assert.Nil(t, err) + + i.Input.Checksum = errChecksum + id, err = i.FindExistingID() + assert.Nil(t, id) + assert.NotNil(t, err) + + readerWriter.AssertExpectations(t) +} + +func TestCreate(t *testing.T) { + readerWriter := &mocks.ImageReaderWriter{} + + image := models.Image{ + Title: modelstest.NullString(title), + } + + imageErr := models.Image{ + Title: modelstest.NullString(imageNameErr), + } + + i := Importer{ + ReaderWriter: readerWriter, + image: image, + } + + errCreate := errors.New("Create error") + readerWriter.On("Create", image).Return(&models.Image{ + ID: imageID, + }, nil).Once() + readerWriter.On("Create", imageErr).Return(nil, errCreate).Once() + + id, err := i.Create() + assert.Equal(t, imageID, *id) + assert.Nil(t, err) + assert.Equal(t, imageID, i.ID) + + i.image = imageErr + id, err = i.Create() + assert.Nil(t, id) + assert.NotNil(t, err) + + readerWriter.AssertExpectations(t) +} + +func TestUpdate(t *testing.T) { + readerWriter := &mocks.ImageReaderWriter{} + + image := models.Image{ + Title: modelstest.NullString(title), + } + + imageErr := models.Image{ + Title: modelstest.NullString(imageNameErr), + } + + i := Importer{ + ReaderWriter: readerWriter, + image: image, + } + + errUpdate := errors.New("Update error") + + // id needs to be set for the mock input + image.ID = imageID + readerWriter.On("UpdateFull", image).Return(nil, nil).Once() + + err := i.Update(imageID) + assert.Nil(t, err) + assert.Equal(t, imageID, i.ID) + + i.image = imageErr + + // need to set id separately + imageErr.ID = errImageID + readerWriter.On("UpdateFull", imageErr).Return(nil, errUpdate).Once() + + err = i.Update(errImageID) + assert.NotNil(t, err) + + readerWriter.AssertExpectations(t) +} diff --git a/pkg/image/thumbnail.go b/pkg/image/thumbnail.go new file mode 100644 index 000000000..107b77143 --- /dev/null +++ b/pkg/image/thumbnail.go @@ -0,0 +1,40 @@ +package image + +import ( + "bytes" + "image" + "image/jpeg" + + "github.com/disintegration/imaging" +) + +func ThumbnailNeeded(srcImage image.Image, maxSize int) bool { + dim := srcImage.Bounds().Max + w := dim.X + h := dim.Y + + return w > maxSize || h > maxSize +} + +// GetThumbnail returns the thumbnail image of the provided image resized to +// the provided max size. It resizes based on the largest X/Y direction. +// It returns nil and an error if an error occurs reading, decoding or encoding +// the image. +func GetThumbnail(srcImage image.Image, maxSize int) ([]byte, error) { + var resizedImage image.Image + + // if height is longer then resize by height instead of width + dim := srcImage.Bounds().Max + if dim.Y > dim.X { + resizedImage = imaging.Resize(srcImage, 0, maxSize, imaging.Box) + } else { + resizedImage = imaging.Resize(srcImage, maxSize, 0, imaging.Box) + } + + buf := new(bytes.Buffer) + err := jpeg.Encode(buf, resizedImage, nil) + if err != nil { + return nil, err + } + return buf.Bytes(), nil +} diff --git a/pkg/manager/config/config.go b/pkg/manager/config/config.go index 383febe7a..770b9da38 100644 --- a/pkg/manager/config/config.go +++ b/pkg/manager/config/config.go @@ -3,6 +3,7 @@ package config import ( "golang.org/x/crypto/bcrypt" + "errors" "io/ioutil" "path/filepath" @@ -26,6 +27,21 @@ const DefaultMaxSessionAge = 60 * 60 * 1 // 1 hours const Database = "database" const Exclude = "exclude" +const ImageExclude = "image_exclude" + +const VideoExtensions = "video_extensions" + +var defaultVideoExtensions = []string{"m4v", "mp4", "mov", "wmv", "avi", "mpg", "mpeg", "rmvb", "rm", "flv", "asf", "mkv", "webm"} + +const ImageExtensions = "image_extensions" + +var defaultImageExtensions = []string{"png", "jpg", "jpeg", "gif", "webp"} + +const GalleryExtensions = "gallery_extensions" + +var defaultGalleryExtensions = []string{"zip", "cbz"} + +const CreateGalleriesFromFolders = "create_galleries_from_folders" // CalculateMD5 is the config key used to determine if MD5 should be calculated // for video files. @@ -67,6 +83,9 @@ const ScrapersPath = "scrapers_path" const ScraperUserAgent = "scraper_user_agent" const ScraperCDPPath = "scraper_cdp_path" +// stash-box options +const StashBoxes = "stash_boxes" + // plugin options const PluginsPath = "plugins_path" @@ -114,8 +133,21 @@ func GetConfigPath() string { return filepath.Dir(configFileUsed) } -func GetStashPaths() []string { - return viper.GetStringSlice(Stash) +func GetStashPaths() []*models.StashConfig { + var ret []*models.StashConfig + if err := viper.UnmarshalKey(Stash, &ret); err != nil || len(ret) == 0 { + // fallback to legacy format + ss := viper.GetStringSlice(Stash) + ret = nil + for _, path := range ss { + toAdd := &models.StashConfig{ + Path: path, + } + ret = append(ret, toAdd) + } + } + + return ret } func GetCachePath() string { @@ -154,6 +186,38 @@ func GetExcludes() []string { return viper.GetStringSlice(Exclude) } +func GetImageExcludes() []string { + return viper.GetStringSlice(ImageExclude) +} + +func GetVideoExtensions() []string { + ret := viper.GetStringSlice(VideoExtensions) + if ret == nil { + ret = defaultVideoExtensions + } + return ret +} + +func GetImageExtensions() []string { + ret := viper.GetStringSlice(ImageExtensions) + if ret == nil { + ret = defaultImageExtensions + } + return ret +} + +func GetGalleryExtensions() []string { + ret := viper.GetStringSlice(GalleryExtensions) + if ret == nil { + ret = defaultGalleryExtensions + } + return ret +} + +func GetCreateGalleriesFromFolders() bool { + return viper.GetBool(CreateGalleriesFromFolders) +} + func GetLanguage() string { ret := viper.GetString(Language) @@ -198,6 +262,12 @@ func GetScraperCDPPath() string { return viper.GetString(ScraperCDPPath) } +func GetStashBoxes() []*models.StashBox { + var boxes []*models.StashBox + viper.UnmarshalKey(StashBoxes, &boxes) + return boxes +} + func GetDefaultPluginsPath() string { // default to the same directory as the config file fn := filepath.Join(GetConfigPath(), "plugins") @@ -332,6 +402,21 @@ func ValidateCredentials(username string, password string) bool { return username == authUser && err == nil } +func ValidateStashBoxes(boxes []*models.StashBoxInput) error { + isMulti := len(boxes) > 1 + + for _, box := range boxes { + if box.APIKey == "" { + return errors.New("Stash-box API Key cannot be blank") + } else if box.Endpoint == "" { + return errors.New("Stash-box Endpoint cannot be blank") + } else if isMulti && box.Name == "" { + return errors.New("Stash-box Name cannot be blank") + } + } + return nil +} + // GetMaxSessionAge gets the maximum age for session cookies, in seconds. // Session cookie expiry times are refreshed every request. func GetMaxSessionAge() int { diff --git a/pkg/manager/downloads.go b/pkg/manager/downloads.go new file mode 100644 index 000000000..4706298f2 --- /dev/null +++ b/pkg/manager/downloads.go @@ -0,0 +1,109 @@ +package manager + +import ( + "net/http" + "os" + "sync" + "time" + + "github.com/stashapp/stash/pkg/logger" + "github.com/stashapp/stash/pkg/utils" +) + +// DownloadStore manages single-use generated files for the UI to download. +type DownloadStore struct { + m map[string]*storeFile + mutex sync.Mutex +} + +type storeFile struct { + path string + contentType string + keep bool + wg sync.WaitGroup + once sync.Once +} + +func NewDownloadStore() *DownloadStore { + return &DownloadStore{ + m: make(map[string]*storeFile), + } +} + +func (s *DownloadStore) RegisterFile(fp string, contentType string, keep bool) string { + const keyLength = 4 + const attempts = 100 + + // keep generating random keys until we get a free one + // prevent infinite loop by only attempting a finite amount of times + var hash string + generate := true + a := 0 + + s.mutex.Lock() + for generate && a < attempts { + hash = utils.GenerateRandomKey(keyLength) + _, generate = s.m[hash] + a = a + 1 + } + + s.m[hash] = &storeFile{ + path: fp, + contentType: contentType, + keep: keep, + } + s.mutex.Unlock() + + return hash +} + +func (s *DownloadStore) Serve(hash string, w http.ResponseWriter, r *http.Request) { + s.mutex.Lock() + f, ok := s.m[hash] + + if !ok { + s.mutex.Unlock() + http.NotFound(w, r) + return + } + + if !f.keep { + s.waitAndRemoveFile(hash, &w, r) + } + + s.mutex.Unlock() + + if f.contentType != "" { + w.Header().Add("Content-Type", f.contentType) + } + http.ServeFile(w, r, f.path) +} + +func (s *DownloadStore) waitAndRemoveFile(hash string, w *http.ResponseWriter, r *http.Request) { + f := s.m[hash] + notify := r.Context().Done() + f.wg.Add(1) + + go func() { + <-notify + s.mutex.Lock() + defer s.mutex.Unlock() + + f.wg.Done() + }() + + go f.once.Do(func() { + // leave it up for 30 seconds after the first request to allow for multiple requests + time.Sleep(30 * time.Second) + + f.wg.Wait() + s.mutex.Lock() + defer s.mutex.Unlock() + + delete(s.m, hash) + err := os.Remove(f.path) + if err != nil { + logger.Errorf("error removing %s after downloading: %s", f.path, err.Error()) + } + }) +} diff --git a/pkg/manager/exclude_files.go b/pkg/manager/exclude_files.go index 6d5a28f9f..c3edd9648 100644 --- a/pkg/manager/exclude_files.go +++ b/pkg/manager/exclude_files.go @@ -1,6 +1,7 @@ package manager import ( + "path/filepath" "regexp" "strings" @@ -37,15 +38,20 @@ func excludeFiles(files []string, patterns []string) ([]string, int) { } } +func matchFileRegex(file string, fileRegexps []*regexp.Regexp) bool { + for _, regPattern := range fileRegexps { + if regPattern.MatchString(strings.ToLower(file)) { + return true + } + } + return false +} + func matchFile(file string, patterns []string) bool { if patterns != nil { fileRegexps := generateRegexps(patterns) - for _, regPattern := range fileRegexps { - if regPattern.MatchString(strings.ToLower(file)) { - return true - } - } + return matchFileRegex(file, fileRegexps) } return false @@ -80,3 +86,14 @@ func matchFileSimple(file string, regExps []*regexp.Regexp) bool { } return false } + +func matchExtension(path string, extensions []string) bool { + ext := filepath.Ext(path) + for _, e := range extensions { + if strings.ToLower(ext) == strings.ToLower("."+e) { + return true + } + } + + return false +} diff --git a/pkg/manager/gallery.go b/pkg/manager/gallery.go new file mode 100644 index 000000000..b7929ee67 --- /dev/null +++ b/pkg/manager/gallery.go @@ -0,0 +1,17 @@ +package manager + +import ( + "os" + + "github.com/stashapp/stash/pkg/logger" + "github.com/stashapp/stash/pkg/models" +) + +func DeleteGalleryFile(gallery *models.Gallery) { + if gallery.Path.Valid { + err := os.Remove(gallery.Path.String) + if err != nil { + logger.Warnf("Could not delete file %s: %s", gallery.Path.String, err.Error()) + } + } +} diff --git a/pkg/manager/image.go b/pkg/manager/image.go new file mode 100644 index 000000000..4c6b3682b --- /dev/null +++ b/pkg/manager/image.go @@ -0,0 +1,102 @@ +package manager + +import ( + "archive/zip" + "os" + "strings" + + "github.com/jmoiron/sqlx" + + "github.com/stashapp/stash/pkg/logger" + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/utils" +) + +// DestroyImage deletes an image and its associated relationships from the +// database. +func DestroyImage(imageID int, tx *sqlx.Tx) error { + qb := models.NewImageQueryBuilder() + jqb := models.NewJoinsQueryBuilder() + + _, err := qb.Find(imageID) + if err != nil { + return err + } + + if err := jqb.DestroyImagesTags(imageID, tx); err != nil { + return err + } + + if err := jqb.DestroyPerformersImages(imageID, tx); err != nil { + return err + } + + if err := jqb.DestroyImageGalleries(imageID, tx); err != nil { + return err + } + + if err := qb.Destroy(imageID, tx); err != nil { + return err + } + + return nil +} + +// DeleteGeneratedImageFiles deletes generated files for the provided image. +func DeleteGeneratedImageFiles(image *models.Image) { + thumbPath := GetInstance().Paths.Generated.GetThumbnailPath(image.Checksum, models.DefaultGthumbWidth) + exists, _ := utils.FileExists(thumbPath) + if exists { + err := os.Remove(thumbPath) + if err != nil { + logger.Warnf("Could not delete file %s: %s", thumbPath, err.Error()) + } + } +} + +// DeleteImageFile deletes the image file from the filesystem. +func DeleteImageFile(image *models.Image) { + err := os.Remove(image.Path) + if err != nil { + logger.Warnf("Could not delete file %s: %s", image.Path, err.Error()) + } +} + +func walkGalleryZip(path string, walkFunc func(file *zip.File) error) error { + readCloser, err := zip.OpenReader(path) + if err != nil { + return err + } + defer readCloser.Close() + + for _, file := range readCloser.File { + if file.FileInfo().IsDir() { + continue + } + + if strings.Contains(file.Name, "__MACOSX") { + continue + } + + if !isImage(file.Name) { + continue + } + + err := walkFunc(file) + if err != nil { + return err + } + } + + return nil +} + +func countImagesInZip(path string) int { + ret := 0 + walkGalleryZip(path, func(file *zip.File) error { + ret++ + return nil + }) + + return ret +} diff --git a/pkg/manager/import.go b/pkg/manager/import.go new file mode 100644 index 000000000..d5f61bcf2 --- /dev/null +++ b/pkg/manager/import.go @@ -0,0 +1,61 @@ +package manager + +import ( + "fmt" + + "github.com/stashapp/stash/pkg/logger" + "github.com/stashapp/stash/pkg/models" +) + +type importer interface { + PreImport() error + PostImport(id int) error + Name() string + FindExistingID() (*int, error) + Create() (*int, error) + Update(id int) error +} + +func performImport(i importer, duplicateBehaviour models.ImportDuplicateEnum) error { + if err := i.PreImport(); err != nil { + return err + } + + // try to find an existing object with the same name + name := i.Name() + existing, err := i.FindExistingID() + if err != nil { + return fmt.Errorf("error finding existing objects: %s", err.Error()) + } + + var id int + + if existing != nil { + if duplicateBehaviour == models.ImportDuplicateEnumFail { + return fmt.Errorf("existing object with name '%s'", name) + } else if duplicateBehaviour == models.ImportDuplicateEnumIgnore { + logger.Info("Skipping existing object") + return nil + } + + // must be overwriting + id = *existing + if err := i.Update(id); err != nil { + return fmt.Errorf("error updating existing object: %s", err.Error()) + } + } else { + // creating + createdID, err := i.Create() + if err != nil { + return fmt.Errorf("error creating object: %s", err.Error()) + } + + id = *createdID + } + + if err := i.PostImport(id); err != nil { + return err + } + + return nil +} diff --git a/pkg/manager/json_utils.go b/pkg/manager/json_utils.go index 1d5dbc4a0..9a04e45cf 100644 --- a/pkg/manager/json_utils.go +++ b/pkg/manager/json_utils.go @@ -2,62 +2,81 @@ package manager import ( "github.com/stashapp/stash/pkg/manager/jsonschema" + "github.com/stashapp/stash/pkg/manager/paths" ) -type jsonUtils struct{} +type jsonUtils struct { + json paths.JSONPaths +} func (jp *jsonUtils) getMappings() (*jsonschema.Mappings, error) { - return jsonschema.LoadMappingsFile(instance.Paths.JSON.MappingsFile) + return jsonschema.LoadMappingsFile(jp.json.MappingsFile) } func (jp *jsonUtils) saveMappings(mappings *jsonschema.Mappings) error { - return jsonschema.SaveMappingsFile(instance.Paths.JSON.MappingsFile, mappings) + return jsonschema.SaveMappingsFile(jp.json.MappingsFile, mappings) } func (jp *jsonUtils) getScraped() ([]jsonschema.ScrapedItem, error) { - return jsonschema.LoadScrapedFile(instance.Paths.JSON.ScrapedFile) + return jsonschema.LoadScrapedFile(jp.json.ScrapedFile) } func (jp *jsonUtils) saveScaped(scraped []jsonschema.ScrapedItem) error { - return jsonschema.SaveScrapedFile(instance.Paths.JSON.ScrapedFile, scraped) + return jsonschema.SaveScrapedFile(jp.json.ScrapedFile, scraped) } func (jp *jsonUtils) getPerformer(checksum string) (*jsonschema.Performer, error) { - return jsonschema.LoadPerformerFile(instance.Paths.JSON.PerformerJSONPath(checksum)) + return jsonschema.LoadPerformerFile(jp.json.PerformerJSONPath(checksum)) } func (jp *jsonUtils) savePerformer(checksum string, performer *jsonschema.Performer) error { - return jsonschema.SavePerformerFile(instance.Paths.JSON.PerformerJSONPath(checksum), performer) + return jsonschema.SavePerformerFile(jp.json.PerformerJSONPath(checksum), performer) } func (jp *jsonUtils) getStudio(checksum string) (*jsonschema.Studio, error) { - return jsonschema.LoadStudioFile(instance.Paths.JSON.StudioJSONPath(checksum)) + return jsonschema.LoadStudioFile(jp.json.StudioJSONPath(checksum)) } func (jp *jsonUtils) saveStudio(checksum string, studio *jsonschema.Studio) error { - return jsonschema.SaveStudioFile(instance.Paths.JSON.StudioJSONPath(checksum), studio) + return jsonschema.SaveStudioFile(jp.json.StudioJSONPath(checksum), studio) } func (jp *jsonUtils) getTag(checksum string) (*jsonschema.Tag, error) { - return jsonschema.LoadTagFile(instance.Paths.JSON.TagJSONPath(checksum)) + return jsonschema.LoadTagFile(jp.json.TagJSONPath(checksum)) } func (jp *jsonUtils) saveTag(checksum string, tag *jsonschema.Tag) error { - return jsonschema.SaveTagFile(instance.Paths.JSON.TagJSONPath(checksum), tag) + return jsonschema.SaveTagFile(jp.json.TagJSONPath(checksum), tag) } func (jp *jsonUtils) getMovie(checksum string) (*jsonschema.Movie, error) { - return jsonschema.LoadMovieFile(instance.Paths.JSON.MovieJSONPath(checksum)) + return jsonschema.LoadMovieFile(jp.json.MovieJSONPath(checksum)) } func (jp *jsonUtils) saveMovie(checksum string, movie *jsonschema.Movie) error { - return jsonschema.SaveMovieFile(instance.Paths.JSON.MovieJSONPath(checksum), movie) + return jsonschema.SaveMovieFile(jp.json.MovieJSONPath(checksum), movie) } func (jp *jsonUtils) getScene(checksum string) (*jsonschema.Scene, error) { - return jsonschema.LoadSceneFile(instance.Paths.JSON.SceneJSONPath(checksum)) + return jsonschema.LoadSceneFile(jp.json.SceneJSONPath(checksum)) } func (jp *jsonUtils) saveScene(checksum string, scene *jsonschema.Scene) error { - return jsonschema.SaveSceneFile(instance.Paths.JSON.SceneJSONPath(checksum), scene) + return jsonschema.SaveSceneFile(jp.json.SceneJSONPath(checksum), scene) +} + +func (jp *jsonUtils) getImage(checksum string) (*jsonschema.Image, error) { + return jsonschema.LoadImageFile(jp.json.ImageJSONPath(checksum)) +} + +func (jp *jsonUtils) saveImage(checksum string, image *jsonschema.Image) error { + return jsonschema.SaveImageFile(jp.json.ImageJSONPath(checksum), image) +} + +func (jp *jsonUtils) getGallery(checksum string) (*jsonschema.Gallery, error) { + return jsonschema.LoadGalleryFile(jp.json.GalleryJSONPath(checksum)) +} + +func (jp *jsonUtils) saveGallery(checksum string, gallery *jsonschema.Gallery) error { + return jsonschema.SaveGalleryFile(jp.json.GalleryJSONPath(checksum), gallery) } diff --git a/pkg/manager/jsonschema/gallery.go b/pkg/manager/jsonschema/gallery.go new file mode 100644 index 000000000..b128ee248 --- /dev/null +++ b/pkg/manager/jsonschema/gallery.go @@ -0,0 +1,49 @@ +package jsonschema + +import ( + "fmt" + "os" + + jsoniter "github.com/json-iterator/go" + "github.com/stashapp/stash/pkg/models" +) + +type Gallery struct { + Path string `json:"path,omitempty"` + Checksum string `json:"checksum,omitempty"` + Zip bool `json:"zip,omitempty"` + Title string `json:"title,omitempty"` + URL string `json:"url,omitempty"` + Date string `json:"date,omitempty"` + Details string `json:"details,omitempty"` + Rating int `json:"rating,omitempty"` + Studio string `json:"studio,omitempty"` + Performers []string `json:"performers,omitempty"` + Tags []string `json:"tags,omitempty"` + FileModTime models.JSONTime `json:"file_mod_time,omitempty"` + CreatedAt models.JSONTime `json:"created_at,omitempty"` + UpdatedAt models.JSONTime `json:"updated_at,omitempty"` +} + +func LoadGalleryFile(filePath string) (*Gallery, error) { + var gallery Gallery + file, err := os.Open(filePath) + defer file.Close() + if err != nil { + return nil, err + } + var json = jsoniter.ConfigCompatibleWithStandardLibrary + jsonParser := json.NewDecoder(file) + err = jsonParser.Decode(&gallery) + if err != nil { + return nil, err + } + return &gallery, nil +} + +func SaveGalleryFile(filePath string, gallery *Gallery) error { + if gallery == nil { + return fmt.Errorf("gallery must not be nil") + } + return marshalToFile(filePath, gallery) +} diff --git a/pkg/manager/jsonschema/image.go b/pkg/manager/jsonschema/image.go new file mode 100644 index 000000000..d018ab2ab --- /dev/null +++ b/pkg/manager/jsonschema/image.go @@ -0,0 +1,53 @@ +package jsonschema + +import ( + "fmt" + "os" + + jsoniter "github.com/json-iterator/go" + "github.com/stashapp/stash/pkg/models" +) + +type ImageFile struct { + ModTime models.JSONTime `json:"mod_time,omitempty"` + Size int `json:"size"` + Width int `json:"width"` + Height int `json:"height"` +} + +type Image struct { + Title string `json:"title,omitempty"` + Checksum string `json:"checksum,omitempty"` + Studio string `json:"studio,omitempty"` + Rating int `json:"rating,omitempty"` + OCounter int `json:"o_counter,omitempty"` + Galleries []string `json:"galleries,omitempty"` + Performers []string `json:"performers,omitempty"` + Tags []string `json:"tags,omitempty"` + File *ImageFile `json:"file,omitempty"` + CreatedAt models.JSONTime `json:"created_at,omitempty"` + UpdatedAt models.JSONTime `json:"updated_at,omitempty"` +} + +func LoadImageFile(filePath string) (*Image, error) { + var image Image + file, err := os.Open(filePath) + defer file.Close() + if err != nil { + return nil, err + } + var json = jsoniter.ConfigCompatibleWithStandardLibrary + jsonParser := json.NewDecoder(file) + err = jsonParser.Decode(&image) + if err != nil { + return nil, err + } + return &image, nil +} + +func SaveImageFile(filePath string, image *Image) error { + if image == nil { + return fmt.Errorf("image must not be nil") + } + return marshalToFile(filePath, image) +} diff --git a/pkg/manager/jsonschema/mappings.go b/pkg/manager/jsonschema/mappings.go index 2e65f1023..1622e52a4 100644 --- a/pkg/manager/jsonschema/mappings.go +++ b/pkg/manager/jsonschema/mappings.go @@ -7,23 +7,20 @@ import ( jsoniter "github.com/json-iterator/go" ) -type NameMapping struct { - Name string `json:"name"` - Checksum string `json:"checksum"` -} - -type PathMapping struct { - Path string `json:"path"` +type PathNameMapping struct { + Path string `json:"path,omitempty"` + Name string `json:"name,omitempty"` Checksum string `json:"checksum"` } type Mappings struct { - Tags []NameMapping `json:"tags"` - Performers []NameMapping `json:"performers"` - Studios []NameMapping `json:"studios"` - Movies []NameMapping `json:"movies"` - Galleries []PathMapping `json:"galleries"` - Scenes []PathMapping `json:"scenes"` + Tags []PathNameMapping `json:"tags"` + Performers []PathNameMapping `json:"performers"` + Studios []PathNameMapping `json:"studios"` + Movies []PathNameMapping `json:"movies"` + Galleries []PathNameMapping `json:"galleries"` + Scenes []PathNameMapping `json:"scenes"` + Images []PathNameMapping `json:"images"` } func LoadMappingsFile(filePath string) (*Mappings, error) { diff --git a/pkg/manager/jsonschema/scene.go b/pkg/manager/jsonschema/scene.go index 8118a47a1..cd9a066dd 100644 --- a/pkg/manager/jsonschema/scene.go +++ b/pkg/manager/jsonschema/scene.go @@ -18,15 +18,16 @@ type SceneMarker struct { } type SceneFile struct { - Size string `json:"size"` - Duration string `json:"duration"` - VideoCodec string `json:"video_codec"` - AudioCodec string `json:"audio_codec"` - Format string `json:"format"` - Width int `json:"width"` - Height int `json:"height"` - Framerate string `json:"framerate"` - Bitrate int `json:"bitrate"` + ModTime models.JSONTime `json:"mod_time,omitempty"` + Size string `json:"size"` + Duration string `json:"duration"` + VideoCodec string `json:"video_codec"` + AudioCodec string `json:"audio_codec"` + Format string `json:"format"` + Width int `json:"width"` + Height int `json:"height"` + Framerate string `json:"framerate"` + Bitrate int `json:"bitrate"` } type SceneMovie struct { diff --git a/pkg/manager/jsonschema/utils.go b/pkg/manager/jsonschema/utils.go index cbe3cd3ad..921d68fec 100644 --- a/pkg/manager/jsonschema/utils.go +++ b/pkg/manager/jsonschema/utils.go @@ -2,10 +2,11 @@ package jsonschema import ( "bytes" - "github.com/json-iterator/go" "io/ioutil" "time" + + jsoniter "github.com/json-iterator/go" ) var nilTime = (time.Time{}).UnixNano() diff --git a/pkg/manager/manager.go b/pkg/manager/manager.go index cedcc278a..5f0679fc3 100644 --- a/pkg/manager/manager.go +++ b/pkg/manager/manager.go @@ -18,13 +18,14 @@ import ( type singleton struct { Status TaskStatus Paths *paths.Paths - JSON *jsonUtils FFMPEGPath string FFProbePath string PluginCache *plugin.Cache ScraperCache *scraper.Cache + + DownloadStore *DownloadStore } var instance *singleton @@ -51,14 +52,19 @@ func Initialize() *singleton { instance = &singleton{ Status: TaskStatus{Status: Idle, Progress: -1}, Paths: paths.NewPaths(), - JSON: &jsonUtils{}, PluginCache: initPluginCache(), ScraperCache: initScraperCache(), + + DownloadStore: NewDownloadStore(), } instance.RefreshConfig() + // clear the downloads and tmp directories + utils.EmptyDir(instance.Paths.Generated.Downloads) + utils.EmptyDir(instance.Paths.Generated.Tmp) + initFFMPEG() }) @@ -125,10 +131,14 @@ func initEnvs() { viper.BindEnv("host") // STASH_HOST viper.BindEnv("port") // STASH_PORT viper.BindEnv("external_host") // STASH_EXTERNAL_HOST - viper.BindEnv("stash") // STASH_STASH viper.BindEnv("generated") // STASH_GENERATED viper.BindEnv("metadata") // STASH_METADATA viper.BindEnv("cache") // STASH_CACHE + + // only set stash config flag if not already set + if config.GetStashPaths() == nil { + viper.BindEnv("stash") // STASH_STASH + } } func initFFMPEG() { @@ -188,12 +198,12 @@ func initScraperCache() *scraper.Cache { func (s *singleton) RefreshConfig() { s.Paths = paths.NewPaths() if config.IsValid() { - _ = utils.EnsureDir(s.Paths.Generated.Screenshots) - _ = utils.EnsureDir(s.Paths.Generated.Vtt) - _ = utils.EnsureDir(s.Paths.Generated.Markers) - _ = utils.EnsureDir(s.Paths.Generated.Transcodes) - - paths.EnsureJSONDirs() + utils.EnsureDir(s.Paths.Generated.Screenshots) + utils.EnsureDir(s.Paths.Generated.Vtt) + utils.EnsureDir(s.Paths.Generated.Markers) + utils.EnsureDir(s.Paths.Generated.Transcodes) + utils.EnsureDir(s.Paths.Generated.Downloads) + paths.EnsureJSONDirs(config.GetMetadataPath()) } } diff --git a/pkg/manager/manager_tasks.go b/pkg/manager/manager_tasks.go index a82faaa7f..2c13e740e 100644 --- a/pkg/manager/manager_tasks.go +++ b/pkg/manager/manager_tasks.go @@ -1,38 +1,31 @@ package manager import ( - "path/filepath" + "errors" + "os" "strconv" - "strings" "sync" "time" - "github.com/bmatcuk/doublestar/v2" "github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/manager/config" "github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/utils" ) -var extensionsToScan = []string{"zip", "cbz", "m4v", "mp4", "mov", "wmv", "avi", "mpg", "mpeg", "rmvb", "rm", "flv", "asf", "mkv", "webm"} -var extensionsGallery = []string{"zip", "cbz"} - -func constructGlob() string { // create a sequence for glob doublestar from our extensions - var extList []string - for _, ext := range extensionsToScan { - extList = append(extList, strings.ToLower(ext)) - extList = append(extList, strings.ToUpper(ext)) - } - return "{" + strings.Join(extList, ",") + "}" +func isGallery(pathname string) bool { + gExt := config.GetGalleryExtensions() + return matchExtension(pathname, gExt) } -func isGallery(pathname string) bool { - for _, ext := range extensionsGallery { - if strings.ToLower(filepath.Ext(pathname)) == "."+strings.ToLower(ext) { - return true - } - } - return false +func isVideo(pathname string) bool { + vidExt := config.GetVideoExtensions() + return matchExtension(pathname, vidExt) +} + +func isImage(pathname string) bool { + imgExt := config.GetImageExtensions() + return matchExtension(pathname, imgExt) } type TaskStatus struct { @@ -85,7 +78,79 @@ func (t *TaskStatus) updated() { t.LastUpdate = time.Now() } -func (s *singleton) Scan(useFileMetadata bool) { +func getScanPaths(inputPaths []string) []*models.StashConfig { + if len(inputPaths) == 0 { + return config.GetStashPaths() + } + + var ret []*models.StashConfig + for _, p := range inputPaths { + s := getStashFromDirPath(p) + if s == nil { + logger.Warnf("%s is not in the configured stash paths", p) + continue + } + + // make a copy, changing the path + ss := *s + ss.Path = p + ret = append(ret, &ss) + } + + return ret +} + +func (s *singleton) neededScan(paths []*models.StashConfig) (total *int, newFiles *int) { + const timeout = 90 * time.Second + + // create a control channel through which to signal the counting loop when the timeout is reached + chTimeout := time.After(timeout) + + logger.Infof("Counting files to scan...") + + t := 0 + n := 0 + + timeoutErr := errors.New("timed out") + + for _, sp := range paths { + err := walkFilesToScan(sp, func(path string, info os.FileInfo, err error) error { + t++ + task := ScanTask{FilePath: path} + if !task.doesPathExist() { + n++ + } + + //check for timeout + select { + case <-chTimeout: + return timeoutErr + default: + } + + // check stop + if s.Status.stopping { + return timeoutErr + } + + return nil + }) + + if err == timeoutErr { + // timeout should return nil counts + return nil, nil + } + + if err != nil { + logger.Errorf("Error encountered counting files to scan: %s", err.Error()) + return nil, nil + } + } + + return &t, &n +} + +func (s *singleton) Scan(input models.ScanMetadataInput) { if s.Status.Status != Idle { return } @@ -95,11 +160,63 @@ func (s *singleton) Scan(useFileMetadata bool) { go func() { defer s.returnToIdleState() - var results []string - for _, path := range config.GetStashPaths() { - globPath := filepath.Join(path, "**/*."+constructGlob()) - globResults, _ := doublestar.Glob(globPath) - results = append(results, globResults...) + paths := getScanPaths(input.Paths) + + total, newFiles := s.neededScan(paths) + + if s.Status.stopping { + logger.Info("Stopping due to user request") + return + } + + if total == nil || newFiles == nil { + logger.Infof("Taking too long to count content. Skipping...") + logger.Infof("Starting scan") + } else { + logger.Infof("Starting scan of %d files. %d New files found", *total, *newFiles) + } + + var wg sync.WaitGroup + s.Status.Progress = 0 + fileNamingAlgo := config.GetVideoFileNamingAlgorithm() + calculateMD5 := config.IsCalculateMD5() + + i := 0 + stoppingErr := errors.New("stopping") + + var galleries []string + + for _, sp := range paths { + err := walkFilesToScan(sp, func(path string, info os.FileInfo, err error) error { + if total != nil { + s.Status.setProgress(i, *total) + i++ + } + + if s.Status.stopping { + return stoppingErr + } + + if isGallery(path) { + galleries = append(galleries, path) + } + + wg.Add(1) + task := ScanTask{FilePath: path, UseFileMetadata: input.UseFileMetadata, fileNamingAlgorithm: fileNamingAlgo, calculateMD5: calculateMD5} + go task.Start(&wg) + wg.Wait() + + return nil + }) + + if err == stoppingErr { + break + } + + if err != nil { + logger.Errorf("Error encountered scanning files: %s", err.Error()) + return + } } if s.Status.stopping { @@ -107,34 +224,12 @@ func (s *singleton) Scan(useFileMetadata bool) { return } - results, _ = excludeFiles(results, config.GetExcludes()) - total := len(results) - logger.Infof("Starting scan of %d files. %d New files found", total, s.neededScan(results)) - - var wg sync.WaitGroup - s.Status.Progress = 0 - fileNamingAlgo := config.GetVideoFileNamingAlgorithm() - calculateMD5 := config.IsCalculateMD5() - for i, path := range results { - s.Status.setProgress(i, total) - if s.Status.stopping { - logger.Info("Stopping due to user request") - return - } - wg.Add(1) - task := ScanTask{FilePath: path, UseFileMetadata: useFileMetadata, fileNamingAlgorithm: fileNamingAlgo, calculateMD5: calculateMD5} - go task.Start(&wg) - wg.Wait() - } - logger.Info("Finished scan") - for _, path := range results { - if isGallery(path) { - wg.Add(1) - task := ScanTask{FilePath: path, UseFileMetadata: false} - go task.associateGallery(&wg) - wg.Wait() - } + for _, path := range galleries { + wg.Add(1) + task := ScanTask{FilePath: path, UseFileMetadata: false} + go task.associateGallery(&wg) + wg.Wait() } logger.Info("Finished gallery association") }() @@ -152,7 +247,13 @@ func (s *singleton) Import() { var wg sync.WaitGroup wg.Add(1) - task := ImportTask{fileNamingAlgorithm: config.GetVideoFileNamingAlgorithm()} + task := ImportTask{ + BaseDir: config.GetMetadataPath(), + Reset: true, + DuplicateBehaviour: models.ImportDuplicateEnumFail, + MissingRefBehaviour: models.ImportMissingRefEnumFail, + fileNamingAlgorithm: config.GetVideoFileNamingAlgorithm(), + } go task.Start(&wg) wg.Wait() }() @@ -170,12 +271,32 @@ func (s *singleton) Export() { var wg sync.WaitGroup wg.Add(1) - task := ExportTask{fileNamingAlgorithm: config.GetVideoFileNamingAlgorithm()} + task := ExportTask{full: true, fileNamingAlgorithm: config.GetVideoFileNamingAlgorithm()} go task.Start(&wg) wg.Wait() }() } +func (s *singleton) RunSingleTask(t Task) (*sync.WaitGroup, error) { + if s.Status.Status != Idle { + return nil, errors.New("task already running") + } + + s.Status.SetStatus(t.GetStatus()) + s.Status.indefiniteProgress() + var wg sync.WaitGroup + wg.Add(1) + + go func() { + defer s.returnToIdleState() + + go t.Start(&wg) + wg.Wait() + }() + + return &wg, nil +} + func setGeneratePreviewOptionsInput(optionsInput *models.GeneratePreviewOptionsInput) { if optionsInput.PreviewSegments == nil { val := config.GetPreviewSegments() @@ -211,13 +332,11 @@ func (s *singleton) Generate(input models.GenerateMetadataInput) { s.Status.indefiniteProgress() qb := models.NewSceneQueryBuilder() - qg := models.NewGalleryQueryBuilder() mqb := models.NewSceneMarkerQueryBuilder() //this.job.total = await ObjectionUtils.getCount(Scene); instance.Paths.Generated.EnsureTmpDir() - galleryIDs := utils.StringSliceToIntSlice(input.GalleryIDs) sceneIDs := utils.StringSliceToIntSlice(input.SceneIDs) markerIDs := utils.StringSliceToIntSlice(input.MarkerIDs) @@ -245,21 +364,6 @@ func (s *singleton) Generate(input models.GenerateMetadataInput) { lenScenes := len(scenes) total := lenScenes - var galleries []*models.Gallery - if input.Thumbnails { - if len(galleryIDs) > 0 { - galleries, err = qg.FindMany(galleryIDs) - } else { - galleries, err = qg.All() - } - - if err != nil { - logger.Errorf("failed to get galleries for generate") - return - } - total += len(galleries) - } - var markers []*models.SceneMarker if len(markerIDs) > 0 { markers, err = mqb.FindMany(markerIDs) @@ -341,29 +445,8 @@ func (s *singleton) Generate(input models.GenerateMetadataInput) { wg.Wait() } - if input.Thumbnails { - logger.Infof("Generating thumbnails for the galleries") - for i, gallery := range galleries { - s.Status.setProgress(lenScenes+i, total) - if s.Status.stopping { - logger.Info("Stopping due to user request") - return - } - - if gallery == nil { - logger.Errorf("nil gallery, skipping generate") - continue - } - - wg.Add(1) - task := GenerateGthumbsTask{Gallery: *gallery, Overwrite: overwrite} - go task.Start(&wg) - wg.Wait() - } - } - for i, marker := range markers { - s.Status.setProgress(lenScenes+len(galleries)+i, total) + s.Status.setProgress(lenScenes+i, total) if s.Status.stopping { logger.Info("Stopping due to user request") return @@ -608,6 +691,7 @@ func (s *singleton) Clean() { s.Status.indefiniteProgress() qb := models.NewSceneQueryBuilder() + iqb := models.NewImageQueryBuilder() gqb := models.NewGalleryQueryBuilder() go func() { defer s.returnToIdleState() @@ -619,6 +703,12 @@ func (s *singleton) Clean() { return } + images, err := iqb.All() + if err != nil { + logger.Errorf("failed to fetch list of images for cleaning") + return + } + galleries, err := gqb.All() if err != nil { logger.Errorf("failed to fetch list of galleries for cleaning") @@ -632,7 +722,7 @@ func (s *singleton) Clean() { var wg sync.WaitGroup s.Status.Progress = 0 - total := len(scenes) + len(galleries) + total := len(scenes) + len(images) + len(galleries) fileNamingAlgo := config.GetVideoFileNamingAlgorithm() for i, scene := range scenes { s.Status.setProgress(i, total) @@ -653,13 +743,32 @@ func (s *singleton) Clean() { wg.Wait() } - for i, gallery := range galleries { + for i, img := range images { s.Status.setProgress(len(scenes)+i, total) if s.Status.stopping { logger.Info("Stopping due to user request") return } + if img == nil { + logger.Errorf("nil image, skipping Clean") + continue + } + + wg.Add(1) + + task := CleanTask{Image: img} + go task.Start(&wg) + wg.Wait() + } + + for i, gallery := range galleries { + s.Status.setProgress(len(scenes)+len(galleries)+i, total) + if s.Status.stopping { + logger.Info("Stopping due to user request") + return + } + if gallery == nil { logger.Errorf("nil gallery, skipping Clean") continue @@ -737,18 +846,6 @@ func (s *singleton) returnToIdleState() { s.Status.stopping = false } -func (s *singleton) neededScan(paths []string) int64 { - var neededScans int64 - - for _, path := range paths { - task := ScanTask{FilePath: path} - if !task.doesPathExist() { - neededScans++ - } - } - return neededScans -} - type totalsGenerate struct { sprites int64 previews int64 diff --git a/pkg/manager/paths/paths.go b/pkg/manager/paths/paths.go index bc10b03e9..459c60943 100644 --- a/pkg/manager/paths/paths.go +++ b/pkg/manager/paths/paths.go @@ -1,15 +1,14 @@ package paths import ( - "github.com/stashapp/stash/pkg/utils" "path/filepath" + + "github.com/stashapp/stash/pkg/utils" ) type Paths struct { Generated *generatedPaths - JSON *jsonPaths - Gallery *galleryPaths Scene *scenePaths SceneMarkers *sceneMarkerPaths } @@ -17,9 +16,7 @@ type Paths struct { func NewPaths() *Paths { p := Paths{} p.Generated = newGeneratedPaths() - p.JSON = newJSONPaths() - p.Gallery = newGalleryPaths() p.Scene = newScenePaths(p) p.SceneMarkers = newSceneMarkerPaths(p) return &p diff --git a/pkg/manager/paths/paths_gallery.go b/pkg/manager/paths/paths_gallery.go deleted file mode 100644 index 9e4665c95..000000000 --- a/pkg/manager/paths/paths_gallery.go +++ /dev/null @@ -1,39 +0,0 @@ -package paths - -import ( - "fmt" - "github.com/stashapp/stash/pkg/manager/config" - "github.com/stashapp/stash/pkg/utils" - "path/filepath" -) - -type galleryPaths struct{} - -const thumbDir = "gthumbs" -const thumbDirDepth int = 2 -const thumbDirLength int = 2 // thumbDirDepth * thumbDirLength must be smaller than the length of checksum - -func newGalleryPaths() *galleryPaths { - return &galleryPaths{} -} - -func (gp *galleryPaths) GetExtractedPath(checksum string) string { - return filepath.Join(config.GetCachePath(), checksum) -} - -func GetGthumbCache() string { - return filepath.Join(config.GetCachePath(), thumbDir) -} - -func GetGthumbDir(checksum string) string { - return filepath.Join(config.GetCachePath(), thumbDir, utils.GetIntraDir(checksum, thumbDirDepth, thumbDirLength), checksum) -} - -func GetGthumbPath(checksum string, index int, width int) string { - fname := fmt.Sprintf("%s_%d_%d.jpg", checksum, index, width) - return filepath.Join(config.GetCachePath(), thumbDir, utils.GetIntraDir(checksum, thumbDirDepth, thumbDirLength), checksum, fname) -} - -func (gp *galleryPaths) GetExtractedFilePath(checksum string, fileName string) string { - return filepath.Join(config.GetCachePath(), checksum, fileName) -} diff --git a/pkg/manager/paths/paths_generated.go b/pkg/manager/paths/paths_generated.go index d732f29a8..91bbabed7 100644 --- a/pkg/manager/paths/paths_generated.go +++ b/pkg/manager/paths/paths_generated.go @@ -1,25 +1,35 @@ package paths import ( + "fmt" + "io/ioutil" + "path/filepath" + "github.com/stashapp/stash/pkg/manager/config" "github.com/stashapp/stash/pkg/utils" - "path/filepath" ) +const thumbDirDepth int = 2 +const thumbDirLength int = 2 // thumbDirDepth * thumbDirLength must be smaller than the length of checksum + type generatedPaths struct { Screenshots string + Thumbnails string Vtt string Markers string Transcodes string + Downloads string Tmp string } func newGeneratedPaths() *generatedPaths { gp := generatedPaths{} gp.Screenshots = filepath.Join(config.GetGeneratedPath(), "screenshots") + gp.Thumbnails = filepath.Join(config.GetGeneratedPath(), "thumbnails") gp.Vtt = filepath.Join(config.GetGeneratedPath(), "vtt") gp.Markers = filepath.Join(config.GetGeneratedPath(), "markers") gp.Transcodes = filepath.Join(config.GetGeneratedPath(), "transcodes") + gp.Downloads = filepath.Join(config.GetGeneratedPath(), "downloads") gp.Tmp = filepath.Join(config.GetGeneratedPath(), "tmp") return &gp } @@ -29,13 +39,30 @@ func (gp *generatedPaths) GetTmpPath(fileName string) string { } func (gp *generatedPaths) EnsureTmpDir() { - _ = utils.EnsureDir(gp.Tmp) + utils.EnsureDir(gp.Tmp) } func (gp *generatedPaths) EmptyTmpDir() { - _ = utils.EmptyDir(gp.Tmp) + utils.EmptyDir(gp.Tmp) } func (gp *generatedPaths) RemoveTmpDir() { - _ = utils.RemoveDir(gp.Tmp) + utils.RemoveDir(gp.Tmp) +} + +func (gp *generatedPaths) TempDir(pattern string) (string, error) { + gp.EnsureTmpDir() + ret, err := ioutil.TempDir(gp.Tmp, pattern) + if err != nil { + return "", err + } + + utils.EmptyDir(ret) + + return ret, nil +} + +func (gp *generatedPaths) GetThumbnailPath(checksum string, width int) string { + fname := fmt.Sprintf("%s_%d.jpg", checksum, width) + return filepath.Join(gp.Thumbnails, utils.GetIntraDir(checksum, thumbDirDepth, thumbDirLength), fname) } diff --git a/pkg/manager/paths/paths_json.go b/pkg/manager/paths/paths_json.go index 448b3734c..c7f3b7490 100644 --- a/pkg/manager/paths/paths_json.go +++ b/pkg/manager/paths/paths_json.go @@ -3,11 +3,10 @@ package paths import ( "path/filepath" - "github.com/stashapp/stash/pkg/manager/config" "github.com/stashapp/stash/pkg/utils" ) -type jsonPaths struct { +type JSONPaths struct { Metadata string MappingsFile string @@ -15,35 +14,38 @@ type jsonPaths struct { Performers string Scenes string + Images string Galleries string Studios string Tags string Movies string } -func newJSONPaths() *jsonPaths { - jp := jsonPaths{} - jp.Metadata = config.GetMetadataPath() - jp.MappingsFile = filepath.Join(config.GetMetadataPath(), "mappings.json") - jp.ScrapedFile = filepath.Join(config.GetMetadataPath(), "scraped.json") - jp.Performers = filepath.Join(config.GetMetadataPath(), "performers") - jp.Scenes = filepath.Join(config.GetMetadataPath(), "scenes") - jp.Galleries = filepath.Join(config.GetMetadataPath(), "galleries") - jp.Studios = filepath.Join(config.GetMetadataPath(), "studios") - jp.Movies = filepath.Join(config.GetMetadataPath(), "movies") - jp.Tags = filepath.Join(config.GetMetadataPath(), "tags") +func newJSONPaths(baseDir string) *JSONPaths { + jp := JSONPaths{} + jp.Metadata = baseDir + jp.MappingsFile = filepath.Join(baseDir, "mappings.json") + jp.ScrapedFile = filepath.Join(baseDir, "scraped.json") + jp.Performers = filepath.Join(baseDir, "performers") + jp.Scenes = filepath.Join(baseDir, "scenes") + jp.Images = filepath.Join(baseDir, "images") + jp.Galleries = filepath.Join(baseDir, "galleries") + jp.Studios = filepath.Join(baseDir, "studios") + jp.Movies = filepath.Join(baseDir, "movies") + jp.Tags = filepath.Join(baseDir, "tags") return &jp } -func GetJSONPaths() *jsonPaths { - jp := newJSONPaths() +func GetJSONPaths(baseDir string) *JSONPaths { + jp := newJSONPaths(baseDir) return jp } -func EnsureJSONDirs() { - jsonPaths := GetJSONPaths() +func EnsureJSONDirs(baseDir string) { + jsonPaths := GetJSONPaths(baseDir) utils.EnsureDir(jsonPaths.Metadata) utils.EnsureDir(jsonPaths.Scenes) + utils.EnsureDir(jsonPaths.Images) utils.EnsureDir(jsonPaths.Galleries) utils.EnsureDir(jsonPaths.Performers) utils.EnsureDir(jsonPaths.Studios) @@ -51,22 +53,30 @@ func EnsureJSONDirs() { utils.EnsureDir(jsonPaths.Tags) } -func (jp *jsonPaths) PerformerJSONPath(checksum string) string { +func (jp *JSONPaths) PerformerJSONPath(checksum string) string { return filepath.Join(jp.Performers, checksum+".json") } -func (jp *jsonPaths) SceneJSONPath(checksum string) string { +func (jp *JSONPaths) SceneJSONPath(checksum string) string { return filepath.Join(jp.Scenes, checksum+".json") } -func (jp *jsonPaths) StudioJSONPath(checksum string) string { +func (jp *JSONPaths) ImageJSONPath(checksum string) string { + return filepath.Join(jp.Images, checksum+".json") +} + +func (jp *JSONPaths) GalleryJSONPath(checksum string) string { + return filepath.Join(jp.Galleries, checksum+".json") +} + +func (jp *JSONPaths) StudioJSONPath(checksum string) string { return filepath.Join(jp.Studios, checksum+".json") } -func (jp *jsonPaths) TagJSONPath(checksum string) string { +func (jp *JSONPaths) TagJSONPath(checksum string) string { return filepath.Join(jp.Tags, checksum+".json") } -func (jp *jsonPaths) MovieJSONPath(checksum string) string { +func (jp *JSONPaths) MovieJSONPath(checksum string) string { return filepath.Join(jp.Movies, checksum+".json") } diff --git a/pkg/manager/scene.go b/pkg/manager/scene.go index 5cb99c9c4..ad8551112 100644 --- a/pkg/manager/scene.go +++ b/pkg/manager/scene.go @@ -228,23 +228,128 @@ func GetSceneStreamPaths(scene *models.Scene, directStreamURL string) ([]*models }) } + hls := models.SceneStreamEndpoint{ + URL: directStreamURL + ".m3u8", + MimeType: &mimeHLS, + Label: &labelHLS, + } + ret = append(ret, &hls) + + // WEBM quality transcoding options + // Note: These have the wrong mime type intentionally to allow jwplayer to selection between mp4/webm + webmLabelFourK := "WEBM 4K (2160p)" // "FOUR_K" + webmLabelFullHD := "WEBM Full HD (1080p)" // "FULL_HD" + webmLabelStardardHD := "WEBM HD (720p)" // "STANDARD_HD" + webmLabelStandard := "WEBM Standard (480p)" // "STANDARD" + webmLabelLow := "WEBM Low (240p)" // "LOW" + + if !scene.Height.Valid || scene.Height.Int64 >= 2160 { + new := models.SceneStreamEndpoint{ + URL: directStreamURL + ".webm?resolution=FOUR_K", + MimeType: &mimeMp4, + Label: &webmLabelFourK, + } + ret = append(ret, &new) + } + + if !scene.Height.Valid || scene.Height.Int64 >= 1080 { + new := models.SceneStreamEndpoint{ + URL: directStreamURL + ".webm?resolution=FULL_HD", + MimeType: &mimeMp4, + Label: &webmLabelFullHD, + } + ret = append(ret, &new) + } + + if !scene.Height.Valid || scene.Height.Int64 >= 720 { + new := models.SceneStreamEndpoint{ + URL: directStreamURL + ".webm?resolution=STANDARD_HD", + MimeType: &mimeMp4, + Label: &webmLabelStardardHD, + } + ret = append(ret, &new) + } + + if !scene.Height.Valid || scene.Height.Int64 >= 480 { + new := models.SceneStreamEndpoint{ + URL: directStreamURL + ".webm?resolution=STANDARD", + MimeType: &mimeMp4, + Label: &webmLabelStandard, + } + ret = append(ret, &new) + } + + if !scene.Height.Valid || scene.Height.Int64 >= 240 { + new := models.SceneStreamEndpoint{ + URL: directStreamURL + ".webm?resolution=LOW", + MimeType: &mimeMp4, + Label: &webmLabelLow, + } + ret = append(ret, &new) + } + + // Setup up lower quality transcoding options (MP4) + mp4LabelFourK := "MP4 4K (2160p)" // "FOUR_K" + mp4LabelFullHD := "MP4 Full HD (1080p)" // "FULL_HD" + mp4LabelStardardHD := "MP4 HD (720p)" // "STANDARD_HD" + mp4LabelStandard := "MP4 Standard (480p)" // "STANDARD" + mp4LabelLow := "MP4 Low (240p)" // "LOW" + + if !scene.Height.Valid || scene.Height.Int64 >= 2160 { + new := models.SceneStreamEndpoint{ + URL: directStreamURL + ".mp4?resolution=FOUR_K", + MimeType: &mimeMp4, + Label: &mp4LabelFourK, + } + ret = append(ret, &new) + } + + if !scene.Height.Valid || scene.Height.Int64 >= 1080 { + new := models.SceneStreamEndpoint{ + URL: directStreamURL + ".mp4?resolution=FULL_HD", + MimeType: &mimeMp4, + Label: &mp4LabelFullHD, + } + ret = append(ret, &new) + } + + if !scene.Height.Valid || scene.Height.Int64 >= 720 { + new := models.SceneStreamEndpoint{ + URL: directStreamURL + ".mp4?resolution=STANDARD_HD", + MimeType: &mimeMp4, + Label: &mp4LabelStardardHD, + } + ret = append(ret, &new) + } + + if !scene.Height.Valid || scene.Height.Int64 >= 480 { + new := models.SceneStreamEndpoint{ + URL: directStreamURL + ".mp4?resolution=STANDARD", + MimeType: &mimeMp4, + Label: &mp4LabelStandard, + } + ret = append(ret, &new) + } + + if !scene.Height.Valid || scene.Height.Int64 >= 240 { + new := models.SceneStreamEndpoint{ + URL: directStreamURL + ".mp4?resolution=LOW", + MimeType: &mimeMp4, + Label: &mp4LabelLow, + } + ret = append(ret, &new) + } + defaultStreams := []*models.SceneStreamEndpoint{ { URL: directStreamURL + ".webm", MimeType: &mimeWebm, Label: &labelWebm, }, - { - URL: directStreamURL + ".m3u8", - MimeType: &mimeHLS, - Label: &labelHLS, - }, } ret = append(ret, defaultStreams...) - // TODO - at some point, look at streaming at various resolutions - return ret, nil } diff --git a/pkg/manager/task.go b/pkg/manager/task.go index fa85ef16a..1694c5f39 100644 --- a/pkg/manager/task.go +++ b/pkg/manager/task.go @@ -4,4 +4,5 @@ import "sync" type Task interface { Start(wg *sync.WaitGroup) + GetStatus() JobStatus } diff --git a/pkg/manager/task_clean.go b/pkg/manager/task_clean.go index 80b5f72e0..dc6091d17 100644 --- a/pkg/manager/task_clean.go +++ b/pkg/manager/task_clean.go @@ -8,44 +8,40 @@ import ( "sync" "github.com/stashapp/stash/pkg/database" + "github.com/stashapp/stash/pkg/image" "github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/manager/config" - "github.com/stashapp/stash/pkg/manager/paths" "github.com/stashapp/stash/pkg/models" ) type CleanTask struct { Scene *models.Scene Gallery *models.Gallery + Image *models.Image fileNamingAlgorithm models.HashAlgorithm } func (t *CleanTask) Start(wg *sync.WaitGroup) { defer wg.Done() - if t.Scene != nil && t.shouldClean(t.Scene.Path) { + if t.Scene != nil && t.shouldCleanScene(t.Scene) { t.deleteScene(t.Scene.ID) } if t.Gallery != nil && t.shouldCleanGallery(t.Gallery) { t.deleteGallery(t.Gallery.ID) } + + if t.Image != nil && t.shouldCleanImage(t.Image) { + t.deleteImage(t.Image.ID) + } } func (t *CleanTask) shouldClean(path string) bool { - fileExists, err := t.fileExists(path) - if err != nil { - logger.Errorf("Error checking existence of %s: %s", path, err.Error()) - return false - } + // use image.FileExists for zip file checking + fileExists := image.FileExists(path) - if fileExists && t.pathInStash(path) { - logger.Debugf("File Found: %s", path) - if matchFile(path, config.GetExcludes()) { - logger.Infof("File matched regex. Cleaning: \"%s\"", path) - return true - } - } else { + if !fileExists || getStashFromPath(path) == nil { logger.Infof("File not found. Cleaning: \"%s\"", path) return true } @@ -53,13 +49,83 @@ func (t *CleanTask) shouldClean(path string) bool { return false } -func (t *CleanTask) shouldCleanGallery(g *models.Gallery) bool { - if t.shouldClean(g.Path) { +func (t *CleanTask) shouldCleanScene(s *models.Scene) bool { + if t.shouldClean(s.Path) { return true } - if t.Gallery.CountFiles() == 0 { - logger.Infof("Gallery has 0 images. Cleaning: \"%s\"", g.Path) + stash := getStashFromPath(s.Path) + if stash.ExcludeVideo { + logger.Infof("File in stash library that excludes video. Cleaning: \"%s\"", s.Path) + return true + } + + if !matchExtension(s.Path, config.GetVideoExtensions()) { + logger.Infof("File extension does not match video extensions. Cleaning: \"%s\"", s.Path) + return true + } + + if matchFile(s.Path, config.GetExcludes()) { + logger.Infof("File matched regex. Cleaning: \"%s\"", s.Path) + return true + } + + return false +} + +func (t *CleanTask) shouldCleanGallery(g *models.Gallery) bool { + // never clean manually created galleries + if !g.Zip { + return false + } + + path := g.Path.String + if t.shouldClean(path) { + return true + } + + stash := getStashFromPath(path) + if stash.ExcludeImage { + logger.Infof("File in stash library that excludes images. Cleaning: \"%s\"", path) + return true + } + + if !matchExtension(path, config.GetGalleryExtensions()) { + logger.Infof("File extension does not match gallery extensions. Cleaning: \"%s\"", path) + return true + } + + if matchFile(path, config.GetImageExcludes()) { + logger.Infof("File matched regex. Cleaning: \"%s\"", path) + return true + } + + if countImagesInZip(path) == 0 { + logger.Infof("Gallery has 0 images. Cleaning: \"%s\"", path) + return true + } + + return false +} + +func (t *CleanTask) shouldCleanImage(s *models.Image) bool { + if t.shouldClean(s.Path) { + return true + } + + stash := getStashFromPath(s.Path) + if stash.ExcludeImage { + logger.Infof("File in stash library that excludes images. Cleaning: \"%s\"", s.Path) + return true + } + + if !matchExtension(s.Path, config.GetImageExtensions()) { + logger.Infof("File extension does not match image extensions. Cleaning: \"%s\"", s.Path) + return true + } + + if matchFile(s.Path, config.GetImageExcludes()) { + logger.Infof("File matched regex. Cleaning: \"%s\"", s.Path) return true } @@ -105,10 +171,29 @@ func (t *CleanTask) deleteGallery(galleryID int) { logger.Errorf("Error deleting gallery from database: %s", err.Error()) return } +} - pathErr := os.RemoveAll(paths.GetGthumbDir(t.Gallery.Checksum)) // remove cache dir of gallery +func (t *CleanTask) deleteImage(imageID int) { + ctx := context.TODO() + qb := models.NewImageQueryBuilder() + tx := database.DB.MustBeginTx(ctx, nil) + + err := qb.Destroy(imageID, tx) + + if err != nil { + logger.Errorf("Error deleting image from database: %s", err.Error()) + tx.Rollback() + return + } + + if err := tx.Commit(); err != nil { + logger.Errorf("Error deleting image from database: %s", err.Error()) + return + } + + pathErr := os.Remove(GetInstance().Paths.Generated.GetThumbnailPath(t.Image.Checksum, models.DefaultGthumbWidth)) // remove cache dir of gallery if pathErr != nil { - logger.Errorf("Error deleting gallery directory from cache: %s", pathErr) + logger.Errorf("Error deleting thumbnail image from cache: %s", pathErr) } } @@ -126,19 +211,30 @@ func (t *CleanTask) fileExists(filename string) (bool, error) { return !info.IsDir(), nil } -func (t *CleanTask) pathInStash(pathToCheck string) bool { - for _, path := range config.GetStashPaths() { - - rel, error := filepath.Rel(path, filepath.Dir(pathToCheck)) +func getStashFromPath(pathToCheck string) *models.StashConfig { + for _, s := range config.GetStashPaths() { + rel, error := filepath.Rel(s.Path, filepath.Dir(pathToCheck)) if error == nil { if !strings.HasPrefix(rel, ".."+string(filepath.Separator)) { - logger.Debugf("File %s belongs to stash path %s", pathToCheck, path) - return true + return s } } } - logger.Debugf("File %s is out from stash path", pathToCheck) - return false + return nil +} + +func getStashFromDirPath(pathToCheck string) *models.StashConfig { + for _, s := range config.GetStashPaths() { + rel, error := filepath.Rel(s.Path, pathToCheck) + + if error == nil { + if !strings.HasPrefix(rel, ".."+string(filepath.Separator)) { + return s + } + } + + } + return nil } diff --git a/pkg/manager/task_export.go b/pkg/manager/task_export.go index 098f6cfc1..5e654876c 100644 --- a/pkg/manager/task_export.go +++ b/pkg/manager/task_export.go @@ -1,27 +1,95 @@ package manager import ( - "context" + "archive/zip" "fmt" - "math" + "io" + "io/ioutil" + "os" + "path/filepath" "runtime" - "strconv" "sync" "time" - "github.com/jmoiron/sqlx" - "github.com/stashapp/stash/pkg/database" + "github.com/stashapp/stash/pkg/gallery" + "github.com/stashapp/stash/pkg/image" "github.com/stashapp/stash/pkg/logger" + "github.com/stashapp/stash/pkg/manager/config" "github.com/stashapp/stash/pkg/manager/jsonschema" "github.com/stashapp/stash/pkg/manager/paths" "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/movie" + "github.com/stashapp/stash/pkg/performer" + "github.com/stashapp/stash/pkg/scene" + "github.com/stashapp/stash/pkg/studio" + "github.com/stashapp/stash/pkg/tag" "github.com/stashapp/stash/pkg/utils" ) type ExportTask struct { + full bool + + baseDir string + json jsonUtils + Mappings *jsonschema.Mappings - Scraped []jsonschema.ScrapedItem fileNamingAlgorithm models.HashAlgorithm + + scenes *exportSpec + images *exportSpec + performers *exportSpec + movies *exportSpec + tags *exportSpec + studios *exportSpec + galleries *exportSpec + + includeDependencies bool + + DownloadHash string +} + +type exportSpec struct { + IDs []int + all bool +} + +func newExportSpec(input *models.ExportObjectTypeInput) *exportSpec { + if input == nil { + return &exportSpec{} + } + + ret := &exportSpec{ + IDs: utils.StringSliceToIntSlice(input.Ids), + } + + if input.All != nil { + ret.all = *input.All + } + + return ret +} + +func CreateExportTask(a models.HashAlgorithm, input models.ExportObjectsInput) *ExportTask { + includeDeps := false + if input.IncludeDependencies != nil { + includeDeps = *input.IncludeDependencies + } + + return &ExportTask{ + fileNamingAlgorithm: a, + scenes: newExportSpec(input.Scenes), + images: newExportSpec(input.Images), + performers: newExportSpec(input.Performers), + movies: newExportSpec(input.Movies), + tags: newExportSpec(input.Tags), + studios: newExportSpec(input.Studios), + galleries: newExportSpec(input.Galleries), + includeDependencies: includeDeps, + } +} + +func (t *ExportTask) GetStatus() JobStatus { + return Export } func (t *ExportTask) Start(wg *sync.WaitGroup) { @@ -30,36 +98,226 @@ func (t *ExportTask) Start(wg *sync.WaitGroup) { workerCount := runtime.GOMAXPROCS(0) // set worker count to number of cpus available t.Mappings = &jsonschema.Mappings{} - t.Scraped = []jsonschema.ScrapedItem{} - ctx := context.TODO() startTime := time.Now() - paths.EnsureJSONDirs() + if t.full { + t.baseDir = config.GetMetadataPath() + } else { + var err error + t.baseDir, err = instance.Paths.Generated.TempDir("export") + if err != nil { + logger.Errorf("error creating temporary directory for export: %s", err.Error()) + return + } - t.ExportScenes(ctx, workerCount) - t.ExportGalleries(ctx) - t.ExportPerformers(ctx, workerCount) - t.ExportStudios(ctx, workerCount) - t.ExportMovies(ctx, workerCount) - t.ExportTags(ctx, workerCount) + defer func() { + err := utils.RemoveDir(t.baseDir) + if err != nil { + logger.Errorf("error removing directory %s: %s", t.baseDir, err.Error()) + } + }() + } - if err := instance.JSON.saveMappings(t.Mappings); err != nil { + t.json = jsonUtils{ + json: *paths.GetJSONPaths(t.baseDir), + } + + paths.EnsureJSONDirs(t.baseDir) + + // include movie scenes and gallery images + if !t.full { + // only include movie scenes if includeDependencies is also set + if !t.scenes.all && t.includeDependencies { + t.populateMovieScenes() + } + + // always export gallery images + if !t.images.all { + t.populateGalleryImages() + } + } + + t.ExportScenes(workerCount) + t.ExportImages(workerCount) + t.ExportGalleries(workerCount) + t.ExportMovies(workerCount) + t.ExportPerformers(workerCount) + t.ExportStudios(workerCount) + t.ExportTags(workerCount) + + if err := t.json.saveMappings(t.Mappings); err != nil { logger.Errorf("[mappings] failed to save json: %s", err.Error()) } - t.ExportScrapedItems(ctx) + if t.full { + t.ExportScrapedItems() + } else { + err := t.generateDownload() + if err != nil { + logger.Errorf("error generating download link: %s", err.Error()) + return + } + } logger.Infof("Export complete in %s.", time.Since(startTime)) } -func (t *ExportTask) ExportScenes(ctx context.Context, workers int) { +func (t *ExportTask) generateDownload() error { + // zip the files and register a download link + utils.EnsureDir(instance.Paths.Generated.Downloads) + z, err := ioutil.TempFile(instance.Paths.Generated.Downloads, "export*.zip") + if err != nil { + return err + } + defer z.Close() + + err = t.zipFiles(z) + if err != nil { + return err + } + + t.DownloadHash = instance.DownloadStore.RegisterFile(z.Name(), "", false) + logger.Debugf("Generated zip file %s with hash %s", z.Name(), t.DownloadHash) + return nil +} + +func (t *ExportTask) zipFiles(w io.Writer) error { + z := zip.NewWriter(w) + defer z.Close() + + u := jsonUtils{ + json: *paths.GetJSONPaths(""), + } + + // write the mappings file + err := t.zipFile(t.json.json.MappingsFile, "", z) + if err != nil { + return err + } + + filepath.Walk(t.json.json.Tags, t.zipWalkFunc(u.json.Tags, z)) + filepath.Walk(t.json.json.Galleries, t.zipWalkFunc(u.json.Galleries, z)) + filepath.Walk(t.json.json.Performers, t.zipWalkFunc(u.json.Performers, z)) + filepath.Walk(t.json.json.Studios, t.zipWalkFunc(u.json.Studios, z)) + filepath.Walk(t.json.json.Movies, t.zipWalkFunc(u.json.Movies, z)) + filepath.Walk(t.json.json.Scenes, t.zipWalkFunc(u.json.Scenes, z)) + filepath.Walk(t.json.json.Images, t.zipWalkFunc(u.json.Images, z)) + + return nil +} + +func (t *ExportTask) zipWalkFunc(outDir string, z *zip.Writer) filepath.WalkFunc { + return func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + if info.IsDir() { + return nil + } + + return t.zipFile(path, outDir, z) + } +} + +func (t *ExportTask) zipFile(fn, outDir string, z *zip.Writer) error { + bn := filepath.Base(fn) + + f, err := z.Create(filepath.Join(outDir, bn)) + if err != nil { + return fmt.Errorf("error creating zip entry for %s: %s", fn, err.Error()) + } + + i, err := os.Open(fn) + if err != nil { + return fmt.Errorf("error opening %s: %s", fn, err.Error()) + } + + defer i.Close() + + if _, err := io.Copy(f, i); err != nil { + return fmt.Errorf("error writing %s to zip: %s", fn, err.Error()) + } + + return nil +} + +func (t *ExportTask) populateMovieScenes() { + reader := models.NewMovieReaderWriter(nil) + sceneReader := models.NewSceneReaderWriter(nil) + + var movies []*models.Movie + var err error + all := t.full || (t.movies != nil && t.movies.all) + if all { + movies, err = reader.All() + } else if t.movies != nil && len(t.movies.IDs) > 0 { + movies, err = reader.FindMany(t.movies.IDs) + } + + if err != nil { + logger.Errorf("[movies] failed to fetch movies: %s", err.Error()) + } + + for _, m := range movies { + scenes, err := sceneReader.FindByMovieID(m.ID) + if err != nil { + logger.Errorf("[movies] <%s> failed to fetch scenes for movie: %s", m.Checksum, err.Error()) + continue + } + + for _, s := range scenes { + t.scenes.IDs = utils.IntAppendUnique(t.scenes.IDs, s.ID) + } + } +} + +func (t *ExportTask) populateGalleryImages() { + reader := models.NewGalleryReaderWriter(nil) + imageReader := models.NewImageReaderWriter(nil) + + var galleries []*models.Gallery + var err error + all := t.full || (t.galleries != nil && t.galleries.all) + if all { + galleries, err = reader.All() + } else if t.galleries != nil && len(t.galleries.IDs) > 0 { + galleries, err = reader.FindMany(t.galleries.IDs) + } + + if err != nil { + logger.Errorf("[galleries] failed to fetch galleries: %s", err.Error()) + } + + for _, g := range galleries { + images, err := imageReader.FindByGalleryID(g.ID) + if err != nil { + logger.Errorf("[galleries] <%s> failed to fetch images for gallery: %s", g.Checksum, err.Error()) + continue + } + + for _, i := range images { + t.images.IDs = utils.IntAppendUnique(t.images.IDs, i.ID) + } + } +} + +func (t *ExportTask) ExportScenes(workers int) { var scenesWg sync.WaitGroup - qb := models.NewSceneQueryBuilder() + sceneReader := models.NewSceneReaderWriter(nil) + + var scenes []*models.Scene + var err error + all := t.full || (t.scenes != nil && t.scenes.all) + if all { + scenes, err = sceneReader.All() + } else if t.scenes != nil && len(t.scenes.IDs) > 0 { + scenes, err = sceneReader.FindMany(t.scenes.IDs) + } - scenes, err := qb.All() if err != nil { - logger.Errorf("[scenes] failed to fetch all scenes: %s", err.Error()) + logger.Errorf("[scenes] failed to fetch scenes: %s", err.Error()) } jobCh := make(chan *models.Scene, workers*2) // make a buffered channel to feed workers @@ -69,7 +327,7 @@ func (t *ExportTask) ExportScenes(ctx context.Context, workers int) { for w := 0; w < workers; w++ { // create export Scene workers scenesWg.Add(1) - go exportScene(&scenesWg, jobCh, t, nil) // no db data is changed so tx is set to nil + go exportScene(&scenesWg, jobCh, t) } for i, scene := range scenes { @@ -78,7 +336,7 @@ func (t *ExportTask) ExportScenes(ctx context.Context, workers int) { if (i % 100) == 0 { // make progress easier to read logger.Progressf("[scenes] %d of %d", index, len(scenes)) } - t.Mappings.Scenes = append(t.Mappings.Scenes, jsonschema.PathMapping{Path: scene.Path, Checksum: scene.GetHash(t.fileNamingAlgorithm)}) + t.Mappings.Scenes = append(t.Mappings.Scenes, jsonschema.PathNameMapping{Path: scene.Path, Checksum: scene.GetHash(t.fileNamingAlgorithm)}) jobCh <- scene // feed workers } @@ -87,197 +345,342 @@ func (t *ExportTask) ExportScenes(ctx context.Context, workers int) { logger.Infof("[scenes] export complete in %s. %d workers used.", time.Since(startTime), workers) } -func exportScene(wg *sync.WaitGroup, jobChan <-chan *models.Scene, t *ExportTask, tx *sqlx.Tx) { + +func exportScene(wg *sync.WaitGroup, jobChan <-chan *models.Scene, t *ExportTask) { defer wg.Done() - sceneQB := models.NewSceneQueryBuilder() - studioQB := models.NewStudioQueryBuilder() - movieQB := models.NewMovieQueryBuilder() - galleryQB := models.NewGalleryQueryBuilder() - performerQB := models.NewPerformerQueryBuilder() - tagQB := models.NewTagQueryBuilder() - sceneMarkerQB := models.NewSceneMarkerQueryBuilder() - joinQB := models.NewJoinsQueryBuilder() + sceneReader := models.NewSceneReaderWriter(nil) + studioReader := models.NewStudioReaderWriter(nil) + movieReader := models.NewMovieReaderWriter(nil) + galleryReader := models.NewGalleryReaderWriter(nil) + performerReader := models.NewPerformerReaderWriter(nil) + tagReader := models.NewTagReaderWriter(nil) + sceneMarkerReader := models.NewSceneMarkerReaderWriter(nil) + joinReader := models.NewJoinReaderWriter(nil) - for scene := range jobChan { - newSceneJSON := jsonschema.Scene{ - CreatedAt: models.JSONTime{Time: scene.CreatedAt.Timestamp}, - UpdatedAt: models.JSONTime{Time: scene.UpdatedAt.Timestamp}, - } + for s := range jobChan { + sceneHash := s.GetHash(t.fileNamingAlgorithm) - if scene.Checksum.Valid { - newSceneJSON.Checksum = scene.Checksum.String - } - - if scene.OSHash.Valid { - newSceneJSON.OSHash = scene.OSHash.String - } - - var studioName string - if scene.StudioID.Valid { - studio, _ := studioQB.Find(int(scene.StudioID.Int64), tx) - if studio != nil { - studioName = studio.Name.String - } - } - - var galleryChecksum string - gallery, _ := galleryQB.FindBySceneID(scene.ID, tx) - if gallery != nil { - galleryChecksum = gallery.Checksum - } - - performers, _ := performerQB.FindNameBySceneID(scene.ID, tx) - sceneMovies, _ := joinQB.GetSceneMovies(scene.ID, tx) - tags, _ := tagQB.FindBySceneID(scene.ID, tx) - sceneMarkers, _ := sceneMarkerQB.FindBySceneID(scene.ID, tx) - - if scene.Title.Valid { - newSceneJSON.Title = scene.Title.String - } - if studioName != "" { - newSceneJSON.Studio = studioName - } - if scene.URL.Valid { - newSceneJSON.URL = scene.URL.String - } - if scene.Date.Valid { - newSceneJSON.Date = utils.GetYMDFromDatabaseDate(scene.Date.String) - } - if scene.Rating.Valid { - newSceneJSON.Rating = int(scene.Rating.Int64) - } - - newSceneJSON.OCounter = scene.OCounter - - if scene.Details.Valid { - newSceneJSON.Details = scene.Details.String - } - if galleryChecksum != "" { - newSceneJSON.Gallery = galleryChecksum - } - - newSceneJSON.Performers = t.getPerformerNames(performers) - newSceneJSON.Tags = t.getTagNames(tags) - - sceneHash := scene.GetHash(t.fileNamingAlgorithm) - - for _, sceneMarker := range sceneMarkers { - primaryTag, err := tagQB.Find(sceneMarker.PrimaryTagID, tx) - if err != nil { - logger.Errorf("[scenes] <%s> invalid primary tag for scene marker: %s", sceneHash, err.Error()) - continue - } - sceneMarkerTags, err := tagQB.FindBySceneMarkerID(sceneMarker.ID, tx) - if err != nil { - logger.Errorf("[scenes] <%s> invalid tags for scene marker: %s", sceneHash, err.Error()) - continue - } - if sceneMarker.Seconds == 0 || primaryTag.Name == "" { - logger.Errorf("[scenes] invalid scene marker: %v", sceneMarker) - } - - sceneMarkerJSON := jsonschema.SceneMarker{ - Title: sceneMarker.Title, - Seconds: t.getDecimalString(sceneMarker.Seconds), - PrimaryTag: primaryTag.Name, - Tags: t.getTagNames(sceneMarkerTags), - CreatedAt: models.JSONTime{Time: sceneMarker.CreatedAt.Timestamp}, - UpdatedAt: models.JSONTime{Time: sceneMarker.UpdatedAt.Timestamp}, - } - - newSceneJSON.Markers = append(newSceneJSON.Markers, sceneMarkerJSON) - } - - for _, sceneMovie := range sceneMovies { - movie, _ := movieQB.Find(sceneMovie.MovieID, tx) - - if movie.Name.Valid { - sceneMovieJSON := jsonschema.SceneMovie{ - MovieName: movie.Name.String, - SceneIndex: int(sceneMovie.SceneIndex.Int64), - } - newSceneJSON.Movies = append(newSceneJSON.Movies, sceneMovieJSON) - } - } - - newSceneJSON.File = &jsonschema.SceneFile{} - if scene.Size.Valid { - newSceneJSON.File.Size = scene.Size.String - } - if scene.Duration.Valid { - newSceneJSON.File.Duration = t.getDecimalString(scene.Duration.Float64) - } - if scene.VideoCodec.Valid { - newSceneJSON.File.VideoCodec = scene.VideoCodec.String - } - if scene.AudioCodec.Valid { - newSceneJSON.File.AudioCodec = scene.AudioCodec.String - } - if scene.Format.Valid { - newSceneJSON.File.Format = scene.Format.String - } - if scene.Width.Valid { - newSceneJSON.File.Width = int(scene.Width.Int64) - } - if scene.Height.Valid { - newSceneJSON.File.Height = int(scene.Height.Int64) - } - if scene.Framerate.Valid { - newSceneJSON.File.Framerate = t.getDecimalString(scene.Framerate.Float64) - } - if scene.Bitrate.Valid { - newSceneJSON.File.Bitrate = int(scene.Bitrate.Int64) - } - - cover, err := sceneQB.GetSceneCover(scene.ID, tx) + newSceneJSON, err := scene.ToBasicJSON(sceneReader, s) if err != nil { - logger.Errorf("[scenes] <%s> error getting scene cover: %s", sceneHash, err.Error()) + logger.Errorf("[scenes] <%s> error getting scene JSON: %s", sceneHash, err.Error()) continue } - if len(cover) > 0 { - newSceneJSON.Cover = utils.GetBase64StringFromData(cover) - } - - sceneJSON, err := instance.JSON.getScene(sceneHash) + newSceneJSON.Studio, err = scene.GetStudioName(studioReader, s) if err != nil { - logger.Debugf("[scenes] error reading scene json: %s", err.Error()) - } else if jsonschema.CompareJSON(*sceneJSON, newSceneJSON) { + logger.Errorf("[scenes] <%s> error getting scene studio name: %s", sceneHash, err.Error()) continue } - if err := instance.JSON.saveScene(sceneHash, &newSceneJSON); err != nil { + sceneGallery, err := galleryReader.FindBySceneID(s.ID) + if err != nil { + logger.Errorf("[scenes] <%s> error getting scene gallery: %s", sceneHash, err.Error()) + continue + } + + if sceneGallery != nil { + newSceneJSON.Gallery = sceneGallery.Checksum + } + + performers, err := performerReader.FindBySceneID(s.ID) + if err != nil { + logger.Errorf("[scenes] <%s> error getting scene performer names: %s", sceneHash, err.Error()) + continue + } + + newSceneJSON.Performers = performer.GetNames(performers) + + newSceneJSON.Tags, err = scene.GetTagNames(tagReader, s) + if err != nil { + logger.Errorf("[scenes] <%s> error getting scene tag names: %s", sceneHash, err.Error()) + continue + } + + newSceneJSON.Markers, err = scene.GetSceneMarkersJSON(sceneMarkerReader, tagReader, s) + if err != nil { + logger.Errorf("[scenes] <%s> error getting scene markers JSON: %s", sceneHash, err.Error()) + continue + } + + newSceneJSON.Movies, err = scene.GetSceneMoviesJSON(movieReader, joinReader, s) + if err != nil { + logger.Errorf("[scenes] <%s> error getting scene movies JSON: %s", sceneHash, err.Error()) + continue + } + + if t.includeDependencies { + if s.StudioID.Valid { + t.studios.IDs = utils.IntAppendUnique(t.studios.IDs, int(s.StudioID.Int64)) + } + + if sceneGallery != nil { + t.galleries.IDs = utils.IntAppendUnique(t.galleries.IDs, sceneGallery.ID) + } + + tagIDs, err := scene.GetDependentTagIDs(tagReader, joinReader, sceneMarkerReader, s) + if err != nil { + logger.Errorf("[scenes] <%s> error getting scene tags: %s", sceneHash, err.Error()) + continue + } + t.tags.IDs = utils.IntAppendUniques(t.tags.IDs, tagIDs) + + movieIDs, err := scene.GetDependentMovieIDs(joinReader, s) + if err != nil { + logger.Errorf("[scenes] <%s> error getting scene movies: %s", sceneHash, err.Error()) + continue + } + t.movies.IDs = utils.IntAppendUniques(t.movies.IDs, movieIDs) + + t.performers.IDs = utils.IntAppendUniques(t.performers.IDs, performer.GetIDs(performers)) + } + + sceneJSON, err := t.json.getScene(sceneHash) + if err == nil && jsonschema.CompareJSON(*sceneJSON, *newSceneJSON) { + continue + } + + if err := t.json.saveScene(sceneHash, newSceneJSON); err != nil { logger.Errorf("[scenes] <%s> failed to save json: %s", sceneHash, err.Error()) } } - } -func (t *ExportTask) ExportGalleries(ctx context.Context) { - qb := models.NewGalleryQueryBuilder() - galleries, err := qb.All() - if err != nil { - logger.Errorf("[galleries] failed to fetch all galleries: %s", err.Error()) +func (t *ExportTask) ExportImages(workers int) { + var imagesWg sync.WaitGroup + + imageReader := models.NewImageReaderWriter(nil) + + var images []*models.Image + var err error + all := t.full || (t.images != nil && t.images.all) + if all { + images, err = imageReader.All() + } else if t.images != nil && len(t.images.IDs) > 0 { + images, err = imageReader.FindMany(t.images.IDs) } + if err != nil { + logger.Errorf("[images] failed to fetch images: %s", err.Error()) + } + + jobCh := make(chan *models.Image, workers*2) // make a buffered channel to feed workers + + logger.Info("[images] exporting") + startTime := time.Now() + + for w := 0; w < workers; w++ { // create export Image workers + imagesWg.Add(1) + go exportImage(&imagesWg, jobCh, t) + } + + for i, image := range images { + index := i + 1 + + if (i % 100) == 0 { // make progress easier to read + logger.Progressf("[images] %d of %d", index, len(images)) + } + t.Mappings.Images = append(t.Mappings.Images, jsonschema.PathNameMapping{Path: image.Path, Checksum: image.Checksum}) + jobCh <- image // feed workers + } + + close(jobCh) // close channel so that workers will know no more jobs are available + imagesWg.Wait() + + logger.Infof("[images] export complete in %s. %d workers used.", time.Since(startTime), workers) +} + +func exportImage(wg *sync.WaitGroup, jobChan <-chan *models.Image, t *ExportTask) { + defer wg.Done() + studioReader := models.NewStudioReaderWriter(nil) + galleryReader := models.NewGalleryReaderWriter(nil) + performerReader := models.NewPerformerReaderWriter(nil) + tagReader := models.NewTagReaderWriter(nil) + + for s := range jobChan { + imageHash := s.Checksum + + newImageJSON := image.ToBasicJSON(s) + + var err error + newImageJSON.Studio, err = image.GetStudioName(studioReader, s) + if err != nil { + logger.Errorf("[images] <%s> error getting image studio name: %s", imageHash, err.Error()) + continue + } + + imageGalleries, err := galleryReader.FindByImageID(s.ID) + if err != nil { + logger.Errorf("[images] <%s> error getting image galleries: %s", imageHash, err.Error()) + continue + } + + newImageJSON.Galleries = t.getGalleryChecksums(imageGalleries) + + performers, err := performerReader.FindByImageID(s.ID) + if err != nil { + logger.Errorf("[images] <%s> error getting image performer names: %s", imageHash, err.Error()) + continue + } + + newImageJSON.Performers = performer.GetNames(performers) + + tags, err := tagReader.FindByImageID(s.ID) + if err != nil { + logger.Errorf("[images] <%s> error getting image tag names: %s", imageHash, err.Error()) + continue + } + + newImageJSON.Tags = tag.GetNames(tags) + + if t.includeDependencies { + if s.StudioID.Valid { + t.studios.IDs = utils.IntAppendUnique(t.studios.IDs, int(s.StudioID.Int64)) + } + + t.galleries.IDs = utils.IntAppendUniques(t.galleries.IDs, gallery.GetIDs(imageGalleries)) + t.tags.IDs = utils.IntAppendUniques(t.tags.IDs, tag.GetIDs(tags)) + t.performers.IDs = utils.IntAppendUniques(t.performers.IDs, performer.GetIDs(performers)) + } + + imageJSON, err := t.json.getImage(imageHash) + if err == nil && jsonschema.CompareJSON(*imageJSON, *newImageJSON) { + continue + } + + if err := t.json.saveImage(imageHash, newImageJSON); err != nil { + logger.Errorf("[images] <%s> failed to save json: %s", imageHash, err.Error()) + } + } +} + +func (t *ExportTask) getGalleryChecksums(galleries []*models.Gallery) (ret []string) { + for _, g := range galleries { + ret = append(ret, g.Checksum) + } + return +} + +func (t *ExportTask) ExportGalleries(workers int) { + var galleriesWg sync.WaitGroup + + reader := models.NewGalleryReaderWriter(nil) + + var galleries []*models.Gallery + var err error + all := t.full || (t.galleries != nil && t.galleries.all) + if all { + galleries, err = reader.All() + } else if t.galleries != nil && len(t.galleries.IDs) > 0 { + galleries, err = reader.FindMany(t.galleries.IDs) + } + + if err != nil { + logger.Errorf("[galleries] failed to fetch galleries: %s", err.Error()) + } + + jobCh := make(chan *models.Gallery, workers*2) // make a buffered channel to feed workers + logger.Info("[galleries] exporting") + startTime := time.Now() + + for w := 0; w < workers; w++ { // create export Scene workers + galleriesWg.Add(1) + go exportGallery(&galleriesWg, jobCh, t) + } for i, gallery := range galleries { index := i + 1 - logger.Progressf("[galleries] %d of %d", index, len(galleries)) - t.Mappings.Galleries = append(t.Mappings.Galleries, jsonschema.PathMapping{Path: gallery.Path, Checksum: gallery.Checksum}) + + if (i % 100) == 0 { // make progress easier to read + logger.Progressf("[galleries] %d of %d", index, len(galleries)) + } + + t.Mappings.Galleries = append(t.Mappings.Galleries, jsonschema.PathNameMapping{ + Path: gallery.Path.String, + Name: gallery.Title.String, + Checksum: gallery.Checksum, + }) + jobCh <- gallery } - logger.Infof("[galleries] export complete") + close(jobCh) // close channel so that workers will know no more jobs are available + galleriesWg.Wait() + + logger.Infof("[galleries] export complete in %s. %d workers used.", time.Since(startTime), workers) } -func (t *ExportTask) ExportPerformers(ctx context.Context, workers int) { +func exportGallery(wg *sync.WaitGroup, jobChan <-chan *models.Gallery, t *ExportTask) { + defer wg.Done() + studioReader := models.NewStudioReaderWriter(nil) + performerReader := models.NewPerformerReaderWriter(nil) + tagReader := models.NewTagReaderWriter(nil) + + for g := range jobChan { + galleryHash := g.Checksum + + newGalleryJSON, err := gallery.ToBasicJSON(g) + if err != nil { + logger.Errorf("[galleries] <%s> error getting gallery JSON: %s", galleryHash, err.Error()) + continue + } + + newGalleryJSON.Studio, err = gallery.GetStudioName(studioReader, g) + if err != nil { + logger.Errorf("[galleries] <%s> error getting gallery studio name: %s", galleryHash, err.Error()) + continue + } + + performers, err := performerReader.FindByGalleryID(g.ID) + if err != nil { + logger.Errorf("[galleries] <%s> error getting gallery performer names: %s", galleryHash, err.Error()) + continue + } + + newGalleryJSON.Performers = performer.GetNames(performers) + + tags, err := tagReader.FindByGalleryID(g.ID) + if err != nil { + logger.Errorf("[galleries] <%s> error getting gallery tag names: %s", galleryHash, err.Error()) + continue + } + + newGalleryJSON.Tags = tag.GetNames(tags) + + if t.includeDependencies { + if g.StudioID.Valid { + t.studios.IDs = utils.IntAppendUnique(t.studios.IDs, int(g.StudioID.Int64)) + } + + t.tags.IDs = utils.IntAppendUniques(t.tags.IDs, tag.GetIDs(tags)) + t.performers.IDs = utils.IntAppendUniques(t.performers.IDs, performer.GetIDs(performers)) + } + + galleryJSON, err := t.json.getGallery(galleryHash) + if err == nil && jsonschema.CompareJSON(*galleryJSON, *newGalleryJSON) { + continue + } + + if err := t.json.saveGallery(galleryHash, newGalleryJSON); err != nil { + logger.Errorf("[galleries] <%s> failed to save json: %s", galleryHash, err.Error()) + } + } +} + +func (t *ExportTask) ExportPerformers(workers int) { var performersWg sync.WaitGroup - qb := models.NewPerformerQueryBuilder() - performers, err := qb.All() + reader := models.NewPerformerReaderWriter(nil) + var performers []*models.Performer + var err error + all := t.full || (t.performers != nil && t.performers.all) + if all { + performers, err = reader.All() + } else if t.performers != nil && len(t.performers.IDs) > 0 { + performers, err = reader.FindMany(t.performers.IDs) + } + if err != nil { - logger.Errorf("[performers] failed to fetch all performers: %s", err.Error()) + logger.Errorf("[performers] failed to fetch performers: %s", err.Error()) } jobCh := make(chan *models.Performer, workers*2) // make a buffered channel to feed workers @@ -286,14 +689,14 @@ func (t *ExportTask) ExportPerformers(ctx context.Context, workers int) { for w := 0; w < workers; w++ { // create export Performer workers performersWg.Add(1) - go exportPerformer(&performersWg, jobCh) + go t.exportPerformer(&performersWg, jobCh) } for i, performer := range performers { index := i + 1 logger.Progressf("[performers] %d of %d", index, len(performers)) - t.Mappings.Performers = append(t.Mappings.Performers, jsonschema.NameMapping{Name: performer.Name.String, Checksum: performer.Checksum}) + t.Mappings.Performers = append(t.Mappings.Performers, jsonschema.PathNameMapping{Name: performer.Name.String, Checksum: performer.Checksum}) jobCh <- performer // feed workers } @@ -303,99 +706,47 @@ func (t *ExportTask) ExportPerformers(ctx context.Context, workers int) { logger.Infof("[performers] export complete in %s. %d workers used.", time.Since(startTime), workers) } -func exportPerformer(wg *sync.WaitGroup, jobChan <-chan *models.Performer) { +func (t *ExportTask) exportPerformer(wg *sync.WaitGroup, jobChan <-chan *models.Performer) { defer wg.Done() - performerQB := models.NewPerformerQueryBuilder() + performerReader := models.NewPerformerReaderWriter(nil) - for performer := range jobChan { - newPerformerJSON := jsonschema.Performer{ - CreatedAt: models.JSONTime{Time: performer.CreatedAt.Timestamp}, - UpdatedAt: models.JSONTime{Time: performer.UpdatedAt.Timestamp}, - } + for p := range jobChan { + newPerformerJSON, err := performer.ToJSON(performerReader, p) - if performer.Name.Valid { - newPerformerJSON.Name = performer.Name.String - } - if performer.Gender.Valid { - newPerformerJSON.Gender = performer.Gender.String - } - if performer.URL.Valid { - newPerformerJSON.URL = performer.URL.String - } - if performer.Birthdate.Valid { - newPerformerJSON.Birthdate = utils.GetYMDFromDatabaseDate(performer.Birthdate.String) - } - if performer.Ethnicity.Valid { - newPerformerJSON.Ethnicity = performer.Ethnicity.String - } - if performer.Country.Valid { - newPerformerJSON.Country = performer.Country.String - } - if performer.EyeColor.Valid { - newPerformerJSON.EyeColor = performer.EyeColor.String - } - if performer.Height.Valid { - newPerformerJSON.Height = performer.Height.String - } - if performer.Measurements.Valid { - newPerformerJSON.Measurements = performer.Measurements.String - } - if performer.FakeTits.Valid { - newPerformerJSON.FakeTits = performer.FakeTits.String - } - if performer.CareerLength.Valid { - newPerformerJSON.CareerLength = performer.CareerLength.String - } - if performer.Tattoos.Valid { - newPerformerJSON.Tattoos = performer.Tattoos.String - } - if performer.Piercings.Valid { - newPerformerJSON.Piercings = performer.Piercings.String - } - if performer.Aliases.Valid { - newPerformerJSON.Aliases = performer.Aliases.String - } - if performer.Twitter.Valid { - newPerformerJSON.Twitter = performer.Twitter.String - } - if performer.Instagram.Valid { - newPerformerJSON.Instagram = performer.Instagram.String - } - if performer.Favorite.Valid { - newPerformerJSON.Favorite = performer.Favorite.Bool - } - - image, err := performerQB.GetPerformerImage(performer.ID, nil) if err != nil { - logger.Errorf("[performers] <%s> error getting performers image: %s", performer.Checksum, err.Error()) + logger.Errorf("[performers] <%s> error getting performer JSON: %s", p.Checksum, err.Error()) continue } - if len(image) > 0 { - newPerformerJSON.Image = utils.GetBase64StringFromData(image) - } - - performerJSON, err := instance.JSON.getPerformer(performer.Checksum) + performerJSON, err := t.json.getPerformer(p.Checksum) if err != nil { logger.Debugf("[performers] error reading performer json: %s", err.Error()) - } else if jsonschema.CompareJSON(*performerJSON, newPerformerJSON) { + } else if jsonschema.CompareJSON(*performerJSON, *newPerformerJSON) { continue } - if err := instance.JSON.savePerformer(performer.Checksum, &newPerformerJSON); err != nil { - logger.Errorf("[performers] <%s> failed to save json: %s", performer.Checksum, err.Error()) + if err := t.json.savePerformer(p.Checksum, newPerformerJSON); err != nil { + logger.Errorf("[performers] <%s> failed to save json: %s", p.Checksum, err.Error()) } } } -func (t *ExportTask) ExportStudios(ctx context.Context, workers int) { +func (t *ExportTask) ExportStudios(workers int) { var studiosWg sync.WaitGroup - qb := models.NewStudioQueryBuilder() - studios, err := qb.All() + reader := models.NewStudioReaderWriter(nil) + var studios []*models.Studio + var err error + all := t.full || (t.studios != nil && t.studios.all) + if all { + studios, err = reader.All() + } else if t.studios != nil && len(t.studios.IDs) > 0 { + studios, err = reader.FindMany(t.studios.IDs) + } + if err != nil { - logger.Errorf("[studios] failed to fetch all studios: %s", err.Error()) + logger.Errorf("[studios] failed to fetch studios: %s", err.Error()) } logger.Info("[studios] exporting") @@ -405,14 +756,14 @@ func (t *ExportTask) ExportStudios(ctx context.Context, workers int) { for w := 0; w < workers; w++ { // create export Studio workers studiosWg.Add(1) - go exportStudio(&studiosWg, jobCh) + go t.exportStudio(&studiosWg, jobCh) } for i, studio := range studios { index := i + 1 logger.Progressf("[studios] %d of %d", index, len(studios)) - t.Mappings.Studios = append(t.Mappings.Studios, jsonschema.NameMapping{Name: studio.Name.String, Checksum: studio.Checksum}) + t.Mappings.Studios = append(t.Mappings.Studios, jsonschema.PathNameMapping{Name: studio.Name.String, Checksum: studio.Checksum}) jobCh <- studio // feed workers } @@ -422,61 +773,45 @@ func (t *ExportTask) ExportStudios(ctx context.Context, workers int) { logger.Infof("[studios] export complete in %s. %d workers used.", time.Since(startTime), workers) } -func exportStudio(wg *sync.WaitGroup, jobChan <-chan *models.Studio) { +func (t *ExportTask) exportStudio(wg *sync.WaitGroup, jobChan <-chan *models.Studio) { defer wg.Done() - studioQB := models.NewStudioQueryBuilder() + studioReader := models.NewStudioReaderWriter(nil) - for studio := range jobChan { + for s := range jobChan { + newStudioJSON, err := studio.ToJSON(studioReader, s) - newStudioJSON := jsonschema.Studio{ - CreatedAt: models.JSONTime{Time: studio.CreatedAt.Timestamp}, - UpdatedAt: models.JSONTime{Time: studio.UpdatedAt.Timestamp}, - } - - if studio.Name.Valid { - newStudioJSON.Name = studio.Name.String - } - if studio.URL.Valid { - newStudioJSON.URL = studio.URL.String - } - if studio.ParentID.Valid { - parent, _ := studioQB.Find(int(studio.ParentID.Int64), nil) - if parent != nil { - newStudioJSON.ParentStudio = parent.Name.String - } - } - - image, err := studioQB.GetStudioImage(studio.ID, nil) if err != nil { - logger.Errorf("[studios] <%s> error getting studio image: %s", studio.Checksum, err.Error()) + logger.Errorf("[studios] <%s> error getting studio JSON: %s", s.Checksum, err.Error()) continue } - if len(image) > 0 { - newStudioJSON.Image = utils.GetBase64StringFromData(image) - } - - studioJSON, err := instance.JSON.getStudio(studio.Checksum) - if err != nil { - logger.Debugf("[studios] error reading studio json: %s", err.Error()) - } else if jsonschema.CompareJSON(*studioJSON, newStudioJSON) { + studioJSON, err := t.json.getStudio(s.Checksum) + if err == nil && jsonschema.CompareJSON(*studioJSON, *newStudioJSON) { continue } - if err := instance.JSON.saveStudio(studio.Checksum, &newStudioJSON); err != nil { - logger.Errorf("[studios] <%s> failed to save json: %s", studio.Checksum, err.Error()) + if err := t.json.saveStudio(s.Checksum, newStudioJSON); err != nil { + logger.Errorf("[studios] <%s> failed to save json: %s", s.Checksum, err.Error()) } } } -func (t *ExportTask) ExportTags(ctx context.Context, workers int) { +func (t *ExportTask) ExportTags(workers int) { var tagsWg sync.WaitGroup - qb := models.NewTagQueryBuilder() - tags, err := qb.All() + reader := models.NewTagReaderWriter(nil) + var tags []*models.Tag + var err error + all := t.full || (t.tags != nil && t.tags.all) + if all { + tags, err = reader.All() + } else if t.tags != nil && len(t.tags.IDs) > 0 { + tags, err = reader.FindMany(t.tags.IDs) + } + if err != nil { - logger.Errorf("[tags] failed to fetch all tags: %s", err.Error()) + logger.Errorf("[tags] failed to fetch tags: %s", err.Error()) } logger.Info("[tags] exporting") @@ -486,7 +821,7 @@ func (t *ExportTask) ExportTags(ctx context.Context, workers int) { for w := 0; w < workers; w++ { // create export Tag workers tagsWg.Add(1) - go exportTag(&tagsWg, jobCh) + go t.exportTag(&tagsWg, jobCh) } for i, tag := range tags { @@ -496,7 +831,7 @@ func (t *ExportTask) ExportTags(ctx context.Context, workers int) { // generate checksum on the fly by name, since we don't store it checksum := utils.MD5FromString(tag.Name) - t.Mappings.Tags = append(t.Mappings.Tags, jsonschema.NameMapping{Name: tag.Name, Checksum: checksum}) + t.Mappings.Tags = append(t.Mappings.Tags, jsonschema.PathNameMapping{Name: tag.Name, Checksum: checksum}) jobCh <- tag // feed workers } @@ -506,52 +841,48 @@ func (t *ExportTask) ExportTags(ctx context.Context, workers int) { logger.Infof("[tags] export complete in %s. %d workers used.", time.Since(startTime), workers) } -func exportTag(wg *sync.WaitGroup, jobChan <-chan *models.Tag) { +func (t *ExportTask) exportTag(wg *sync.WaitGroup, jobChan <-chan *models.Tag) { defer wg.Done() - tagQB := models.NewTagQueryBuilder() + tagReader := models.NewTagReaderWriter(nil) - for tag := range jobChan { + for thisTag := range jobChan { + newTagJSON, err := tag.ToJSON(tagReader, thisTag) - newTagJSON := jsonschema.Tag{ - Name: tag.Name, - CreatedAt: models.JSONTime{Time: tag.CreatedAt.Timestamp}, - UpdatedAt: models.JSONTime{Time: tag.UpdatedAt.Timestamp}, - } - - image, err := tagQB.GetTagImage(tag.ID, nil) if err != nil { - logger.Errorf("[tags] <%s> error getting tag image: %s", tag.Name, err.Error()) + logger.Errorf("[tags] <%s> error getting tag JSON: %s", thisTag.Name, err.Error()) continue } - if len(image) > 0 { - newTagJSON.Image = utils.GetBase64StringFromData(image) - } - // generate checksum on the fly by name, since we don't store it - checksum := utils.MD5FromString(tag.Name) + checksum := utils.MD5FromString(thisTag.Name) - tagJSON, err := instance.JSON.getTag(checksum) - if err != nil { - logger.Debugf("[tags] error reading tag json: %s", err.Error()) - } else if jsonschema.CompareJSON(*tagJSON, newTagJSON) { + tagJSON, err := t.json.getTag(checksum) + if err == nil && jsonschema.CompareJSON(*tagJSON, *newTagJSON) { continue } - if err := instance.JSON.saveTag(checksum, &newTagJSON); err != nil { + if err := t.json.saveTag(checksum, newTagJSON); err != nil { logger.Errorf("[tags] <%s> failed to save json: %s", checksum, err.Error()) } } } -func (t *ExportTask) ExportMovies(ctx context.Context, workers int) { +func (t *ExportTask) ExportMovies(workers int) { var moviesWg sync.WaitGroup - qb := models.NewMovieQueryBuilder() - movies, err := qb.All() + reader := models.NewMovieReaderWriter(nil) + var movies []*models.Movie + var err error + all := t.full || (t.movies != nil && t.movies.all) + if all { + movies, err = reader.All() + } else if t.movies != nil && len(t.movies.IDs) > 0 { + movies, err = reader.FindMany(t.movies.IDs) + } + if err != nil { - logger.Errorf("[movies] failed to fetch all movies: %s", err.Error()) + logger.Errorf("[movies] failed to fetch movies: %s", err.Error()) } logger.Info("[movies] exporting") @@ -561,14 +892,14 @@ func (t *ExportTask) ExportMovies(ctx context.Context, workers int) { for w := 0; w < workers; w++ { // create export Studio workers moviesWg.Add(1) - go exportMovie(&moviesWg, jobCh) + go t.exportMovie(&moviesWg, jobCh) } for i, movie := range movies { index := i + 1 logger.Progressf("[movies] %d of %d", index, len(movies)) - t.Mappings.Movies = append(t.Mappings.Movies, jsonschema.NameMapping{Name: movie.Name.String, Checksum: movie.Checksum}) + t.Mappings.Movies = append(t.Mappings.Movies, jsonschema.PathNameMapping{Name: movie.Name.String, Checksum: movie.Checksum}) jobCh <- movie // feed workers } @@ -578,89 +909,40 @@ func (t *ExportTask) ExportMovies(ctx context.Context, workers int) { logger.Infof("[movies] export complete in %s. %d workers used.", time.Since(startTime), workers) } -func exportMovie(wg *sync.WaitGroup, jobChan <-chan *models.Movie) { +func (t *ExportTask) exportMovie(wg *sync.WaitGroup, jobChan <-chan *models.Movie) { defer wg.Done() - movieQB := models.NewMovieQueryBuilder() - studioQB := models.NewStudioQueryBuilder() + movieReader := models.NewMovieReaderWriter(nil) + studioReader := models.NewStudioReaderWriter(nil) - for movie := range jobChan { - newMovieJSON := jsonschema.Movie{ - CreatedAt: models.JSONTime{Time: movie.CreatedAt.Timestamp}, - UpdatedAt: models.JSONTime{Time: movie.UpdatedAt.Timestamp}, + for m := range jobChan { + newMovieJSON, err := movie.ToJSON(movieReader, studioReader, m) + + if err != nil { + logger.Errorf("[movies] <%s> error getting tag JSON: %s", m.Checksum, err.Error()) + continue } - if movie.Name.Valid { - newMovieJSON.Name = movie.Name.String - } - if movie.Aliases.Valid { - newMovieJSON.Aliases = movie.Aliases.String - } - if movie.Date.Valid { - newMovieJSON.Date = utils.GetYMDFromDatabaseDate(movie.Date.String) - } - if movie.Rating.Valid { - newMovieJSON.Rating = int(movie.Rating.Int64) - } - if movie.Duration.Valid { - newMovieJSON.Duration = int(movie.Duration.Int64) - } - - if movie.Director.Valid { - newMovieJSON.Director = movie.Director.String - } - - if movie.Synopsis.Valid { - newMovieJSON.Synopsis = movie.Synopsis.String - } - - if movie.URL.Valid { - newMovieJSON.URL = movie.URL.String - } - - if movie.StudioID.Valid { - studio, _ := studioQB.Find(int(movie.StudioID.Int64), nil) - if studio != nil { - newMovieJSON.Studio = studio.Name.String + if t.includeDependencies { + if m.StudioID.Valid { + t.studios.IDs = utils.IntAppendUnique(t.studios.IDs, int(m.StudioID.Int64)) } } - frontImage, err := movieQB.GetFrontImage(movie.ID, nil) - if err != nil { - logger.Errorf("[movies] <%s> error getting movie front image: %s", movie.Checksum, err.Error()) - continue - } - - if len(frontImage) > 0 { - newMovieJSON.FrontImage = utils.GetBase64StringFromData(frontImage) - } - - backImage, err := movieQB.GetBackImage(movie.ID, nil) - if err != nil { - logger.Errorf("[movies] <%s> error getting movie back image: %s", movie.Checksum, err.Error()) - continue - } - - if len(backImage) > 0 { - newMovieJSON.BackImage = utils.GetBase64StringFromData(backImage) - } - - movieJSON, err := instance.JSON.getMovie(movie.Checksum) + movieJSON, err := t.json.getMovie(m.Checksum) if err != nil { logger.Debugf("[movies] error reading movie json: %s", err.Error()) - } else if jsonschema.CompareJSON(*movieJSON, newMovieJSON) { + } else if jsonschema.CompareJSON(*movieJSON, *newMovieJSON) { continue } - if err := instance.JSON.saveMovie(movie.Checksum, &newMovieJSON); err != nil { - logger.Errorf("[movies] <%s> failed to save json: %s", movie.Checksum, err.Error()) + if err := t.json.saveMovie(m.Checksum, newMovieJSON); err != nil { + logger.Errorf("[movies] <%s> failed to save json: %s", m.Checksum, err.Error()) } } } -func (t *ExportTask) ExportScrapedItems(ctx context.Context) { - tx := database.DB.MustBeginTx(ctx, nil) - defer tx.Commit() +func (t *ExportTask) ExportScrapedItems() { qb := models.NewScrapedItemQueryBuilder() sqb := models.NewStudioQueryBuilder() scrapedItems, err := qb.All() @@ -670,13 +952,15 @@ func (t *ExportTask) ExportScrapedItems(ctx context.Context) { logger.Info("[scraped sites] exporting") + scraped := []jsonschema.ScrapedItem{} + for i, scrapedItem := range scrapedItems { index := i + 1 logger.Progressf("[scraped sites] %d of %d", index, len(scrapedItems)) var studioName string if scrapedItem.StudioID.Valid { - studio, _ := sqb.Find(int(scrapedItem.StudioID.Int64), tx) + studio, _ := sqb.Find(int(scrapedItem.StudioID.Int64), nil) if studio != nil { studioName = studio.Name.String } @@ -725,74 +1009,18 @@ func (t *ExportTask) ExportScrapedItems(ctx context.Context) { updatedAt := models.JSONTime{Time: scrapedItem.UpdatedAt.Timestamp} // TODO keeping ruby format newScrapedItemJSON.UpdatedAt = updatedAt - t.Scraped = append(t.Scraped, newScrapedItemJSON) + scraped = append(scraped, newScrapedItemJSON) } - scrapedJSON, err := instance.JSON.getScraped() + scrapedJSON, err := t.json.getScraped() if err != nil { logger.Debugf("[scraped sites] error reading json: %s", err.Error()) } - if !jsonschema.CompareJSON(scrapedJSON, t.Scraped) { - if err := instance.JSON.saveScaped(t.Scraped); err != nil { + if !jsonschema.CompareJSON(scrapedJSON, scraped) { + if err := t.json.saveScaped(scraped); err != nil { logger.Errorf("[scraped sites] failed to save json: %s", err.Error()) } } logger.Infof("[scraped sites] export complete") } - -func (t *ExportTask) getPerformerNames(performers []*models.Performer) []string { - if len(performers) == 0 { - return nil - } - - var results []string - for _, performer := range performers { - if performer.Name.Valid { - results = append(results, performer.Name.String) - } - } - - return results -} - -func (t *ExportTask) getTagNames(tags []*models.Tag) []string { - if len(tags) == 0 { - return nil - } - - var results []string - for _, tag := range tags { - if tag.Name != "" { - results = append(results, tag.Name) - } - } - - return results -} - -func (t *ExportTask) getDecimalString(num float64) string { - if num == 0 { - return "" - } - - precision := getPrecision(num) - if precision == 0 { - precision = 1 - } - return fmt.Sprintf("%."+strconv.Itoa(precision)+"f", num) -} - -func getPrecision(num float64) int { - if num == 0 { - return 0 - } - - e := 1.0 - p := 0 - for (math.Round(num*e) / e) != num { - e *= 10 - p++ - } - return p -} diff --git a/pkg/manager/task_generate_gallery_thumbs.go b/pkg/manager/task_generate_gallery_thumbs.go deleted file mode 100644 index 6aad9c80d..000000000 --- a/pkg/manager/task_generate_gallery_thumbs.go +++ /dev/null @@ -1,39 +0,0 @@ -package manager - -import ( - "sync" - - "github.com/stashapp/stash/pkg/logger" - "github.com/stashapp/stash/pkg/manager/paths" - "github.com/stashapp/stash/pkg/models" - "github.com/stashapp/stash/pkg/utils" -) - -type GenerateGthumbsTask struct { - Gallery models.Gallery - Overwrite bool -} - -func (t *GenerateGthumbsTask) Start(wg *sync.WaitGroup) { - defer wg.Done() - generated := 0 - count := t.Gallery.ImageCount() - for i := 0; i < count; i++ { - thumbPath := paths.GetGthumbPath(t.Gallery.Checksum, i, models.DefaultGthumbWidth) - exists, _ := utils.FileExists(thumbPath) - if !t.Overwrite && exists { - continue - } - data := t.Gallery.GetThumbnail(i, models.DefaultGthumbWidth) - err := utils.WriteFile(thumbPath, data) - if err != nil { - logger.Errorf("error writing gallery thumbnail: %s", err) - } else { - generated++ - } - - } - if generated > 0 { - logger.Infof("Generated %d thumbnails for %s", generated, t.Gallery.Path) - } -} diff --git a/pkg/manager/task_import.go b/pkg/manager/task_import.go index c721414bc..600497e48 100644 --- a/pkg/manager/task_import.go +++ b/pkg/manager/task_import.go @@ -1,47 +1,116 @@ package manager import ( + "archive/zip" "context" "database/sql" "fmt" - "strconv" + "io" + "os" + "path/filepath" "sync" "time" "github.com/jmoiron/sqlx" "github.com/stashapp/stash/pkg/database" + "github.com/stashapp/stash/pkg/gallery" + "github.com/stashapp/stash/pkg/image" "github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/manager/config" "github.com/stashapp/stash/pkg/manager/jsonschema" + "github.com/stashapp/stash/pkg/manager/paths" "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/movie" + "github.com/stashapp/stash/pkg/performer" + "github.com/stashapp/stash/pkg/scene" + "github.com/stashapp/stash/pkg/studio" + "github.com/stashapp/stash/pkg/tag" "github.com/stashapp/stash/pkg/utils" ) type ImportTask struct { - Mappings *jsonschema.Mappings - Scraped []jsonschema.ScrapedItem + json jsonUtils + + BaseDir string + ZipFile io.Reader + Reset bool + DuplicateBehaviour models.ImportDuplicateEnum + MissingRefBehaviour models.ImportMissingRefEnum + + mappings *jsonschema.Mappings + scraped []jsonschema.ScrapedItem fileNamingAlgorithm models.HashAlgorithm } +func CreateImportTask(a models.HashAlgorithm, input models.ImportObjectsInput) *ImportTask { + return &ImportTask{ + ZipFile: input.File.File, + Reset: false, + DuplicateBehaviour: input.DuplicateBehaviour, + MissingRefBehaviour: input.MissingRefBehaviour, + fileNamingAlgorithm: a, + } +} + +func (t *ImportTask) GetStatus() JobStatus { + return Import +} + func (t *ImportTask) Start(wg *sync.WaitGroup) { defer wg.Done() - t.Mappings, _ = instance.JSON.getMappings() - if t.Mappings == nil { + if t.ZipFile != nil { + // unzip the file and defer remove the temp directory + var err error + t.BaseDir, err = instance.Paths.Generated.TempDir("import") + if err != nil { + logger.Errorf("error creating temporary directory for import: %s", err.Error()) + return + } + + defer func() { + err := utils.RemoveDir(t.BaseDir) + if err != nil { + logger.Errorf("error removing directory %s: %s", t.BaseDir, err.Error()) + } + }() + + if err := t.unzipFile(); err != nil { + logger.Errorf("error unzipping provided file for import: %s", err.Error()) + return + } + } + + t.json = jsonUtils{ + json: *paths.GetJSONPaths(t.BaseDir), + } + + // set default behaviour if not provided + if !t.DuplicateBehaviour.IsValid() { + t.DuplicateBehaviour = models.ImportDuplicateEnumFail + } + if !t.MissingRefBehaviour.IsValid() { + t.MissingRefBehaviour = models.ImportMissingRefEnumFail + } + + t.mappings, _ = t.json.getMappings() + if t.mappings == nil { logger.Error("missing mappings json") return } - scraped, _ := instance.JSON.getScraped() + scraped, _ := t.json.getScraped() if scraped == nil { logger.Warn("missing scraped json") } - t.Scraped = scraped + t.scraped = scraped - err := database.Reset(config.GetDatabasePath()) + if t.Reset { + err := database.Reset(config.GetDatabasePath()) - if err != nil { - logger.Errorf("Error resetting database: %s", err.Error()) - return + if err != nil { + logger.Errorf("Error resetting database: %s", err.Error()) + return + } } ctx := context.TODO() @@ -54,142 +123,141 @@ func (t *ImportTask) Start(wg *sync.WaitGroup) { t.ImportScrapedItems(ctx) t.ImportScenes(ctx) + t.ImportImages(ctx) +} + +func (t *ImportTask) unzipFile() error { + // copy the zip file to the temporary directory + tmpZip := filepath.Join(t.BaseDir, "import.zip") + out, err := os.Create(tmpZip) + if err != nil { + return err + } + + if _, err := io.Copy(out, t.ZipFile); err != nil { + out.Close() + return err + } + + out.Close() + + defer func() { + err := os.Remove(tmpZip) + if err != nil { + logger.Errorf("error removing temporary zip file %s: %s", tmpZip, err.Error()) + } + }() + + // now we can read the zip file + r, err := zip.OpenReader(tmpZip) + if err != nil { + return err + } + defer r.Close() + + for _, f := range r.File { + fn := filepath.Join(t.BaseDir, f.Name) + + if f.FileInfo().IsDir() { + os.MkdirAll(fn, os.ModePerm) + continue + } + + if err := os.MkdirAll(filepath.Dir(fn), os.ModePerm); err != nil { + return err + } + + o, err := os.OpenFile(fn, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) + if err != nil { + return err + } + defer o.Close() + + i, err := f.Open() + if err != nil { + return err + } + defer i.Close() + + if _, err := io.Copy(o, i); err != nil { + return err + } + } + + return nil } func (t *ImportTask) ImportPerformers(ctx context.Context) { - tx := database.DB.MustBeginTx(ctx, nil) - qb := models.NewPerformerQueryBuilder() + logger.Info("[performers] importing") - for i, mappingJSON := range t.Mappings.Performers { + for i, mappingJSON := range t.mappings.Performers { index := i + 1 - performerJSON, err := instance.JSON.getPerformer(mappingJSON.Checksum) + performerJSON, err := t.json.getPerformer(mappingJSON.Checksum) if err != nil { logger.Errorf("[performers] failed to read json: %s", err.Error()) continue } - if mappingJSON.Checksum == "" || mappingJSON.Name == "" || performerJSON == nil { - return + + logger.Progressf("[performers] %d of %d", index, len(t.mappings.Performers)) + + tx := database.DB.MustBeginTx(ctx, nil) + readerWriter := models.NewPerformerReaderWriter(tx) + importer := &performer.Importer{ + ReaderWriter: readerWriter, + Input: *performerJSON, } - logger.Progressf("[performers] %d of %d", index, len(t.Mappings.Performers)) - - // generate checksum from performer name rather than image - checksum := utils.MD5FromString(performerJSON.Name) - - // Process the base 64 encoded image string - var imageData []byte - if len(performerJSON.Image) > 0 { - _, imageData, err = utils.ProcessBase64Image(performerJSON.Image) - if err != nil { - _ = tx.Rollback() - logger.Errorf("[performers] <%s> invalid image: %s", mappingJSON.Checksum, err.Error()) - return - } + if err := performImport(importer, t.DuplicateBehaviour); err != nil { + tx.Rollback() + logger.Errorf("[performers] <%s> failed to import: %s", mappingJSON.Checksum, err.Error()) + continue } - // Populate a new performer from the input - newPerformer := models.Performer{ - Checksum: checksum, - Favorite: sql.NullBool{Bool: performerJSON.Favorite, Valid: true}, - CreatedAt: models.SQLiteTimestamp{Timestamp: t.getTimeFromJSONTime(performerJSON.CreatedAt)}, - UpdatedAt: models.SQLiteTimestamp{Timestamp: t.getTimeFromJSONTime(performerJSON.UpdatedAt)}, - } - - if performerJSON.Name != "" { - newPerformer.Name = sql.NullString{String: performerJSON.Name, Valid: true} - } - if performerJSON.Gender != "" { - newPerformer.Gender = sql.NullString{String: performerJSON.Gender, Valid: true} - } - if performerJSON.URL != "" { - newPerformer.URL = sql.NullString{String: performerJSON.URL, Valid: true} - } - if performerJSON.Birthdate != "" { - newPerformer.Birthdate = models.SQLiteDate{String: performerJSON.Birthdate, Valid: true} - } - if performerJSON.Ethnicity != "" { - newPerformer.Ethnicity = sql.NullString{String: performerJSON.Ethnicity, Valid: true} - } - if performerJSON.Country != "" { - newPerformer.Country = sql.NullString{String: performerJSON.Country, Valid: true} - } - if performerJSON.EyeColor != "" { - newPerformer.EyeColor = sql.NullString{String: performerJSON.EyeColor, Valid: true} - } - if performerJSON.Height != "" { - newPerformer.Height = sql.NullString{String: performerJSON.Height, Valid: true} - } - if performerJSON.Measurements != "" { - newPerformer.Measurements = sql.NullString{String: performerJSON.Measurements, Valid: true} - } - if performerJSON.FakeTits != "" { - newPerformer.FakeTits = sql.NullString{String: performerJSON.FakeTits, Valid: true} - } - if performerJSON.CareerLength != "" { - newPerformer.CareerLength = sql.NullString{String: performerJSON.CareerLength, Valid: true} - } - if performerJSON.Tattoos != "" { - newPerformer.Tattoos = sql.NullString{String: performerJSON.Tattoos, Valid: true} - } - if performerJSON.Piercings != "" { - newPerformer.Piercings = sql.NullString{String: performerJSON.Piercings, Valid: true} - } - if performerJSON.Aliases != "" { - newPerformer.Aliases = sql.NullString{String: performerJSON.Aliases, Valid: true} - } - if performerJSON.Twitter != "" { - newPerformer.Twitter = sql.NullString{String: performerJSON.Twitter, Valid: true} - } - if performerJSON.Instagram != "" { - newPerformer.Instagram = sql.NullString{String: performerJSON.Instagram, Valid: true} - } - - createdPerformer, err := qb.Create(newPerformer, tx) - if err != nil { - _ = tx.Rollback() - logger.Errorf("[performers] <%s> failed to create: %s", mappingJSON.Checksum, err.Error()) - return - } - - // Add the performer image if set - if len(imageData) > 0 { - if err := qb.UpdatePerformerImage(createdPerformer.ID, imageData, tx); err != nil { - _ = tx.Rollback() - logger.Errorf("[performers] <%s> error setting performer image: %s", mappingJSON.Checksum, err.Error()) - return - } + if err := tx.Commit(); err != nil { + tx.Rollback() + logger.Errorf("[performers] <%s> import failed to commit: %s", mappingJSON.Checksum, err.Error()) } } - logger.Info("[performers] importing") - if err := tx.Commit(); err != nil { - logger.Errorf("[performers] import failed to commit: %s", err.Error()) - } logger.Info("[performers] import complete") } func (t *ImportTask) ImportStudios(ctx context.Context) { - tx := database.DB.MustBeginTx(ctx, nil) - pendingParent := make(map[string][]*jsonschema.Studio) - for i, mappingJSON := range t.Mappings.Studios { + logger.Info("[studios] importing") + + for i, mappingJSON := range t.mappings.Studios { index := i + 1 - studioJSON, err := instance.JSON.getStudio(mappingJSON.Checksum) + studioJSON, err := t.json.getStudio(mappingJSON.Checksum) if err != nil { logger.Errorf("[studios] failed to read json: %s", err.Error()) continue } - if mappingJSON.Checksum == "" || mappingJSON.Name == "" || studioJSON == nil { - return - } - logger.Progressf("[studios] %d of %d", index, len(t.Mappings.Studios)) + logger.Progressf("[studios] %d of %d", index, len(t.mappings.Studios)) + tx := database.DB.MustBeginTx(ctx, nil) + + // fail on missing parent studio to begin with if err := t.ImportStudio(studioJSON, pendingParent, tx); err != nil { tx.Rollback() + + if err == studio.ErrParentStudioNotExist { + // add to the pending parent list so that it is created after the parent + s := pendingParent[studioJSON.ParentStudio] + s = append(s, studioJSON) + pendingParent[studioJSON.ParentStudio] = s + continue + } + logger.Errorf("[studios] <%s> failed to create: %s", mappingJSON.Checksum, err.Error()) - return + continue + } + + if err := tx.Commit(); err != nil { + logger.Errorf("[studios] import failed to commit: %s", err.Error()) + continue } } @@ -199,84 +267,42 @@ func (t *ImportTask) ImportStudios(ctx context.Context) { for _, s := range pendingParent { for _, orphanStudioJSON := range s { + tx := database.DB.MustBeginTx(ctx, nil) + if err := t.ImportStudio(orphanStudioJSON, nil, tx); err != nil { tx.Rollback() logger.Errorf("[studios] <%s> failed to create: %s", orphanStudioJSON.Name, err.Error()) - return + continue + } + + if err := tx.Commit(); err != nil { + logger.Errorf("[studios] import failed to commit: %s", err.Error()) + continue } } } } - logger.Info("[studios] importing") - if err := tx.Commit(); err != nil { - logger.Errorf("[studios] import failed to commit: %s", err.Error()) - } logger.Info("[studios] import complete") } func (t *ImportTask) ImportStudio(studioJSON *jsonschema.Studio, pendingParent map[string][]*jsonschema.Studio, tx *sqlx.Tx) error { - qb := models.NewStudioQueryBuilder() - - // generate checksum from studio name rather than image - checksum := utils.MD5FromString(studioJSON.Name) - - // Process the base 64 encoded image string - var imageData []byte - var err error - if len(studioJSON.Image) > 0 { - _, imageData, err = utils.ProcessBase64Image(studioJSON.Image) - if err != nil { - return fmt.Errorf("invalid image: %s", err.Error()) - } + readerWriter := models.NewStudioReaderWriter(tx) + importer := &studio.Importer{ + ReaderWriter: readerWriter, + Input: *studioJSON, + MissingRefBehaviour: t.MissingRefBehaviour, } - // Populate a new studio from the input - newStudio := models.Studio{ - Checksum: checksum, - Name: sql.NullString{String: studioJSON.Name, Valid: true}, - URL: sql.NullString{String: studioJSON.URL, Valid: true}, - CreatedAt: models.SQLiteTimestamp{Timestamp: t.getTimeFromJSONTime(studioJSON.CreatedAt)}, - UpdatedAt: models.SQLiteTimestamp{Timestamp: t.getTimeFromJSONTime(studioJSON.UpdatedAt)}, + // first phase: return error if parent does not exist + if pendingParent != nil { + importer.MissingRefBehaviour = models.ImportMissingRefEnumFail } - // Populate the parent ID - if studioJSON.ParentStudio != "" { - studio, err := qb.FindByName(studioJSON.ParentStudio, tx, false) - if err != nil { - return fmt.Errorf("error finding studio by name <%s>: %s", studioJSON.ParentStudio, err.Error()) - } - - if studio == nil { - // its possible that the parent hasn't been created yet - // do it after it is created - if pendingParent == nil { - logger.Warnf("[studios] studio <%s> does not exist", studioJSON.ParentStudio) - } else { - // add to the pending parent list so that it is created after the parent - s := pendingParent[studioJSON.ParentStudio] - s = append(s, studioJSON) - pendingParent[studioJSON.ParentStudio] = s - - // skip - return nil - } - } else { - newStudio.ParentID = sql.NullInt64{Int64: int64(studio.ID), Valid: true} - } - } - - createdStudio, err := qb.Create(newStudio, tx) - if err != nil { + if err := performImport(importer, t.DuplicateBehaviour); err != nil { return err } - if len(imageData) > 0 { - if err := qb.UpdateStudioImage(createdStudio.ID, imageData, tx); err != nil { - return fmt.Errorf("error setting studio image: %s", err.Error()) - } - } - // now create the studios pending this studios creation s := pendingParent[studioJSON.Name] for _, childStudioJSON := range s { @@ -289,198 +315,128 @@ func (t *ImportTask) ImportStudio(studioJSON *jsonschema.Studio, pendingParent m // delete the entry from the map so that we know its not left over delete(pendingParent, studioJSON.Name) - return err + return nil } func (t *ImportTask) ImportMovies(ctx context.Context) { - tx := database.DB.MustBeginTx(ctx, nil) - qb := models.NewMovieQueryBuilder() + logger.Info("[movies] importing") - for i, mappingJSON := range t.Mappings.Movies { + for i, mappingJSON := range t.mappings.Movies { index := i + 1 - movieJSON, err := instance.JSON.getMovie(mappingJSON.Checksum) + movieJSON, err := t.json.getMovie(mappingJSON.Checksum) if err != nil { logger.Errorf("[movies] failed to read json: %s", err.Error()) continue } - if mappingJSON.Checksum == "" || mappingJSON.Name == "" || movieJSON == nil { - return + + logger.Progressf("[movies] %d of %d", index, len(t.mappings.Movies)) + + tx := database.DB.MustBeginTx(ctx, nil) + readerWriter := models.NewMovieReaderWriter(tx) + studioReaderWriter := models.NewStudioReaderWriter(tx) + + movieImporter := &movie.Importer{ + ReaderWriter: readerWriter, + StudioWriter: studioReaderWriter, + Input: *movieJSON, + MissingRefBehaviour: t.MissingRefBehaviour, } - logger.Progressf("[movies] %d of %d", index, len(t.Mappings.Movies)) - - // generate checksum from movie name rather than image - checksum := utils.MD5FromString(movieJSON.Name) - - // Process the base 64 encoded image string - var frontimageData []byte - var backimageData []byte - if len(movieJSON.FrontImage) > 0 { - _, frontimageData, err = utils.ProcessBase64Image(movieJSON.FrontImage) - if err != nil { - _ = tx.Rollback() - logger.Errorf("[movies] <%s> invalid front_image: %s", mappingJSON.Checksum, err.Error()) - return - } - } - if len(movieJSON.BackImage) > 0 { - _, backimageData, err = utils.ProcessBase64Image(movieJSON.BackImage) - if err != nil { - _ = tx.Rollback() - logger.Errorf("[movies] <%s> invalid back_image: %s", mappingJSON.Checksum, err.Error()) - return - } + if err := performImport(movieImporter, t.DuplicateBehaviour); err != nil { + tx.Rollback() + logger.Errorf("[movies] <%s> failed to import: %s", mappingJSON.Checksum, err.Error()) + continue } - // Populate a new movie from the input - newMovie := models.Movie{ - Checksum: checksum, - Name: sql.NullString{String: movieJSON.Name, Valid: true}, - Aliases: sql.NullString{String: movieJSON.Aliases, Valid: true}, - Date: models.SQLiteDate{String: movieJSON.Date, Valid: true}, - Director: sql.NullString{String: movieJSON.Director, Valid: true}, - Synopsis: sql.NullString{String: movieJSON.Synopsis, Valid: true}, - URL: sql.NullString{String: movieJSON.URL, Valid: true}, - CreatedAt: models.SQLiteTimestamp{Timestamp: t.getTimeFromJSONTime(movieJSON.CreatedAt)}, - UpdatedAt: models.SQLiteTimestamp{Timestamp: t.getTimeFromJSONTime(movieJSON.UpdatedAt)}, - } - - if movieJSON.Rating != 0 { - newMovie.Rating = sql.NullInt64{Int64: int64(movieJSON.Rating), Valid: true} - } - if movieJSON.Duration != 0 { - newMovie.Duration = sql.NullInt64{Int64: int64(movieJSON.Duration), Valid: true} - } - - // Populate the studio ID - if movieJSON.Studio != "" { - sqb := models.NewStudioQueryBuilder() - studio, err := sqb.FindByName(movieJSON.Studio, tx, false) - if err != nil { - logger.Warnf("[movies] error getting studio <%s>: %s", movieJSON.Studio, err.Error()) - } else if studio == nil { - logger.Warnf("[movies] studio <%s> does not exist", movieJSON.Studio) - } else { - newMovie.StudioID = sql.NullInt64{Int64: int64(studio.ID), Valid: true} - } - } - - createdMovie, err := qb.Create(newMovie, tx) - if err != nil { - _ = tx.Rollback() - logger.Errorf("[movies] <%s> failed to create: %s", mappingJSON.Checksum, err.Error()) - return - } - - // Add the movie images if set - if len(frontimageData) > 0 { - if err := qb.UpdateMovieImages(createdMovie.ID, frontimageData, backimageData, tx); err != nil { - _ = tx.Rollback() - logger.Errorf("[movies] <%s> error setting movie images: %s", mappingJSON.Checksum, err.Error()) - return - } + if err := tx.Commit(); err != nil { + tx.Rollback() + logger.Errorf("[movies] <%s> import failed to commit: %s", mappingJSON.Checksum, err.Error()) + continue } } - logger.Info("[movies] importing") - if err := tx.Commit(); err != nil { - logger.Errorf("[movies] import failed to commit: %s", err.Error()) - } logger.Info("[movies] import complete") } func (t *ImportTask) ImportGalleries(ctx context.Context) { - tx := database.DB.MustBeginTx(ctx, nil) - qb := models.NewGalleryQueryBuilder() - - for i, mappingJSON := range t.Mappings.Galleries { - index := i + 1 - if mappingJSON.Checksum == "" || mappingJSON.Path == "" { - return - } - - logger.Progressf("[galleries] %d of %d", index, len(t.Mappings.Galleries)) - - // Populate a new gallery from the input - currentTime := time.Now() - newGallery := models.Gallery{ - Checksum: mappingJSON.Checksum, - Path: mappingJSON.Path, - CreatedAt: models.SQLiteTimestamp{Timestamp: currentTime}, - UpdatedAt: models.SQLiteTimestamp{Timestamp: currentTime}, - } - - _, err := qb.Create(newGallery, tx) - if err != nil { - _ = tx.Rollback() - logger.Errorf("[galleries] <%s> failed to create: %s", mappingJSON.Checksum, err.Error()) - return - } - } - logger.Info("[galleries] importing") - if err := tx.Commit(); err != nil { - logger.Errorf("[galleries] import failed to commit: %s", err.Error()) + + for i, mappingJSON := range t.mappings.Galleries { + index := i + 1 + galleryJSON, err := t.json.getGallery(mappingJSON.Checksum) + if err != nil { + logger.Errorf("[galleries] failed to read json: %s", err.Error()) + continue + } + + logger.Progressf("[galleries] %d of %d", index, len(t.mappings.Galleries)) + + tx := database.DB.MustBeginTx(ctx, nil) + readerWriter := models.NewGalleryReaderWriter(tx) + tagWriter := models.NewTagReaderWriter(tx) + joinWriter := models.NewJoinReaderWriter(tx) + performerWriter := models.NewPerformerReaderWriter(tx) + studioWriter := models.NewStudioReaderWriter(tx) + + galleryImporter := &gallery.Importer{ + ReaderWriter: readerWriter, + PerformerWriter: performerWriter, + StudioWriter: studioWriter, + TagWriter: tagWriter, + JoinWriter: joinWriter, + Input: *galleryJSON, + MissingRefBehaviour: t.MissingRefBehaviour, + } + + if err := performImport(galleryImporter, t.DuplicateBehaviour); err != nil { + tx.Rollback() + logger.Errorf("[galleries] <%s> failed to import: %s", mappingJSON.Checksum, err.Error()) + continue + } + + if err := tx.Commit(); err != nil { + tx.Rollback() + logger.Errorf("[galleries] <%s> import failed to commit: %s", mappingJSON.Checksum, err.Error()) + continue + } } + logger.Info("[galleries] import complete") } func (t *ImportTask) ImportTags(ctx context.Context) { - tx := database.DB.MustBeginTx(ctx, nil) - qb := models.NewTagQueryBuilder() + logger.Info("[tags] importing") - for i, mappingJSON := range t.Mappings.Tags { + for i, mappingJSON := range t.mappings.Tags { index := i + 1 - tagJSON, err := instance.JSON.getTag(mappingJSON.Checksum) + tagJSON, err := t.json.getTag(mappingJSON.Checksum) if err != nil { logger.Errorf("[tags] failed to read json: %s", err.Error()) continue } - if mappingJSON.Checksum == "" || mappingJSON.Name == "" || tagJSON == nil { - return + + logger.Progressf("[tags] %d of %d", index, len(t.mappings.Tags)) + + tx := database.DB.MustBeginTx(ctx, nil) + readerWriter := models.NewTagReaderWriter(tx) + + tagImporter := &tag.Importer{ + ReaderWriter: readerWriter, + Input: *tagJSON, } - logger.Progressf("[tags] %d of %d", index, len(t.Mappings.Tags)) - - // Process the base 64 encoded image string - var imageData []byte - if len(tagJSON.Image) > 0 { - _, imageData, err = utils.ProcessBase64Image(tagJSON.Image) - if err != nil { - _ = tx.Rollback() - logger.Errorf("[tags] <%s> invalid image: %s", mappingJSON.Checksum, err.Error()) - return - } + if err := performImport(tagImporter, t.DuplicateBehaviour); err != nil { + tx.Rollback() + logger.Errorf("[tags] <%s> failed to import: %s", mappingJSON.Checksum, err.Error()) + continue } - // Populate a new tag from the input - newTag := models.Tag{ - Name: tagJSON.Name, - CreatedAt: models.SQLiteTimestamp{Timestamp: t.getTimeFromJSONTime(tagJSON.CreatedAt)}, - UpdatedAt: models.SQLiteTimestamp{Timestamp: t.getTimeFromJSONTime(tagJSON.UpdatedAt)}, - } - - createdTag, err := qb.Create(newTag, tx) - if err != nil { - _ = tx.Rollback() - logger.Errorf("[tags] <%s> failed to create: %s", mappingJSON.Checksum, err.Error()) - return - } - - // Add the tag image if set - if len(imageData) > 0 { - if err := qb.UpdateTagImage(createdTag.ID, imageData, tx); err != nil { - _ = tx.Rollback() - logger.Errorf("[tags] <%s> error setting tag image: %s", mappingJSON.Checksum, err.Error()) - return - } + if err := tx.Commit(); err != nil { + tx.Rollback() + logger.Errorf("[tags] <%s> import failed to commit: %s", mappingJSON.Checksum, err.Error()) } } - logger.Info("[tags] importing") - if err := tx.Commit(); err != nil { - logger.Errorf("[tags] import failed to commit: %s", err.Error()) - } logger.Info("[tags] import complete") } @@ -490,9 +446,9 @@ func (t *ImportTask) ImportScrapedItems(ctx context.Context) { sqb := models.NewStudioQueryBuilder() currentTime := time.Now() - for i, mappingJSON := range t.Scraped { + for i, mappingJSON := range t.scraped { index := i + 1 - logger.Progressf("[scraped sites] %d of %d", index, len(t.Mappings.Scenes)) + logger.Progressf("[scraped sites] %d of %d", index, len(t.mappings.Scenes)) newScrapedItem := models.ScrapedItem{ Title: sql.NullString{String: mappingJSON.Title, Valid: true}, @@ -533,21 +489,14 @@ func (t *ImportTask) ImportScrapedItems(ctx context.Context) { } func (t *ImportTask) ImportScenes(ctx context.Context) { - tx := database.DB.MustBeginTx(ctx, nil) - qb := models.NewSceneQueryBuilder() - jqb := models.NewJoinsQueryBuilder() + logger.Info("[scenes] importing") - for i, mappingJSON := range t.Mappings.Scenes { + for i, mappingJSON := range t.mappings.Scenes { index := i + 1 - if mappingJSON.Checksum == "" || mappingJSON.Path == "" { - _ = tx.Rollback() - logger.Warn("[scenes] scene mapping without checksum or path: ", mappingJSON) - return - } - logger.Progressf("[scenes] %d of %d", index, len(t.Mappings.Scenes)) + logger.Progressf("[scenes] %d of %d", index, len(t.mappings.Scenes)) - sceneJSON, err := instance.JSON.getScene(mappingJSON.Checksum) + sceneJSON, err := t.json.getScene(mappingJSON.Checksum) if err != nil { logger.Infof("[scenes] <%s> json parse failure: %s", mappingJSON.Checksum, err.Error()) continue @@ -555,247 +504,124 @@ func (t *ImportTask) ImportScenes(ctx context.Context) { sceneHash := mappingJSON.Checksum - newScene := models.Scene{ - Checksum: sql.NullString{String: sceneJSON.Checksum, Valid: sceneJSON.Checksum != ""}, - OSHash: sql.NullString{String: sceneJSON.OSHash, Valid: sceneJSON.OSHash != ""}, - Path: mappingJSON.Path, + tx := database.DB.MustBeginTx(ctx, nil) + readerWriter := models.NewSceneReaderWriter(tx) + tagWriter := models.NewTagReaderWriter(tx) + galleryWriter := models.NewGalleryReaderWriter(tx) + joinWriter := models.NewJoinReaderWriter(tx) + movieWriter := models.NewMovieReaderWriter(tx) + performerWriter := models.NewPerformerReaderWriter(tx) + studioWriter := models.NewStudioReaderWriter(tx) + markerWriter := models.NewSceneMarkerReaderWriter(tx) + + sceneImporter := &scene.Importer{ + ReaderWriter: readerWriter, + Input: *sceneJSON, + Path: mappingJSON.Path, + + FileNamingAlgorithm: t.fileNamingAlgorithm, + MissingRefBehaviour: t.MissingRefBehaviour, + + GalleryWriter: galleryWriter, + JoinWriter: joinWriter, + MovieWriter: movieWriter, + PerformerWriter: performerWriter, + StudioWriter: studioWriter, + TagWriter: tagWriter, } - // Process the base 64 encoded cover image string - var coverImageData []byte - if sceneJSON.Cover != "" { - _, coverImageData, err = utils.ProcessBase64Image(sceneJSON.Cover) - if err != nil { - logger.Warnf("[scenes] <%s> invalid cover image: %s", sceneHash, err.Error()) - } - if len(coverImageData) > 0 { - if err = SetSceneScreenshot(sceneHash, coverImageData); err != nil { - logger.Warnf("[scenes] <%s> failed to create cover image: %s", sceneHash, err.Error()) - } + if err := performImport(sceneImporter, t.DuplicateBehaviour); err != nil { + tx.Rollback() + logger.Errorf("[scenes] <%s> failed to import: %s", sceneHash, err.Error()) + continue + } - // write the cover image data after creating the scene + // import the scene markers + failedMarkers := false + for _, m := range sceneJSON.Markers { + markerImporter := &scene.MarkerImporter{ + SceneID: sceneImporter.ID, + Input: m, + MissingRefBehaviour: t.MissingRefBehaviour, + ReaderWriter: markerWriter, + JoinWriter: joinWriter, + TagWriter: tagWriter, + } + + if err := performImport(markerImporter, t.DuplicateBehaviour); err != nil { + failedMarkers = true + logger.Errorf("[scenes] <%s> failed to import markers: %s", sceneHash, err.Error()) + break } } - // Populate scene fields - if sceneJSON != nil { - if sceneJSON.Title != "" { - newScene.Title = sql.NullString{String: sceneJSON.Title, Valid: true} - } - if sceneJSON.Details != "" { - newScene.Details = sql.NullString{String: sceneJSON.Details, Valid: true} - } - if sceneJSON.URL != "" { - newScene.URL = sql.NullString{String: sceneJSON.URL, Valid: true} - } - if sceneJSON.Date != "" { - newScene.Date = models.SQLiteDate{String: sceneJSON.Date, Valid: true} - } - if sceneJSON.Rating != 0 { - newScene.Rating = sql.NullInt64{Int64: int64(sceneJSON.Rating), Valid: true} - } - - newScene.OCounter = sceneJSON.OCounter - newScene.CreatedAt = models.SQLiteTimestamp{Timestamp: t.getTimeFromJSONTime(sceneJSON.CreatedAt)} - newScene.UpdatedAt = models.SQLiteTimestamp{Timestamp: t.getTimeFromJSONTime(sceneJSON.UpdatedAt)} - - if sceneJSON.File != nil { - if sceneJSON.File.Size != "" { - newScene.Size = sql.NullString{String: sceneJSON.File.Size, Valid: true} - } - if sceneJSON.File.Duration != "" { - duration, _ := strconv.ParseFloat(sceneJSON.File.Duration, 64) - newScene.Duration = sql.NullFloat64{Float64: duration, Valid: true} - } - if sceneJSON.File.VideoCodec != "" { - newScene.VideoCodec = sql.NullString{String: sceneJSON.File.VideoCodec, Valid: true} - } - if sceneJSON.File.AudioCodec != "" { - newScene.AudioCodec = sql.NullString{String: sceneJSON.File.AudioCodec, Valid: true} - } - if sceneJSON.File.Format != "" { - newScene.Format = sql.NullString{String: sceneJSON.File.Format, Valid: true} - } - if sceneJSON.File.Width != 0 { - newScene.Width = sql.NullInt64{Int64: int64(sceneJSON.File.Width), Valid: true} - } - if sceneJSON.File.Height != 0 { - newScene.Height = sql.NullInt64{Int64: int64(sceneJSON.File.Height), Valid: true} - } - if sceneJSON.File.Framerate != "" { - framerate, _ := strconv.ParseFloat(sceneJSON.File.Framerate, 64) - newScene.Framerate = sql.NullFloat64{Float64: framerate, Valid: true} - } - if sceneJSON.File.Bitrate != 0 { - newScene.Bitrate = sql.NullInt64{Int64: int64(sceneJSON.File.Bitrate), Valid: true} - } - } else { - // TODO: Get FFMPEG data? - } + if failedMarkers { + tx.Rollback() + continue } - // Populate the studio ID - if sceneJSON.Studio != "" { - sqb := models.NewStudioQueryBuilder() - studio, err := sqb.FindByName(sceneJSON.Studio, tx, false) - if err != nil { - logger.Warnf("[scenes] error getting studio <%s>: %s", sceneJSON.Studio, err.Error()) - } else if studio == nil { - logger.Warnf("[scenes] studio <%s> does not exist", sceneJSON.Studio) - } else { - newScene.StudioID = sql.NullInt64{Int64: int64(studio.ID), Valid: true} - } - } - - // Create the scene in the DB - scene, err := qb.Create(newScene, tx) - if err != nil { - _ = tx.Rollback() - logger.Errorf("[scenes] <%s> failed to create: %s", sceneHash, err.Error()) - return - } - if scene.ID == 0 { - _ = tx.Rollback() - logger.Errorf("[scenes] <%s> invalid id after scene creation", sceneHash) - return - } - - // Add the scene cover if set - if len(coverImageData) > 0 { - if err := qb.UpdateSceneCover(scene.ID, coverImageData, tx); err != nil { - _ = tx.Rollback() - logger.Errorf("[scenes] <%s> error setting scene cover: %s", sceneHash, err.Error()) - return - } - } - - // Relate the scene to the gallery - if sceneJSON.Gallery != "" { - gqb := models.NewGalleryQueryBuilder() - gallery, err := gqb.FindByChecksum(sceneJSON.Gallery, tx) - if err != nil { - logger.Warnf("[scenes] gallery <%s> does not exist: %s", sceneJSON.Gallery, err.Error()) - } else { - gallery.SceneID = sql.NullInt64{Int64: int64(scene.ID), Valid: true} - _, err := gqb.Update(*gallery, tx) - if err != nil { - logger.Errorf("[scenes] <%s> failed to update gallery: %s", sceneHash, err.Error()) - } - } - } - - // Relate the scene to the performers - if len(sceneJSON.Performers) > 0 { - performers, err := t.getPerformers(sceneJSON.Performers, tx) - if err != nil { - logger.Warnf("[scenes] <%s> failed to fetch performers: %s", sceneHash, err.Error()) - } else { - var performerJoins []models.PerformersScenes - for _, performer := range performers { - join := models.PerformersScenes{ - PerformerID: performer.ID, - SceneID: scene.ID, - } - performerJoins = append(performerJoins, join) - } - if err := jqb.CreatePerformersScenes(performerJoins, tx); err != nil { - logger.Errorf("[scenes] <%s> failed to associate performers: %s", sceneHash, err.Error()) - } - } - } - - // Relate the scene to the movies - if len(sceneJSON.Movies) > 0 { - moviesScenes, err := t.getMoviesScenes(sceneJSON.Movies, scene.ID, tx) - if err != nil { - logger.Warnf("[scenes] <%s> failed to fetch movies: %s", sceneHash, err.Error()) - } else { - if err := jqb.CreateMoviesScenes(moviesScenes, tx); err != nil { - logger.Errorf("[scenes] <%s> failed to associate movies: %s", sceneHash, err.Error()) - } - } - } - - // Relate the scene to the tags - if len(sceneJSON.Tags) > 0 { - tags, err := t.getTags(sceneHash, sceneJSON.Tags, tx) - if err != nil { - logger.Warnf("[scenes] <%s> failed to fetch tags: %s", sceneHash, err.Error()) - } else { - var tagJoins []models.ScenesTags - for _, tag := range tags { - join := models.ScenesTags{ - SceneID: scene.ID, - TagID: tag.ID, - } - tagJoins = append(tagJoins, join) - } - if err := jqb.CreateScenesTags(tagJoins, tx); err != nil { - logger.Errorf("[scenes] <%s> failed to associate tags: %s", sceneHash, err.Error()) - } - } - } - - // Relate the scene to the scene markers - if len(sceneJSON.Markers) > 0 { - smqb := models.NewSceneMarkerQueryBuilder() - tqb := models.NewTagQueryBuilder() - for _, marker := range sceneJSON.Markers { - seconds, _ := strconv.ParseFloat(marker.Seconds, 64) - newSceneMarker := models.SceneMarker{ - Title: marker.Title, - Seconds: seconds, - SceneID: sql.NullInt64{Int64: int64(scene.ID), Valid: true}, - CreatedAt: models.SQLiteTimestamp{Timestamp: t.getTimeFromJSONTime(marker.CreatedAt)}, - UpdatedAt: models.SQLiteTimestamp{Timestamp: t.getTimeFromJSONTime(marker.UpdatedAt)}, - } - - primaryTag, err := tqb.FindByName(marker.PrimaryTag, tx, false) - if err != nil { - logger.Errorf("[scenes] <%s> failed to find primary tag for marker: %s", sceneHash, err.Error()) - } else { - newSceneMarker.PrimaryTagID = primaryTag.ID - } - - // Create the scene marker in the DB - sceneMarker, err := smqb.Create(newSceneMarker, tx) - if err != nil { - logger.Warnf("[scenes] <%s> failed to create scene marker: %s", sceneHash, err.Error()) - continue - } - if sceneMarker.ID == 0 { - logger.Warnf("[scenes] <%s> invalid scene marker id after scene marker creation", sceneHash) - continue - } - - // Get the scene marker tags and create the joins - tags, err := t.getTags(sceneHash, marker.Tags, tx) - if err != nil { - logger.Warnf("[scenes] <%s> failed to fetch scene marker tags: %s", sceneHash, err.Error()) - } else { - var tagJoins []models.SceneMarkersTags - for _, tag := range tags { - join := models.SceneMarkersTags{ - SceneMarkerID: sceneMarker.ID, - TagID: tag.ID, - } - tagJoins = append(tagJoins, join) - } - if err := jqb.CreateSceneMarkersTags(tagJoins, tx); err != nil { - logger.Errorf("[scenes] <%s> failed to associate scene marker tags: %s", sceneHash, err.Error()) - } - } - } + if err := tx.Commit(); err != nil { + tx.Rollback() + logger.Errorf("[scenes] <%s> import failed to commit: %s", sceneHash, err.Error()) } } - logger.Info("[scenes] importing") - if err := tx.Commit(); err != nil { - logger.Errorf("[scenes] import failed to commit: %s", err.Error()) - } logger.Info("[scenes] import complete") } +func (t *ImportTask) ImportImages(ctx context.Context) { + logger.Info("[images] importing") + + for i, mappingJSON := range t.mappings.Images { + index := i + 1 + + logger.Progressf("[images] %d of %d", index, len(t.mappings.Images)) + + imageJSON, err := t.json.getImage(mappingJSON.Checksum) + if err != nil { + logger.Infof("[images] <%s> json parse failure: %s", mappingJSON.Checksum, err.Error()) + continue + } + + imageHash := mappingJSON.Checksum + + tx := database.DB.MustBeginTx(ctx, nil) + readerWriter := models.NewImageReaderWriter(tx) + tagWriter := models.NewTagReaderWriter(tx) + galleryWriter := models.NewGalleryReaderWriter(tx) + joinWriter := models.NewJoinReaderWriter(tx) + performerWriter := models.NewPerformerReaderWriter(tx) + studioWriter := models.NewStudioReaderWriter(tx) + + imageImporter := &image.Importer{ + ReaderWriter: readerWriter, + Input: *imageJSON, + Path: mappingJSON.Path, + + MissingRefBehaviour: t.MissingRefBehaviour, + + GalleryWriter: galleryWriter, + JoinWriter: joinWriter, + PerformerWriter: performerWriter, + StudioWriter: studioWriter, + TagWriter: tagWriter, + } + + if err := performImport(imageImporter, t.DuplicateBehaviour); err != nil { + tx.Rollback() + logger.Errorf("[images] <%s> failed to import: %s", imageHash, err.Error()) + continue + } + + if err := tx.Commit(); err != nil { + tx.Rollback() + logger.Errorf("[images] <%s> import failed to commit: %s", imageHash, err.Error()) + } + } + + logger.Info("[images] import complete") +} + func (t *ImportTask) getPerformers(names []string, tx *sqlx.Tx) ([]*models.Performer, error) { pqb := models.NewPerformerQueryBuilder() performers, err := pqb.FindByNames(names, tx, false) diff --git a/pkg/manager/task_scan.go b/pkg/manager/task_scan.go index 1474b6b96..c1e7899a2 100644 --- a/pkg/manager/task_scan.go +++ b/pkg/manager/task_scan.go @@ -1,17 +1,24 @@ package manager import ( + "archive/zip" "context" "database/sql" + "fmt" + "os" "path/filepath" "strconv" "strings" "sync" "time" + "github.com/jmoiron/sqlx" + "github.com/stashapp/stash/pkg/database" "github.com/stashapp/stash/pkg/ffmpeg" + "github.com/stashapp/stash/pkg/image" "github.com/stashapp/stash/pkg/logger" + "github.com/stashapp/stash/pkg/manager/config" "github.com/stashapp/stash/pkg/models" "github.com/stashapp/stash/pkg/utils" ) @@ -21,13 +28,17 @@ type ScanTask struct { UseFileMetadata bool calculateMD5 bool fileNamingAlgorithm models.HashAlgorithm + + zipGallery *models.Gallery } func (t *ScanTask) Start(wg *sync.WaitGroup) { if isGallery(t.FilePath) { t.scanGallery() - } else { + } else if isVideo(t.FilePath) { t.scanScene() + } else if isImage(t.FilePath) { + t.scanImage() } wg.Done() @@ -37,8 +48,78 @@ func (t *ScanTask) scanGallery() { qb := models.NewGalleryQueryBuilder() gallery, _ := qb.FindByPath(t.FilePath) + fileModTime, err := t.getFileModTime() + if err != nil { + logger.Error(err.Error()) + return + } + if gallery != nil { // We already have this item in the database, keep going + + // if file mod time is not set, set it now + // we will also need to rescan the zip contents + updateModTime := false + if !gallery.FileModTime.Valid { + updateModTime = true + t.updateFileModTime(gallery.ID, fileModTime, &qb) + + // update our copy of the gallery + var err error + gallery, err = qb.Find(gallery.ID, nil) + if err != nil { + logger.Error(err.Error()) + return + } + } + + // if the mod time of the zip file is different than that of the associated + // gallery, then recalculate the checksum + modified := t.isFileModified(fileModTime, gallery.FileModTime) + if modified { + logger.Infof("%s has been updated: rescanning", t.FilePath) + + // update the checksum and the modification time + checksum, err := t.calculateChecksum() + if err != nil { + logger.Error(err.Error()) + return + } + + currentTime := time.Now() + galleryPartial := models.GalleryPartial{ + ID: gallery.ID, + Checksum: &checksum, + FileModTime: &models.NullSQLiteTimestamp{ + Timestamp: fileModTime, + Valid: true, + }, + UpdatedAt: &models.SQLiteTimestamp{Timestamp: currentTime}, + } + + err = database.WithTxn(func(tx *sqlx.Tx) error { + _, err := qb.UpdatePartial(galleryPartial, tx) + return err + }) + if err != nil { + logger.Error(err.Error()) + return + } + } + + // scan the zip files if the gallery has no images + iqb := models.NewImageQueryBuilder() + images, err := iqb.CountByGalleryID(gallery.ID) + if err != nil { + logger.Errorf("error getting images for zip gallery %s: %s", t.FilePath, err.Error()) + } + + if images == 0 || modified || updateModTime { + t.scanZipImages(gallery) + } else { + // in case thumbnails have been deleted, regenerate them + t.regenerateZipImages(gallery) + } return } @@ -47,10 +128,6 @@ func (t *ScanTask) scanGallery() { return } - ok, err := utils.IsZipFileUncompressed(t.FilePath) - if err == nil && !ok { - logger.Warnf("%s is using above store (0) level compression.", t.FilePath) - } checksum, err := t.calculateChecksum() if err != nil { logger.Error(err.Error()) @@ -61,38 +138,102 @@ func (t *ScanTask) scanGallery() { tx := database.DB.MustBeginTx(ctx, nil) gallery, _ = qb.FindByChecksum(checksum, tx) if gallery != nil { - exists, _ := utils.FileExists(gallery.Path) + exists, _ := utils.FileExists(gallery.Path.String) if exists { - logger.Infof("%s already exists. Duplicate of %s ", t.FilePath, gallery.Path) + logger.Infof("%s already exists. Duplicate of %s ", t.FilePath, gallery.Path.String) } else { - logger.Infof("%s already exists. Updating path...", t.FilePath) - gallery.Path = t.FilePath - _, err = qb.Update(*gallery, tx) + gallery.Path = sql.NullString{ + String: t.FilePath, + Valid: true, + } + gallery, err = qb.Update(*gallery, tx) } } else { currentTime := time.Now() newGallery := models.Gallery{ - Checksum: checksum, - Path: t.FilePath, + Checksum: checksum, + Zip: true, + Path: sql.NullString{ + String: t.FilePath, + Valid: true, + }, + FileModTime: models.NullSQLiteTimestamp{ + Timestamp: fileModTime, + Valid: true, + }, CreatedAt: models.SQLiteTimestamp{Timestamp: currentTime}, UpdatedAt: models.SQLiteTimestamp{Timestamp: currentTime}, } // don't create gallery if it has no images - if newGallery.CountFiles() > 0 { + if countImagesInZip(t.FilePath) > 0 { + // only warn when creating the gallery + ok, err := utils.IsZipFileUncompressed(t.FilePath) + if err == nil && !ok { + logger.Warnf("%s is using above store (0) level compression.", t.FilePath) + } + logger.Infof("%s doesn't exist. Creating new item...", t.FilePath) - _, err = qb.Create(newGallery, tx) + gallery, err = qb.Create(newGallery, tx) } } if err != nil { logger.Error(err.Error()) - _ = tx.Rollback() - } else if err := tx.Commit(); err != nil { - logger.Error(err.Error()) + tx.Rollback() + return } + + err = tx.Commit() + if err != nil { + logger.Error(err.Error()) + return + } + + // if the gallery has no associated images, then scan the zip for images + if gallery != nil { + t.scanZipImages(gallery) + } +} + +type fileModTimeUpdater interface { + UpdateFileModTime(id int, modTime models.NullSQLiteTimestamp, tx *sqlx.Tx) error +} + +func (t *ScanTask) updateFileModTime(id int, fileModTime time.Time, updater fileModTimeUpdater) error { + logger.Infof("setting file modification time on %s", t.FilePath) + + err := database.WithTxn(func(tx *sqlx.Tx) error { + return updater.UpdateFileModTime(id, models.NullSQLiteTimestamp{ + Timestamp: fileModTime, + Valid: true, + }, tx) + }) + + if err != nil { + return err + } + + return nil +} + +func (t *ScanTask) getFileModTime() (time.Time, error) { + fi, err := os.Stat(t.FilePath) + if err != nil { + return time.Time{}, fmt.Errorf("error performing stat on %s: %s", t.FilePath, err.Error()) + } + + ret := fi.ModTime() + // truncate to seconds, since we don't store beyond that in the database + ret = ret.Truncate(time.Second) + + return ret, nil +} + +func (t *ScanTask) isFileModified(fileModTime time.Time, modTime models.NullSQLiteTimestamp) bool { + return !modTime.Timestamp.Equal(fileModTime) } // associates a gallery to a scene with the same basename @@ -107,19 +248,24 @@ func (t *ScanTask) associateGallery(wg *sync.WaitGroup) { return } - if !gallery.SceneID.Valid { // gallery has no SceneID + // gallery has no SceneID + if !gallery.SceneID.Valid { basename := strings.TrimSuffix(t.FilePath, filepath.Ext(t.FilePath)) var relatedFiles []string - for _, ext := range extensionsToScan { // make a list of media files that can be related to the gallery + vExt := config.GetVideoExtensions() + // make a list of media files that can be related to the gallery + for _, ext := range vExt { related := basename + "." + ext - if !isGallery(related) { //exclude gallery extensions from the related files + // exclude gallery extensions from the related files + if !isGallery(related) { relatedFiles = append(relatedFiles, related) } } for _, scenePath := range relatedFiles { qbScene := models.NewSceneQueryBuilder() scene, _ := qbScene.FindByPath(scenePath) - if scene != nil { // found related Scene + // found related Scene + if scene != nil { logger.Infof("associate: Gallery %s is related to scene: %d", t.FilePath, scene.ID) gallery.SceneID.Int64 = int64(scene.ID) @@ -136,12 +282,11 @@ func (t *ScanTask) associateGallery(wg *sync.WaitGroup) { logger.Error(err.Error()) } - break // since a gallery can have only one related scene + // since a gallery can have only one related scene // only first found is associated + break } - } - } wg.Done() } @@ -149,7 +294,38 @@ func (t *ScanTask) associateGallery(wg *sync.WaitGroup) { func (t *ScanTask) scanScene() { qb := models.NewSceneQueryBuilder() scene, _ := qb.FindByPath(t.FilePath) + + fileModTime, err := t.getFileModTime() + if err != nil { + logger.Error(err.Error()) + return + } + if scene != nil { + // if file mod time is not set, set it now + if !scene.FileModTime.Valid { + t.updateFileModTime(scene.ID, fileModTime, &qb) + + // update our copy of the scene + var err error + scene, err = qb.Find(scene.ID) + if err != nil { + logger.Error(err.Error()) + return + } + } + + // if the mod time of the file is different than that of the associated + // scene, then recalculate the checksum and regenerate the thumbnail + modified := t.isFileModified(fileModTime, scene.FileModTime) + if modified { + scene, err = t.rescanScene(scene, fileModTime) + if err != nil { + logger.Error(err.Error()) + return + } + } + // We already have this item in the database // check for thumbnails,screenshots t.makeScreenshots(nil, scene.GetHash(t.fileNamingAlgorithm)) @@ -251,7 +427,7 @@ func (t *ScanTask) scanScene() { var checksum string - logger.Infof("%s not found. Calculating oshash...", t.FilePath) + logger.Infof("%s not found. Calculating oshash...", t.FilePath) oshash, err := utils.OSHashFromFilePath(t.FilePath) if err != nil { logger.Error(err.Error()) @@ -289,9 +465,9 @@ func (t *ScanTask) scanScene() { if scene != nil { exists, _ := utils.FileExists(scene.Path) if exists { - logger.Infof("%s already exists. Duplicate of %s ", t.FilePath, scene.Path) + logger.Infof("%s already exists. Duplicate of %s", t.FilePath, scene.Path) } else { - logger.Infof("%s already exists. Updating path...", t.FilePath) + logger.Infof("%s already exists. Updating path...", t.FilePath) scenePartial := models.ScenePartial{ ID: scene.ID, Path: &t.FilePath, @@ -299,7 +475,7 @@ func (t *ScanTask) scanScene() { _, err = qb.Update(scenePartial, tx) } } else { - logger.Infof("%s doesn't exist. Creating new item...", t.FilePath) + logger.Infof("%s doesn't exist. Creating new item...", t.FilePath) currentTime := time.Now() newScene := models.Scene{ Checksum: sql.NullString{String: checksum, Valid: checksum != ""}, @@ -315,8 +491,12 @@ func (t *ScanTask) scanScene() { Framerate: sql.NullFloat64{Float64: videoFile.FrameRate, Valid: true}, Bitrate: sql.NullInt64{Int64: videoFile.Bitrate, Valid: true}, Size: sql.NullString{String: strconv.Itoa(int(videoFile.Size)), Valid: true}, - CreatedAt: models.SQLiteTimestamp{Timestamp: currentTime}, - UpdatedAt: models.SQLiteTimestamp{Timestamp: currentTime}, + FileModTime: models.NullSQLiteTimestamp{ + Timestamp: fileModTime, + Valid: true, + }, + CreatedAt: models.SQLiteTimestamp{Timestamp: currentTime}, + UpdatedAt: models.SQLiteTimestamp{Timestamp: currentTime}, } if t.UseFileMetadata { @@ -334,6 +514,77 @@ func (t *ScanTask) scanScene() { } } +func (t *ScanTask) rescanScene(scene *models.Scene, fileModTime time.Time) (*models.Scene, error) { + logger.Infof("%s has been updated: rescanning", t.FilePath) + + // update the oshash/checksum and the modification time + logger.Infof("Calculating oshash for existing file %s ...", t.FilePath) + oshash, err := utils.OSHashFromFilePath(t.FilePath) + if err != nil { + return nil, err + } + + var checksum *sql.NullString + if t.calculateMD5 { + cs, err := t.calculateChecksum() + if err != nil { + return nil, err + } + + checksum = &sql.NullString{ + String: cs, + Valid: true, + } + } + + // regenerate the file details as well + videoFile, err := ffmpeg.NewVideoFile(instance.FFProbePath, t.FilePath) + if err != nil { + return nil, err + } + container := ffmpeg.MatchContainer(videoFile.Container, t.FilePath) + + currentTime := time.Now() + scenePartial := models.ScenePartial{ + ID: scene.ID, + Checksum: checksum, + OSHash: &sql.NullString{ + String: oshash, + Valid: true, + }, + Duration: &sql.NullFloat64{Float64: videoFile.Duration, Valid: true}, + VideoCodec: &sql.NullString{String: videoFile.VideoCodec, Valid: true}, + AudioCodec: &sql.NullString{String: videoFile.AudioCodec, Valid: true}, + Format: &sql.NullString{String: string(container), Valid: true}, + Width: &sql.NullInt64{Int64: int64(videoFile.Width), Valid: true}, + Height: &sql.NullInt64{Int64: int64(videoFile.Height), Valid: true}, + Framerate: &sql.NullFloat64{Float64: videoFile.FrameRate, Valid: true}, + Bitrate: &sql.NullInt64{Int64: videoFile.Bitrate, Valid: true}, + Size: &sql.NullString{String: strconv.Itoa(int(videoFile.Size)), Valid: true}, + FileModTime: &models.NullSQLiteTimestamp{ + Timestamp: fileModTime, + Valid: true, + }, + UpdatedAt: &models.SQLiteTimestamp{Timestamp: currentTime}, + } + + var ret *models.Scene + err = database.WithTxn(func(tx *sqlx.Tx) error { + qb := models.NewSceneQueryBuilder() + var txnErr error + ret, txnErr = qb.Update(scenePartial, tx) + return txnErr + }) + if err != nil { + logger.Error(err.Error()) + return nil, err + } + + // leave the generated files as is - the scene file may have been moved + // elsewhere + + return ret, nil +} func (t *ScanTask) makeScreenshots(probeResult *ffmpeg.VideoFile, checksum string) { thumbPath := instance.Paths.Scene.GetThumbnailScreenshotPath(checksum) normalPath := instance.Paths.Scene.GetScreenshotPath(checksum) @@ -342,7 +593,6 @@ func (t *ScanTask) makeScreenshots(probeResult *ffmpeg.VideoFile, checksum strin normalExists, _ := utils.FileExists(normalPath) if thumbExists && normalExists { - logger.Debug("Screenshots already exist for this path... skipping") return } @@ -370,6 +620,275 @@ func (t *ScanTask) makeScreenshots(probeResult *ffmpeg.VideoFile, checksum strin } } +func (t *ScanTask) scanZipImages(zipGallery *models.Gallery) { + err := walkGalleryZip(zipGallery.Path.String, func(file *zip.File) error { + // copy this task and change the filename + subTask := *t + + // filepath is the zip file and the internal file name, separated by a null byte + subTask.FilePath = image.ZipFilename(zipGallery.Path.String, file.Name) + subTask.zipGallery = zipGallery + + // run the subtask and wait for it to complete + var wg sync.WaitGroup + wg.Add(1) + subTask.Start(&wg) + return nil + }) + if err != nil { + logger.Warnf("failed to scan zip file images for %s: %s", zipGallery.Path.String, err.Error()) + } +} + +func (t *ScanTask) regenerateZipImages(zipGallery *models.Gallery) { + iqb := models.NewImageQueryBuilder() + + images, err := iqb.FindByGalleryID(zipGallery.ID) + if err != nil { + logger.Warnf("failed to find gallery images: %s", err.Error()) + return + } + + for _, img := range images { + t.generateThumbnail(img) + } +} + +func (t *ScanTask) scanImage() { + qb := models.NewImageQueryBuilder() + i, _ := qb.FindByPath(t.FilePath) + + fileModTime, err := image.GetFileModTime(t.FilePath) + if err != nil { + logger.Error(err.Error()) + return + } + + if i != nil { + // if file mod time is not set, set it now + if !i.FileModTime.Valid { + t.updateFileModTime(i.ID, fileModTime, &qb) + + // update our copy of the gallery + var err error + i, err = qb.Find(i.ID) + if err != nil { + logger.Error(err.Error()) + return + } + } + + // if the mod time of the file is different than that of the associated + // image, then recalculate the checksum and regenerate the thumbnail + modified := t.isFileModified(fileModTime, i.FileModTime) + if modified { + i, err = t.rescanImage(i, fileModTime) + if err != nil { + logger.Error(err.Error()) + return + } + } + + // We already have this item in the database + // check for thumbnails + t.generateThumbnail(i) + + return + } + + // Ignore directories. + if isDir, _ := utils.DirExists(t.FilePath); isDir { + return + } + + var checksum string + + logger.Infof("%s not found. Calculating checksum...", t.FilePath) + checksum, err = t.calculateImageChecksum() + if err != nil { + logger.Errorf("error calculating checksum for %s: %s", t.FilePath, err.Error()) + return + } + + // check for scene by checksum and oshash - MD5 should be + // redundant, but check both + i, _ = qb.FindByChecksum(checksum) + + ctx := context.TODO() + tx := database.DB.MustBeginTx(ctx, nil) + if i != nil { + exists := image.FileExists(i.Path) + if exists { + logger.Infof("%s already exists. Duplicate of %s ", image.PathDisplayName(t.FilePath), image.PathDisplayName(i.Path)) + } else { + logger.Infof("%s already exists. Updating path...", image.PathDisplayName(t.FilePath)) + imagePartial := models.ImagePartial{ + ID: i.ID, + Path: &t.FilePath, + } + _, err = qb.Update(imagePartial, tx) + } + } else { + logger.Infof("%s doesn't exist. Creating new item...", image.PathDisplayName(t.FilePath)) + currentTime := time.Now() + newImage := models.Image{ + Checksum: checksum, + Path: t.FilePath, + FileModTime: models.NullSQLiteTimestamp{ + Timestamp: fileModTime, + Valid: true, + }, + CreatedAt: models.SQLiteTimestamp{Timestamp: currentTime}, + UpdatedAt: models.SQLiteTimestamp{Timestamp: currentTime}, + } + err = image.SetFileDetails(&newImage) + if err == nil { + i, err = qb.Create(newImage, tx) + } + } + + if err == nil { + jqb := models.NewJoinsQueryBuilder() + if t.zipGallery != nil { + // associate with gallery + _, err = jqb.AddImageGallery(i.ID, t.zipGallery.ID, tx) + } else if config.GetCreateGalleriesFromFolders() { + // create gallery from folder or associate with existing gallery + logger.Infof("Associating image %s with folder gallery", i.Path) + err = t.associateImageWithFolderGallery(i.ID, tx) + } + } + + if err != nil { + logger.Error(err.Error()) + _ = tx.Rollback() + return + } else if err := tx.Commit(); err != nil { + logger.Error(err.Error()) + return + } + + t.generateThumbnail(i) +} + +func (t *ScanTask) rescanImage(i *models.Image, fileModTime time.Time) (*models.Image, error) { + logger.Infof("%s has been updated: rescanning", t.FilePath) + + oldChecksum := i.Checksum + + // update the checksum and the modification time + checksum, err := t.calculateImageChecksum() + if err != nil { + return nil, err + } + + // regenerate the file details as well + fileDetails, err := image.GetFileDetails(t.FilePath) + if err != nil { + return nil, err + } + + currentTime := time.Now() + imagePartial := models.ImagePartial{ + ID: i.ID, + Checksum: &checksum, + Width: &fileDetails.Width, + Height: &fileDetails.Height, + Size: &fileDetails.Size, + FileModTime: &models.NullSQLiteTimestamp{ + Timestamp: fileModTime, + Valid: true, + }, + UpdatedAt: &models.SQLiteTimestamp{Timestamp: currentTime}, + } + + var ret *models.Image + err = database.WithTxn(func(tx *sqlx.Tx) error { + qb := models.NewImageQueryBuilder() + var txnErr error + ret, txnErr = qb.Update(imagePartial, tx) + return txnErr + }) + if err != nil { + return nil, err + } + + // remove the old thumbnail if the checksum changed - we'll regenerate it + if oldChecksum != checksum { + err = os.Remove(GetInstance().Paths.Generated.GetThumbnailPath(oldChecksum, models.DefaultGthumbWidth)) // remove cache dir of gallery + if err != nil { + logger.Errorf("Error deleting thumbnail image: %s", err) + } + } + + return ret, nil +} + +func (t *ScanTask) associateImageWithFolderGallery(imageID int, tx *sqlx.Tx) error { + // find a gallery with the path specified + path := filepath.Dir(t.FilePath) + gqb := models.NewGalleryQueryBuilder() + jqb := models.NewJoinsQueryBuilder() + g, err := gqb.FindByPath(path) + if err != nil { + return err + } + + if g == nil { + checksum := utils.MD5FromString(path) + + // create the gallery + currentTime := time.Now() + + newGallery := models.Gallery{ + Checksum: checksum, + Path: sql.NullString{ + String: path, + Valid: true, + }, + CreatedAt: models.SQLiteTimestamp{Timestamp: currentTime}, + UpdatedAt: models.SQLiteTimestamp{Timestamp: currentTime}, + } + + logger.Infof("Creating gallery for folder %s", path) + g, err = gqb.Create(newGallery, tx) + if err != nil { + return err + } + } + + // associate image with gallery + _, err = jqb.AddImageGallery(imageID, g.ID, tx) + return err +} + +func (t *ScanTask) generateThumbnail(i *models.Image) { + thumbPath := GetInstance().Paths.Generated.GetThumbnailPath(i.Checksum, models.DefaultGthumbWidth) + exists, _ := utils.FileExists(thumbPath) + if exists { + return + } + + srcImage, err := image.GetSourceImage(i) + if err != nil { + logger.Errorf("error reading image %s: %s", i.Path, err.Error()) + return + } + + if image.ThumbnailNeeded(srcImage, models.DefaultGthumbWidth) { + data, err := image.GetThumbnail(srcImage, models.DefaultGthumbWidth) + if err != nil { + logger.Errorf("error getting thumbnail for image %s: %s", i.Path, err.Error()) + return + } + + err = utils.WriteFile(thumbPath, data) + if err != nil { + logger.Errorf("error writing thumbnail for image %s: %s", i.Path, err) + } + } +} + func (t *ScanTask) calculateChecksum() (string, error) { logger.Infof("Calculating checksum for %s...", t.FilePath) checksum, err := utils.MD5FromFilePath(t.FilePath) @@ -380,19 +899,72 @@ func (t *ScanTask) calculateChecksum() (string, error) { return checksum, nil } +func (t *ScanTask) calculateImageChecksum() (string, error) { + logger.Infof("Calculating checksum for %s...", image.PathDisplayName(t.FilePath)) + // uses image.CalculateMD5 to read files in zips + checksum, err := image.CalculateMD5(t.FilePath) + if err != nil { + return "", err + } + logger.Debugf("Checksum calculated: %s", checksum) + return checksum, nil +} + func (t *ScanTask) doesPathExist() bool { - if filepath.Ext(t.FilePath) == ".zip" { + vidExt := config.GetVideoExtensions() + imgExt := config.GetImageExtensions() + gExt := config.GetGalleryExtensions() + + if matchExtension(t.FilePath, gExt) { qb := models.NewGalleryQueryBuilder() gallery, _ := qb.FindByPath(t.FilePath) if gallery != nil { return true } - } else { + } else if matchExtension(t.FilePath, vidExt) { qb := models.NewSceneQueryBuilder() scene, _ := qb.FindByPath(t.FilePath) if scene != nil { return true } + } else if matchExtension(t.FilePath, imgExt) { + qb := models.NewImageQueryBuilder() + i, _ := qb.FindByPath(t.FilePath) + if i != nil { + return true + } } + return false } + +func walkFilesToScan(s *models.StashConfig, f filepath.WalkFunc) error { + vidExt := config.GetVideoExtensions() + imgExt := config.GetImageExtensions() + gExt := config.GetGalleryExtensions() + excludeVidRegex := generateRegexps(config.GetExcludes()) + excludeImgRegex := generateRegexps(config.GetImageExcludes()) + + return utils.SymWalk(s.Path, func(path string, info os.FileInfo, err error) error { + if err != nil { + logger.Warnf("error scanning %s: %s", path, err.Error()) + return nil + } + + if info.IsDir() { + return nil + } + + if !s.ExcludeVideo && matchExtension(path, vidExt) && !matchFileRegex(path, excludeVidRegex) { + return f(path, info, err) + } + + if !s.ExcludeImage { + if (matchExtension(path, imgExt) || matchExtension(path, gExt)) && !matchFileRegex(path, excludeImgRegex) { + return f(path, info, err) + } + } + + return nil + }) +} diff --git a/pkg/models/gallery.go b/pkg/models/gallery.go new file mode 100644 index 000000000..0aa5f3cc3 --- /dev/null +++ b/pkg/models/gallery.go @@ -0,0 +1,74 @@ +package models + +import ( + "github.com/jmoiron/sqlx" +) + +type GalleryReader interface { + // Find(id int) (*Gallery, error) + FindMany(ids []int) ([]*Gallery, error) + FindByChecksum(checksum string) (*Gallery, error) + FindByPath(path string) (*Gallery, error) + FindBySceneID(sceneID int) (*Gallery, error) + FindByImageID(imageID int) ([]*Gallery, error) + // ValidGalleriesForScenePath(scenePath string) ([]*Gallery, error) + // Count() (int, error) + All() ([]*Gallery, error) + // Query(galleryFilter *GalleryFilterType, findFilter *FindFilterType) ([]*Gallery, int) +} + +type GalleryWriter interface { + Create(newGallery Gallery) (*Gallery, error) + Update(updatedGallery Gallery) (*Gallery, error) + // Destroy(id int) error + // ClearGalleryId(sceneID int) error +} + +type GalleryReaderWriter interface { + GalleryReader + GalleryWriter +} + +func NewGalleryReaderWriter(tx *sqlx.Tx) GalleryReaderWriter { + return &galleryReaderWriter{ + tx: tx, + qb: NewGalleryQueryBuilder(), + } +} + +type galleryReaderWriter struct { + tx *sqlx.Tx + qb GalleryQueryBuilder +} + +func (t *galleryReaderWriter) FindMany(ids []int) ([]*Gallery, error) { + return t.qb.FindMany(ids) +} + +func (t *galleryReaderWriter) FindByChecksum(checksum string) (*Gallery, error) { + return t.qb.FindByChecksum(checksum, t.tx) +} + +func (t *galleryReaderWriter) All() ([]*Gallery, error) { + return t.qb.All() +} + +func (t *galleryReaderWriter) FindByPath(path string) (*Gallery, error) { + return t.qb.FindByPath(path) +} + +func (t *galleryReaderWriter) FindBySceneID(sceneID int) (*Gallery, error) { + return t.qb.FindBySceneID(sceneID, t.tx) +} + +func (t *galleryReaderWriter) FindByImageID(imageID int) ([]*Gallery, error) { + return t.qb.FindByImageID(imageID, t.tx) +} + +func (t *galleryReaderWriter) Create(newGallery Gallery) (*Gallery, error) { + return t.qb.Create(newGallery, t.tx) +} + +func (t *galleryReaderWriter) Update(updatedGallery Gallery) (*Gallery, error) { + return t.qb.Update(updatedGallery, t.tx) +} diff --git a/pkg/models/image.go b/pkg/models/image.go new file mode 100644 index 000000000..5ea398bed --- /dev/null +++ b/pkg/models/image.go @@ -0,0 +1,77 @@ +package models + +import ( + "github.com/jmoiron/sqlx" +) + +type ImageReader interface { + // Find(id int) (*Image, error) + FindMany(ids []int) ([]*Image, error) + FindByChecksum(checksum string) (*Image, error) + FindByGalleryID(galleryID int) ([]*Image, error) + // FindByPath(path string) (*Image, error) + // FindByPerformerID(performerID int) ([]*Image, error) + // CountByPerformerID(performerID int) (int, error) + // FindByStudioID(studioID int) ([]*Image, error) + // Count() (int, error) + // SizeCount() (string, error) + // CountByStudioID(studioID int) (int, error) + // CountByTagID(tagID int) (int, error) + All() ([]*Image, error) + // Query(imageFilter *ImageFilterType, findFilter *FindFilterType) ([]*Image, int) +} + +type ImageWriter interface { + Create(newImage Image) (*Image, error) + Update(updatedImage ImagePartial) (*Image, error) + UpdateFull(updatedImage Image) (*Image, error) + // IncrementOCounter(id int) (int, error) + // DecrementOCounter(id int) (int, error) + // ResetOCounter(id int) (int, error) + // Destroy(id string) error +} + +type ImageReaderWriter interface { + ImageReader + ImageWriter +} + +func NewImageReaderWriter(tx *sqlx.Tx) ImageReaderWriter { + return &imageReaderWriter{ + tx: tx, + qb: NewImageQueryBuilder(), + } +} + +type imageReaderWriter struct { + tx *sqlx.Tx + qb ImageQueryBuilder +} + +func (t *imageReaderWriter) FindMany(ids []int) ([]*Image, error) { + return t.qb.FindMany(ids) +} + +func (t *imageReaderWriter) FindByChecksum(checksum string) (*Image, error) { + return t.qb.FindByChecksum(checksum) +} + +func (t *imageReaderWriter) FindByGalleryID(galleryID int) ([]*Image, error) { + return t.qb.FindByGalleryID(galleryID) +} + +func (t *imageReaderWriter) All() ([]*Image, error) { + return t.qb.All() +} + +func (t *imageReaderWriter) Create(newImage Image) (*Image, error) { + return t.qb.Create(newImage, t.tx) +} + +func (t *imageReaderWriter) Update(updatedImage ImagePartial) (*Image, error) { + return t.qb.Update(updatedImage, t.tx) +} + +func (t *imageReaderWriter) UpdateFull(updatedImage Image) (*Image, error) { + return t.qb.UpdateFull(updatedImage, t.tx) +} diff --git a/pkg/models/join.go b/pkg/models/join.go new file mode 100644 index 000000000..3b0d259ba --- /dev/null +++ b/pkg/models/join.go @@ -0,0 +1,101 @@ +package models + +import ( + "github.com/jmoiron/sqlx" +) + +type JoinReader interface { + // GetScenePerformers(sceneID int) ([]PerformersScenes, error) + GetSceneMovies(sceneID int) ([]MoviesScenes, error) + // GetSceneTags(sceneID int) ([]ScenesTags, error) +} + +type JoinWriter interface { + CreatePerformersScenes(newJoins []PerformersScenes) error + // AddPerformerScene(sceneID int, performerID int) (bool, error) + UpdatePerformersScenes(sceneID int, updatedJoins []PerformersScenes) error + // DestroyPerformersScenes(sceneID int) error + CreateMoviesScenes(newJoins []MoviesScenes) error + // AddMoviesScene(sceneID int, movieID int, sceneIdx *int) (bool, error) + UpdateMoviesScenes(sceneID int, updatedJoins []MoviesScenes) error + // DestroyMoviesScenes(sceneID int) error + // CreateScenesTags(newJoins []ScenesTags) error + UpdateScenesTags(sceneID int, updatedJoins []ScenesTags) error + // AddSceneTag(sceneID int, tagID int) (bool, error) + // DestroyScenesTags(sceneID int) error + // CreateSceneMarkersTags(newJoins []SceneMarkersTags) error + UpdateSceneMarkersTags(sceneMarkerID int, updatedJoins []SceneMarkersTags) error + // DestroySceneMarkersTags(sceneMarkerID int, updatedJoins []SceneMarkersTags) error + // DestroyScenesGalleries(sceneID int) error + // DestroyScenesMarkers(sceneID int) error + UpdatePerformersGalleries(galleryID int, updatedJoins []PerformersGalleries) error + UpdateGalleriesTags(galleryID int, updatedJoins []GalleriesTags) error + UpdateGalleriesImages(imageID int, updatedJoins []GalleriesImages) error + UpdatePerformersImages(imageID int, updatedJoins []PerformersImages) error + UpdateImagesTags(imageID int, updatedJoins []ImagesTags) error +} + +type JoinReaderWriter interface { + JoinReader + JoinWriter +} + +func NewJoinReaderWriter(tx *sqlx.Tx) JoinReaderWriter { + return &joinReaderWriter{ + tx: tx, + qb: NewJoinsQueryBuilder(), + } +} + +type joinReaderWriter struct { + tx *sqlx.Tx + qb JoinsQueryBuilder +} + +func (t *joinReaderWriter) GetSceneMovies(sceneID int) ([]MoviesScenes, error) { + return t.qb.GetSceneMovies(sceneID, t.tx) +} + +func (t *joinReaderWriter) CreatePerformersScenes(newJoins []PerformersScenes) error { + return t.qb.CreatePerformersScenes(newJoins, t.tx) +} + +func (t *joinReaderWriter) UpdatePerformersScenes(sceneID int, updatedJoins []PerformersScenes) error { + return t.qb.UpdatePerformersScenes(sceneID, updatedJoins, t.tx) +} + +func (t *joinReaderWriter) CreateMoviesScenes(newJoins []MoviesScenes) error { + return t.qb.CreateMoviesScenes(newJoins, t.tx) +} + +func (t *joinReaderWriter) UpdateMoviesScenes(sceneID int, updatedJoins []MoviesScenes) error { + return t.qb.UpdateMoviesScenes(sceneID, updatedJoins, t.tx) +} + +func (t *joinReaderWriter) UpdateScenesTags(sceneID int, updatedJoins []ScenesTags) error { + return t.qb.UpdateScenesTags(sceneID, updatedJoins, t.tx) +} + +func (t *joinReaderWriter) UpdateSceneMarkersTags(sceneMarkerID int, updatedJoins []SceneMarkersTags) error { + return t.qb.UpdateSceneMarkersTags(sceneMarkerID, updatedJoins, t.tx) +} + +func (t *joinReaderWriter) UpdatePerformersGalleries(galleryID int, updatedJoins []PerformersGalleries) error { + return t.qb.UpdatePerformersGalleries(galleryID, updatedJoins, t.tx) +} + +func (t *joinReaderWriter) UpdateGalleriesTags(galleryID int, updatedJoins []GalleriesTags) error { + return t.qb.UpdateGalleriesTags(galleryID, updatedJoins, t.tx) +} + +func (t *joinReaderWriter) UpdateGalleriesImages(imageID int, updatedJoins []GalleriesImages) error { + return t.qb.UpdateGalleriesImages(imageID, updatedJoins, t.tx) +} + +func (t *joinReaderWriter) UpdatePerformersImages(imageID int, updatedJoins []PerformersImages) error { + return t.qb.UpdatePerformersImages(imageID, updatedJoins, t.tx) +} + +func (t *joinReaderWriter) UpdateImagesTags(imageID int, updatedJoins []ImagesTags) error { + return t.qb.UpdateImagesTags(imageID, updatedJoins, t.tx) +} diff --git a/pkg/models/json_time.go b/pkg/models/json_time.go index af3fe3308..d1a400d3e 100644 --- a/pkg/models/json_time.go +++ b/pkg/models/json_time.go @@ -2,24 +2,34 @@ package models import ( "fmt" - "github.com/stashapp/stash/pkg/utils" "strings" "time" + + "github.com/stashapp/stash/pkg/logger" + "github.com/stashapp/stash/pkg/utils" ) +var currentLocation = time.Now().Location() + type JSONTime struct { time.Time } -func (jt *JSONTime) UnmarshalJSON(b []byte) (err error) { +func (jt *JSONTime) UnmarshalJSON(b []byte) error { s := strings.Trim(string(b), "\"") if s == "null" { jt.Time = time.Time{} - return + return nil } + // #731 - returning an error here causes the entire JSON parse to fail for ffprobe. + // Changing so that it logs a warning instead. + var err error jt.Time, err = utils.ParseDateStringAsTime(s) - return + if err != nil { + logger.Warnf("error unmarshalling JSONTime: %s", err.Error()) + } + return nil } func (jt *JSONTime) MarshalJSON() ([]byte, error) { @@ -28,3 +38,19 @@ func (jt *JSONTime) MarshalJSON() ([]byte, error) { } return []byte(fmt.Sprintf("\"%s\"", jt.Time.Format(time.RFC3339))), nil } + +func (jt JSONTime) GetTime() time.Time { + if currentLocation != nil { + if jt.IsZero() { + return time.Now().In(currentLocation) + } else { + return jt.Time.In(currentLocation) + } + } else { + if jt.IsZero() { + return time.Now() + } else { + return jt.Time + } + } +} diff --git a/pkg/models/mocks/GalleryReaderWriter.go b/pkg/models/mocks/GalleryReaderWriter.go new file mode 100644 index 000000000..dcc37fc47 --- /dev/null +++ b/pkg/models/mocks/GalleryReaderWriter.go @@ -0,0 +1,197 @@ +// Code generated by mockery v0.0.0-dev. DO NOT EDIT. + +package mocks + +import ( + models "github.com/stashapp/stash/pkg/models" + mock "github.com/stretchr/testify/mock" +) + +// GalleryReaderWriter is an autogenerated mock type for the GalleryReaderWriter type +type GalleryReaderWriter struct { + mock.Mock +} + +// All provides a mock function with given fields: +func (_m *GalleryReaderWriter) All() ([]*models.Gallery, error) { + ret := _m.Called() + + var r0 []*models.Gallery + if rf, ok := ret.Get(0).(func() []*models.Gallery); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*models.Gallery) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Create provides a mock function with given fields: newGallery +func (_m *GalleryReaderWriter) Create(newGallery models.Gallery) (*models.Gallery, error) { + ret := _m.Called(newGallery) + + var r0 *models.Gallery + if rf, ok := ret.Get(0).(func(models.Gallery) *models.Gallery); ok { + r0 = rf(newGallery) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Gallery) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(models.Gallery) error); ok { + r1 = rf(newGallery) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindByChecksum provides a mock function with given fields: checksum +func (_m *GalleryReaderWriter) FindByChecksum(checksum string) (*models.Gallery, error) { + ret := _m.Called(checksum) + + var r0 *models.Gallery + if rf, ok := ret.Get(0).(func(string) *models.Gallery); ok { + r0 = rf(checksum) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Gallery) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(checksum) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindByImageID provides a mock function with given fields: imageID +func (_m *GalleryReaderWriter) FindByImageID(imageID int) ([]*models.Gallery, error) { + ret := _m.Called(imageID) + + var r0 []*models.Gallery + if rf, ok := ret.Get(0).(func(int) []*models.Gallery); ok { + r0 = rf(imageID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*models.Gallery) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(int) error); ok { + r1 = rf(imageID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindByPath provides a mock function with given fields: path +func (_m *GalleryReaderWriter) FindByPath(path string) (*models.Gallery, error) { + ret := _m.Called(path) + + var r0 *models.Gallery + if rf, ok := ret.Get(0).(func(string) *models.Gallery); ok { + r0 = rf(path) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Gallery) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(path) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindBySceneID provides a mock function with given fields: sceneID +func (_m *GalleryReaderWriter) FindBySceneID(sceneID int) (*models.Gallery, error) { + ret := _m.Called(sceneID) + + var r0 *models.Gallery + if rf, ok := ret.Get(0).(func(int) *models.Gallery); ok { + r0 = rf(sceneID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Gallery) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(int) error); ok { + r1 = rf(sceneID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindMany provides a mock function with given fields: ids +func (_m *GalleryReaderWriter) FindMany(ids []int) ([]*models.Gallery, error) { + ret := _m.Called(ids) + + var r0 []*models.Gallery + if rf, ok := ret.Get(0).(func([]int) []*models.Gallery); ok { + r0 = rf(ids) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*models.Gallery) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func([]int) error); ok { + r1 = rf(ids) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Update provides a mock function with given fields: updatedGallery +func (_m *GalleryReaderWriter) Update(updatedGallery models.Gallery) (*models.Gallery, error) { + ret := _m.Called(updatedGallery) + + var r0 *models.Gallery + if rf, ok := ret.Get(0).(func(models.Gallery) *models.Gallery); ok { + r0 = rf(updatedGallery) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Gallery) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(models.Gallery) error); ok { + r1 = rf(updatedGallery) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} diff --git a/pkg/models/mocks/ImageReaderWriter.go b/pkg/models/mocks/ImageReaderWriter.go new file mode 100644 index 000000000..7010075cb --- /dev/null +++ b/pkg/models/mocks/ImageReaderWriter.go @@ -0,0 +1,174 @@ +// Code generated by mockery v0.0.0-dev. DO NOT EDIT. + +package mocks + +import ( + models "github.com/stashapp/stash/pkg/models" + mock "github.com/stretchr/testify/mock" +) + +// ImageReaderWriter is an autogenerated mock type for the ImageReaderWriter type +type ImageReaderWriter struct { + mock.Mock +} + +// All provides a mock function with given fields: +func (_m *ImageReaderWriter) All() ([]*models.Image, error) { + ret := _m.Called() + + var r0 []*models.Image + if rf, ok := ret.Get(0).(func() []*models.Image); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*models.Image) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Create provides a mock function with given fields: newImage +func (_m *ImageReaderWriter) Create(newImage models.Image) (*models.Image, error) { + ret := _m.Called(newImage) + + var r0 *models.Image + if rf, ok := ret.Get(0).(func(models.Image) *models.Image); ok { + r0 = rf(newImage) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Image) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(models.Image) error); ok { + r1 = rf(newImage) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindByChecksum provides a mock function with given fields: checksum +func (_m *ImageReaderWriter) FindByChecksum(checksum string) (*models.Image, error) { + ret := _m.Called(checksum) + + var r0 *models.Image + if rf, ok := ret.Get(0).(func(string) *models.Image); ok { + r0 = rf(checksum) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Image) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(checksum) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindByGalleryID provides a mock function with given fields: galleryID +func (_m *ImageReaderWriter) FindByGalleryID(galleryID int) ([]*models.Image, error) { + ret := _m.Called(galleryID) + + var r0 []*models.Image + if rf, ok := ret.Get(0).(func(int) []*models.Image); ok { + r0 = rf(galleryID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*models.Image) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(int) error); ok { + r1 = rf(galleryID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindMany provides a mock function with given fields: ids +func (_m *ImageReaderWriter) FindMany(ids []int) ([]*models.Image, error) { + ret := _m.Called(ids) + + var r0 []*models.Image + if rf, ok := ret.Get(0).(func([]int) []*models.Image); ok { + r0 = rf(ids) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*models.Image) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func([]int) error); ok { + r1 = rf(ids) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Update provides a mock function with given fields: updatedImage +func (_m *ImageReaderWriter) Update(updatedImage models.ImagePartial) (*models.Image, error) { + ret := _m.Called(updatedImage) + + var r0 *models.Image + if rf, ok := ret.Get(0).(func(models.ImagePartial) *models.Image); ok { + r0 = rf(updatedImage) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Image) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(models.ImagePartial) error); ok { + r1 = rf(updatedImage) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdateFull provides a mock function with given fields: updatedImage +func (_m *ImageReaderWriter) UpdateFull(updatedImage models.Image) (*models.Image, error) { + ret := _m.Called(updatedImage) + + var r0 *models.Image + if rf, ok := ret.Get(0).(func(models.Image) *models.Image); ok { + r0 = rf(updatedImage) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Image) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(models.Image) error); ok { + r1 = rf(updatedImage) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} diff --git a/pkg/models/mocks/JoinReaderWriter.go b/pkg/models/mocks/JoinReaderWriter.go new file mode 100644 index 000000000..6fc20057e --- /dev/null +++ b/pkg/models/mocks/JoinReaderWriter.go @@ -0,0 +1,190 @@ +// Code generated by mockery v0.0.0-dev. DO NOT EDIT. + +package mocks + +import ( + models "github.com/stashapp/stash/pkg/models" + mock "github.com/stretchr/testify/mock" +) + +// JoinReaderWriter is an autogenerated mock type for the JoinReaderWriter type +type JoinReaderWriter struct { + mock.Mock +} + +// CreateMoviesScenes provides a mock function with given fields: newJoins +func (_m *JoinReaderWriter) CreateMoviesScenes(newJoins []models.MoviesScenes) error { + ret := _m.Called(newJoins) + + var r0 error + if rf, ok := ret.Get(0).(func([]models.MoviesScenes) error); ok { + r0 = rf(newJoins) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// CreatePerformersScenes provides a mock function with given fields: newJoins +func (_m *JoinReaderWriter) CreatePerformersScenes(newJoins []models.PerformersScenes) error { + ret := _m.Called(newJoins) + + var r0 error + if rf, ok := ret.Get(0).(func([]models.PerformersScenes) error); ok { + r0 = rf(newJoins) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// GetSceneMovies provides a mock function with given fields: sceneID +func (_m *JoinReaderWriter) GetSceneMovies(sceneID int) ([]models.MoviesScenes, error) { + ret := _m.Called(sceneID) + + var r0 []models.MoviesScenes + if rf, ok := ret.Get(0).(func(int) []models.MoviesScenes); ok { + r0 = rf(sceneID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]models.MoviesScenes) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(int) error); ok { + r1 = rf(sceneID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdateGalleriesImages provides a mock function with given fields: imageID, updatedJoins +func (_m *JoinReaderWriter) UpdateGalleriesImages(imageID int, updatedJoins []models.GalleriesImages) error { + ret := _m.Called(imageID, updatedJoins) + + var r0 error + if rf, ok := ret.Get(0).(func(int, []models.GalleriesImages) error); ok { + r0 = rf(imageID, updatedJoins) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// UpdateGalleriesTags provides a mock function with given fields: galleryID, updatedJoins +func (_m *JoinReaderWriter) UpdateGalleriesTags(galleryID int, updatedJoins []models.GalleriesTags) error { + ret := _m.Called(galleryID, updatedJoins) + + var r0 error + if rf, ok := ret.Get(0).(func(int, []models.GalleriesTags) error); ok { + r0 = rf(galleryID, updatedJoins) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// UpdateImagesTags provides a mock function with given fields: imageID, updatedJoins +func (_m *JoinReaderWriter) UpdateImagesTags(imageID int, updatedJoins []models.ImagesTags) error { + ret := _m.Called(imageID, updatedJoins) + + var r0 error + if rf, ok := ret.Get(0).(func(int, []models.ImagesTags) error); ok { + r0 = rf(imageID, updatedJoins) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// UpdateMoviesScenes provides a mock function with given fields: sceneID, updatedJoins +func (_m *JoinReaderWriter) UpdateMoviesScenes(sceneID int, updatedJoins []models.MoviesScenes) error { + ret := _m.Called(sceneID, updatedJoins) + + var r0 error + if rf, ok := ret.Get(0).(func(int, []models.MoviesScenes) error); ok { + r0 = rf(sceneID, updatedJoins) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// UpdatePerformersGalleries provides a mock function with given fields: galleryID, updatedJoins +func (_m *JoinReaderWriter) UpdatePerformersGalleries(galleryID int, updatedJoins []models.PerformersGalleries) error { + ret := _m.Called(galleryID, updatedJoins) + + var r0 error + if rf, ok := ret.Get(0).(func(int, []models.PerformersGalleries) error); ok { + r0 = rf(galleryID, updatedJoins) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// UpdatePerformersImages provides a mock function with given fields: imageID, updatedJoins +func (_m *JoinReaderWriter) UpdatePerformersImages(imageID int, updatedJoins []models.PerformersImages) error { + ret := _m.Called(imageID, updatedJoins) + + var r0 error + if rf, ok := ret.Get(0).(func(int, []models.PerformersImages) error); ok { + r0 = rf(imageID, updatedJoins) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// UpdatePerformersScenes provides a mock function with given fields: sceneID, updatedJoins +func (_m *JoinReaderWriter) UpdatePerformersScenes(sceneID int, updatedJoins []models.PerformersScenes) error { + ret := _m.Called(sceneID, updatedJoins) + + var r0 error + if rf, ok := ret.Get(0).(func(int, []models.PerformersScenes) error); ok { + r0 = rf(sceneID, updatedJoins) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// UpdateSceneMarkersTags provides a mock function with given fields: sceneMarkerID, updatedJoins +func (_m *JoinReaderWriter) UpdateSceneMarkersTags(sceneMarkerID int, updatedJoins []models.SceneMarkersTags) error { + ret := _m.Called(sceneMarkerID, updatedJoins) + + var r0 error + if rf, ok := ret.Get(0).(func(int, []models.SceneMarkersTags) error); ok { + r0 = rf(sceneMarkerID, updatedJoins) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// UpdateScenesTags provides a mock function with given fields: sceneID, updatedJoins +func (_m *JoinReaderWriter) UpdateScenesTags(sceneID int, updatedJoins []models.ScenesTags) error { + ret := _m.Called(sceneID, updatedJoins) + + var r0 error + if rf, ok := ret.Get(0).(func(int, []models.ScenesTags) error); ok { + r0 = rf(sceneID, updatedJoins) + } else { + r0 = ret.Error(0) + } + + return r0 +} diff --git a/pkg/models/mocks/MovieReaderWriter.go b/pkg/models/mocks/MovieReaderWriter.go new file mode 100644 index 000000000..5521f25e4 --- /dev/null +++ b/pkg/models/mocks/MovieReaderWriter.go @@ -0,0 +1,257 @@ +// Code generated by mockery v0.0.0-dev. DO NOT EDIT. + +package mocks + +import ( + models "github.com/stashapp/stash/pkg/models" + mock "github.com/stretchr/testify/mock" +) + +// MovieReaderWriter is an autogenerated mock type for the MovieReaderWriter type +type MovieReaderWriter struct { + mock.Mock +} + +// All provides a mock function with given fields: +func (_m *MovieReaderWriter) All() ([]*models.Movie, error) { + ret := _m.Called() + + var r0 []*models.Movie + if rf, ok := ret.Get(0).(func() []*models.Movie); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*models.Movie) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Create provides a mock function with given fields: newMovie +func (_m *MovieReaderWriter) Create(newMovie models.Movie) (*models.Movie, error) { + ret := _m.Called(newMovie) + + var r0 *models.Movie + if rf, ok := ret.Get(0).(func(models.Movie) *models.Movie); ok { + r0 = rf(newMovie) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Movie) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(models.Movie) error); ok { + r1 = rf(newMovie) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Find provides a mock function with given fields: id +func (_m *MovieReaderWriter) Find(id int) (*models.Movie, error) { + ret := _m.Called(id) + + var r0 *models.Movie + if rf, ok := ret.Get(0).(func(int) *models.Movie); ok { + r0 = rf(id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Movie) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(int) error); ok { + r1 = rf(id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindByName provides a mock function with given fields: name, nocase +func (_m *MovieReaderWriter) FindByName(name string, nocase bool) (*models.Movie, error) { + ret := _m.Called(name, nocase) + + var r0 *models.Movie + if rf, ok := ret.Get(0).(func(string, bool) *models.Movie); ok { + r0 = rf(name, nocase) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Movie) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(string, bool) error); ok { + r1 = rf(name, nocase) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindByNames provides a mock function with given fields: names, nocase +func (_m *MovieReaderWriter) FindByNames(names []string, nocase bool) ([]*models.Movie, error) { + ret := _m.Called(names, nocase) + + var r0 []*models.Movie + if rf, ok := ret.Get(0).(func([]string, bool) []*models.Movie); ok { + r0 = rf(names, nocase) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*models.Movie) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func([]string, bool) error); ok { + r1 = rf(names, nocase) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindMany provides a mock function with given fields: ids +func (_m *MovieReaderWriter) FindMany(ids []int) ([]*models.Movie, error) { + ret := _m.Called(ids) + + var r0 []*models.Movie + if rf, ok := ret.Get(0).(func([]int) []*models.Movie); ok { + r0 = rf(ids) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*models.Movie) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func([]int) error); ok { + r1 = rf(ids) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetBackImage provides a mock function with given fields: movieID +func (_m *MovieReaderWriter) GetBackImage(movieID int) ([]byte, error) { + ret := _m.Called(movieID) + + var r0 []byte + if rf, ok := ret.Get(0).(func(int) []byte); ok { + r0 = rf(movieID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(int) error); ok { + r1 = rf(movieID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetFrontImage provides a mock function with given fields: movieID +func (_m *MovieReaderWriter) GetFrontImage(movieID int) ([]byte, error) { + ret := _m.Called(movieID) + + var r0 []byte + if rf, ok := ret.Get(0).(func(int) []byte); ok { + r0 = rf(movieID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(int) error); ok { + r1 = rf(movieID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Update provides a mock function with given fields: updatedMovie +func (_m *MovieReaderWriter) Update(updatedMovie models.MoviePartial) (*models.Movie, error) { + ret := _m.Called(updatedMovie) + + var r0 *models.Movie + if rf, ok := ret.Get(0).(func(models.MoviePartial) *models.Movie); ok { + r0 = rf(updatedMovie) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Movie) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(models.MoviePartial) error); ok { + r1 = rf(updatedMovie) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdateFull provides a mock function with given fields: updatedMovie +func (_m *MovieReaderWriter) UpdateFull(updatedMovie models.Movie) (*models.Movie, error) { + ret := _m.Called(updatedMovie) + + var r0 *models.Movie + if rf, ok := ret.Get(0).(func(models.Movie) *models.Movie); ok { + r0 = rf(updatedMovie) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Movie) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(models.Movie) error); ok { + r1 = rf(updatedMovie) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdateMovieImages provides a mock function with given fields: movieID, frontImage, backImage +func (_m *MovieReaderWriter) UpdateMovieImages(movieID int, frontImage []byte, backImage []byte) error { + ret := _m.Called(movieID, frontImage, backImage) + + var r0 error + if rf, ok := ret.Get(0).(func(int, []byte, []byte) error); ok { + r0 = rf(movieID, frontImage, backImage) + } else { + r0 = ret.Error(0) + } + + return r0 +} diff --git a/pkg/models/mocks/PerformerReaderWriter.go b/pkg/models/mocks/PerformerReaderWriter.go new file mode 100644 index 000000000..908529b2a --- /dev/null +++ b/pkg/models/mocks/PerformerReaderWriter.go @@ -0,0 +1,257 @@ +// Code generated by mockery v0.0.0-dev. DO NOT EDIT. + +package mocks + +import ( + models "github.com/stashapp/stash/pkg/models" + mock "github.com/stretchr/testify/mock" +) + +// PerformerReaderWriter is an autogenerated mock type for the PerformerReaderWriter type +type PerformerReaderWriter struct { + mock.Mock +} + +// All provides a mock function with given fields: +func (_m *PerformerReaderWriter) All() ([]*models.Performer, error) { + ret := _m.Called() + + var r0 []*models.Performer + if rf, ok := ret.Get(0).(func() []*models.Performer); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*models.Performer) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Create provides a mock function with given fields: newPerformer +func (_m *PerformerReaderWriter) Create(newPerformer models.Performer) (*models.Performer, error) { + ret := _m.Called(newPerformer) + + var r0 *models.Performer + if rf, ok := ret.Get(0).(func(models.Performer) *models.Performer); ok { + r0 = rf(newPerformer) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Performer) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(models.Performer) error); ok { + r1 = rf(newPerformer) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindByGalleryID provides a mock function with given fields: galleryID +func (_m *PerformerReaderWriter) FindByGalleryID(galleryID int) ([]*models.Performer, error) { + ret := _m.Called(galleryID) + + var r0 []*models.Performer + if rf, ok := ret.Get(0).(func(int) []*models.Performer); ok { + r0 = rf(galleryID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*models.Performer) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(int) error); ok { + r1 = rf(galleryID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindByImageID provides a mock function with given fields: imageID +func (_m *PerformerReaderWriter) FindByImageID(imageID int) ([]*models.Performer, error) { + ret := _m.Called(imageID) + + var r0 []*models.Performer + if rf, ok := ret.Get(0).(func(int) []*models.Performer); ok { + r0 = rf(imageID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*models.Performer) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(int) error); ok { + r1 = rf(imageID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindByNames provides a mock function with given fields: names, nocase +func (_m *PerformerReaderWriter) FindByNames(names []string, nocase bool) ([]*models.Performer, error) { + ret := _m.Called(names, nocase) + + var r0 []*models.Performer + if rf, ok := ret.Get(0).(func([]string, bool) []*models.Performer); ok { + r0 = rf(names, nocase) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*models.Performer) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func([]string, bool) error); ok { + r1 = rf(names, nocase) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindBySceneID provides a mock function with given fields: sceneID +func (_m *PerformerReaderWriter) FindBySceneID(sceneID int) ([]*models.Performer, error) { + ret := _m.Called(sceneID) + + var r0 []*models.Performer + if rf, ok := ret.Get(0).(func(int) []*models.Performer); ok { + r0 = rf(sceneID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*models.Performer) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(int) error); ok { + r1 = rf(sceneID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindMany provides a mock function with given fields: ids +func (_m *PerformerReaderWriter) FindMany(ids []int) ([]*models.Performer, error) { + ret := _m.Called(ids) + + var r0 []*models.Performer + if rf, ok := ret.Get(0).(func([]int) []*models.Performer); ok { + r0 = rf(ids) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*models.Performer) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func([]int) error); ok { + r1 = rf(ids) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindNamesBySceneID provides a mock function with given fields: sceneID +func (_m *PerformerReaderWriter) FindNamesBySceneID(sceneID int) ([]*models.Performer, error) { + ret := _m.Called(sceneID) + + var r0 []*models.Performer + if rf, ok := ret.Get(0).(func(int) []*models.Performer); ok { + r0 = rf(sceneID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*models.Performer) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(int) error); ok { + r1 = rf(sceneID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetPerformerImage provides a mock function with given fields: performerID +func (_m *PerformerReaderWriter) GetPerformerImage(performerID int) ([]byte, error) { + ret := _m.Called(performerID) + + var r0 []byte + if rf, ok := ret.Get(0).(func(int) []byte); ok { + r0 = rf(performerID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(int) error); ok { + r1 = rf(performerID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Update provides a mock function with given fields: updatedPerformer +func (_m *PerformerReaderWriter) Update(updatedPerformer models.Performer) (*models.Performer, error) { + ret := _m.Called(updatedPerformer) + + var r0 *models.Performer + if rf, ok := ret.Get(0).(func(models.Performer) *models.Performer); ok { + r0 = rf(updatedPerformer) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Performer) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(models.Performer) error); ok { + r1 = rf(updatedPerformer) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdatePerformerImage provides a mock function with given fields: performerID, image +func (_m *PerformerReaderWriter) UpdatePerformerImage(performerID int, image []byte) error { + ret := _m.Called(performerID, image) + + var r0 error + if rf, ok := ret.Get(0).(func(int, []byte) error); ok { + r0 = rf(performerID, image) + } else { + r0 = ret.Error(0) + } + + return r0 +} diff --git a/pkg/models/mocks/SceneMarkerReaderWriter.go b/pkg/models/mocks/SceneMarkerReaderWriter.go new file mode 100644 index 000000000..df1b4f937 --- /dev/null +++ b/pkg/models/mocks/SceneMarkerReaderWriter.go @@ -0,0 +1,82 @@ +// Code generated by mockery v0.0.0-dev. DO NOT EDIT. + +package mocks + +import ( + models "github.com/stashapp/stash/pkg/models" + mock "github.com/stretchr/testify/mock" +) + +// SceneMarkerReaderWriter is an autogenerated mock type for the SceneMarkerReaderWriter type +type SceneMarkerReaderWriter struct { + mock.Mock +} + +// Create provides a mock function with given fields: newSceneMarker +func (_m *SceneMarkerReaderWriter) Create(newSceneMarker models.SceneMarker) (*models.SceneMarker, error) { + ret := _m.Called(newSceneMarker) + + var r0 *models.SceneMarker + if rf, ok := ret.Get(0).(func(models.SceneMarker) *models.SceneMarker); ok { + r0 = rf(newSceneMarker) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.SceneMarker) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(models.SceneMarker) error); ok { + r1 = rf(newSceneMarker) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindBySceneID provides a mock function with given fields: sceneID +func (_m *SceneMarkerReaderWriter) FindBySceneID(sceneID int) ([]*models.SceneMarker, error) { + ret := _m.Called(sceneID) + + var r0 []*models.SceneMarker + if rf, ok := ret.Get(0).(func(int) []*models.SceneMarker); ok { + r0 = rf(sceneID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*models.SceneMarker) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(int) error); ok { + r1 = rf(sceneID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Update provides a mock function with given fields: updatedSceneMarker +func (_m *SceneMarkerReaderWriter) Update(updatedSceneMarker models.SceneMarker) (*models.SceneMarker, error) { + ret := _m.Called(updatedSceneMarker) + + var r0 *models.SceneMarker + if rf, ok := ret.Get(0).(func(models.SceneMarker) *models.SceneMarker); ok { + r0 = rf(updatedSceneMarker) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.SceneMarker) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(models.SceneMarker) error); ok { + r1 = rf(updatedSceneMarker) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} diff --git a/pkg/models/mocks/SceneReaderWriter.go b/pkg/models/mocks/SceneReaderWriter.go new file mode 100644 index 000000000..a88acc723 --- /dev/null +++ b/pkg/models/mocks/SceneReaderWriter.go @@ -0,0 +1,234 @@ +// Code generated by mockery v0.0.0-dev. DO NOT EDIT. + +package mocks + +import ( + models "github.com/stashapp/stash/pkg/models" + mock "github.com/stretchr/testify/mock" +) + +// SceneReaderWriter is an autogenerated mock type for the SceneReaderWriter type +type SceneReaderWriter struct { + mock.Mock +} + +// All provides a mock function with given fields: +func (_m *SceneReaderWriter) All() ([]*models.Scene, error) { + ret := _m.Called() + + var r0 []*models.Scene + if rf, ok := ret.Get(0).(func() []*models.Scene); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*models.Scene) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Create provides a mock function with given fields: newScene +func (_m *SceneReaderWriter) Create(newScene models.Scene) (*models.Scene, error) { + ret := _m.Called(newScene) + + var r0 *models.Scene + if rf, ok := ret.Get(0).(func(models.Scene) *models.Scene); ok { + r0 = rf(newScene) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Scene) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(models.Scene) error); ok { + r1 = rf(newScene) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindByChecksum provides a mock function with given fields: checksum +func (_m *SceneReaderWriter) FindByChecksum(checksum string) (*models.Scene, error) { + ret := _m.Called(checksum) + + var r0 *models.Scene + if rf, ok := ret.Get(0).(func(string) *models.Scene); ok { + r0 = rf(checksum) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Scene) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(checksum) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindByMovieID provides a mock function with given fields: movieID +func (_m *SceneReaderWriter) FindByMovieID(movieID int) ([]*models.Scene, error) { + ret := _m.Called(movieID) + + var r0 []*models.Scene + if rf, ok := ret.Get(0).(func(int) []*models.Scene); ok { + r0 = rf(movieID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*models.Scene) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(int) error); ok { + r1 = rf(movieID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindByOSHash provides a mock function with given fields: oshash +func (_m *SceneReaderWriter) FindByOSHash(oshash string) (*models.Scene, error) { + ret := _m.Called(oshash) + + var r0 *models.Scene + if rf, ok := ret.Get(0).(func(string) *models.Scene); ok { + r0 = rf(oshash) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Scene) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(oshash) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindMany provides a mock function with given fields: ids +func (_m *SceneReaderWriter) FindMany(ids []int) ([]*models.Scene, error) { + ret := _m.Called(ids) + + var r0 []*models.Scene + if rf, ok := ret.Get(0).(func([]int) []*models.Scene); ok { + r0 = rf(ids) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*models.Scene) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func([]int) error); ok { + r1 = rf(ids) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetSceneCover provides a mock function with given fields: sceneID +func (_m *SceneReaderWriter) GetSceneCover(sceneID int) ([]byte, error) { + ret := _m.Called(sceneID) + + var r0 []byte + if rf, ok := ret.Get(0).(func(int) []byte); ok { + r0 = rf(sceneID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(int) error); ok { + r1 = rf(sceneID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Update provides a mock function with given fields: updatedScene +func (_m *SceneReaderWriter) Update(updatedScene models.ScenePartial) (*models.Scene, error) { + ret := _m.Called(updatedScene) + + var r0 *models.Scene + if rf, ok := ret.Get(0).(func(models.ScenePartial) *models.Scene); ok { + r0 = rf(updatedScene) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Scene) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(models.ScenePartial) error); ok { + r1 = rf(updatedScene) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdateFull provides a mock function with given fields: updatedScene +func (_m *SceneReaderWriter) UpdateFull(updatedScene models.Scene) (*models.Scene, error) { + ret := _m.Called(updatedScene) + + var r0 *models.Scene + if rf, ok := ret.Get(0).(func(models.Scene) *models.Scene); ok { + r0 = rf(updatedScene) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Scene) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(models.Scene) error); ok { + r1 = rf(updatedScene) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdateSceneCover provides a mock function with given fields: sceneID, cover +func (_m *SceneReaderWriter) UpdateSceneCover(sceneID int, cover []byte) error { + ret := _m.Called(sceneID, cover) + + var r0 error + if rf, ok := ret.Get(0).(func(int, []byte) error); ok { + r0 = rf(sceneID, cover) + } else { + r0 = ret.Error(0) + } + + return r0 +} diff --git a/pkg/models/mocks/StudioReaderWriter.go b/pkg/models/mocks/StudioReaderWriter.go new file mode 100644 index 000000000..cc51d0755 --- /dev/null +++ b/pkg/models/mocks/StudioReaderWriter.go @@ -0,0 +1,211 @@ +// Code generated by mockery v0.0.0-dev. DO NOT EDIT. + +package mocks + +import ( + models "github.com/stashapp/stash/pkg/models" + mock "github.com/stretchr/testify/mock" +) + +// StudioReaderWriter is an autogenerated mock type for the StudioReaderWriter type +type StudioReaderWriter struct { + mock.Mock +} + +// All provides a mock function with given fields: +func (_m *StudioReaderWriter) All() ([]*models.Studio, error) { + ret := _m.Called() + + var r0 []*models.Studio + if rf, ok := ret.Get(0).(func() []*models.Studio); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*models.Studio) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Create provides a mock function with given fields: newStudio +func (_m *StudioReaderWriter) Create(newStudio models.Studio) (*models.Studio, error) { + ret := _m.Called(newStudio) + + var r0 *models.Studio + if rf, ok := ret.Get(0).(func(models.Studio) *models.Studio); ok { + r0 = rf(newStudio) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Studio) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(models.Studio) error); ok { + r1 = rf(newStudio) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Find provides a mock function with given fields: id +func (_m *StudioReaderWriter) Find(id int) (*models.Studio, error) { + ret := _m.Called(id) + + var r0 *models.Studio + if rf, ok := ret.Get(0).(func(int) *models.Studio); ok { + r0 = rf(id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Studio) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(int) error); ok { + r1 = rf(id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindByName provides a mock function with given fields: name, nocase +func (_m *StudioReaderWriter) FindByName(name string, nocase bool) (*models.Studio, error) { + ret := _m.Called(name, nocase) + + var r0 *models.Studio + if rf, ok := ret.Get(0).(func(string, bool) *models.Studio); ok { + r0 = rf(name, nocase) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Studio) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(string, bool) error); ok { + r1 = rf(name, nocase) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindMany provides a mock function with given fields: ids +func (_m *StudioReaderWriter) FindMany(ids []int) ([]*models.Studio, error) { + ret := _m.Called(ids) + + var r0 []*models.Studio + if rf, ok := ret.Get(0).(func([]int) []*models.Studio); ok { + r0 = rf(ids) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*models.Studio) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func([]int) error); ok { + r1 = rf(ids) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetStudioImage provides a mock function with given fields: studioID +func (_m *StudioReaderWriter) GetStudioImage(studioID int) ([]byte, error) { + ret := _m.Called(studioID) + + var r0 []byte + if rf, ok := ret.Get(0).(func(int) []byte); ok { + r0 = rf(studioID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(int) error); ok { + r1 = rf(studioID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Update provides a mock function with given fields: updatedStudio +func (_m *StudioReaderWriter) Update(updatedStudio models.StudioPartial) (*models.Studio, error) { + ret := _m.Called(updatedStudio) + + var r0 *models.Studio + if rf, ok := ret.Get(0).(func(models.StudioPartial) *models.Studio); ok { + r0 = rf(updatedStudio) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Studio) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(models.StudioPartial) error); ok { + r1 = rf(updatedStudio) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdateFull provides a mock function with given fields: updatedStudio +func (_m *StudioReaderWriter) UpdateFull(updatedStudio models.Studio) (*models.Studio, error) { + ret := _m.Called(updatedStudio) + + var r0 *models.Studio + if rf, ok := ret.Get(0).(func(models.Studio) *models.Studio); ok { + r0 = rf(updatedStudio) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Studio) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(models.Studio) error); ok { + r1 = rf(updatedStudio) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdateStudioImage provides a mock function with given fields: studioID, image +func (_m *StudioReaderWriter) UpdateStudioImage(studioID int, image []byte) error { + ret := _m.Called(studioID, image) + + var r0 error + if rf, ok := ret.Get(0).(func(int, []byte) error); ok { + r0 = rf(studioID, image) + } else { + r0 = ret.Error(0) + } + + return r0 +} diff --git a/pkg/models/mocks/TagReaderWriter.go b/pkg/models/mocks/TagReaderWriter.go new file mode 100644 index 000000000..ddeea97bf --- /dev/null +++ b/pkg/models/mocks/TagReaderWriter.go @@ -0,0 +1,303 @@ +// Code generated by mockery v0.0.0-dev. DO NOT EDIT. + +package mocks + +import ( + models "github.com/stashapp/stash/pkg/models" + mock "github.com/stretchr/testify/mock" +) + +// TagReaderWriter is an autogenerated mock type for the TagReaderWriter type +type TagReaderWriter struct { + mock.Mock +} + +// All provides a mock function with given fields: +func (_m *TagReaderWriter) All() ([]*models.Tag, error) { + ret := _m.Called() + + var r0 []*models.Tag + if rf, ok := ret.Get(0).(func() []*models.Tag); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*models.Tag) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func() error); ok { + r1 = rf() + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Create provides a mock function with given fields: newTag +func (_m *TagReaderWriter) Create(newTag models.Tag) (*models.Tag, error) { + ret := _m.Called(newTag) + + var r0 *models.Tag + if rf, ok := ret.Get(0).(func(models.Tag) *models.Tag); ok { + r0 = rf(newTag) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Tag) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(models.Tag) error); ok { + r1 = rf(newTag) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Find provides a mock function with given fields: id +func (_m *TagReaderWriter) Find(id int) (*models.Tag, error) { + ret := _m.Called(id) + + var r0 *models.Tag + if rf, ok := ret.Get(0).(func(int) *models.Tag); ok { + r0 = rf(id) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Tag) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(int) error); ok { + r1 = rf(id) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindByGalleryID provides a mock function with given fields: galleryID +func (_m *TagReaderWriter) FindByGalleryID(galleryID int) ([]*models.Tag, error) { + ret := _m.Called(galleryID) + + var r0 []*models.Tag + if rf, ok := ret.Get(0).(func(int) []*models.Tag); ok { + r0 = rf(galleryID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*models.Tag) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(int) error); ok { + r1 = rf(galleryID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindByImageID provides a mock function with given fields: imageID +func (_m *TagReaderWriter) FindByImageID(imageID int) ([]*models.Tag, error) { + ret := _m.Called(imageID) + + var r0 []*models.Tag + if rf, ok := ret.Get(0).(func(int) []*models.Tag); ok { + r0 = rf(imageID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*models.Tag) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(int) error); ok { + r1 = rf(imageID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindByName provides a mock function with given fields: name, nocase +func (_m *TagReaderWriter) FindByName(name string, nocase bool) (*models.Tag, error) { + ret := _m.Called(name, nocase) + + var r0 *models.Tag + if rf, ok := ret.Get(0).(func(string, bool) *models.Tag); ok { + r0 = rf(name, nocase) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Tag) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(string, bool) error); ok { + r1 = rf(name, nocase) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindByNames provides a mock function with given fields: names, nocase +func (_m *TagReaderWriter) FindByNames(names []string, nocase bool) ([]*models.Tag, error) { + ret := _m.Called(names, nocase) + + var r0 []*models.Tag + if rf, ok := ret.Get(0).(func([]string, bool) []*models.Tag); ok { + r0 = rf(names, nocase) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*models.Tag) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func([]string, bool) error); ok { + r1 = rf(names, nocase) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindBySceneID provides a mock function with given fields: sceneID +func (_m *TagReaderWriter) FindBySceneID(sceneID int) ([]*models.Tag, error) { + ret := _m.Called(sceneID) + + var r0 []*models.Tag + if rf, ok := ret.Get(0).(func(int) []*models.Tag); ok { + r0 = rf(sceneID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*models.Tag) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(int) error); ok { + r1 = rf(sceneID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindBySceneMarkerID provides a mock function with given fields: sceneMarkerID +func (_m *TagReaderWriter) FindBySceneMarkerID(sceneMarkerID int) ([]*models.Tag, error) { + ret := _m.Called(sceneMarkerID) + + var r0 []*models.Tag + if rf, ok := ret.Get(0).(func(int) []*models.Tag); ok { + r0 = rf(sceneMarkerID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*models.Tag) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(int) error); ok { + r1 = rf(sceneMarkerID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// FindMany provides a mock function with given fields: ids +func (_m *TagReaderWriter) FindMany(ids []int) ([]*models.Tag, error) { + ret := _m.Called(ids) + + var r0 []*models.Tag + if rf, ok := ret.Get(0).(func([]int) []*models.Tag); ok { + r0 = rf(ids) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]*models.Tag) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func([]int) error); ok { + r1 = rf(ids) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// GetTagImage provides a mock function with given fields: tagID +func (_m *TagReaderWriter) GetTagImage(tagID int) ([]byte, error) { + ret := _m.Called(tagID) + + var r0 []byte + if rf, ok := ret.Get(0).(func(int) []byte); ok { + r0 = rf(tagID) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]byte) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(int) error); ok { + r1 = rf(tagID) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Update provides a mock function with given fields: updatedTag +func (_m *TagReaderWriter) Update(updatedTag models.Tag) (*models.Tag, error) { + ret := _m.Called(updatedTag) + + var r0 *models.Tag + if rf, ok := ret.Get(0).(func(models.Tag) *models.Tag); ok { + r0 = rf(updatedTag) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*models.Tag) + } + } + + var r1 error + if rf, ok := ret.Get(1).(func(models.Tag) error); ok { + r1 = rf(updatedTag) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// UpdateTagImage provides a mock function with given fields: tagID, image +func (_m *TagReaderWriter) UpdateTagImage(tagID int, image []byte) error { + ret := _m.Called(tagID, image) + + var r0 error + if rf, ok := ret.Get(0).(func(int, []byte) error); ok { + r0 = rf(tagID, image) + } else { + r0 = ret.Error(0) + } + + return r0 +} diff --git a/pkg/models/model_gallery.go b/pkg/models/model_gallery.go index fe864b866..ed8809f79 100644 --- a/pkg/models/model_gallery.go +++ b/pkg/models/model_gallery.go @@ -1,175 +1,42 @@ package models import ( - "archive/zip" - "bytes" "database/sql" - "image" - "image/jpeg" - "io/ioutil" - "path/filepath" - "sort" - "strings" - - "github.com/disintegration/imaging" - "github.com/stashapp/stash/pkg/api/urlbuilders" - "github.com/stashapp/stash/pkg/logger" - "github.com/stashapp/stash/pkg/utils" - _ "golang.org/x/image/webp" ) type Gallery struct { - ID int `db:"id" json:"id"` - Path string `db:"path" json:"path"` - Checksum string `db:"checksum" json:"checksum"` - SceneID sql.NullInt64 `db:"scene_id,omitempty" json:"scene_id"` - CreatedAt SQLiteTimestamp `db:"created_at" json:"created_at"` - UpdatedAt SQLiteTimestamp `db:"updated_at" json:"updated_at"` + ID int `db:"id" json:"id"` + Path sql.NullString `db:"path" json:"path"` + Checksum string `db:"checksum" json:"checksum"` + Zip bool `db:"zip" json:"zip"` + Title sql.NullString `db:"title" json:"title"` + URL sql.NullString `db:"url" json:"url"` + Date SQLiteDate `db:"date" json:"date"` + Details sql.NullString `db:"details" json:"details"` + Rating sql.NullInt64 `db:"rating" json:"rating"` + StudioID sql.NullInt64 `db:"studio_id,omitempty" json:"studio_id"` + SceneID sql.NullInt64 `db:"scene_id,omitempty" json:"scene_id"` + FileModTime NullSQLiteTimestamp `db:"file_mod_time" json:"file_mod_time"` + CreatedAt SQLiteTimestamp `db:"created_at" json:"created_at"` + UpdatedAt SQLiteTimestamp `db:"updated_at" json:"updated_at"` } -const DefaultGthumbWidth int = 200 - -func (g *Gallery) CountFiles() int { - filteredFiles, readCloser, err := g.listZipContents() - if err != nil { - return 0 - } - defer readCloser.Close() - - return len(filteredFiles) +// GalleryPartial represents part of a Gallery object. It is used to update +// the database entry. Only non-nil fields will be updated. +type GalleryPartial struct { + ID int `db:"id" json:"id"` + Path *sql.NullString `db:"path" json:"path"` + Checksum *string `db:"checksum" json:"checksum"` + Title *sql.NullString `db:"title" json:"title"` + URL *sql.NullString `db:"url" json:"url"` + Date *SQLiteDate `db:"date" json:"date"` + Details *sql.NullString `db:"details" json:"details"` + Rating *sql.NullInt64 `db:"rating" json:"rating"` + StudioID *sql.NullInt64 `db:"studio_id,omitempty" json:"studio_id"` + SceneID *sql.NullInt64 `db:"scene_id,omitempty" json:"scene_id"` + FileModTime *NullSQLiteTimestamp `db:"file_mod_time" json:"file_mod_time"` + CreatedAt *SQLiteTimestamp `db:"created_at" json:"created_at"` + UpdatedAt *SQLiteTimestamp `db:"updated_at" json:"updated_at"` } -func (g *Gallery) GetFiles(baseURL string) []*GalleryFilesType { - var galleryFiles []*GalleryFilesType - filteredFiles, readCloser, err := g.listZipContents() - if err != nil { - return nil - } - defer readCloser.Close() - - builder := urlbuilders.NewGalleryURLBuilder(baseURL, g.ID) - for i, file := range filteredFiles { - galleryURL := builder.GetGalleryImageURL(i) - galleryFile := GalleryFilesType{ - Index: i, - Name: &file.Name, - Path: &galleryURL, - } - galleryFiles = append(galleryFiles, &galleryFile) - } - - return galleryFiles -} - -func (g *Gallery) GetImage(index int) []byte { - data, _ := g.readZipFile(index) - return data -} - -func (g *Gallery) GetThumbnail(index int, width int) []byte { - data, _ := g.readZipFile(index) - srcImage, _, err := image.Decode(bytes.NewReader(data)) - if err != nil { - return data - } - resizedImage := imaging.Resize(srcImage, width, 0, imaging.Box) - buf := new(bytes.Buffer) - err = jpeg.Encode(buf, resizedImage, nil) - if err != nil { - return data - } - return buf.Bytes() -} - -func (g *Gallery) readZipFile(index int) ([]byte, error) { - filteredFiles, readCloser, err := g.listZipContents() - if err != nil { - return nil, err - } - defer readCloser.Close() - - zipFile := filteredFiles[index] - zipFileReadCloser, err := zipFile.Open() - if err != nil { - logger.Warn("failed to read file inside zip file") - return nil, err - } - defer zipFileReadCloser.Close() - - return ioutil.ReadAll(zipFileReadCloser) -} - -func (g *Gallery) listZipContents() ([]*zip.File, *zip.ReadCloser, error) { - readCloser, err := zip.OpenReader(g.Path) - if err != nil { - logger.Warnf("failed to read zip file %s", g.Path) - return nil, nil, err - } - - filteredFiles := make([]*zip.File, 0) - for _, file := range readCloser.File { - if file.FileInfo().IsDir() { - continue - } - ext := filepath.Ext(file.Name) - ext = strings.ToLower(ext) - if ext != ".jpg" && ext != ".jpeg" && ext != ".png" && ext != ".gif" && ext != ".webp" { - continue - } - if strings.Contains(file.Name, "__MACOSX") { - continue - } - filteredFiles = append(filteredFiles, file) - } - sort.Slice(filteredFiles, func(i, j int) bool { - a := filteredFiles[i] - b := filteredFiles[j] - return utils.NaturalCompare(a.Name, b.Name) - }) - - cover := contains(filteredFiles, "cover.jpg") // first image with cover.jpg in the name - if cover >= 0 { // will be moved to the start - reorderedFiles := reorder(filteredFiles, cover) - if reorderedFiles != nil { - return reorderedFiles, readCloser, nil - } - } - - return filteredFiles, readCloser, nil -} - -// return index of first occurrenece of string x ( case insensitive ) in name of zip contents, -1 otherwise -func contains(a []*zip.File, x string) int { - for i, n := range a { - if strings.Contains(strings.ToLower(n.Name), strings.ToLower(x)) { - return i - } - } - return -1 -} - -// reorder slice so that element with position toFirst gets at the start -func reorder(a []*zip.File, toFirst int) []*zip.File { - var first *zip.File - switch { - case toFirst < 0 || toFirst >= len(a): - return nil - case toFirst == 0: - return a - default: - first = a[toFirst] - copy(a[toFirst:], a[toFirst+1:]) // Shift a[toFirst+1:] left one index removing a[toFirst] element - a[len(a)-1] = nil // Nil now unused element for garbage collection - a = a[:len(a)-1] // Truncate slice - a = append([]*zip.File{first}, a...) // Push first to the start of the slice - } - return a -} - -func (g *Gallery) ImageCount() int { - images, _, _ := g.listZipContents() - if images == nil { - return 0 - } - return len(images) -} +const DefaultGthumbWidth int = 640 diff --git a/pkg/models/model_image.go b/pkg/models/model_image.go new file mode 100644 index 000000000..0da92c00f --- /dev/null +++ b/pkg/models/model_image.go @@ -0,0 +1,46 @@ +package models + +import ( + "database/sql" +) + +// Image stores the metadata for a single image. +type Image struct { + ID int `db:"id" json:"id"` + Checksum string `db:"checksum" json:"checksum"` + Path string `db:"path" json:"path"` + Title sql.NullString `db:"title" json:"title"` + Rating sql.NullInt64 `db:"rating" json:"rating"` + OCounter int `db:"o_counter" json:"o_counter"` + Size sql.NullInt64 `db:"size" json:"size"` + Width sql.NullInt64 `db:"width" json:"width"` + Height sql.NullInt64 `db:"height" json:"height"` + StudioID sql.NullInt64 `db:"studio_id,omitempty" json:"studio_id"` + FileModTime NullSQLiteTimestamp `db:"file_mod_time" json:"file_mod_time"` + CreatedAt SQLiteTimestamp `db:"created_at" json:"created_at"` + UpdatedAt SQLiteTimestamp `db:"updated_at" json:"updated_at"` +} + +// ImagePartial represents part of a Image object. It is used to update +// the database entry. Only non-nil fields will be updated. +type ImagePartial struct { + ID int `db:"id" json:"id"` + Checksum *string `db:"checksum" json:"checksum"` + Path *string `db:"path" json:"path"` + Title *sql.NullString `db:"title" json:"title"` + Rating *sql.NullInt64 `db:"rating" json:"rating"` + Size *sql.NullInt64 `db:"size" json:"size"` + Width *sql.NullInt64 `db:"width" json:"width"` + Height *sql.NullInt64 `db:"height" json:"height"` + StudioID *sql.NullInt64 `db:"studio_id,omitempty" json:"studio_id"` + FileModTime *NullSQLiteTimestamp `db:"file_mod_time" json:"file_mod_time"` + CreatedAt *SQLiteTimestamp `db:"created_at" json:"created_at"` + UpdatedAt *SQLiteTimestamp `db:"updated_at" json:"updated_at"` +} + +// ImageFileType represents the file metadata for an image. +type ImageFileType struct { + Size *int `graphql:"size" json:"size"` + Width *int `graphql:"width" json:"width"` + Height *int `graphql:"height" json:"height"` +} diff --git a/pkg/models/model_joins.go b/pkg/models/model_joins.go index f69be8946..9c522f70a 100644 --- a/pkg/models/model_joins.go +++ b/pkg/models/model_joins.go @@ -22,3 +22,33 @@ type SceneMarkersTags struct { SceneMarkerID int `db:"scene_marker_id" json:"scene_marker_id"` TagID int `db:"tag_id" json:"tag_id"` } + +type StashID struct { + StashID string `db:"stash_id" json:"stash_id"` + Endpoint string `db:"endpoint" json:"endpoint"` +} + +type PerformersImages struct { + PerformerID int `db:"performer_id" json:"performer_id"` + ImageID int `db:"image_id" json:"image_id"` +} + +type ImagesTags struct { + ImageID int `db:"image_id" json:"image_id"` + TagID int `db:"tag_id" json:"tag_id"` +} + +type GalleriesImages struct { + GalleryID int `db:"gallery_id" json:"gallery_id"` + ImageID int `db:"image_id" json:"image_id"` +} + +type PerformersGalleries struct { + PerformerID int `db:"performer_id" json:"performer_id"` + GalleryID int `db:"gallery_id" json:"gallery_id"` +} + +type GalleriesTags struct { + TagID int `db:"tag_id" json:"tag_id"` + GalleryID int `db:"gallery_id" json:"gallery_id"` +} diff --git a/pkg/models/model_movie.go b/pkg/models/model_movie.go index bc9939b25..678ce2944 100644 --- a/pkg/models/model_movie.go +++ b/pkg/models/model_movie.go @@ -2,6 +2,9 @@ package models import ( "database/sql" + "time" + + "github.com/stashapp/stash/pkg/utils" ) type Movie struct { @@ -37,3 +40,13 @@ type MoviePartial struct { } var DefaultMovieImage = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAAB3RJTUUH4wgVBQsJl1CMZAAAASJJREFUeNrt3N0JwyAYhlEj3cj9R3Cm5rbkqtAP+qrnGaCYHPwJpLlaa++mmLpbAERAgAgIEAEBIiBABERAgAgIEAEBIiBABERAgAgIEAHZuVflj40x4i94zhk9vqsVvEq6AsQqMP1EjORx20OACAgQRRx7T+zzcFBxcjNDfoB4ntQqTm5Awo7MlqywZxcgYQ+RlqywJ3ozJAQCSBiEJSsQA0gYBpDAgAARECACAkRAgAgIEAERECACAmSjUv6eAOSB8m8YIGGzBUjYbAESBgMkbBkDEjZbgITBAClcxiqQvEoatreYIWEBASIgJ4Gkf11ntXH3nS9uxfGWfJ5J9hAgAgJEQAQEiIAAERAgAgJEQAQEiIAAERAgAgJEQAQEiL7qBuc6RKLHxr0CAAAAAElFTkSuQmCC" + +func NewMovie(name string) *Movie { + currentTime := time.Now() + return &Movie{ + Checksum: utils.MD5FromString(name), + Name: sql.NullString{String: name, Valid: true}, + CreatedAt: SQLiteTimestamp{Timestamp: currentTime}, + UpdatedAt: SQLiteTimestamp{Timestamp: currentTime}, + } +} diff --git a/pkg/models/model_performer.go b/pkg/models/model_performer.go index 12348bc75..26b033d61 100644 --- a/pkg/models/model_performer.go +++ b/pkg/models/model_performer.go @@ -2,6 +2,9 @@ package models import ( "database/sql" + "time" + + "github.com/stashapp/stash/pkg/utils" ) type Performer struct { @@ -27,3 +30,14 @@ type Performer struct { CreatedAt SQLiteTimestamp `db:"created_at" json:"created_at"` UpdatedAt SQLiteTimestamp `db:"updated_at" json:"updated_at"` } + +func NewPerformer(name string) *Performer { + currentTime := time.Now() + return &Performer{ + Checksum: utils.MD5FromString(name), + Name: sql.NullString{String: name, Valid: true}, + Favorite: sql.NullBool{Bool: false, Valid: true}, + CreatedAt: SQLiteTimestamp{Timestamp: currentTime}, + UpdatedAt: SQLiteTimestamp{Timestamp: currentTime}, + } +} diff --git a/pkg/models/model_scene.go b/pkg/models/model_scene.go index a38d7960a..9d32bf874 100644 --- a/pkg/models/model_scene.go +++ b/pkg/models/model_scene.go @@ -7,54 +7,57 @@ import ( // Scene stores the metadata for a single video scene. type Scene struct { - ID int `db:"id" json:"id"` - Checksum sql.NullString `db:"checksum" json:"checksum"` - OSHash sql.NullString `db:"oshash" json:"oshash"` - Path string `db:"path" json:"path"` - Title sql.NullString `db:"title" json:"title"` - Details sql.NullString `db:"details" json:"details"` - URL sql.NullString `db:"url" json:"url"` - Date SQLiteDate `db:"date" json:"date"` - Rating sql.NullInt64 `db:"rating" json:"rating"` - OCounter int `db:"o_counter" json:"o_counter"` - Size sql.NullString `db:"size" json:"size"` - Duration sql.NullFloat64 `db:"duration" json:"duration"` - VideoCodec sql.NullString `db:"video_codec" json:"video_codec"` - Format sql.NullString `db:"format" json:"format_name"` - AudioCodec sql.NullString `db:"audio_codec" json:"audio_codec"` - Width sql.NullInt64 `db:"width" json:"width"` - Height sql.NullInt64 `db:"height" json:"height"` - Framerate sql.NullFloat64 `db:"framerate" json:"framerate"` - Bitrate sql.NullInt64 `db:"bitrate" json:"bitrate"` - StudioID sql.NullInt64 `db:"studio_id,omitempty" json:"studio_id"` - CreatedAt SQLiteTimestamp `db:"created_at" json:"created_at"` - UpdatedAt SQLiteTimestamp `db:"updated_at" json:"updated_at"` + ID int `db:"id" json:"id"` + Checksum sql.NullString `db:"checksum" json:"checksum"` + OSHash sql.NullString `db:"oshash" json:"oshash"` + Path string `db:"path" json:"path"` + Title sql.NullString `db:"title" json:"title"` + Details sql.NullString `db:"details" json:"details"` + URL sql.NullString `db:"url" json:"url"` + Date SQLiteDate `db:"date" json:"date"` + Rating sql.NullInt64 `db:"rating" json:"rating"` + OCounter int `db:"o_counter" json:"o_counter"` + Size sql.NullString `db:"size" json:"size"` + Duration sql.NullFloat64 `db:"duration" json:"duration"` + VideoCodec sql.NullString `db:"video_codec" json:"video_codec"` + Format sql.NullString `db:"format" json:"format_name"` + AudioCodec sql.NullString `db:"audio_codec" json:"audio_codec"` + Width sql.NullInt64 `db:"width" json:"width"` + Height sql.NullInt64 `db:"height" json:"height"` + Framerate sql.NullFloat64 `db:"framerate" json:"framerate"` + Bitrate sql.NullInt64 `db:"bitrate" json:"bitrate"` + StudioID sql.NullInt64 `db:"studio_id,omitempty" json:"studio_id"` + FileModTime NullSQLiteTimestamp `db:"file_mod_time" json:"file_mod_time"` + CreatedAt SQLiteTimestamp `db:"created_at" json:"created_at"` + UpdatedAt SQLiteTimestamp `db:"updated_at" json:"updated_at"` } // ScenePartial represents part of a Scene object. It is used to update // the database entry. Only non-nil fields will be updated. type ScenePartial struct { - ID int `db:"id" json:"id"` - Checksum *sql.NullString `db:"checksum" json:"checksum"` - OSHash *sql.NullString `db:"oshash" json:"oshash"` - Path *string `db:"path" json:"path"` - Title *sql.NullString `db:"title" json:"title"` - Details *sql.NullString `db:"details" json:"details"` - URL *sql.NullString `db:"url" json:"url"` - Date *SQLiteDate `db:"date" json:"date"` - Rating *sql.NullInt64 `db:"rating" json:"rating"` - Size *sql.NullString `db:"size" json:"size"` - Duration *sql.NullFloat64 `db:"duration" json:"duration"` - VideoCodec *sql.NullString `db:"video_codec" json:"video_codec"` - AudioCodec *sql.NullString `db:"audio_codec" json:"audio_codec"` - Width *sql.NullInt64 `db:"width" json:"width"` - Height *sql.NullInt64 `db:"height" json:"height"` - Framerate *sql.NullFloat64 `db:"framerate" json:"framerate"` - Bitrate *sql.NullInt64 `db:"bitrate" json:"bitrate"` - StudioID *sql.NullInt64 `db:"studio_id,omitempty" json:"studio_id"` - MovieID *sql.NullInt64 `db:"movie_id,omitempty" json:"movie_id"` - CreatedAt *SQLiteTimestamp `db:"created_at" json:"created_at"` - UpdatedAt *SQLiteTimestamp `db:"updated_at" json:"updated_at"` + ID int `db:"id" json:"id"` + Checksum *sql.NullString `db:"checksum" json:"checksum"` + OSHash *sql.NullString `db:"oshash" json:"oshash"` + Path *string `db:"path" json:"path"` + Title *sql.NullString `db:"title" json:"title"` + Details *sql.NullString `db:"details" json:"details"` + URL *sql.NullString `db:"url" json:"url"` + Date *SQLiteDate `db:"date" json:"date"` + Rating *sql.NullInt64 `db:"rating" json:"rating"` + Size *sql.NullString `db:"size" json:"size"` + Duration *sql.NullFloat64 `db:"duration" json:"duration"` + VideoCodec *sql.NullString `db:"video_codec" json:"video_codec"` + Format *sql.NullString `db:"format" json:"format_name"` + AudioCodec *sql.NullString `db:"audio_codec" json:"audio_codec"` + Width *sql.NullInt64 `db:"width" json:"width"` + Height *sql.NullInt64 `db:"height" json:"height"` + Framerate *sql.NullFloat64 `db:"framerate" json:"framerate"` + Bitrate *sql.NullInt64 `db:"bitrate" json:"bitrate"` + StudioID *sql.NullInt64 `db:"studio_id,omitempty" json:"studio_id"` + MovieID *sql.NullInt64 `db:"movie_id,omitempty" json:"movie_id"` + FileModTime *NullSQLiteTimestamp `db:"file_mod_time" json:"file_mod_time"` + CreatedAt *SQLiteTimestamp `db:"created_at" json:"created_at"` + UpdatedAt *SQLiteTimestamp `db:"updated_at" json:"updated_at"` } // GetTitle returns the title of the scene. If the Title field is empty, diff --git a/pkg/models/model_scraped_item.go b/pkg/models/model_scraped_item.go index fd3314197..db1e05b7c 100644 --- a/pkg/models/model_scraped_item.go +++ b/pkg/models/model_scraped_item.go @@ -64,16 +64,19 @@ type ScrapedPerformerStash struct { } type ScrapedScene struct { - Title *string `graphql:"title" json:"title"` - Details *string `graphql:"details" json:"details"` - URL *string `graphql:"url" json:"url"` - Date *string `graphql:"date" json:"date"` - Image *string `graphql:"image" json:"image"` - File *SceneFileType `graphql:"file" json:"file"` - Studio *ScrapedSceneStudio `graphql:"studio" json:"studio"` - Movies []*ScrapedSceneMovie `graphql:"movies" json:"movies"` - Tags []*ScrapedSceneTag `graphql:"tags" json:"tags"` - Performers []*ScrapedScenePerformer `graphql:"performers" json:"performers"` + Title *string `graphql:"title" json:"title"` + Details *string `graphql:"details" json:"details"` + URL *string `graphql:"url" json:"url"` + Date *string `graphql:"date" json:"date"` + Image *string `graphql:"image" json:"image"` + RemoteSiteID *string `graphql:"remote_site_id" json:"remote_site_id"` + Duration *int `graphql:"duration" json:"duration"` + File *SceneFileType `graphql:"file" json:"file"` + Fingerprints []*StashBoxFingerprint `graphql:"fingerprints" json:"fingerprints"` + Studio *ScrapedSceneStudio `graphql:"studio" json:"studio"` + Movies []*ScrapedSceneMovie `graphql:"movies" json:"movies"` + Tags []*ScrapedSceneTag `graphql:"tags" json:"tags"` + Performers []*ScrapedScenePerformer `graphql:"performers" json:"performers"` } // stash doesn't return image, and we need id @@ -89,32 +92,47 @@ type ScrapedSceneStash struct { Performers []*ScrapedScenePerformer `graphql:"performers" json:"performers"` } +type ScrapedGalleryStash struct { + ID string `graphql:"id" json:"id"` + Title *string `graphql:"title" json:"title"` + Details *string `graphql:"details" json:"details"` + URL *string `graphql:"url" json:"url"` + Date *string `graphql:"date" json:"date"` + File *SceneFileType `graphql:"file" json:"file"` + Studio *ScrapedSceneStudio `graphql:"studio" json:"studio"` + Tags []*ScrapedSceneTag `graphql:"tags" json:"tags"` + Performers []*ScrapedScenePerformer `graphql:"performers" json:"performers"` +} + type ScrapedScenePerformer struct { // Set if performer matched - ID *string `graphql:"id" json:"id"` - Name string `graphql:"name" json:"name"` - Gender *string `graphql:"gender" json:"gender"` - URL *string `graphql:"url" json:"url"` - Twitter *string `graphql:"twitter" json:"twitter"` - Instagram *string `graphql:"instagram" json:"instagram"` - Birthdate *string `graphql:"birthdate" json:"birthdate"` - Ethnicity *string `graphql:"ethnicity" json:"ethnicity"` - Country *string `graphql:"country" json:"country"` - EyeColor *string `graphql:"eye_color" json:"eye_color"` - Height *string `graphql:"height" json:"height"` - Measurements *string `graphql:"measurements" json:"measurements"` - FakeTits *string `graphql:"fake_tits" json:"fake_tits"` - CareerLength *string `graphql:"career_length" json:"career_length"` - Tattoos *string `graphql:"tattoos" json:"tattoos"` - Piercings *string `graphql:"piercings" json:"piercings"` - Aliases *string `graphql:"aliases" json:"aliases"` + ID *string `graphql:"id" json:"id"` + Name string `graphql:"name" json:"name"` + Gender *string `graphql:"gender" json:"gender"` + URL *string `graphql:"url" json:"url"` + Twitter *string `graphql:"twitter" json:"twitter"` + Instagram *string `graphql:"instagram" json:"instagram"` + Birthdate *string `graphql:"birthdate" json:"birthdate"` + Ethnicity *string `graphql:"ethnicity" json:"ethnicity"` + Country *string `graphql:"country" json:"country"` + EyeColor *string `graphql:"eye_color" json:"eye_color"` + Height *string `graphql:"height" json:"height"` + Measurements *string `graphql:"measurements" json:"measurements"` + FakeTits *string `graphql:"fake_tits" json:"fake_tits"` + CareerLength *string `graphql:"career_length" json:"career_length"` + Tattoos *string `graphql:"tattoos" json:"tattoos"` + Piercings *string `graphql:"piercings" json:"piercings"` + Aliases *string `graphql:"aliases" json:"aliases"` + RemoteSiteID *string `graphql:"remote_site_id" json:"remote_site_id"` + Images []string `graphql:"images" json:"images"` } type ScrapedSceneStudio struct { // Set if studio matched - ID *string `graphql:"id" json:"id"` - Name string `graphql:"name" json:"name"` - URL *string `graphql:"url" json:"url"` + ID *string `graphql:"id" json:"id"` + Name string `graphql:"name" json:"name"` + URL *string `graphql:"url" json:"url"` + RemoteSiteID *string `graphql:"remote_site_id" json:"remote_site_id"` } type ScrapedSceneMovie struct { diff --git a/pkg/models/model_studio.go b/pkg/models/model_studio.go index 6880d3986..d3a4940ff 100644 --- a/pkg/models/model_studio.go +++ b/pkg/models/model_studio.go @@ -2,6 +2,9 @@ package models import ( "database/sql" + "time" + + "github.com/stashapp/stash/pkg/utils" ) type Studio struct { @@ -25,3 +28,13 @@ type StudioPartial struct { } var DefaultStudioImage = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA3XAAAN1wFCKJt4AAAAB3RJTUUH4wgVBQsJl1CMZAAAASJJREFUeNrt3N0JwyAYhlEj3cj9R3Cm5rbkqtAP+qrnGaCYHPwJpLlaa++mmLpbAERAgAgIEAEBIiBABERAgAgIEAEBIiBABERAgAgIEAHZuVflj40x4i94zhk9vqsVvEq6AsQqMP1EjORx20OACAgQRRx7T+zzcFBxcjNDfoB4ntQqTm5Awo7MlqywZxcgYQ+RlqywJ3ozJAQCSBiEJSsQA0gYBpDAgAARECACAkRAgAgIEAERECACAmSjUv6eAOSB8m8YIGGzBUjYbAESBgMkbBkDEjZbgITBAClcxiqQvEoatreYIWEBASIgJ4Gkf11ntXH3nS9uxfGWfJ5J9hAgAgJEQAQEiIAAERAgAgJEQAQEiIAAERAgAgJEQAQEiL7qBuc6RKLHxr0CAAAAAElFTkSuQmCC" + +func NewStudio(name string) *Studio { + currentTime := time.Now() + return &Studio{ + Checksum: utils.MD5FromString(name), + Name: sql.NullString{String: name, Valid: true}, + CreatedAt: SQLiteTimestamp{Timestamp: currentTime}, + UpdatedAt: SQLiteTimestamp{Timestamp: currentTime}, + } +} diff --git a/pkg/models/model_tag.go b/pkg/models/model_tag.go index d62d83784..181f188a3 100644 --- a/pkg/models/model_tag.go +++ b/pkg/models/model_tag.go @@ -1,5 +1,7 @@ package models +import "time" + type Tag struct { ID int `db:"id" json:"id"` Name string `db:"name" json:"name"` // TODO make schema not null @@ -7,6 +9,15 @@ type Tag struct { UpdatedAt SQLiteTimestamp `db:"updated_at" json:"updated_at"` } +func NewTag(name string) *Tag { + currentTime := time.Now() + return &Tag{ + Name: name, + CreatedAt: SQLiteTimestamp{Timestamp: currentTime}, + UpdatedAt: SQLiteTimestamp{Timestamp: currentTime}, + } +} + // Original Tag image from: https://fontawesome.com/icons/tag?style=solid // Modified to change color and rotate // Licensed under CC Attribution 4.0: https://fontawesome.com/license diff --git a/pkg/models/modelstest/sql.go b/pkg/models/modelstest/sql.go new file mode 100644 index 000000000..d60af2fed --- /dev/null +++ b/pkg/models/modelstest/sql.go @@ -0,0 +1,17 @@ +package modelstest + +import "database/sql" + +func NullString(v string) sql.NullString { + return sql.NullString{ + String: v, + Valid: true, + } +} + +func NullInt64(v int64) sql.NullInt64 { + return sql.NullInt64{ + Int64: v, + Valid: true, + } +} diff --git a/pkg/models/movie.go b/pkg/models/movie.go new file mode 100644 index 000000000..f4dd7ea89 --- /dev/null +++ b/pkg/models/movie.go @@ -0,0 +1,88 @@ +package models + +import ( + "github.com/jmoiron/sqlx" +) + +type MovieReader interface { + Find(id int) (*Movie, error) + FindMany(ids []int) ([]*Movie, error) + // FindBySceneID(sceneID int) ([]*Movie, error) + FindByName(name string, nocase bool) (*Movie, error) + FindByNames(names []string, nocase bool) ([]*Movie, error) + All() ([]*Movie, error) + // AllSlim() ([]*Movie, error) + // Query(movieFilter *MovieFilterType, findFilter *FindFilterType) ([]*Movie, int) + GetFrontImage(movieID int) ([]byte, error) + GetBackImage(movieID int) ([]byte, error) +} + +type MovieWriter interface { + Create(newMovie Movie) (*Movie, error) + Update(updatedMovie MoviePartial) (*Movie, error) + UpdateFull(updatedMovie Movie) (*Movie, error) + // Destroy(id string) error + UpdateMovieImages(movieID int, frontImage []byte, backImage []byte) error + // DestroyMovieImages(movieID int) error +} + +type MovieReaderWriter interface { + MovieReader + MovieWriter +} + +func NewMovieReaderWriter(tx *sqlx.Tx) MovieReaderWriter { + return &movieReaderWriter{ + tx: tx, + qb: NewMovieQueryBuilder(), + } +} + +type movieReaderWriter struct { + tx *sqlx.Tx + qb MovieQueryBuilder +} + +func (t *movieReaderWriter) Find(id int) (*Movie, error) { + return t.qb.Find(id, t.tx) +} + +func (t *movieReaderWriter) FindMany(ids []int) ([]*Movie, error) { + return t.qb.FindMany(ids) +} + +func (t *movieReaderWriter) FindByName(name string, nocase bool) (*Movie, error) { + return t.qb.FindByName(name, t.tx, nocase) +} + +func (t *movieReaderWriter) FindByNames(names []string, nocase bool) ([]*Movie, error) { + return t.qb.FindByNames(names, t.tx, nocase) +} + +func (t *movieReaderWriter) All() ([]*Movie, error) { + return t.qb.All() +} + +func (t *movieReaderWriter) GetFrontImage(movieID int) ([]byte, error) { + return t.qb.GetFrontImage(movieID, t.tx) +} + +func (t *movieReaderWriter) GetBackImage(movieID int) ([]byte, error) { + return t.qb.GetBackImage(movieID, t.tx) +} + +func (t *movieReaderWriter) Create(newMovie Movie) (*Movie, error) { + return t.qb.Create(newMovie, t.tx) +} + +func (t *movieReaderWriter) Update(updatedMovie MoviePartial) (*Movie, error) { + return t.qb.Update(updatedMovie, t.tx) +} + +func (t *movieReaderWriter) UpdateFull(updatedMovie Movie) (*Movie, error) { + return t.qb.UpdateFull(updatedMovie, t.tx) +} + +func (t *movieReaderWriter) UpdateMovieImages(movieID int, frontImage []byte, backImage []byte) error { + return t.qb.UpdateMovieImages(movieID, frontImage, backImage, t.tx) +} diff --git a/pkg/models/performer.go b/pkg/models/performer.go new file mode 100644 index 000000000..d3956b3bb --- /dev/null +++ b/pkg/models/performer.go @@ -0,0 +1,89 @@ +package models + +import ( + "github.com/jmoiron/sqlx" +) + +type PerformerReader interface { + // Find(id int) (*Performer, error) + FindMany(ids []int) ([]*Performer, error) + FindBySceneID(sceneID int) ([]*Performer, error) + FindNamesBySceneID(sceneID int) ([]*Performer, error) + FindByImageID(imageID int) ([]*Performer, error) + FindByGalleryID(galleryID int) ([]*Performer, error) + FindByNames(names []string, nocase bool) ([]*Performer, error) + // Count() (int, error) + All() ([]*Performer, error) + // AllSlim() ([]*Performer, error) + // Query(performerFilter *PerformerFilterType, findFilter *FindFilterType) ([]*Performer, int) + GetPerformerImage(performerID int) ([]byte, error) +} + +type PerformerWriter interface { + Create(newPerformer Performer) (*Performer, error) + Update(updatedPerformer Performer) (*Performer, error) + // Destroy(id string) error + UpdatePerformerImage(performerID int, image []byte) error + // DestroyPerformerImage(performerID int) error +} + +type PerformerReaderWriter interface { + PerformerReader + PerformerWriter +} + +func NewPerformerReaderWriter(tx *sqlx.Tx) PerformerReaderWriter { + return &performerReaderWriter{ + tx: tx, + qb: NewPerformerQueryBuilder(), + } +} + +type performerReaderWriter struct { + tx *sqlx.Tx + qb PerformerQueryBuilder +} + +func (t *performerReaderWriter) FindMany(ids []int) ([]*Performer, error) { + return t.qb.FindMany(ids) +} + +func (t *performerReaderWriter) FindByNames(names []string, nocase bool) ([]*Performer, error) { + return t.qb.FindByNames(names, t.tx, nocase) +} + +func (t *performerReaderWriter) All() ([]*Performer, error) { + return t.qb.All() +} + +func (t *performerReaderWriter) GetPerformerImage(performerID int) ([]byte, error) { + return t.qb.GetPerformerImage(performerID, t.tx) +} + +func (t *performerReaderWriter) FindBySceneID(id int) ([]*Performer, error) { + return t.qb.FindBySceneID(id, t.tx) +} + +func (t *performerReaderWriter) FindNamesBySceneID(sceneID int) ([]*Performer, error) { + return t.qb.FindNameBySceneID(sceneID, t.tx) +} + +func (t *performerReaderWriter) FindByImageID(id int) ([]*Performer, error) { + return t.qb.FindByImageID(id, t.tx) +} + +func (t *performerReaderWriter) FindByGalleryID(id int) ([]*Performer, error) { + return t.qb.FindByGalleryID(id, t.tx) +} + +func (t *performerReaderWriter) Create(newPerformer Performer) (*Performer, error) { + return t.qb.Create(newPerformer, t.tx) +} + +func (t *performerReaderWriter) Update(updatedPerformer Performer) (*Performer, error) { + return t.qb.Update(updatedPerformer, t.tx) +} + +func (t *performerReaderWriter) UpdatePerformerImage(performerID int, image []byte) error { + return t.qb.UpdatePerformerImage(performerID, image, t.tx) +} diff --git a/pkg/models/querybuilder_gallery.go b/pkg/models/querybuilder_gallery.go index c5fdf532c..6166bc73a 100644 --- a/pkg/models/querybuilder_gallery.go +++ b/pkg/models/querybuilder_gallery.go @@ -21,8 +21,8 @@ func NewGalleryQueryBuilder() GalleryQueryBuilder { func (qb *GalleryQueryBuilder) Create(newGallery Gallery, tx *sqlx.Tx) (*Gallery, error) { ensureTx(tx) result, err := tx.NamedExec( - `INSERT INTO galleries (path, checksum, scene_id, created_at, updated_at) - VALUES (:path, :checksum, :scene_id, :created_at, :updated_at) + `INSERT INTO galleries (path, checksum, zip, title, date, details, url, studio_id, rating, scene_id, file_mod_time, created_at, updated_at) + VALUES (:path, :checksum, :zip, :title, :date, :details, :url, :studio_id, :rating, :scene_id, :file_mod_time, :created_at, :updated_at) `, newGallery, ) @@ -55,6 +55,45 @@ func (qb *GalleryQueryBuilder) Update(updatedGallery Gallery, tx *sqlx.Tx) (*Gal return &updatedGallery, nil } +func (qb *GalleryQueryBuilder) UpdatePartial(updatedGallery GalleryPartial, tx *sqlx.Tx) (*Gallery, error) { + ensureTx(tx) + _, err := tx.NamedExec( + `UPDATE galleries SET `+SQLGenKeysPartial(updatedGallery)+` WHERE galleries.id = :id`, + updatedGallery, + ) + if err != nil { + return nil, err + } + + return qb.Find(updatedGallery.ID, tx) +} + +func (qb *GalleryQueryBuilder) UpdateChecksum(id int, checksum string, tx *sqlx.Tx) error { + ensureTx(tx) + _, err := tx.Exec( + `UPDATE galleries SET checksum = ? WHERE galleries.id = ? `, + checksum, id, + ) + if err != nil { + return err + } + + return nil +} + +func (qb *GalleryQueryBuilder) UpdateFileModTime(id int, modTime NullSQLiteTimestamp, tx *sqlx.Tx) error { + ensureTx(tx) + _, err := tx.Exec( + `UPDATE galleries SET file_mod_time = ? WHERE galleries.id = ? `, + modTime, id, + ) + if err != nil { + return err + } + + return nil +} + func (qb *GalleryQueryBuilder) Destroy(id int, tx *sqlx.Tx) error { return executeDeleteQuery("galleries", strconv.Itoa(id), tx) } @@ -77,16 +116,16 @@ func (qb *GalleryQueryBuilder) ClearGalleryId(sceneID int, tx *sqlx.Tx) error { return err } -func (qb *GalleryQueryBuilder) Find(id int) (*Gallery, error) { +func (qb *GalleryQueryBuilder) Find(id int, tx *sqlx.Tx) (*Gallery, error) { query := "SELECT * FROM galleries WHERE id = ? LIMIT 1" args := []interface{}{id} - return qb.queryGallery(query, args, nil) + return qb.queryGallery(query, args, tx) } func (qb *GalleryQueryBuilder) FindMany(ids []int) ([]*Gallery, error) { var galleries []*Gallery for _, id := range ids { - gallery, err := qb.Find(id) + gallery, err := qb.Find(id, nil) if err != nil { return nil, err } @@ -125,6 +164,24 @@ func (qb *GalleryQueryBuilder) ValidGalleriesForScenePath(scenePath string) ([]* return qb.queryGalleries(query, nil, nil) } +func (qb *GalleryQueryBuilder) FindByImageID(imageID int, tx *sqlx.Tx) ([]*Gallery, error) { + query := selectAll(galleryTable) + ` + LEFT JOIN galleries_images as images_join on images_join.gallery_id = galleries.id + WHERE images_join.image_id = ? + GROUP BY galleries.id + ` + args := []interface{}{imageID} + return qb.queryGalleries(query, args, tx) +} + +func (qb *GalleryQueryBuilder) CountByImageID(imageID int) (int, error) { + query := `SELECT image_id FROM galleries_images + WHERE image_id = ? + GROUP BY gallery_id` + args := []interface{}{imageID} + return runCountQuery(buildCountQuery(query), args) +} + func (qb *GalleryQueryBuilder) Count() (int, error) { return runCountQuery(buildCountQuery("SELECT galleries.id FROM galleries"), nil) } @@ -146,6 +203,13 @@ func (qb *GalleryQueryBuilder) Query(galleryFilter *GalleryFilterType, findFilte } query.body = selectDistinctIDs("galleries") + query.body += ` + left join performers_galleries as performers_join on performers_join.gallery_id = galleries.id + left join studios as studio on studio.id = galleries.studio_id + left join galleries_tags as tags_join on tags_join.gallery_id = galleries.id + left join galleries_images as images_join on images_join.gallery_id = galleries.id + left join images on images_join.image_id = images.id + ` if q := findFilter.Q; q != nil && *q != "" { searchColumns := []string{"galleries.path", "galleries.checksum"} @@ -154,25 +218,123 @@ func (qb *GalleryQueryBuilder) Query(galleryFilter *GalleryFilterType, findFilte query.addArg(thisArgs...) } + if zipFilter := galleryFilter.IsZip; zipFilter != nil { + var favStr string + if *zipFilter == true { + favStr = "1" + } else { + favStr = "0" + } + query.addWhere("galleries.zip = " + favStr) + } + + query.handleStringCriterionInput(galleryFilter.Path, "galleries.path") + query.handleIntCriterionInput(galleryFilter.Rating, "galleries.rating") + qb.handleAverageResolutionFilter(&query, galleryFilter.AverageResolution) + if isMissingFilter := galleryFilter.IsMissing; isMissingFilter != nil && *isMissingFilter != "" { switch *isMissingFilter { case "scene": query.addWhere("galleries.scene_id IS NULL") + case "studio": + query.addWhere("galleries.studio_id IS NULL") + case "performers": + query.addWhere("performers_join.gallery_id IS NULL") + case "date": + query.addWhere("galleries.date IS \"\" OR galleries.date IS \"0001-01-01\"") + case "tags": + query.addWhere("tags_join.gallery_id IS NULL") + default: + query.addWhere("galleries." + *isMissingFilter + " IS NULL") } } + if tagsFilter := galleryFilter.Tags; tagsFilter != nil && len(tagsFilter.Value) > 0 { + for _, tagID := range tagsFilter.Value { + query.addArg(tagID) + } + + query.body += " LEFT JOIN tags on tags_join.tag_id = tags.id" + whereClause, havingClause := getMultiCriterionClause("galleries", "tags", "tags_join", "gallery_id", "tag_id", tagsFilter) + query.addWhere(whereClause) + query.addHaving(havingClause) + } + + if performersFilter := galleryFilter.Performers; performersFilter != nil && len(performersFilter.Value) > 0 { + for _, performerID := range performersFilter.Value { + query.addArg(performerID) + } + + query.body += " LEFT JOIN performers ON performers_join.performer_id = performers.id" + whereClause, havingClause := getMultiCriterionClause("galleries", "performers", "performers_join", "gallery_id", "performer_id", performersFilter) + query.addWhere(whereClause) + query.addHaving(havingClause) + } + + if studiosFilter := galleryFilter.Studios; studiosFilter != nil && len(studiosFilter.Value) > 0 { + for _, studioID := range studiosFilter.Value { + query.addArg(studioID) + } + + whereClause, havingClause := getMultiCriterionClause("galleries", "studio", "", "", "studio_id", studiosFilter) + query.addWhere(whereClause) + query.addHaving(havingClause) + } + query.sortAndPagination = qb.getGallerySort(findFilter) + getPagination(findFilter) idsResult, countResult := query.executeFind() var galleries []*Gallery for _, id := range idsResult { - gallery, _ := qb.Find(id) + gallery, _ := qb.Find(id, nil) galleries = append(galleries, gallery) } return galleries, countResult } +func (qb *GalleryQueryBuilder) handleAverageResolutionFilter(query *queryBuilder, resolutionFilter *ResolutionEnum) { + if resolutionFilter == nil { + return + } + + if resolution := resolutionFilter.String(); resolutionFilter.IsValid() { + var low int + var high int + + switch resolution { + case "LOW": + high = 480 + case "STANDARD": + low = 480 + high = 720 + case "STANDARD_HD": + low = 720 + high = 1080 + case "FULL_HD": + low = 1080 + high = 2160 + case "FOUR_K": + low = 2160 + } + + havingClause := "" + if low != 0 { + havingClause = "avg(images.height) >= " + strconv.Itoa(low) + } + if high != 0 { + if havingClause != "" { + havingClause += " AND " + } + havingClause += "avg(images.height) < " + strconv.Itoa(high) + } + + if havingClause != "" { + query.addHaving(havingClause) + } + } +} + func (qb *GalleryQueryBuilder) getGallerySort(findFilter *FindFilterType) string { var sort string var direction string diff --git a/pkg/models/querybuilder_gallery_test.go b/pkg/models/querybuilder_gallery_test.go index ad2234092..da46a18d9 100644 --- a/pkg/models/querybuilder_gallery_test.go +++ b/pkg/models/querybuilder_gallery_test.go @@ -14,15 +14,15 @@ func TestGalleryFind(t *testing.T) { gqb := models.NewGalleryQueryBuilder() const galleryIdx = 0 - gallery, err := gqb.Find(galleryIDs[galleryIdx]) + gallery, err := gqb.Find(galleryIDs[galleryIdx], nil) if err != nil { t.Fatalf("Error finding gallery: %s", err.Error()) } - assert.Equal(t, getGalleryStringValue(galleryIdx, "Path"), gallery.Path) + assert.Equal(t, getGalleryStringValue(galleryIdx, "Path"), gallery.Path.String) - gallery, err = gqb.Find(0) + gallery, err = gqb.Find(0, nil) if err != nil { t.Fatalf("Error finding gallery: %s", err.Error()) @@ -42,7 +42,7 @@ func TestGalleryFindByChecksum(t *testing.T) { t.Fatalf("Error finding gallery: %s", err.Error()) } - assert.Equal(t, getGalleryStringValue(galleryIdx, "Path"), gallery.Path) + assert.Equal(t, getGalleryStringValue(galleryIdx, "Path"), gallery.Path.String) galleryChecksum = "not exist" gallery, err = gqb.FindByChecksum(galleryChecksum, nil) @@ -65,7 +65,7 @@ func TestGalleryFindByPath(t *testing.T) { t.Fatalf("Error finding gallery: %s", err.Error()) } - assert.Equal(t, galleryPath, gallery.Path) + assert.Equal(t, galleryPath, gallery.Path.String) galleryPath = "not exist" gallery, err = gqb.FindByPath(galleryPath) @@ -87,7 +87,7 @@ func TestGalleryFindBySceneID(t *testing.T) { t.Fatalf("Error finding gallery: %s", err.Error()) } - assert.Equal(t, getGalleryStringValue(galleryIdxWithScene, "Path"), gallery.Path) + assert.Equal(t, getGalleryStringValue(galleryIdxWithScene, "Path"), gallery.Path.String) gallery, err = gqb.FindBySceneID(0, nil) @@ -125,6 +125,72 @@ func galleryQueryQ(t *testing.T, qb models.GalleryQueryBuilder, q string, expect assert.Len(t, galleries, totalGalleries) } +func TestGalleryQueryPath(t *testing.T) { + const galleryIdx = 1 + galleryPath := getGalleryStringValue(galleryIdx, "Path") + + pathCriterion := models.StringCriterionInput{ + Value: galleryPath, + Modifier: models.CriterionModifierEquals, + } + + verifyGalleriesPath(t, pathCriterion) + + pathCriterion.Modifier = models.CriterionModifierNotEquals + verifyGalleriesPath(t, pathCriterion) +} + +func verifyGalleriesPath(t *testing.T, pathCriterion models.StringCriterionInput) { + sqb := models.NewGalleryQueryBuilder() + galleryFilter := models.GalleryFilterType{ + Path: &pathCriterion, + } + + galleries, _ := sqb.Query(&galleryFilter, nil) + + for _, gallery := range galleries { + verifyNullString(t, gallery.Path, pathCriterion) + } +} + +func TestGalleryQueryRating(t *testing.T) { + const rating = 3 + ratingCriterion := models.IntCriterionInput{ + Value: rating, + Modifier: models.CriterionModifierEquals, + } + + verifyGalleriesRating(t, ratingCriterion) + + ratingCriterion.Modifier = models.CriterionModifierNotEquals + verifyGalleriesRating(t, ratingCriterion) + + ratingCriterion.Modifier = models.CriterionModifierGreaterThan + verifyGalleriesRating(t, ratingCriterion) + + ratingCriterion.Modifier = models.CriterionModifierLessThan + verifyGalleriesRating(t, ratingCriterion) + + ratingCriterion.Modifier = models.CriterionModifierIsNull + verifyGalleriesRating(t, ratingCriterion) + + ratingCriterion.Modifier = models.CriterionModifierNotNull + verifyGalleriesRating(t, ratingCriterion) +} + +func verifyGalleriesRating(t *testing.T, ratingCriterion models.IntCriterionInput) { + sqb := models.NewGalleryQueryBuilder() + galleryFilter := models.GalleryFilterType{ + Rating: &ratingCriterion, + } + + galleries, _ := sqb.Query(&galleryFilter, nil) + + for _, gallery := range galleries { + verifyInt64(t, gallery.Rating, ratingCriterion) + } +} + func TestGalleryQueryIsMissingScene(t *testing.T) { qb := models.NewGalleryQueryBuilder() isMissing := "scene" diff --git a/pkg/models/querybuilder_image.go b/pkg/models/querybuilder_image.go new file mode 100644 index 000000000..6441db5d0 --- /dev/null +++ b/pkg/models/querybuilder_image.go @@ -0,0 +1,444 @@ +package models + +import ( + "database/sql" + "fmt" + "strconv" + + "github.com/jmoiron/sqlx" + "github.com/stashapp/stash/pkg/database" +) + +const imageTable = "images" + +var imagesForPerformerQuery = selectAll(imageTable) + ` +LEFT JOIN performers_images as performers_join on performers_join.image_id = images.id +WHERE performers_join.performer_id = ? +GROUP BY images.id +` + +var countImagesForPerformerQuery = ` +SELECT performer_id FROM performers_images as performers_join +WHERE performer_id = ? +GROUP BY image_id +` + +var imagesForStudioQuery = selectAll(imageTable) + ` +JOIN studios ON studios.id = images.studio_id +WHERE studios.id = ? +GROUP BY images.id +` +var imagesForMovieQuery = selectAll(imageTable) + ` +LEFT JOIN movies_images as movies_join on movies_join.image_id = images.id +WHERE movies_join.movie_id = ? +GROUP BY images.id +` + +var countImagesForTagQuery = ` +SELECT tag_id AS id FROM images_tags +WHERE images_tags.tag_id = ? +GROUP BY images_tags.image_id +` + +var imagesForGalleryQuery = selectAll(imageTable) + ` +LEFT JOIN galleries_images as galleries_join on galleries_join.image_id = images.id +WHERE galleries_join.gallery_id = ? +GROUP BY images.id +` + +var countImagesForGalleryQuery = ` +SELECT gallery_id FROM galleries_images +WHERE gallery_id = ? +GROUP BY image_id +` + +type ImageQueryBuilder struct{} + +func NewImageQueryBuilder() ImageQueryBuilder { + return ImageQueryBuilder{} +} + +func (qb *ImageQueryBuilder) Create(newImage Image, tx *sqlx.Tx) (*Image, error) { + ensureTx(tx) + result, err := tx.NamedExec( + `INSERT INTO images (checksum, path, title, rating, o_counter, size, + width, height, studio_id, file_mod_time, created_at, updated_at) + VALUES (:checksum, :path, :title, :rating, :o_counter, :size, + :width, :height, :studio_id, :file_mod_time, :created_at, :updated_at) + `, + newImage, + ) + if err != nil { + return nil, err + } + imageID, err := result.LastInsertId() + if err != nil { + return nil, err + } + if err := tx.Get(&newImage, `SELECT * FROM images WHERE id = ? LIMIT 1`, imageID); err != nil { + return nil, err + } + return &newImage, nil +} + +func (qb *ImageQueryBuilder) Update(updatedImage ImagePartial, tx *sqlx.Tx) (*Image, error) { + ensureTx(tx) + _, err := tx.NamedExec( + `UPDATE images SET `+SQLGenKeysPartial(updatedImage)+` WHERE images.id = :id`, + updatedImage, + ) + if err != nil { + return nil, err + } + + return qb.find(updatedImage.ID, tx) +} + +func (qb *ImageQueryBuilder) UpdateFull(updatedImage Image, tx *sqlx.Tx) (*Image, error) { + ensureTx(tx) + _, err := tx.NamedExec( + `UPDATE images SET `+SQLGenKeys(updatedImage)+` WHERE images.id = :id`, + updatedImage, + ) + if err != nil { + return nil, err + } + + return qb.find(updatedImage.ID, tx) +} + +func (qb *ImageQueryBuilder) UpdateFileModTime(id int, modTime NullSQLiteTimestamp, tx *sqlx.Tx) error { + ensureTx(tx) + _, err := tx.Exec( + `UPDATE images SET file_mod_time = ? WHERE images.id = ? `, + modTime, id, + ) + if err != nil { + return err + } + + return nil +} + +func (qb *ImageQueryBuilder) IncrementOCounter(id int, tx *sqlx.Tx) (int, error) { + ensureTx(tx) + _, err := tx.Exec( + `UPDATE images SET o_counter = o_counter + 1 WHERE images.id = ?`, + id, + ) + if err != nil { + return 0, err + } + + image, err := qb.find(id, tx) + if err != nil { + return 0, err + } + + return image.OCounter, nil +} + +func (qb *ImageQueryBuilder) DecrementOCounter(id int, tx *sqlx.Tx) (int, error) { + ensureTx(tx) + _, err := tx.Exec( + `UPDATE images SET o_counter = o_counter - 1 WHERE images.id = ? and images.o_counter > 0`, + id, + ) + if err != nil { + return 0, err + } + + image, err := qb.find(id, tx) + if err != nil { + return 0, err + } + + return image.OCounter, nil +} + +func (qb *ImageQueryBuilder) ResetOCounter(id int, tx *sqlx.Tx) (int, error) { + ensureTx(tx) + _, err := tx.Exec( + `UPDATE images SET o_counter = 0 WHERE images.id = ?`, + id, + ) + if err != nil { + return 0, err + } + + image, err := qb.find(id, tx) + if err != nil { + return 0, err + } + + return image.OCounter, nil +} + +func (qb *ImageQueryBuilder) Destroy(id int, tx *sqlx.Tx) error { + return executeDeleteQuery("images", strconv.Itoa(id), tx) +} +func (qb *ImageQueryBuilder) Find(id int) (*Image, error) { + return qb.find(id, nil) +} + +func (qb *ImageQueryBuilder) FindMany(ids []int) ([]*Image, error) { + var images []*Image + for _, id := range ids { + image, err := qb.Find(id) + if err != nil { + return nil, err + } + + if image == nil { + return nil, fmt.Errorf("image with id %d not found", id) + } + + images = append(images, image) + } + + return images, nil +} + +func (qb *ImageQueryBuilder) find(id int, tx *sqlx.Tx) (*Image, error) { + query := selectAll(imageTable) + "WHERE id = ? LIMIT 1" + args := []interface{}{id} + return qb.queryImage(query, args, tx) +} + +func (qb *ImageQueryBuilder) FindByChecksum(checksum string) (*Image, error) { + query := "SELECT * FROM images WHERE checksum = ? LIMIT 1" + args := []interface{}{checksum} + return qb.queryImage(query, args, nil) +} + +func (qb *ImageQueryBuilder) FindByPath(path string) (*Image, error) { + query := selectAll(imageTable) + "WHERE path = ? LIMIT 1" + args := []interface{}{path} + return qb.queryImage(query, args, nil) +} + +func (qb *ImageQueryBuilder) FindByPerformerID(performerID int) ([]*Image, error) { + args := []interface{}{performerID} + return qb.queryImages(imagesForPerformerQuery, args, nil) +} + +func (qb *ImageQueryBuilder) CountByPerformerID(performerID int) (int, error) { + args := []interface{}{performerID} + return runCountQuery(buildCountQuery(countImagesForPerformerQuery), args) +} + +func (qb *ImageQueryBuilder) FindByStudioID(studioID int) ([]*Image, error) { + args := []interface{}{studioID} + return qb.queryImages(imagesForStudioQuery, args, nil) +} + +func (qb *ImageQueryBuilder) FindByGalleryID(galleryID int) ([]*Image, error) { + args := []interface{}{galleryID} + return qb.queryImages(imagesForGalleryQuery+qb.getImageSort(nil), args, nil) +} + +func (qb *ImageQueryBuilder) CountByGalleryID(galleryID int) (int, error) { + args := []interface{}{galleryID} + return runCountQuery(buildCountQuery(countImagesForGalleryQuery), args) +} + +func (qb *ImageQueryBuilder) Count() (int, error) { + return runCountQuery(buildCountQuery("SELECT images.id FROM images"), nil) +} + +func (qb *ImageQueryBuilder) Size() (uint64, error) { + return runSumQuery("SELECT SUM(size) as sum FROM images", nil) +} + +func (qb *ImageQueryBuilder) CountByStudioID(studioID int) (int, error) { + args := []interface{}{studioID} + return runCountQuery(buildCountQuery(imagesForStudioQuery), args) +} + +func (qb *ImageQueryBuilder) CountByTagID(tagID int) (int, error) { + args := []interface{}{tagID} + return runCountQuery(buildCountQuery(countImagesForTagQuery), args) +} + +func (qb *ImageQueryBuilder) All() ([]*Image, error) { + return qb.queryImages(selectAll(imageTable)+qb.getImageSort(nil), nil, nil) +} + +func (qb *ImageQueryBuilder) Query(imageFilter *ImageFilterType, findFilter *FindFilterType) ([]*Image, int) { + if imageFilter == nil { + imageFilter = &ImageFilterType{} + } + if findFilter == nil { + findFilter = &FindFilterType{} + } + + query := queryBuilder{ + tableName: imageTable, + } + + query.body = selectDistinctIDs(imageTable) + query.body += ` + left join performers_images as performers_join on performers_join.image_id = images.id + left join studios as studio on studio.id = images.studio_id + left join images_tags as tags_join on tags_join.image_id = images.id + left join galleries_images as galleries_join on galleries_join.image_id = images.id + ` + + if q := findFilter.Q; q != nil && *q != "" { + searchColumns := []string{"images.title", "images.path", "images.checksum"} + clause, thisArgs := getSearchBinding(searchColumns, *q, false) + query.addWhere(clause) + query.addArg(thisArgs...) + } + + query.handleStringCriterionInput(imageFilter.Path, "images.path") + + if rating := imageFilter.Rating; rating != nil { + clause, count := getIntCriterionWhereClause("images.rating", *imageFilter.Rating) + query.addWhere(clause) + if count == 1 { + query.addArg(imageFilter.Rating.Value) + } + } + + if oCounter := imageFilter.OCounter; oCounter != nil { + clause, count := getIntCriterionWhereClause("images.o_counter", *imageFilter.OCounter) + query.addWhere(clause) + if count == 1 { + query.addArg(imageFilter.OCounter.Value) + } + } + + if resolutionFilter := imageFilter.Resolution; resolutionFilter != nil { + if resolution := resolutionFilter.String(); resolutionFilter.IsValid() { + switch resolution { + case "LOW": + query.addWhere("images.height < 480") + case "STANDARD": + query.addWhere("(images.height >= 480 AND images.height < 720)") + case "STANDARD_HD": + query.addWhere("(images.height >= 720 AND images.height < 1080)") + case "FULL_HD": + query.addWhere("(images.height >= 1080 AND images.height < 2160)") + case "FOUR_K": + query.addWhere("images.height >= 2160") + } + } + } + + if isMissingFilter := imageFilter.IsMissing; isMissingFilter != nil && *isMissingFilter != "" { + switch *isMissingFilter { + case "studio": + query.addWhere("images.studio_id IS NULL") + case "performers": + query.addWhere("performers_join.image_id IS NULL") + case "galleries": + query.addWhere("galleries_join.image_id IS NULL") + case "tags": + query.addWhere("tags_join.image_id IS NULL") + default: + query.addWhere("images." + *isMissingFilter + " IS NULL OR TRIM(images." + *isMissingFilter + ") = ''") + } + } + + if tagsFilter := imageFilter.Tags; tagsFilter != nil && len(tagsFilter.Value) > 0 { + for _, tagID := range tagsFilter.Value { + query.addArg(tagID) + } + + query.body += " LEFT JOIN tags on tags_join.tag_id = tags.id" + whereClause, havingClause := getMultiCriterionClause("images", "tags", "images_tags", "image_id", "tag_id", tagsFilter) + query.addWhere(whereClause) + query.addHaving(havingClause) + } + + if galleriesFilter := imageFilter.Galleries; galleriesFilter != nil && len(galleriesFilter.Value) > 0 { + for _, galleryID := range galleriesFilter.Value { + query.addArg(galleryID) + } + + query.body += " LEFT JOIN galleries ON galleries_join.gallery_id = galleries.id" + whereClause, havingClause := getMultiCriterionClause("images", "galleries", "galleries_images", "image_id", "gallery_id", galleriesFilter) + query.addWhere(whereClause) + query.addHaving(havingClause) + } + + if performersFilter := imageFilter.Performers; performersFilter != nil && len(performersFilter.Value) > 0 { + for _, performerID := range performersFilter.Value { + query.addArg(performerID) + } + + query.body += " LEFT JOIN performers ON performers_join.performer_id = performers.id" + whereClause, havingClause := getMultiCriterionClause("images", "performers", "performers_images", "image_id", "performer_id", performersFilter) + query.addWhere(whereClause) + query.addHaving(havingClause) + } + + if studiosFilter := imageFilter.Studios; studiosFilter != nil && len(studiosFilter.Value) > 0 { + for _, studioID := range studiosFilter.Value { + query.addArg(studioID) + } + + whereClause, havingClause := getMultiCriterionClause("images", "studio", "", "", "studio_id", studiosFilter) + query.addWhere(whereClause) + query.addHaving(havingClause) + } + + query.sortAndPagination = qb.getImageSort(findFilter) + getPagination(findFilter) + idsResult, countResult := query.executeFind() + + var images []*Image + for _, id := range idsResult { + image, _ := qb.Find(id) + images = append(images, image) + } + + return images, countResult +} + +func (qb *ImageQueryBuilder) getImageSort(findFilter *FindFilterType) string { + if findFilter == nil { + return " ORDER BY images.path ASC " + } + sort := findFilter.GetSort("title") + direction := findFilter.GetDirection() + return getSort(sort, direction, "images") +} + +func (qb *ImageQueryBuilder) queryImage(query string, args []interface{}, tx *sqlx.Tx) (*Image, error) { + results, err := qb.queryImages(query, args, tx) + if err != nil || len(results) < 1 { + return nil, err + } + return results[0], nil +} + +func (qb *ImageQueryBuilder) queryImages(query string, args []interface{}, tx *sqlx.Tx) ([]*Image, error) { + var rows *sqlx.Rows + var err error + if tx != nil { + rows, err = tx.Queryx(query, args...) + } else { + rows, err = database.DB.Queryx(query, args...) + } + + if err != nil && err != sql.ErrNoRows { + return nil, err + } + defer rows.Close() + + images := make([]*Image, 0) + for rows.Next() { + image := Image{} + if err := rows.StructScan(&image); err != nil { + return nil, err + } + images = append(images, &image) + } + + if err := rows.Err(); err != nil { + return nil, err + } + + return images, nil +} diff --git a/pkg/models/querybuilder_image_test.go b/pkg/models/querybuilder_image_test.go new file mode 100644 index 000000000..83d662991 --- /dev/null +++ b/pkg/models/querybuilder_image_test.go @@ -0,0 +1,652 @@ +// +build integration + +package models_test + +import ( + "database/sql" + "strconv" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/stashapp/stash/pkg/models" +) + +func TestImageFind(t *testing.T) { + // assume that the first image is imageWithGalleryPath + sqb := models.NewImageQueryBuilder() + + const imageIdx = 0 + imageID := imageIDs[imageIdx] + image, err := sqb.Find(imageID) + + if err != nil { + t.Fatalf("Error finding image: %s", err.Error()) + } + + assert.Equal(t, getImageStringValue(imageIdx, "Path"), image.Path) + + imageID = 0 + image, err = sqb.Find(imageID) + + if err != nil { + t.Fatalf("Error finding image: %s", err.Error()) + } + + assert.Nil(t, image) +} + +func TestImageFindByPath(t *testing.T) { + sqb := models.NewImageQueryBuilder() + + const imageIdx = 1 + imagePath := getImageStringValue(imageIdx, "Path") + image, err := sqb.FindByPath(imagePath) + + if err != nil { + t.Fatalf("Error finding image: %s", err.Error()) + } + + assert.Equal(t, imageIDs[imageIdx], image.ID) + assert.Equal(t, imagePath, image.Path) + + imagePath = "not exist" + image, err = sqb.FindByPath(imagePath) + + if err != nil { + t.Fatalf("Error finding image: %s", err.Error()) + } + + assert.Nil(t, image) +} + +func TestImageCountByPerformerID(t *testing.T) { + sqb := models.NewImageQueryBuilder() + count, err := sqb.CountByPerformerID(performerIDs[performerIdxWithImage]) + + if err != nil { + t.Fatalf("Error counting images: %s", err.Error()) + } + + assert.Equal(t, 1, count) + + count, err = sqb.CountByPerformerID(0) + + if err != nil { + t.Fatalf("Error counting images: %s", err.Error()) + } + + assert.Equal(t, 0, count) +} + +func TestImageQueryQ(t *testing.T) { + const imageIdx = 2 + + q := getImageStringValue(imageIdx, titleField) + + sqb := models.NewImageQueryBuilder() + + imageQueryQ(t, sqb, q, imageIdx) +} + +func imageQueryQ(t *testing.T, sqb models.ImageQueryBuilder, q string, expectedImageIdx int) { + filter := models.FindFilterType{ + Q: &q, + } + images, _ := sqb.Query(nil, &filter) + + assert.Len(t, images, 1) + image := images[0] + assert.Equal(t, imageIDs[expectedImageIdx], image.ID) + + // no Q should return all results + filter.Q = nil + images, _ = sqb.Query(nil, &filter) + + assert.Len(t, images, totalImages) +} + +func TestImageQueryPath(t *testing.T) { + const imageIdx = 1 + imagePath := getImageStringValue(imageIdx, "Path") + + pathCriterion := models.StringCriterionInput{ + Value: imagePath, + Modifier: models.CriterionModifierEquals, + } + + verifyImagePath(t, pathCriterion) + + pathCriterion.Modifier = models.CriterionModifierNotEquals + verifyImagePath(t, pathCriterion) +} + +func verifyImagePath(t *testing.T, pathCriterion models.StringCriterionInput) { + sqb := models.NewImageQueryBuilder() + imageFilter := models.ImageFilterType{ + Path: &pathCriterion, + } + + images, _ := sqb.Query(&imageFilter, nil) + + for _, image := range images { + verifyString(t, image.Path, pathCriterion) + } +} + +func TestImageQueryRating(t *testing.T) { + const rating = 3 + ratingCriterion := models.IntCriterionInput{ + Value: rating, + Modifier: models.CriterionModifierEquals, + } + + verifyImagesRating(t, ratingCriterion) + + ratingCriterion.Modifier = models.CriterionModifierNotEquals + verifyImagesRating(t, ratingCriterion) + + ratingCriterion.Modifier = models.CriterionModifierGreaterThan + verifyImagesRating(t, ratingCriterion) + + ratingCriterion.Modifier = models.CriterionModifierLessThan + verifyImagesRating(t, ratingCriterion) + + ratingCriterion.Modifier = models.CriterionModifierIsNull + verifyImagesRating(t, ratingCriterion) + + ratingCriterion.Modifier = models.CriterionModifierNotNull + verifyImagesRating(t, ratingCriterion) +} + +func verifyImagesRating(t *testing.T, ratingCriterion models.IntCriterionInput) { + sqb := models.NewImageQueryBuilder() + imageFilter := models.ImageFilterType{ + Rating: &ratingCriterion, + } + + images, _ := sqb.Query(&imageFilter, nil) + + for _, image := range images { + verifyInt64(t, image.Rating, ratingCriterion) + } +} + +func TestImageQueryOCounter(t *testing.T) { + const oCounter = 1 + oCounterCriterion := models.IntCriterionInput{ + Value: oCounter, + Modifier: models.CriterionModifierEquals, + } + + verifyImagesOCounter(t, oCounterCriterion) + + oCounterCriterion.Modifier = models.CriterionModifierNotEquals + verifyImagesOCounter(t, oCounterCriterion) + + oCounterCriterion.Modifier = models.CriterionModifierGreaterThan + verifyImagesOCounter(t, oCounterCriterion) + + oCounterCriterion.Modifier = models.CriterionModifierLessThan + verifyImagesOCounter(t, oCounterCriterion) +} + +func verifyImagesOCounter(t *testing.T, oCounterCriterion models.IntCriterionInput) { + sqb := models.NewImageQueryBuilder() + imageFilter := models.ImageFilterType{ + OCounter: &oCounterCriterion, + } + + images, _ := sqb.Query(&imageFilter, nil) + + for _, image := range images { + verifyInt(t, image.OCounter, oCounterCriterion) + } +} + +func TestImageQueryResolution(t *testing.T) { + verifyImagesResolution(t, models.ResolutionEnumLow) + verifyImagesResolution(t, models.ResolutionEnumStandard) + verifyImagesResolution(t, models.ResolutionEnumStandardHd) + verifyImagesResolution(t, models.ResolutionEnumFullHd) + verifyImagesResolution(t, models.ResolutionEnumFourK) + verifyImagesResolution(t, models.ResolutionEnum("unknown")) +} + +func verifyImagesResolution(t *testing.T, resolution models.ResolutionEnum) { + sqb := models.NewImageQueryBuilder() + imageFilter := models.ImageFilterType{ + Resolution: &resolution, + } + + images, _ := sqb.Query(&imageFilter, nil) + + for _, image := range images { + verifyImageResolution(t, image.Height, resolution) + } +} + +func verifyImageResolution(t *testing.T, height sql.NullInt64, resolution models.ResolutionEnum) { + assert := assert.New(t) + h := height.Int64 + + switch resolution { + case models.ResolutionEnumLow: + assert.True(h < 480) + case models.ResolutionEnumStandard: + assert.True(h >= 480 && h < 720) + case models.ResolutionEnumStandardHd: + assert.True(h >= 720 && h < 1080) + case models.ResolutionEnumFullHd: + assert.True(h >= 1080 && h < 2160) + case models.ResolutionEnumFourK: + assert.True(h >= 2160) + } +} + +func TestImageQueryIsMissingGalleries(t *testing.T) { + sqb := models.NewImageQueryBuilder() + isMissing := "galleries" + imageFilter := models.ImageFilterType{ + IsMissing: &isMissing, + } + + q := getImageStringValue(imageIdxWithGallery, titleField) + findFilter := models.FindFilterType{ + Q: &q, + } + + images, _ := sqb.Query(&imageFilter, &findFilter) + + assert.Len(t, images, 0) + + findFilter.Q = nil + images, _ = sqb.Query(&imageFilter, &findFilter) + + // ensure non of the ids equal the one with gallery + for _, image := range images { + assert.NotEqual(t, imageIDs[imageIdxWithGallery], image.ID) + } +} + +func TestImageQueryIsMissingStudio(t *testing.T) { + sqb := models.NewImageQueryBuilder() + isMissing := "studio" + imageFilter := models.ImageFilterType{ + IsMissing: &isMissing, + } + + q := getImageStringValue(imageIdxWithStudio, titleField) + findFilter := models.FindFilterType{ + Q: &q, + } + + images, _ := sqb.Query(&imageFilter, &findFilter) + + assert.Len(t, images, 0) + + findFilter.Q = nil + images, _ = sqb.Query(&imageFilter, &findFilter) + + // ensure non of the ids equal the one with studio + for _, image := range images { + assert.NotEqual(t, imageIDs[imageIdxWithStudio], image.ID) + } +} + +func TestImageQueryIsMissingPerformers(t *testing.T) { + sqb := models.NewImageQueryBuilder() + isMissing := "performers" + imageFilter := models.ImageFilterType{ + IsMissing: &isMissing, + } + + q := getImageStringValue(imageIdxWithPerformer, titleField) + findFilter := models.FindFilterType{ + Q: &q, + } + + images, _ := sqb.Query(&imageFilter, &findFilter) + + assert.Len(t, images, 0) + + findFilter.Q = nil + images, _ = sqb.Query(&imageFilter, &findFilter) + + assert.True(t, len(images) > 0) + + // ensure non of the ids equal the one with movies + for _, image := range images { + assert.NotEqual(t, imageIDs[imageIdxWithPerformer], image.ID) + } +} + +func TestImageQueryIsMissingTags(t *testing.T) { + sqb := models.NewImageQueryBuilder() + isMissing := "tags" + imageFilter := models.ImageFilterType{ + IsMissing: &isMissing, + } + + q := getImageStringValue(imageIdxWithTwoTags, titleField) + findFilter := models.FindFilterType{ + Q: &q, + } + + images, _ := sqb.Query(&imageFilter, &findFilter) + + assert.Len(t, images, 0) + + findFilter.Q = nil + images, _ = sqb.Query(&imageFilter, &findFilter) + + assert.True(t, len(images) > 0) +} + +func TestImageQueryIsMissingRating(t *testing.T) { + sqb := models.NewImageQueryBuilder() + isMissing := "rating" + imageFilter := models.ImageFilterType{ + IsMissing: &isMissing, + } + + images, _ := sqb.Query(&imageFilter, nil) + + assert.True(t, len(images) > 0) + + // ensure date is null, empty or "0001-01-01" + for _, image := range images { + assert.True(t, !image.Rating.Valid) + } +} + +func TestImageQueryPerformers(t *testing.T) { + sqb := models.NewImageQueryBuilder() + performerCriterion := models.MultiCriterionInput{ + Value: []string{ + strconv.Itoa(performerIDs[performerIdxWithImage]), + strconv.Itoa(performerIDs[performerIdx1WithImage]), + }, + Modifier: models.CriterionModifierIncludes, + } + + imageFilter := models.ImageFilterType{ + Performers: &performerCriterion, + } + + images, _ := sqb.Query(&imageFilter, nil) + + assert.Len(t, images, 2) + + // ensure ids are correct + for _, image := range images { + assert.True(t, image.ID == imageIDs[imageIdxWithPerformer] || image.ID == imageIDs[imageIdxWithTwoPerformers]) + } + + performerCriterion = models.MultiCriterionInput{ + Value: []string{ + strconv.Itoa(performerIDs[performerIdx1WithImage]), + strconv.Itoa(performerIDs[performerIdx2WithImage]), + }, + Modifier: models.CriterionModifierIncludesAll, + } + + images, _ = sqb.Query(&imageFilter, nil) + + assert.Len(t, images, 1) + assert.Equal(t, imageIDs[imageIdxWithTwoPerformers], images[0].ID) + + performerCriterion = models.MultiCriterionInput{ + Value: []string{ + strconv.Itoa(performerIDs[performerIdx1WithImage]), + }, + Modifier: models.CriterionModifierExcludes, + } + + q := getImageStringValue(imageIdxWithTwoPerformers, titleField) + findFilter := models.FindFilterType{ + Q: &q, + } + + images, _ = sqb.Query(&imageFilter, &findFilter) + assert.Len(t, images, 0) +} + +func TestImageQueryTags(t *testing.T) { + sqb := models.NewImageQueryBuilder() + tagCriterion := models.MultiCriterionInput{ + Value: []string{ + strconv.Itoa(tagIDs[tagIdxWithImage]), + strconv.Itoa(tagIDs[tagIdx1WithImage]), + }, + Modifier: models.CriterionModifierIncludes, + } + + imageFilter := models.ImageFilterType{ + Tags: &tagCriterion, + } + + images, _ := sqb.Query(&imageFilter, nil) + + assert.Len(t, images, 2) + + // ensure ids are correct + for _, image := range images { + assert.True(t, image.ID == imageIDs[imageIdxWithTag] || image.ID == imageIDs[imageIdxWithTwoTags]) + } + + tagCriterion = models.MultiCriterionInput{ + Value: []string{ + strconv.Itoa(tagIDs[tagIdx1WithImage]), + strconv.Itoa(tagIDs[tagIdx2WithImage]), + }, + Modifier: models.CriterionModifierIncludesAll, + } + + images, _ = sqb.Query(&imageFilter, nil) + + assert.Len(t, images, 1) + assert.Equal(t, imageIDs[imageIdxWithTwoTags], images[0].ID) + + tagCriterion = models.MultiCriterionInput{ + Value: []string{ + strconv.Itoa(tagIDs[tagIdx1WithImage]), + }, + Modifier: models.CriterionModifierExcludes, + } + + q := getImageStringValue(imageIdxWithTwoTags, titleField) + findFilter := models.FindFilterType{ + Q: &q, + } + + images, _ = sqb.Query(&imageFilter, &findFilter) + assert.Len(t, images, 0) +} + +func TestImageQueryStudio(t *testing.T) { + sqb := models.NewImageQueryBuilder() + studioCriterion := models.MultiCriterionInput{ + Value: []string{ + strconv.Itoa(studioIDs[studioIdxWithImage]), + }, + Modifier: models.CriterionModifierIncludes, + } + + imageFilter := models.ImageFilterType{ + Studios: &studioCriterion, + } + + images, _ := sqb.Query(&imageFilter, nil) + + assert.Len(t, images, 1) + + // ensure id is correct + assert.Equal(t, imageIDs[imageIdxWithStudio], images[0].ID) + + studioCriterion = models.MultiCriterionInput{ + Value: []string{ + strconv.Itoa(studioIDs[studioIdxWithImage]), + }, + Modifier: models.CriterionModifierExcludes, + } + + q := getImageStringValue(imageIdxWithStudio, titleField) + findFilter := models.FindFilterType{ + Q: &q, + } + + images, _ = sqb.Query(&imageFilter, &findFilter) + assert.Len(t, images, 0) +} + +func TestImageQuerySorting(t *testing.T) { + sort := titleField + direction := models.SortDirectionEnumAsc + findFilter := models.FindFilterType{ + Sort: &sort, + Direction: &direction, + } + + sqb := models.NewImageQueryBuilder() + images, _ := sqb.Query(nil, &findFilter) + + // images should be in same order as indexes + firstImage := images[0] + lastImage := images[len(images)-1] + + assert.Equal(t, imageIDs[0], firstImage.ID) + assert.Equal(t, imageIDs[len(imageIDs)-1], lastImage.ID) + + // sort in descending order + direction = models.SortDirectionEnumDesc + + images, _ = sqb.Query(nil, &findFilter) + firstImage = images[0] + lastImage = images[len(images)-1] + + assert.Equal(t, imageIDs[len(imageIDs)-1], firstImage.ID) + assert.Equal(t, imageIDs[0], lastImage.ID) +} + +func TestImageQueryPagination(t *testing.T) { + perPage := 1 + findFilter := models.FindFilterType{ + PerPage: &perPage, + } + + sqb := models.NewImageQueryBuilder() + images, _ := sqb.Query(nil, &findFilter) + + assert.Len(t, images, 1) + + firstID := images[0].ID + + page := 2 + findFilter.Page = &page + images, _ = sqb.Query(nil, &findFilter) + + assert.Len(t, images, 1) + secondID := images[0].ID + assert.NotEqual(t, firstID, secondID) + + perPage = 2 + page = 1 + + images, _ = sqb.Query(nil, &findFilter) + assert.Len(t, images, 2) + assert.Equal(t, firstID, images[0].ID) + assert.Equal(t, secondID, images[1].ID) +} + +func TestImageCountByTagID(t *testing.T) { + sqb := models.NewImageQueryBuilder() + + imageCount, err := sqb.CountByTagID(tagIDs[tagIdxWithImage]) + + if err != nil { + t.Fatalf("error calling CountByTagID: %s", err.Error()) + } + + assert.Equal(t, 1, imageCount) + + imageCount, err = sqb.CountByTagID(0) + + if err != nil { + t.Fatalf("error calling CountByTagID: %s", err.Error()) + } + + assert.Equal(t, 0, imageCount) +} + +func TestImageCountByStudioID(t *testing.T) { + sqb := models.NewImageQueryBuilder() + + imageCount, err := sqb.CountByStudioID(studioIDs[studioIdxWithImage]) + + if err != nil { + t.Fatalf("error calling CountByStudioID: %s", err.Error()) + } + + assert.Equal(t, 1, imageCount) + + imageCount, err = sqb.CountByStudioID(0) + + if err != nil { + t.Fatalf("error calling CountByStudioID: %s", err.Error()) + } + + assert.Equal(t, 0, imageCount) +} + +func TestImageFindByPerformerID(t *testing.T) { + sqb := models.NewImageQueryBuilder() + + images, err := sqb.FindByPerformerID(performerIDs[performerIdxWithImage]) + + if err != nil { + t.Fatalf("error calling FindByPerformerID: %s", err.Error()) + } + + assert.Len(t, images, 1) + assert.Equal(t, imageIDs[imageIdxWithPerformer], images[0].ID) + + images, err = sqb.FindByPerformerID(0) + + if err != nil { + t.Fatalf("error calling FindByPerformerID: %s", err.Error()) + } + + assert.Len(t, images, 0) +} + +func TestImageFindByStudioID(t *testing.T) { + sqb := models.NewImageQueryBuilder() + + images, err := sqb.FindByStudioID(performerIDs[studioIdxWithImage]) + + if err != nil { + t.Fatalf("error calling FindByStudioID: %s", err.Error()) + } + + assert.Len(t, images, 1) + assert.Equal(t, imageIDs[imageIdxWithStudio], images[0].ID) + + images, err = sqb.FindByStudioID(0) + + if err != nil { + t.Fatalf("error calling FindByStudioID: %s", err.Error()) + } + + assert.Len(t, images, 0) +} + +// TODO Update +// TODO IncrementOCounter +// TODO DecrementOCounter +// TODO ResetOCounter +// TODO Destroy +// TODO FindByChecksum +// TODO Count +// TODO SizeCount +// TODO All diff --git a/pkg/models/querybuilder_joins.go b/pkg/models/querybuilder_joins.go index 416b85a18..3e3cbbd54 100644 --- a/pkg/models/querybuilder_joins.go +++ b/pkg/models/querybuilder_joins.go @@ -365,3 +365,637 @@ func (qb *JoinsQueryBuilder) DestroyScenesMarkers(sceneID int, tx *sqlx.Tx) erro return err } + +func (qb *JoinsQueryBuilder) CreateStashIDs(entityName string, entityID int, newJoins []StashID, tx *sqlx.Tx) error { + query := "INSERT INTO " + entityName + "_stash_ids (" + entityName + "_id, endpoint, stash_id) VALUES (?, ?, ?)" + ensureTx(tx) + for _, join := range newJoins { + _, err := tx.Exec(query, entityID, join.Endpoint, join.StashID) + if err != nil { + return err + } + } + return nil +} + +func (qb *JoinsQueryBuilder) GetImagePerformers(imageID int, tx *sqlx.Tx) ([]PerformersImages, error) { + ensureTx(tx) + + // Delete the existing joins and then create new ones + query := `SELECT * from performers_images WHERE image_id = ?` + + var rows *sqlx.Rows + var err error + if tx != nil { + rows, err = tx.Queryx(query, imageID) + } else { + rows, err = database.DB.Queryx(query, imageID) + } + + if err != nil && err != sql.ErrNoRows { + return nil, err + } + defer rows.Close() + + performerImages := make([]PerformersImages, 0) + for rows.Next() { + performerImage := PerformersImages{} + if err := rows.StructScan(&performerImage); err != nil { + return nil, err + } + performerImages = append(performerImages, performerImage) + } + + if err := rows.Err(); err != nil { + return nil, err + } + + return performerImages, nil +} + +func (qb *JoinsQueryBuilder) CreatePerformersImages(newJoins []PerformersImages, tx *sqlx.Tx) error { + ensureTx(tx) + for _, join := range newJoins { + _, err := tx.NamedExec( + `INSERT INTO performers_images (performer_id, image_id) VALUES (:performer_id, :image_id)`, + join, + ) + if err != nil { + return err + } + } + return nil +} + +// AddPerformerImage adds a performer to a image. It does not make any change +// if the performer already exists on the image. It returns true if image +// performer was added. +func (qb *JoinsQueryBuilder) AddPerformerImage(imageID int, performerID int, tx *sqlx.Tx) (bool, error) { + ensureTx(tx) + + existingPerformers, err := qb.GetImagePerformers(imageID, tx) + + if err != nil { + return false, err + } + + // ensure not already present + for _, p := range existingPerformers { + if p.PerformerID == performerID && p.ImageID == imageID { + return false, nil + } + } + + performerJoin := PerformersImages{ + PerformerID: performerID, + ImageID: imageID, + } + performerJoins := append(existingPerformers, performerJoin) + + err = qb.UpdatePerformersImages(imageID, performerJoins, tx) + + return err == nil, err +} + +func (qb *JoinsQueryBuilder) UpdatePerformersImages(imageID int, updatedJoins []PerformersImages, tx *sqlx.Tx) error { + ensureTx(tx) + + // Delete the existing joins and then create new ones + _, err := tx.Exec("DELETE FROM performers_images WHERE image_id = ?", imageID) + if err != nil { + return err + } + return qb.CreatePerformersImages(updatedJoins, tx) +} + +func (qb *JoinsQueryBuilder) DestroyPerformersImages(imageID int, tx *sqlx.Tx) error { + ensureTx(tx) + + // Delete the existing joins + _, err := tx.Exec("DELETE FROM performers_images WHERE image_id = ?", imageID) + return err +} + +func (qb *JoinsQueryBuilder) GetImageTags(imageID int, tx *sqlx.Tx) ([]ImagesTags, error) { + ensureTx(tx) + + // Delete the existing joins and then create new ones + query := `SELECT * from images_tags WHERE image_id = ?` + + var rows *sqlx.Rows + var err error + if tx != nil { + rows, err = tx.Queryx(query, imageID) + } else { + rows, err = database.DB.Queryx(query, imageID) + } + + if err != nil && err != sql.ErrNoRows { + return nil, err + } + defer rows.Close() + + imageTags := make([]ImagesTags, 0) + for rows.Next() { + imageTag := ImagesTags{} + if err := rows.StructScan(&imageTag); err != nil { + return nil, err + } + imageTags = append(imageTags, imageTag) + } + + if err := rows.Err(); err != nil { + return nil, err + } + + return imageTags, nil +} + +func (qb *JoinsQueryBuilder) CreateImagesTags(newJoins []ImagesTags, tx *sqlx.Tx) error { + ensureTx(tx) + for _, join := range newJoins { + _, err := tx.NamedExec( + `INSERT INTO images_tags (image_id, tag_id) VALUES (:image_id, :tag_id)`, + join, + ) + if err != nil { + return err + } + } + return nil +} + +func (qb *JoinsQueryBuilder) UpdateImagesTags(imageID int, updatedJoins []ImagesTags, tx *sqlx.Tx) error { + ensureTx(tx) + + // Delete the existing joins and then create new ones + _, err := tx.Exec("DELETE FROM images_tags WHERE image_id = ?", imageID) + if err != nil { + return err + } + return qb.CreateImagesTags(updatedJoins, tx) +} + +// AddImageTag adds a tag to a image. It does not make any change if the tag +// already exists on the image. It returns true if image tag was added. +func (qb *JoinsQueryBuilder) AddImageTag(imageID int, tagID int, tx *sqlx.Tx) (bool, error) { + ensureTx(tx) + + existingTags, err := qb.GetImageTags(imageID, tx) + + if err != nil { + return false, err + } + + // ensure not already present + for _, p := range existingTags { + if p.TagID == tagID && p.ImageID == imageID { + return false, nil + } + } + + tagJoin := ImagesTags{ + TagID: tagID, + ImageID: imageID, + } + tagJoins := append(existingTags, tagJoin) + + err = qb.UpdateImagesTags(imageID, tagJoins, tx) + + return err == nil, err +} + +func (qb *JoinsQueryBuilder) DestroyImagesTags(imageID int, tx *sqlx.Tx) error { + ensureTx(tx) + + // Delete the existing joins + _, err := tx.Exec("DELETE FROM images_tags WHERE image_id = ?", imageID) + + return err +} + +func (qb *JoinsQueryBuilder) GetImageGalleries(imageID int, tx *sqlx.Tx) ([]GalleriesImages, error) { + ensureTx(tx) + + // Delete the existing joins and then create new ones + query := `SELECT * from galleries_images WHERE image_id = ?` + + var rows *sqlx.Rows + var err error + if tx != nil { + rows, err = tx.Queryx(query, imageID) + } else { + rows, err = database.DB.Queryx(query, imageID) + } + + if err != nil && err != sql.ErrNoRows { + return nil, err + } + defer rows.Close() + + galleryImages := make([]GalleriesImages, 0) + for rows.Next() { + galleriesImages := GalleriesImages{} + if err := rows.StructScan(&galleriesImages); err != nil { + return nil, err + } + galleryImages = append(galleryImages, galleriesImages) + } + + if err := rows.Err(); err != nil { + return nil, err + } + + return galleryImages, nil +} + +func (qb *JoinsQueryBuilder) CreateGalleriesImages(newJoins []GalleriesImages, tx *sqlx.Tx) error { + ensureTx(tx) + for _, join := range newJoins { + _, err := tx.NamedExec( + `INSERT INTO galleries_images (gallery_id, image_id) VALUES (:gallery_id, :image_id)`, + join, + ) + if err != nil { + return err + } + } + return nil +} + +func (qb *JoinsQueryBuilder) UpdateGalleriesImages(imageID int, updatedJoins []GalleriesImages, tx *sqlx.Tx) error { + ensureTx(tx) + + // Delete the existing joins and then create new ones + _, err := tx.Exec("DELETE FROM galleries_images WHERE image_id = ?", imageID) + if err != nil { + return err + } + return qb.CreateGalleriesImages(updatedJoins, tx) +} + +// AddGalleryImage adds a gallery to an image. It does not make any change if the tag +// already exists on the image. It returns true if image tag was added. +func (qb *JoinsQueryBuilder) AddImageGallery(imageID int, galleryID int, tx *sqlx.Tx) (bool, error) { + ensureTx(tx) + + existingGalleries, err := qb.GetImageGalleries(imageID, tx) + + if err != nil { + return false, err + } + + // ensure not already present + for _, p := range existingGalleries { + if p.GalleryID == galleryID && p.ImageID == imageID { + return false, nil + } + } + + galleryJoin := GalleriesImages{ + GalleryID: galleryID, + ImageID: imageID, + } + galleryJoins := append(existingGalleries, galleryJoin) + + err = qb.UpdateGalleriesImages(imageID, galleryJoins, tx) + + return err == nil, err +} + +// RemoveImageGallery removes a gallery from an image. Returns true if the join +// was removed. +func (qb *JoinsQueryBuilder) RemoveImageGallery(imageID int, galleryID int, tx *sqlx.Tx) (bool, error) { + ensureTx(tx) + + existingGalleries, err := qb.GetImageGalleries(imageID, tx) + + if err != nil { + return false, err + } + + // remove the join + var updatedJoins []GalleriesImages + found := false + for _, p := range existingGalleries { + if p.GalleryID == galleryID && p.ImageID == imageID { + found = true + continue + } + + updatedJoins = append(updatedJoins, p) + } + + if found { + err = qb.UpdateGalleriesImages(imageID, updatedJoins, tx) + } + + return found && err == nil, err +} + +func (qb *JoinsQueryBuilder) DestroyImageGalleries(imageID int, tx *sqlx.Tx) error { + ensureTx(tx) + + // Delete the existing joins + _, err := tx.Exec("DELETE FROM galleries_images WHERE image_id = ?", imageID) + + return err +} + +func (qb *JoinsQueryBuilder) GetGalleryPerformers(galleryID int, tx *sqlx.Tx) ([]PerformersGalleries, error) { + ensureTx(tx) + + // Delete the existing joins and then create new ones + query := `SELECT * from performers_galleries WHERE gallery_id = ?` + + var rows *sqlx.Rows + var err error + if tx != nil { + rows, err = tx.Queryx(query, galleryID) + } else { + rows, err = database.DB.Queryx(query, galleryID) + } + + if err != nil && err != sql.ErrNoRows { + return nil, err + } + defer rows.Close() + + performerGalleries := make([]PerformersGalleries, 0) + for rows.Next() { + performerGallery := PerformersGalleries{} + if err := rows.StructScan(&performerGallery); err != nil { + return nil, err + } + performerGalleries = append(performerGalleries, performerGallery) + } + + if err := rows.Err(); err != nil { + return nil, err + } + + return performerGalleries, nil +} + +func (qb *JoinsQueryBuilder) CreatePerformersGalleries(newJoins []PerformersGalleries, tx *sqlx.Tx) error { + ensureTx(tx) + for _, join := range newJoins { + _, err := tx.NamedExec( + `INSERT INTO performers_galleries (performer_id, gallery_id) VALUES (:performer_id, :gallery_id)`, + join, + ) + if err != nil { + return err + } + } + return nil +} + +// AddPerformerGallery adds a performer to a gallery. It does not make any change +// if the performer already exists on the gallery. It returns true if gallery +// performer was added. +func (qb *JoinsQueryBuilder) AddPerformerGallery(galleryID int, performerID int, tx *sqlx.Tx) (bool, error) { + ensureTx(tx) + + existingPerformers, err := qb.GetGalleryPerformers(galleryID, tx) + + if err != nil { + return false, err + } + + // ensure not already present + for _, p := range existingPerformers { + if p.PerformerID == performerID && p.GalleryID == galleryID { + return false, nil + } + } + + performerJoin := PerformersGalleries{ + PerformerID: performerID, + GalleryID: galleryID, + } + performerJoins := append(existingPerformers, performerJoin) + + err = qb.UpdatePerformersGalleries(galleryID, performerJoins, tx) + + return err == nil, err +} + +func (qb *JoinsQueryBuilder) UpdatePerformersGalleries(galleryID int, updatedJoins []PerformersGalleries, tx *sqlx.Tx) error { + ensureTx(tx) + + // Delete the existing joins and then create new ones + _, err := tx.Exec("DELETE FROM performers_galleries WHERE gallery_id = ?", galleryID) + if err != nil { + return err + } + return qb.CreatePerformersGalleries(updatedJoins, tx) +} + +func (qb *JoinsQueryBuilder) DestroyPerformersGalleries(galleryID int, tx *sqlx.Tx) error { + ensureTx(tx) + + // Delete the existing joins + _, err := tx.Exec("DELETE FROM performers_galleries WHERE gallery_id = ?", galleryID) + return err +} + +func (qb *JoinsQueryBuilder) GetGalleryTags(galleryID int, tx *sqlx.Tx) ([]GalleriesTags, error) { + ensureTx(tx) + + // Delete the existing joins and then create new ones + query := `SELECT * from galleries_tags WHERE gallery_id = ?` + + var rows *sqlx.Rows + var err error + if tx != nil { + rows, err = tx.Queryx(query, galleryID) + } else { + rows, err = database.DB.Queryx(query, galleryID) + } + + if err != nil && err != sql.ErrNoRows { + return nil, err + } + defer rows.Close() + + galleryTags := make([]GalleriesTags, 0) + for rows.Next() { + galleryTag := GalleriesTags{} + if err := rows.StructScan(&galleryTag); err != nil { + return nil, err + } + galleryTags = append(galleryTags, galleryTag) + } + + if err := rows.Err(); err != nil { + return nil, err + } + + return galleryTags, nil +} + +func (qb *JoinsQueryBuilder) CreateGalleriesTags(newJoins []GalleriesTags, tx *sqlx.Tx) error { + ensureTx(tx) + for _, join := range newJoins { + _, err := tx.NamedExec( + `INSERT INTO galleries_tags (gallery_id, tag_id) VALUES (:gallery_id, :tag_id)`, + join, + ) + if err != nil { + return err + } + } + return nil +} + +func (qb *JoinsQueryBuilder) UpdateGalleriesTags(galleryID int, updatedJoins []GalleriesTags, tx *sqlx.Tx) error { + ensureTx(tx) + + // Delete the existing joins and then create new ones + _, err := tx.Exec("DELETE FROM galleries_tags WHERE gallery_id = ?", galleryID) + if err != nil { + return err + } + return qb.CreateGalleriesTags(updatedJoins, tx) +} + +// AddGalleryTag adds a tag to a gallery. It does not make any change if the tag +// already exists on the gallery. It returns true if gallery tag was added. +func (qb *JoinsQueryBuilder) AddGalleryTag(galleryID int, tagID int, tx *sqlx.Tx) (bool, error) { + ensureTx(tx) + + existingTags, err := qb.GetGalleryTags(galleryID, tx) + + if err != nil { + return false, err + } + + // ensure not already present + for _, p := range existingTags { + if p.TagID == tagID && p.GalleryID == galleryID { + return false, nil + } + } + + tagJoin := GalleriesTags{ + TagID: tagID, + GalleryID: galleryID, + } + tagJoins := append(existingTags, tagJoin) + + err = qb.UpdateGalleriesTags(galleryID, tagJoins, tx) + + return err == nil, err +} + +func (qb *JoinsQueryBuilder) DestroyGalleriesTags(galleryID int, tx *sqlx.Tx) error { + ensureTx(tx) + + // Delete the existing joins + _, err := tx.Exec("DELETE FROM galleries_tags WHERE gallery_id = ?", galleryID) + + return err +} + +func (qb *JoinsQueryBuilder) GetSceneStashIDs(sceneID int) ([]*StashID, error) { + rows, err := database.DB.Queryx(`SELECT stash_id, endpoint from scene_stash_ids WHERE scene_id = ?`, sceneID) + + if err != nil && err != sql.ErrNoRows { + return nil, err + } + defer rows.Close() + + stashIDs := []*StashID{} + for rows.Next() { + stashID := StashID{} + if err := rows.StructScan(&stashID); err != nil { + return nil, err + } + stashIDs = append(stashIDs, &stashID) + } + + if err := rows.Err(); err != nil { + return nil, err + } + + return stashIDs, nil +} + +func (qb *JoinsQueryBuilder) GetPerformerStashIDs(performerID int) ([]*StashID, error) { + rows, err := database.DB.Queryx(`SELECT stash_id, endpoint from performer_stash_ids WHERE performer_id = ?`, performerID) + + if err != nil && err != sql.ErrNoRows { + return nil, err + } + defer rows.Close() + + stashIDs := []*StashID{} + for rows.Next() { + stashID := StashID{} + if err := rows.StructScan(&stashID); err != nil { + return nil, err + } + stashIDs = append(stashIDs, &stashID) + } + + if err := rows.Err(); err != nil { + return nil, err + } + + return stashIDs, nil +} + +func (qb *JoinsQueryBuilder) GetStudioStashIDs(studioID int) ([]*StashID, error) { + rows, err := database.DB.Queryx(`SELECT stash_id, endpoint from studio_stash_ids WHERE studio_id = ?`, studioID) + + if err != nil && err != sql.ErrNoRows { + return nil, err + } + defer rows.Close() + + stashIDs := []*StashID{} + for rows.Next() { + stashID := StashID{} + if err := rows.StructScan(&stashID); err != nil { + return nil, err + } + stashIDs = append(stashIDs, &stashID) + } + + if err := rows.Err(); err != nil { + return nil, err + } + + return stashIDs, nil +} + +func (qb *JoinsQueryBuilder) UpdateSceneStashIDs(sceneID int, updatedJoins []StashID, tx *sqlx.Tx) error { + ensureTx(tx) + + _, err := tx.Exec("DELETE FROM scene_stash_ids WHERE scene_id = ?", sceneID) + if err != nil { + return err + } + return qb.CreateStashIDs("scene", sceneID, updatedJoins, tx) +} + +func (qb *JoinsQueryBuilder) UpdatePerformerStashIDs(performerID int, updatedJoins []StashID, tx *sqlx.Tx) error { + ensureTx(tx) + + _, err := tx.Exec("DELETE FROM performer_stash_ids WHERE performer_id = ?", performerID) + if err != nil { + return err + } + return qb.CreateStashIDs("performer", performerID, updatedJoins, tx) +} + +func (qb *JoinsQueryBuilder) UpdateStudioStashIDs(studioID int, updatedJoins []StashID, tx *sqlx.Tx) error { + ensureTx(tx) + + _, err := tx.Exec("DELETE FROM studio_stash_ids WHERE studio_id = ?", studioID) + if err != nil { + return err + } + return qb.CreateStashIDs("studio", studioID, updatedJoins, tx) +} diff --git a/pkg/models/querybuilder_movies.go b/pkg/models/querybuilder_movies.go index 311548a31..85eeb5708 100644 --- a/pkg/models/querybuilder_movies.go +++ b/pkg/models/querybuilder_movies.go @@ -2,6 +2,7 @@ package models import ( "database/sql" + "fmt" "github.com/jmoiron/sqlx" "github.com/stashapp/stash/pkg/database" @@ -48,6 +49,19 @@ func (qb *MovieQueryBuilder) Update(updatedMovie MoviePartial, tx *sqlx.Tx) (*Mo return qb.Find(updatedMovie.ID, tx) } +func (qb *MovieQueryBuilder) UpdateFull(updatedMovie Movie, tx *sqlx.Tx) (*Movie, error) { + ensureTx(tx) + _, err := tx.NamedExec( + `UPDATE movies SET `+SQLGenKeys(updatedMovie)+` WHERE movies.id = :id`, + updatedMovie, + ) + if err != nil { + return nil, err + } + + return qb.Find(updatedMovie.ID, tx) +} + func (qb *MovieQueryBuilder) Destroy(id string, tx *sqlx.Tx) error { // delete movie from movies_scenes @@ -71,6 +85,24 @@ func (qb *MovieQueryBuilder) Find(id int, tx *sqlx.Tx) (*Movie, error) { return qb.queryMovie(query, args, tx) } +func (qb *MovieQueryBuilder) FindMany(ids []int) ([]*Movie, error) { + var movies []*Movie + for _, id := range ids { + movie, err := qb.Find(id, nil) + if err != nil { + return nil, err + } + + if movie == nil { + return nil, fmt.Errorf("movie with id %d not found", id) + } + + movies = append(movies, movie) + } + + return movies, nil +} + func (qb *MovieQueryBuilder) FindBySceneID(sceneID int, tx *sqlx.Tx) ([]*Movie, error) { query := ` SELECT movies.* FROM movies @@ -162,6 +194,10 @@ func (qb *MovieQueryBuilder) Query(movieFilter *MovieFilterType, findFilter *Fin body += `left join movies_images on movies_images.movie_id = movies.id ` whereClauses = appendClause(whereClauses, "movies_images.back_image IS NULL") + case "scenes": + body += `left join movies_scenes on movies_scenes.movie_id = movies.id + ` + whereClauses = appendClause(whereClauses, "movies_scenes.scene_id IS NULL") default: whereClauses = appendClause(whereClauses, "movies."+*isMissingFilter+" IS NULL") } diff --git a/pkg/models/querybuilder_performer.go b/pkg/models/querybuilder_performer.go index a2cdefd52..7bebf214b 100644 --- a/pkg/models/querybuilder_performer.go +++ b/pkg/models/querybuilder_performer.go @@ -2,6 +2,7 @@ package models import ( "database/sql" + "fmt" "strconv" "time" @@ -76,6 +77,24 @@ func (qb *PerformerQueryBuilder) Find(id int) (*Performer, error) { return results[0], nil } +func (qb *PerformerQueryBuilder) FindMany(ids []int) ([]*Performer, error) { + var performers []*Performer + for _, id := range ids { + performer, err := qb.Find(id) + if err != nil { + return nil, err + } + + if performer == nil { + return nil, fmt.Errorf("performer with id %d not found", id) + } + + performers = append(performers, performer) + } + + return performers, nil +} + func (qb *PerformerQueryBuilder) FindBySceneID(sceneID int, tx *sqlx.Tx) ([]*Performer, error) { query := selectAll("performers") + ` LEFT JOIN performers_scenes as scenes_join on scenes_join.performer_id = performers.id @@ -85,6 +104,24 @@ func (qb *PerformerQueryBuilder) FindBySceneID(sceneID int, tx *sqlx.Tx) ([]*Per return qb.queryPerformers(query, args, tx) } +func (qb *PerformerQueryBuilder) FindByImageID(imageID int, tx *sqlx.Tx) ([]*Performer, error) { + query := selectAll("performers") + ` + LEFT JOIN performers_images as images_join on images_join.performer_id = performers.id + WHERE images_join.image_id = ? + ` + args := []interface{}{imageID} + return qb.queryPerformers(query, args, tx) +} + +func (qb *PerformerQueryBuilder) FindByGalleryID(galleryID int, tx *sqlx.Tx) ([]*Performer, error) { + query := selectAll("performers") + ` + LEFT JOIN performers_galleries as galleries_join on galleries_join.performer_id = performers.id + WHERE galleries_join.gallery_id = ? + ` + args := []interface{}{galleryID} + return qb.queryPerformers(query, args, tx) +} + func (qb *PerformerQueryBuilder) FindNameBySceneID(sceneID int, tx *sqlx.Tx) ([]*Performer, error) { query := ` SELECT performers.name FROM performers @@ -138,6 +175,7 @@ func (qb *PerformerQueryBuilder) Query(performerFilter *PerformerFilterType, fin query.body += ` left join performers_scenes as scenes_join on scenes_join.performer_id = performers.id left join scenes on scenes_join.scene_id = scenes.id + left join performer_stash_ids on performer_stash_ids.performer_id = performers.id ` if q := findFilter.Q; q != nil && *q != "" { @@ -182,23 +220,30 @@ func (qb *PerformerQueryBuilder) Query(performerFilter *PerformerFilterType, fin query.body += `left join performers_image on performers_image.performer_id = performers.id ` query.addWhere("performers_image.performer_id IS NULL") + case "stash_id": + query.addWhere("performer_stash_ids.performer_id IS NULL") default: - query.addWhere("performers." + *isMissingFilter + " IS NULL") + query.addWhere("performers." + *isMissingFilter + " IS NULL OR TRIM(performers." + *isMissingFilter + ") = ''") } } - handleStringCriterion(tableName+".ethnicity", performerFilter.Ethnicity, &query) - handleStringCriterion(tableName+".country", performerFilter.Country, &query) - handleStringCriterion(tableName+".eye_color", performerFilter.EyeColor, &query) - handleStringCriterion(tableName+".height", performerFilter.Height, &query) - handleStringCriterion(tableName+".measurements", performerFilter.Measurements, &query) - handleStringCriterion(tableName+".fake_tits", performerFilter.FakeTits, &query) - handleStringCriterion(tableName+".career_length", performerFilter.CareerLength, &query) - handleStringCriterion(tableName+".tattoos", performerFilter.Tattoos, &query) - handleStringCriterion(tableName+".piercings", performerFilter.Piercings, &query) + if stashIDFilter := performerFilter.StashID; stashIDFilter != nil { + query.addWhere("performer_stash_ids.stash_id = ?") + query.addArg(stashIDFilter) + } + + query.handleStringCriterionInput(performerFilter.Ethnicity, tableName+".ethnicity") + query.handleStringCriterionInput(performerFilter.Country, tableName+".country") + query.handleStringCriterionInput(performerFilter.EyeColor, tableName+".eye_color") + query.handleStringCriterionInput(performerFilter.Height, tableName+".height") + query.handleStringCriterionInput(performerFilter.Measurements, tableName+".measurements") + query.handleStringCriterionInput(performerFilter.FakeTits, tableName+".fake_tits") + query.handleStringCriterionInput(performerFilter.CareerLength, tableName+".career_length") + query.handleStringCriterionInput(performerFilter.Tattoos, tableName+".tattoos") + query.handleStringCriterionInput(performerFilter.Piercings, tableName+".piercings") // TODO - need better handling of aliases - handleStringCriterion(tableName+".aliases", performerFilter.Aliases, &query) + query.handleStringCriterionInput(performerFilter.Aliases, tableName+".aliases") query.sortAndPagination = qb.getPerformerSort(findFilter) + getPagination(findFilter) idsResult, countResult := query.executeFind() @@ -212,27 +257,6 @@ func (qb *PerformerQueryBuilder) Query(performerFilter *PerformerFilterType, fin return performers, countResult } -func handleStringCriterion(column string, value *StringCriterionInput, query *queryBuilder) { - if value != nil { - if modifier := value.Modifier.String(); value.Modifier.IsValid() { - switch modifier { - case "EQUALS": - clause, thisArgs := getSearchBinding([]string{column}, value.Value, false) - query.addWhere(clause) - query.addArg(thisArgs...) - case "NOT_EQUALS": - clause, thisArgs := getSearchBinding([]string{column}, value.Value, true) - query.addWhere(clause) - query.addArg(thisArgs...) - case "IS_NULL": - query.addWhere(column + " IS NULL") - case "NOT_NULL": - query.addWhere(column + " IS NOT NULL") - } - } - } -} - func getBirthYearFilterClause(criterionModifier CriterionModifier, value int) ([]string, []interface{}) { var clauses []string var args []interface{} diff --git a/pkg/models/querybuilder_scene.go b/pkg/models/querybuilder_scene.go index e2cbca00a..5a9ef84e8 100644 --- a/pkg/models/querybuilder_scene.go +++ b/pkg/models/querybuilder_scene.go @@ -7,7 +7,6 @@ import ( "github.com/jmoiron/sqlx" "github.com/stashapp/stash/pkg/database" - "github.com/stashapp/stash/pkg/utils" ) const sceneTable = "scenes" @@ -61,9 +60,9 @@ func (qb *SceneQueryBuilder) Create(newScene Scene, tx *sqlx.Tx) (*Scene, error) ensureTx(tx) result, err := tx.NamedExec( `INSERT INTO scenes (oshash, checksum, path, title, details, url, date, rating, o_counter, size, duration, video_codec, - audio_codec, format, width, height, framerate, bitrate, studio_id, created_at, updated_at) + audio_codec, format, width, height, framerate, bitrate, studio_id, file_mod_time, created_at, updated_at) VALUES (:oshash, :checksum, :path, :title, :details, :url, :date, :rating, :o_counter, :size, :duration, :video_codec, - :audio_codec, :format, :width, :height, :framerate, :bitrate, :studio_id, :created_at, :updated_at) + :audio_codec, :format, :width, :height, :framerate, :bitrate, :studio_id, :file_mod_time, :created_at, :updated_at) `, newScene, ) @@ -93,6 +92,32 @@ func (qb *SceneQueryBuilder) Update(updatedScene ScenePartial, tx *sqlx.Tx) (*Sc return qb.find(updatedScene.ID, tx) } +func (qb *SceneQueryBuilder) UpdateFull(updatedScene Scene, tx *sqlx.Tx) (*Scene, error) { + ensureTx(tx) + _, err := tx.NamedExec( + `UPDATE scenes SET `+SQLGenKeys(updatedScene)+` WHERE scenes.id = :id`, + updatedScene, + ) + if err != nil { + return nil, err + } + + return qb.find(updatedScene.ID, tx) +} + +func (qb *SceneQueryBuilder) UpdateFileModTime(id int, modTime NullSQLiteTimestamp, tx *sqlx.Tx) error { + ensureTx(tx) + _, err := tx.Exec( + `UPDATE scenes SET file_mod_time = ? WHERE scenes.id = ? `, + modTime, id, + ) + if err != nil { + return err + } + + return nil +} + func (qb *SceneQueryBuilder) IncrementOCounter(id int, tx *sqlx.Tx) (int, error) { ensureTx(tx) _, err := tx.Exec( @@ -229,12 +254,8 @@ func (qb *SceneQueryBuilder) Count() (int, error) { return runCountQuery(buildCountQuery("SELECT scenes.id FROM scenes"), nil) } -func (qb *SceneQueryBuilder) SizeCount() (string, error) { - sum, err := runSumQuery("SELECT SUM(size) as sum FROM scenes", nil) - if err != nil { - return "0 B", err - } - return utils.HumanizeBytes(sum), err +func (qb *SceneQueryBuilder) Size() (uint64, error) { + return runSumQuery("SELECT SUM(size) as sum FROM scenes", nil) } func (qb *SceneQueryBuilder) CountByStudioID(studioID int) (int, error) { @@ -290,6 +311,7 @@ func (qb *SceneQueryBuilder) Query(sceneFilter *SceneFilterType, findFilter *Fin left join studios as studio on studio.id = scenes.studio_id left join galleries as gallery on gallery.scene_id = scenes.id left join scenes_tags as tags_join on tags_join.scene_id = scenes.id + left join scene_stash_ids on scene_stash_ids.scene_id = scenes.id ` if q := findFilter.Q; q != nil && *q != "" { @@ -299,21 +321,9 @@ func (qb *SceneQueryBuilder) Query(sceneFilter *SceneFilterType, findFilter *Fin query.addArg(thisArgs...) } - if rating := sceneFilter.Rating; rating != nil { - clause, count := getIntCriterionWhereClause("scenes.rating", *sceneFilter.Rating) - query.addWhere(clause) - if count == 1 { - query.addArg(sceneFilter.Rating.Value) - } - } - - if oCounter := sceneFilter.OCounter; oCounter != nil { - clause, count := getIntCriterionWhereClause("scenes.o_counter", *sceneFilter.OCounter) - query.addWhere(clause) - if count == 1 { - query.addArg(sceneFilter.OCounter.Value) - } - } + query.handleStringCriterionInput(sceneFilter.Path, "scenes.path") + query.handleIntCriterionInput(sceneFilter.Rating, "scenes.rating") + query.handleIntCriterionInput(sceneFilter.OCounter, "scenes.o_counter") if durationFilter := sceneFilter.Duration; durationFilter != nil { clause, thisArgs := getDurationWhereClause(*durationFilter) @@ -360,8 +370,10 @@ func (qb *SceneQueryBuilder) Query(sceneFilter *SceneFilterType, findFilter *Fin query.addWhere("scenes.date IS \"\" OR scenes.date IS \"0001-01-01\"") case "tags": query.addWhere("tags_join.scene_id IS NULL") + case "stash_id": + query.addWhere("scene_stash_ids.scene_id IS NULL") default: - query.addWhere("scenes." + *isMissingFilter + " IS NULL") + query.addWhere("scenes." + *isMissingFilter + " IS NULL OR TRIM(scenes." + *isMissingFilter + ") = ''") } } @@ -408,6 +420,11 @@ func (qb *SceneQueryBuilder) Query(sceneFilter *SceneFilterType, findFilter *Fin query.addHaving(havingClause) } + if stashIDFilter := sceneFilter.StashID; stashIDFilter != nil { + query.addWhere("scene_stash_ids.stash_id = ?") + query.addArg(stashIDFilter) + } + query.sortAndPagination = qb.getSceneSort(findFilter) + getPagination(findFilter) idsResult, countResult := query.executeFind() diff --git a/pkg/models/querybuilder_scene_test.go b/pkg/models/querybuilder_scene_test.go index c86e7c3e6..06b300674 100644 --- a/pkg/models/querybuilder_scene_test.go +++ b/pkg/models/querybuilder_scene_test.go @@ -135,6 +135,62 @@ func sceneQueryQ(t *testing.T, sqb models.SceneQueryBuilder, q string, expectedS assert.Len(t, scenes, totalScenes) } +func TestSceneQueryPath(t *testing.T) { + const sceneIdx = 1 + scenePath := getSceneStringValue(sceneIdx, "Path") + + pathCriterion := models.StringCriterionInput{ + Value: scenePath, + Modifier: models.CriterionModifierEquals, + } + + verifyScenesPath(t, pathCriterion) + + pathCriterion.Modifier = models.CriterionModifierNotEquals + verifyScenesPath(t, pathCriterion) +} + +func verifyScenesPath(t *testing.T, pathCriterion models.StringCriterionInput) { + sqb := models.NewSceneQueryBuilder() + sceneFilter := models.SceneFilterType{ + Path: &pathCriterion, + } + + scenes, _ := sqb.Query(&sceneFilter, nil) + + for _, scene := range scenes { + verifyString(t, scene.Path, pathCriterion) + } +} + +func verifyNullString(t *testing.T, value sql.NullString, criterion models.StringCriterionInput) { + t.Helper() + assert := assert.New(t) + if criterion.Modifier == models.CriterionModifierIsNull { + assert.False(value.Valid, "expect is null values to be null") + } + if criterion.Modifier == models.CriterionModifierNotNull { + assert.True(value.Valid, "expect is null values to be null") + } + if criterion.Modifier == models.CriterionModifierEquals { + assert.Equal(criterion.Value, value.String) + } + if criterion.Modifier == models.CriterionModifierNotEquals { + assert.NotEqual(criterion.Value, value.String) + } +} + +func verifyString(t *testing.T, value string, criterion models.StringCriterionInput) { + t.Helper() + assert := assert.New(t) + if criterion.Modifier == models.CriterionModifierEquals { + assert.Equal(criterion.Value, value) + } + if criterion.Modifier == models.CriterionModifierNotEquals { + assert.NotEqual(criterion.Value, value) + } +} + func TestSceneQueryRating(t *testing.T) { const rating = 3 ratingCriterion := models.IntCriterionInput{ diff --git a/pkg/models/querybuilder_sql.go b/pkg/models/querybuilder_sql.go index 300884491..ef21acb3c 100644 --- a/pkg/models/querybuilder_sql.go +++ b/pkg/models/querybuilder_sql.go @@ -48,6 +48,45 @@ func (qb *queryBuilder) addArg(args ...interface{}) { qb.args = append(qb.args, args...) } +func (qb *queryBuilder) handleIntCriterionInput(c *IntCriterionInput, column string) { + if c != nil { + clause, count := getIntCriterionWhereClause(column, *c) + qb.addWhere(clause) + if count == 1 { + qb.addArg(c.Value) + } + } +} + +func (qb *queryBuilder) handleStringCriterionInput(c *StringCriterionInput, column string) { + if c != nil { + if modifier := c.Modifier; c.Modifier.IsValid() { + switch modifier { + case CriterionModifierIncludes: + clause, thisArgs := getSearchBinding([]string{column}, c.Value, false) + qb.addWhere(clause) + qb.addArg(thisArgs...) + case CriterionModifierExcludes: + clause, thisArgs := getSearchBinding([]string{column}, c.Value, true) + qb.addWhere(clause) + qb.addArg(thisArgs...) + case CriterionModifierEquals: + qb.addWhere(column + " LIKE ?") + qb.addArg(c.Value) + case CriterionModifierNotEquals: + qb.addWhere(column + " NOT LIKE ?") + qb.addArg(c.Value) + default: + clause, count := getSimpleCriterionClause(modifier, "?") + qb.addWhere(column + " " + clause) + if count == 1 { + qb.addArg(c.Value) + } + } + } + } +} + var randomSortFloat = rand.Float64() func selectAll(tableName string) string { @@ -86,8 +125,9 @@ func getPagination(findFilter *FindFilterType) string { } else { perPage = *findFilter.PerPage } - if perPage > 120 { - perPage = 120 + + if perPage > 1000 { + perPage = 1000 } else if perPage < 1 { perPage = 1 } @@ -374,49 +414,39 @@ func sqlGenKeys(i interface{}, partial bool) string { if key == "id" { continue } + + var add bool switch t := v.Field(i).Interface().(type) { case string: - if partial || t != "" { - query = append(query, fmt.Sprintf("%s=:%s", key, key)) - } + add = partial || t != "" case int: - if partial || t != 0 { - query = append(query, fmt.Sprintf("%s=:%s", key, key)) - } + add = partial || t != 0 case float64: - if partial || t != 0 { - query = append(query, fmt.Sprintf("%s=:%s", key, key)) - } + add = partial || t != 0 + case bool: + add = true case SQLiteTimestamp: - if partial || !t.Timestamp.IsZero() { - query = append(query, fmt.Sprintf("%s=:%s", key, key)) - } + add = partial || !t.Timestamp.IsZero() + case NullSQLiteTimestamp: + add = partial || t.Valid case SQLiteDate: - if partial || t.Valid { - query = append(query, fmt.Sprintf("%s=:%s", key, key)) - } + add = partial || t.Valid case sql.NullString: - if partial || t.Valid { - query = append(query, fmt.Sprintf("%s=:%s", key, key)) - } + add = partial || t.Valid case sql.NullBool: - if partial || t.Valid { - query = append(query, fmt.Sprintf("%s=:%s", key, key)) - } + add = partial || t.Valid case sql.NullInt64: - if partial || t.Valid { - query = append(query, fmt.Sprintf("%s=:%s", key, key)) - } + add = partial || t.Valid case sql.NullFloat64: - if partial || t.Valid { - query = append(query, fmt.Sprintf("%s=:%s", key, key)) - } + add = partial || t.Valid default: reflectValue := reflect.ValueOf(t) isNil := reflectValue.IsNil() - if !isNil { - query = append(query, fmt.Sprintf("%s=:%s", key, key)) - } + add = !isNil + } + + if add { + query = append(query, fmt.Sprintf("%s=:%s", key, key)) } } return strings.Join(query, ", ") diff --git a/pkg/models/querybuilder_studio.go b/pkg/models/querybuilder_studio.go index 67a931df8..39528811a 100644 --- a/pkg/models/querybuilder_studio.go +++ b/pkg/models/querybuilder_studio.go @@ -2,6 +2,7 @@ package models import ( "database/sql" + "fmt" "github.com/jmoiron/sqlx" "github.com/stashapp/stash/pkg/database" @@ -17,7 +18,7 @@ func (qb *StudioQueryBuilder) Create(newStudio Studio, tx *sqlx.Tx) (*Studio, er ensureTx(tx) result, err := tx.NamedExec( `INSERT INTO studios (checksum, name, url, parent_id, created_at, updated_at) - VALUES (:checksum, :name, :url, :parent_id, :created_at, :updated_at) + VALUES (:checksum, :name, :url, :parent_id, :created_at, :updated_at) `, newStudio, ) @@ -52,6 +53,23 @@ func (qb *StudioQueryBuilder) Update(updatedStudio StudioPartial, tx *sqlx.Tx) ( return &ret, nil } +func (qb *StudioQueryBuilder) UpdateFull(updatedStudio Studio, tx *sqlx.Tx) (*Studio, error) { + ensureTx(tx) + _, err := tx.NamedExec( + `UPDATE studios SET `+SQLGenKeys(updatedStudio)+` WHERE studios.id = :id`, + updatedStudio, + ) + if err != nil { + return nil, err + } + + var ret Studio + if err := tx.Get(&ret, `SELECT * FROM studios WHERE id = ? LIMIT 1`, updatedStudio.ID); err != nil { + return nil, err + } + return &ret, nil +} + func (qb *StudioQueryBuilder) Destroy(id string, tx *sqlx.Tx) error { // remove studio from scenes _, err := tx.Exec("UPDATE scenes SET studio_id = null WHERE studio_id = ?", id) @@ -74,6 +92,24 @@ func (qb *StudioQueryBuilder) Find(id int, tx *sqlx.Tx) (*Studio, error) { return qb.queryStudio(query, args, tx) } +func (qb *StudioQueryBuilder) FindMany(ids []int) ([]*Studio, error) { + var studios []*Studio + for _, id := range ids { + studio, err := qb.Find(id, nil) + if err != nil { + return nil, err + } + + if studio == nil { + return nil, fmt.Errorf("studio with id %d not found", id) + } + + studios = append(studios, studio) + } + + return studios, nil +} + func (qb *StudioQueryBuilder) FindChildren(id int, tx *sqlx.Tx) ([]*Studio, error) { query := "SELECT studios.* FROM studios WHERE studios.parent_id = ?" args := []interface{}{id} @@ -122,6 +158,7 @@ func (qb *StudioQueryBuilder) Query(studioFilter *StudioFilterType, findFilter * body := selectDistinctIDs("studios") body += ` left join scenes on studios.id = scenes.studio_id + left join studio_stash_ids on studio_stash_ids.studio_id = studios.id ` if q := findFilter.Q; q != nil && *q != "" { @@ -146,12 +183,19 @@ func (qb *StudioQueryBuilder) Query(studioFilter *StudioFilterType, findFilter * havingClauses = appendClause(havingClauses, havingClause) } + if stashIDFilter := studioFilter.StashID; stashIDFilter != nil { + whereClauses = append(whereClauses, "studio_stash_ids.stash_id = ?") + args = append(args, stashIDFilter) + } + if isMissingFilter := studioFilter.IsMissing; isMissingFilter != nil && *isMissingFilter != "" { switch *isMissingFilter { case "image": body += `left join studios_image on studios_image.studio_id = studios.id ` whereClauses = appendClause(whereClauses, "studios_image.studio_id IS NULL") + case "stash_id": + whereClauses = appendClause(whereClauses, "studio_stash_ids.studio_id IS NULL") default: whereClauses = appendClause(whereClauses, "studios."+*isMissingFilter+" IS NULL") } diff --git a/pkg/models/querybuilder_tag.go b/pkg/models/querybuilder_tag.go index b7a840531..de6d78b2f 100644 --- a/pkg/models/querybuilder_tag.go +++ b/pkg/models/querybuilder_tag.go @@ -3,6 +3,7 @@ package models import ( "database/sql" "errors" + "fmt" "github.com/jmoiron/sqlx" "github.com/stashapp/stash/pkg/database" @@ -89,6 +90,24 @@ func (qb *TagQueryBuilder) Find(id int, tx *sqlx.Tx) (*Tag, error) { return qb.queryTag(query, args, tx) } +func (qb *TagQueryBuilder) FindMany(ids []int) ([]*Tag, error) { + var tags []*Tag + for _, id := range ids { + tag, err := qb.Find(id, nil) + if err != nil { + return nil, err + } + + if tag == nil { + return nil, fmt.Errorf("tag with id %d not found", id) + } + + tags = append(tags, tag) + } + + return tags, nil +} + func (qb *TagQueryBuilder) FindBySceneID(sceneID int, tx *sqlx.Tx) ([]*Tag, error) { query := ` SELECT tags.* FROM tags @@ -101,6 +120,30 @@ func (qb *TagQueryBuilder) FindBySceneID(sceneID int, tx *sqlx.Tx) ([]*Tag, erro return qb.queryTags(query, args, tx) } +func (qb *TagQueryBuilder) FindByImageID(imageID int, tx *sqlx.Tx) ([]*Tag, error) { + query := ` + SELECT tags.* FROM tags + LEFT JOIN images_tags as images_join on images_join.tag_id = tags.id + WHERE images_join.image_id = ? + GROUP BY tags.id + ` + query += qb.getTagSort(nil) + args := []interface{}{imageID} + return qb.queryTags(query, args, tx) +} + +func (qb *TagQueryBuilder) FindByGalleryID(galleryID int, tx *sqlx.Tx) ([]*Tag, error) { + query := ` + SELECT tags.* FROM tags + LEFT JOIN galleries_tags as galleries_join on galleries_join.tag_id = tags.id + WHERE galleries_join.gallery_id = ? + GROUP BY tags.id + ` + query += qb.getTagSort(nil) + args := []interface{}{galleryID} + return qb.queryTags(query, args, tx) +} + func (qb *TagQueryBuilder) FindBySceneMarkerID(sceneMarkerID int, tx *sqlx.Tx) ([]*Tag, error) { query := ` SELECT tags.* FROM tags diff --git a/pkg/models/querybuilder_tag_test.go b/pkg/models/querybuilder_tag_test.go index 83357600f..02b431ce7 100644 --- a/pkg/models/querybuilder_tag_test.go +++ b/pkg/models/querybuilder_tag_test.go @@ -116,7 +116,7 @@ func TestTagQueryIsMissingImage(t *testing.T) { IsMissing: &isMissing, } - q := getTagStringValue(tagIdxWithImage, "name") + q := getTagStringValue(tagIdxWithCoverImage, "name") findFilter := models.FindFilterType{ Q: &q, } @@ -130,7 +130,7 @@ func TestTagQueryIsMissingImage(t *testing.T) { // ensure non of the ids equal the one with image for _, tag := range tags { - assert.NotEqual(t, tagIDs[tagIdxWithImage], tag.ID) + assert.NotEqual(t, tagIDs[tagIdxWithCoverImage], tag.ID) } } diff --git a/pkg/models/scene.go b/pkg/models/scene.go new file mode 100644 index 000000000..2580506b6 --- /dev/null +++ b/pkg/models/scene.go @@ -0,0 +1,102 @@ +package models + +import ( + "github.com/jmoiron/sqlx" +) + +type SceneReader interface { + // Find(id int) (*Scene, error) + FindMany(ids []int) ([]*Scene, error) + FindByChecksum(checksum string) (*Scene, error) + FindByOSHash(oshash string) (*Scene, error) + // FindByPath(path string) (*Scene, error) + // FindByPerformerID(performerID int) ([]*Scene, error) + // CountByPerformerID(performerID int) (int, error) + // FindByStudioID(studioID int) ([]*Scene, error) + FindByMovieID(movieID int) ([]*Scene, error) + // CountByMovieID(movieID int) (int, error) + // Count() (int, error) + // SizeCount() (string, error) + // CountByStudioID(studioID int) (int, error) + // CountByTagID(tagID int) (int, error) + // CountMissingChecksum() (int, error) + // CountMissingOSHash() (int, error) + // Wall(q *string) ([]*Scene, error) + All() ([]*Scene, error) + // Query(sceneFilter *SceneFilterType, findFilter *FindFilterType) ([]*Scene, int) + // QueryAllByPathRegex(regex string) ([]*Scene, error) + // QueryByPathRegex(findFilter *FindFilterType) ([]*Scene, int) + GetSceneCover(sceneID int) ([]byte, error) +} + +type SceneWriter interface { + Create(newScene Scene) (*Scene, error) + Update(updatedScene ScenePartial) (*Scene, error) + UpdateFull(updatedScene Scene) (*Scene, error) + // IncrementOCounter(id int) (int, error) + // DecrementOCounter(id int) (int, error) + // ResetOCounter(id int) (int, error) + // Destroy(id string) error + // UpdateFormat(id int, format string) error + // UpdateOSHash(id int, oshash string) error + // UpdateChecksum(id int, checksum string) error + UpdateSceneCover(sceneID int, cover []byte) error + // DestroySceneCover(sceneID int) error +} + +type SceneReaderWriter interface { + SceneReader + SceneWriter +} + +func NewSceneReaderWriter(tx *sqlx.Tx) SceneReaderWriter { + return &sceneReaderWriter{ + tx: tx, + qb: NewSceneQueryBuilder(), + } +} + +type sceneReaderWriter struct { + tx *sqlx.Tx + qb SceneQueryBuilder +} + +func (t *sceneReaderWriter) FindMany(ids []int) ([]*Scene, error) { + return t.qb.FindMany(ids) +} + +func (t *sceneReaderWriter) FindByChecksum(checksum string) (*Scene, error) { + return t.qb.FindByChecksum(checksum) +} + +func (t *sceneReaderWriter) FindByOSHash(oshash string) (*Scene, error) { + return t.qb.FindByOSHash(oshash) +} + +func (t *sceneReaderWriter) FindByMovieID(movieID int) ([]*Scene, error) { + return t.qb.FindByMovieID(movieID) +} + +func (t *sceneReaderWriter) All() ([]*Scene, error) { + return t.qb.All() +} + +func (t *sceneReaderWriter) GetSceneCover(sceneID int) ([]byte, error) { + return t.qb.GetSceneCover(sceneID, t.tx) +} + +func (t *sceneReaderWriter) Create(newScene Scene) (*Scene, error) { + return t.qb.Create(newScene, t.tx) +} + +func (t *sceneReaderWriter) Update(updatedScene ScenePartial) (*Scene, error) { + return t.qb.Update(updatedScene, t.tx) +} + +func (t *sceneReaderWriter) UpdateFull(updatedScene Scene) (*Scene, error) { + return t.qb.UpdateFull(updatedScene, t.tx) +} + +func (t *sceneReaderWriter) UpdateSceneCover(sceneID int, cover []byte) error { + return t.qb.UpdateSceneCover(sceneID, cover, t.tx) +} diff --git a/pkg/models/scene_marker.go b/pkg/models/scene_marker.go new file mode 100644 index 000000000..530e00684 --- /dev/null +++ b/pkg/models/scene_marker.go @@ -0,0 +1,50 @@ +package models + +import ( + "github.com/jmoiron/sqlx" +) + +type SceneMarkerReader interface { + // Find(id int) (*SceneMarker, error) + // FindMany(ids []int) ([]*SceneMarker, error) + FindBySceneID(sceneID int) ([]*SceneMarker, error) + // CountByTagID(tagID int) (int, error) + // GetMarkerStrings(q *string, sort *string) ([]*MarkerStringsResultType, error) + // Wall(q *string) ([]*SceneMarker, error) + // Query(sceneMarkerFilter *SceneMarkerFilterType, findFilter *FindFilterType) ([]*SceneMarker, int) +} + +type SceneMarkerWriter interface { + Create(newSceneMarker SceneMarker) (*SceneMarker, error) + Update(updatedSceneMarker SceneMarker) (*SceneMarker, error) + // Destroy(id string) error +} + +type SceneMarkerReaderWriter interface { + SceneMarkerReader + SceneMarkerWriter +} + +func NewSceneMarkerReaderWriter(tx *sqlx.Tx) SceneMarkerReaderWriter { + return &sceneMarkerReaderWriter{ + tx: tx, + qb: NewSceneMarkerQueryBuilder(), + } +} + +type sceneMarkerReaderWriter struct { + tx *sqlx.Tx + qb SceneMarkerQueryBuilder +} + +func (t *sceneMarkerReaderWriter) FindBySceneID(sceneID int) ([]*SceneMarker, error) { + return t.qb.FindBySceneID(sceneID, t.tx) +} + +func (t *sceneMarkerReaderWriter) Create(newSceneMarker SceneMarker) (*SceneMarker, error) { + return t.qb.Create(newSceneMarker, t.tx) +} + +func (t *sceneMarkerReaderWriter) Update(updatedSceneMarker SceneMarker) (*SceneMarker, error) { + return t.qb.Update(updatedSceneMarker, t.tx) +} diff --git a/pkg/models/scraped.go b/pkg/models/scraped.go new file mode 100644 index 000000000..c2167e902 --- /dev/null +++ b/pkg/models/scraped.go @@ -0,0 +1,87 @@ +package models + +import "strconv" + +// MatchScrapedScenePerformer matches the provided performer with the +// performers in the database and sets the ID field if one is found. +func MatchScrapedScenePerformer(p *ScrapedScenePerformer) error { + qb := NewPerformerQueryBuilder() + + performers, err := qb.FindByNames([]string{p.Name}, nil, true) + + if err != nil { + return err + } + + if len(performers) != 1 { + // ignore - cannot match + return nil + } + + id := strconv.Itoa(performers[0].ID) + p.ID = &id + return nil +} + +// MatchScrapedSceneStudio matches the provided studio with the studios +// in the database and sets the ID field if one is found. +func MatchScrapedSceneStudio(s *ScrapedSceneStudio) error { + qb := NewStudioQueryBuilder() + + studio, err := qb.FindByName(s.Name, nil, true) + + if err != nil { + return err + } + + if studio == nil { + // ignore - cannot match + return nil + } + + id := strconv.Itoa(studio.ID) + s.ID = &id + return nil +} + +// MatchScrapedSceneMovie matches the provided movie with the movies +// in the database and sets the ID field if one is found. +func MatchScrapedSceneMovie(m *ScrapedSceneMovie) error { + qb := NewMovieQueryBuilder() + + movies, err := qb.FindByNames([]string{m.Name}, nil, true) + + if err != nil { + return err + } + + if len(movies) != 1 { + // ignore - cannot match + return nil + } + + id := strconv.Itoa(movies[0].ID) + m.ID = &id + return nil +} + +// MatchScrapedSceneTag matches the provided tag with the tags +// in the database and sets the ID field if one is found. +func MatchScrapedSceneTag(s *ScrapedSceneTag) error { + qb := NewTagQueryBuilder() + + tag, err := qb.FindByName(s.Name, nil, true) + + if err != nil { + return err + } + + if tag == nil { + // ignore - cannot match + return nil + } + + id := strconv.Itoa(tag.ID) + s.ID = &id + return nil +} diff --git a/pkg/models/setup_test.go b/pkg/models/setup_test.go index 36c53905d..489cf54fa 100644 --- a/pkg/models/setup_test.go +++ b/pkg/models/setup_test.go @@ -17,21 +17,24 @@ import ( "github.com/stashapp/stash/pkg/database" "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/models/modelstest" "github.com/stashapp/stash/pkg/utils" ) const totalScenes = 12 -const performersNameCase = 3 +const totalImages = 6 +const performersNameCase = 6 const performersNameNoCase = 2 const moviesNameCase = 2 const moviesNameNoCase = 1 -const totalGalleries = 2 +const totalGalleries = 3 const tagsNameNoCase = 2 -const tagsNameCase = 6 -const studiosNameCase = 4 +const tagsNameCase = 9 +const studiosNameCase = 5 const studiosNameNoCase = 1 var sceneIDs []int +var imageIDs []int var performerIDs []int var movieIDs []int var galleryIDs []int @@ -53,13 +56,23 @@ const sceneIdxWithTwoTags = 5 const sceneIdxWithStudio = 6 const sceneIdxWithMarker = 7 +const imageIdxWithGallery = 0 +const imageIdxWithPerformer = 1 +const imageIdxWithTwoPerformers = 2 +const imageIdxWithTag = 3 +const imageIdxWithTwoTags = 4 +const imageIdxWithStudio = 5 + const performerIdxWithScene = 0 const performerIdx1WithScene = 1 const performerIdx2WithScene = 2 +const performerIdxWithImage = 3 +const performerIdx1WithImage = 4 +const performerIdx2WithImage = 5 // performers with dup names start from the end -const performerIdx1WithDupName = 3 -const performerIdxWithDupName = 4 +const performerIdx1WithDupName = 6 +const performerIdxWithDupName = 7 const movieIdxWithScene = 0 const movieIdxWithStudio = 1 @@ -68,25 +81,30 @@ const movieIdxWithStudio = 1 const movieIdxWithDupName = 2 const galleryIdxWithScene = 0 +const galleryIdxWithImage = 1 const tagIdxWithScene = 0 const tagIdx1WithScene = 1 const tagIdx2WithScene = 2 const tagIdxWithPrimaryMarker = 3 const tagIdxWithMarker = 4 -const tagIdxWithImage = 5 +const tagIdxWithCoverImage = 5 +const tagIdxWithImage = 6 +const tagIdx1WithImage = 7 +const tagIdx2WithImage = 8 // tags with dup names start from the end -const tagIdx1WithDupName = 6 -const tagIdxWithDupName = 7 +const tagIdx1WithDupName = 9 +const tagIdxWithDupName = 10 const studioIdxWithScene = 0 const studioIdxWithMovie = 1 const studioIdxWithChildStudio = 2 const studioIdxWithParentStudio = 3 +const studioIdxWithImage = 4 // studios with dup names start from the end -const studioIdxWithDupName = 4 +const studioIdxWithDupName = 5 const markerIdxWithScene = 0 @@ -144,6 +162,11 @@ func populateDB() error { return err } + if err := createImages(tx, totalImages); err != nil { + tx.Rollback() + return err + } + if err := createGalleries(tx, totalGalleries); err != nil { tx.Rollback() return err @@ -164,7 +187,7 @@ func populateDB() error { return err } - if err := addTagImage(tx, tagIdxWithImage); err != nil { + if err := addTagImage(tx, tagIdxWithCoverImage); err != nil { tx.Rollback() return err } @@ -207,6 +230,26 @@ func populateDB() error { return err } + if err := linkImageGallery(tx, imageIdxWithGallery, galleryIdxWithImage); err != nil { + tx.Rollback() + return err + } + + if err := linkImagePerformers(tx); err != nil { + tx.Rollback() + return err + } + + if err := linkImageTags(tx); err != nil { + tx.Rollback() + return err + } + + if err := linkImageStudio(tx, imageIdxWithStudio, studioIdxWithImage); err != nil { + tx.Rollback() + return err + } + if err := linkMovieStudio(tx, movieIdxWithStudio, studioIdxWithMovie); err != nil { tx.Rollback() return err @@ -233,12 +276,12 @@ func getSceneStringValue(index int, field string) string { return fmt.Sprintf("scene_%04d_%s", index, field) } -func getSceneRating(index int) sql.NullInt64 { +func getRating(index int) sql.NullInt64 { rating := index % 6 return sql.NullInt64{Int64: int64(rating), Valid: rating > 0} } -func getSceneOCounter(index int) int { +func getOCounter(index int) int { return index % 3 } @@ -252,7 +295,7 @@ func getSceneDuration(index int) sql.NullFloat64 { } } -func getSceneHeight(index int) sql.NullInt64 { +func getHeight(index int) sql.NullInt64 { heights := []int64{0, 200, 240, 300, 480, 700, 720, 800, 1080, 1500, 2160, 3000} height := heights[index%len(heights)] return sql.NullInt64{ @@ -279,10 +322,10 @@ func createScenes(tx *sqlx.Tx, n int) error { Title: sql.NullString{String: getSceneStringValue(i, titleField), Valid: true}, Checksum: sql.NullString{String: getSceneStringValue(i, checksumField), Valid: true}, Details: sql.NullString{String: getSceneStringValue(i, "Details"), Valid: true}, - Rating: getSceneRating(i), - OCounter: getSceneOCounter(i), + Rating: getRating(i), + OCounter: getOCounter(i), Duration: getSceneDuration(i), - Height: getSceneHeight(i), + Height: getHeight(i), Date: getSceneDate(i), } @@ -298,6 +341,35 @@ func createScenes(tx *sqlx.Tx, n int) error { return nil } +func getImageStringValue(index int, field string) string { + return fmt.Sprintf("image_%04d_%s", index, field) +} + +func createImages(tx *sqlx.Tx, n int) error { + qb := models.NewImageQueryBuilder() + + for i := 0; i < n; i++ { + image := models.Image{ + Path: getImageStringValue(i, pathField), + Title: sql.NullString{String: getImageStringValue(i, titleField), Valid: true}, + Checksum: getImageStringValue(i, checksumField), + Rating: getRating(i), + OCounter: getOCounter(i), + Height: getHeight(i), + } + + created, err := qb.Create(image, tx) + + if err != nil { + return fmt.Errorf("Error creating image %v+: %s", image, err.Error()) + } + + imageIDs = append(imageIDs, created.ID) + } + + return nil +} + func getGalleryStringValue(index int, field string) string { return "gallery_" + strconv.FormatInt(int64(index), 10) + "_" + field } @@ -307,7 +379,7 @@ func createGalleries(tx *sqlx.Tx, n int) error { for i := 0; i < n; i++ { gallery := models.Gallery{ - Path: getGalleryStringValue(i, pathField), + Path: modelstest.NullString(getGalleryStringValue(i, pathField)), Checksum: getGalleryStringValue(i, checksumField), } @@ -591,7 +663,7 @@ func linkScenePerformer(tx *sqlx.Tx, sceneIndex, performerIndex int) error { func linkSceneGallery(tx *sqlx.Tx, sceneIndex, galleryIndex int) error { gqb := models.NewGalleryQueryBuilder() - gallery, err := gqb.Find(galleryIDs[galleryIndex]) + gallery, err := gqb.Find(galleryIDs[galleryIndex], nil) if err != nil { return fmt.Errorf("error finding gallery: %s", err.Error()) @@ -640,6 +712,68 @@ func linkSceneStudio(tx *sqlx.Tx, sceneIndex, studioIndex int) error { return err } +func linkImageGallery(tx *sqlx.Tx, imageIndex, galleryIndex int) error { + jqb := models.NewJoinsQueryBuilder() + + _, err := jqb.AddImageGallery(imageIDs[imageIndex], galleryIDs[galleryIndex], tx) + + return err +} + +func linkImageTags(tx *sqlx.Tx) error { + if err := linkImageTag(tx, imageIdxWithTag, tagIdxWithImage); err != nil { + return err + } + if err := linkImageTag(tx, imageIdxWithTwoTags, tagIdx1WithImage); err != nil { + return err + } + if err := linkImageTag(tx, imageIdxWithTwoTags, tagIdx2WithImage); err != nil { + return err + } + + return nil +} + +func linkImageTag(tx *sqlx.Tx, imageIndex, tagIndex int) error { + jqb := models.NewJoinsQueryBuilder() + + _, err := jqb.AddImageTag(imageIDs[imageIndex], tagIDs[tagIndex], tx) + return err +} + +func linkImageStudio(tx *sqlx.Tx, imageIndex, studioIndex int) error { + sqb := models.NewImageQueryBuilder() + + image := models.ImagePartial{ + ID: imageIDs[imageIndex], + StudioID: &sql.NullInt64{Int64: int64(studioIDs[studioIndex]), Valid: true}, + } + _, err := sqb.Update(image, tx) + + return err +} + +func linkImagePerformers(tx *sqlx.Tx) error { + if err := linkImagePerformer(tx, imageIdxWithPerformer, performerIdxWithImage); err != nil { + return err + } + if err := linkImagePerformer(tx, imageIdxWithTwoPerformers, performerIdx1WithImage); err != nil { + return err + } + if err := linkImagePerformer(tx, imageIdxWithTwoPerformers, performerIdx2WithImage); err != nil { + return err + } + + return nil +} + +func linkImagePerformer(tx *sqlx.Tx, imageIndex, performerIndex int) error { + jqb := models.NewJoinsQueryBuilder() + + _, err := jqb.AddPerformerImage(imageIDs[imageIndex], performerIDs[performerIndex], tx) + return err +} + func linkMovieStudio(tx *sqlx.Tx, movieIndex, studioIndex int) error { mqb := models.NewMovieQueryBuilder() diff --git a/pkg/models/sqlite_date.go b/pkg/models/sqlite_date.go index c3fb6d012..bd9ebf8cd 100644 --- a/pkg/models/sqlite_date.go +++ b/pkg/models/sqlite_date.go @@ -2,9 +2,10 @@ package models import ( "database/sql/driver" + "time" + "github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/utils" - "time" ) type SQLiteDate struct { @@ -32,6 +33,11 @@ func (t *SQLiteDate) Scan(value interface{}) error { // Value implements the driver Valuer interface. func (t SQLiteDate) Value() (driver.Value, error) { + // handle empty string + if t.String == "" { + return "", nil + } + result, err := utils.ParseDateStringAsFormat(t.String, "2006-01-02") if err != nil { logger.Debugf("sqlite date conversion error: %s", err.Error()) diff --git a/pkg/models/sqlite_timestamp.go b/pkg/models/sqlite_timestamp.go index b8c84f70a..d3383729a 100644 --- a/pkg/models/sqlite_timestamp.go +++ b/pkg/models/sqlite_timestamp.go @@ -19,3 +19,31 @@ func (t *SQLiteTimestamp) Scan(value interface{}) error { func (t SQLiteTimestamp) Value() (driver.Value, error) { return t.Timestamp.Format(time.RFC3339), nil } + +type NullSQLiteTimestamp struct { + Timestamp time.Time + Valid bool +} + +// Scan implements the Scanner interface. +func (t *NullSQLiteTimestamp) Scan(value interface{}) error { + var ok bool + t.Timestamp, ok = value.(time.Time) + if !ok { + t.Timestamp = time.Time{} + t.Valid = false + return nil + } + + t.Valid = true + return nil +} + +// Value implements the driver Valuer interface. +func (t NullSQLiteTimestamp) Value() (driver.Value, error) { + if t.Timestamp.IsZero() { + return nil, nil + } + + return t.Timestamp.Format(time.RFC3339), nil +} diff --git a/pkg/models/studio.go b/pkg/models/studio.go new file mode 100644 index 000000000..8916ad3a4 --- /dev/null +++ b/pkg/models/studio.go @@ -0,0 +1,80 @@ +package models + +import ( + "github.com/jmoiron/sqlx" +) + +type StudioReader interface { + Find(id int) (*Studio, error) + FindMany(ids []int) ([]*Studio, error) + // FindChildren(id int) ([]*Studio, error) + // FindBySceneID(sceneID int) (*Studio, error) + FindByName(name string, nocase bool) (*Studio, error) + // Count() (int, error) + All() ([]*Studio, error) + // AllSlim() ([]*Studio, error) + // Query(studioFilter *StudioFilterType, findFilter *FindFilterType) ([]*Studio, int) + GetStudioImage(studioID int) ([]byte, error) +} + +type StudioWriter interface { + Create(newStudio Studio) (*Studio, error) + Update(updatedStudio StudioPartial) (*Studio, error) + UpdateFull(updatedStudio Studio) (*Studio, error) + // Destroy(id string) error + UpdateStudioImage(studioID int, image []byte) error + // DestroyStudioImage(studioID int) error +} + +type StudioReaderWriter interface { + StudioReader + StudioWriter +} + +func NewStudioReaderWriter(tx *sqlx.Tx) StudioReaderWriter { + return &studioReaderWriter{ + tx: tx, + qb: NewStudioQueryBuilder(), + } +} + +type studioReaderWriter struct { + tx *sqlx.Tx + qb StudioQueryBuilder +} + +func (t *studioReaderWriter) Find(id int) (*Studio, error) { + return t.qb.Find(id, t.tx) +} + +func (t *studioReaderWriter) FindMany(ids []int) ([]*Studio, error) { + return t.qb.FindMany(ids) +} + +func (t *studioReaderWriter) FindByName(name string, nocase bool) (*Studio, error) { + return t.qb.FindByName(name, t.tx, nocase) +} + +func (t *studioReaderWriter) All() ([]*Studio, error) { + return t.qb.All() +} + +func (t *studioReaderWriter) GetStudioImage(studioID int) ([]byte, error) { + return t.qb.GetStudioImage(studioID, t.tx) +} + +func (t *studioReaderWriter) Create(newStudio Studio) (*Studio, error) { + return t.qb.Create(newStudio, t.tx) +} + +func (t *studioReaderWriter) Update(updatedStudio StudioPartial) (*Studio, error) { + return t.qb.Update(updatedStudio, t.tx) +} + +func (t *studioReaderWriter) UpdateFull(updatedStudio Studio) (*Studio, error) { + return t.qb.UpdateFull(updatedStudio, t.tx) +} + +func (t *studioReaderWriter) UpdateStudioImage(studioID int, image []byte) error { + return t.qb.UpdateStudioImage(studioID, image, t.tx) +} diff --git a/pkg/models/tag.go b/pkg/models/tag.go new file mode 100644 index 000000000..5816f60b7 --- /dev/null +++ b/pkg/models/tag.go @@ -0,0 +1,98 @@ +package models + +import ( + "github.com/jmoiron/sqlx" +) + +type TagReader interface { + Find(id int) (*Tag, error) + FindMany(ids []int) ([]*Tag, error) + FindBySceneID(sceneID int) ([]*Tag, error) + FindBySceneMarkerID(sceneMarkerID int) ([]*Tag, error) + FindByImageID(imageID int) ([]*Tag, error) + FindByGalleryID(galleryID int) ([]*Tag, error) + FindByName(name string, nocase bool) (*Tag, error) + FindByNames(names []string, nocase bool) ([]*Tag, error) + // Count() (int, error) + All() ([]*Tag, error) + // AllSlim() ([]*Tag, error) + // Query(tagFilter *TagFilterType, findFilter *FindFilterType) ([]*Tag, int, error) + GetTagImage(tagID int) ([]byte, error) +} + +type TagWriter interface { + Create(newTag Tag) (*Tag, error) + Update(updatedTag Tag) (*Tag, error) + // Destroy(id string) error + UpdateTagImage(tagID int, image []byte) error + // DestroyTagImage(tagID int) error +} + +type TagReaderWriter interface { + TagReader + TagWriter +} + +func NewTagReaderWriter(tx *sqlx.Tx) TagReaderWriter { + return &tagReaderWriter{ + tx: tx, + qb: NewTagQueryBuilder(), + } +} + +type tagReaderWriter struct { + tx *sqlx.Tx + qb TagQueryBuilder +} + +func (t *tagReaderWriter) Find(id int) (*Tag, error) { + return t.qb.Find(id, t.tx) +} + +func (t *tagReaderWriter) FindMany(ids []int) ([]*Tag, error) { + return t.qb.FindMany(ids) +} + +func (t *tagReaderWriter) All() ([]*Tag, error) { + return t.qb.All() +} + +func (t *tagReaderWriter) FindBySceneMarkerID(sceneMarkerID int) ([]*Tag, error) { + return t.qb.FindBySceneMarkerID(sceneMarkerID, t.tx) +} + +func (t *tagReaderWriter) FindByName(name string, nocase bool) (*Tag, error) { + return t.qb.FindByName(name, t.tx, nocase) +} + +func (t *tagReaderWriter) FindByNames(names []string, nocase bool) ([]*Tag, error) { + return t.qb.FindByNames(names, t.tx, nocase) +} + +func (t *tagReaderWriter) GetTagImage(tagID int) ([]byte, error) { + return t.qb.GetTagImage(tagID, t.tx) +} + +func (t *tagReaderWriter) FindBySceneID(sceneID int) ([]*Tag, error) { + return t.qb.FindBySceneID(sceneID, t.tx) +} + +func (t *tagReaderWriter) FindByImageID(imageID int) ([]*Tag, error) { + return t.qb.FindByImageID(imageID, t.tx) +} + +func (t *tagReaderWriter) FindByGalleryID(imageID int) ([]*Tag, error) { + return t.qb.FindByGalleryID(imageID, t.tx) +} + +func (t *tagReaderWriter) Create(newTag Tag) (*Tag, error) { + return t.qb.Create(newTag, t.tx) +} + +func (t *tagReaderWriter) Update(updatedTag Tag) (*Tag, error) { + return t.qb.Update(updatedTag, t.tx) +} + +func (t *tagReaderWriter) UpdateTagImage(tagID int, image []byte) error { + return t.qb.UpdateTagImage(tagID, image, t.tx) +} diff --git a/pkg/movie/export.go b/pkg/movie/export.go new file mode 100644 index 000000000..9ffd31a2f --- /dev/null +++ b/pkg/movie/export.go @@ -0,0 +1,76 @@ +package movie + +import ( + "fmt" + + "github.com/stashapp/stash/pkg/manager/jsonschema" + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/utils" +) + +// ToJSON converts a Movie into its JSON equivalent. +func ToJSON(reader models.MovieReader, studioReader models.StudioReader, movie *models.Movie) (*jsonschema.Movie, error) { + newMovieJSON := jsonschema.Movie{ + CreatedAt: models.JSONTime{Time: movie.CreatedAt.Timestamp}, + UpdatedAt: models.JSONTime{Time: movie.UpdatedAt.Timestamp}, + } + + if movie.Name.Valid { + newMovieJSON.Name = movie.Name.String + } + if movie.Aliases.Valid { + newMovieJSON.Aliases = movie.Aliases.String + } + if movie.Date.Valid { + newMovieJSON.Date = utils.GetYMDFromDatabaseDate(movie.Date.String) + } + if movie.Rating.Valid { + newMovieJSON.Rating = int(movie.Rating.Int64) + } + if movie.Duration.Valid { + newMovieJSON.Duration = int(movie.Duration.Int64) + } + + if movie.Director.Valid { + newMovieJSON.Director = movie.Director.String + } + + if movie.Synopsis.Valid { + newMovieJSON.Synopsis = movie.Synopsis.String + } + + if movie.URL.Valid { + newMovieJSON.URL = movie.URL.String + } + + if movie.StudioID.Valid { + studio, err := studioReader.Find(int(movie.StudioID.Int64)) + if err != nil { + return nil, fmt.Errorf("error getting movie studio: %s", err.Error()) + } + + if studio != nil { + newMovieJSON.Studio = studio.Name.String + } + } + + frontImage, err := reader.GetFrontImage(movie.ID) + if err != nil { + return nil, fmt.Errorf("error getting movie front image: %s", err.Error()) + } + + if len(frontImage) > 0 { + newMovieJSON.FrontImage = utils.GetBase64StringFromData(frontImage) + } + + backImage, err := reader.GetBackImage(movie.ID) + if err != nil { + return nil, fmt.Errorf("error getting movie back image: %s", err.Error()) + } + + if len(backImage) > 0 { + newMovieJSON.BackImage = utils.GetBase64StringFromData(backImage) + } + + return &newMovieJSON, nil +} diff --git a/pkg/movie/export_test.go b/pkg/movie/export_test.go new file mode 100644 index 000000000..c5298dc82 --- /dev/null +++ b/pkg/movie/export_test.go @@ -0,0 +1,222 @@ +package movie + +import ( + "database/sql" + "errors" + + "github.com/stashapp/stash/pkg/manager/jsonschema" + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/models/mocks" + "github.com/stashapp/stash/pkg/models/modelstest" + "github.com/stretchr/testify/assert" + + "testing" + "time" +) + +const ( + movieID = 1 + emptyID = 2 + errFrontImageID = 3 + errBackImageID = 4 + errStudioMovieID = 5 + missingStudioMovieID = 6 +) + +const ( + studioID = 1 + missingStudioID = 2 + errStudioID = 3 +) + +const movieName = "testMovie" +const movieAliases = "aliases" + +var date = models.SQLiteDate{ + String: "2001-01-01", + Valid: true, +} + +const rating = 5 +const duration = 100 +const director = "director" +const synopsis = "synopsis" +const url = "url" + +const studioName = "studio" + +const frontImage = "ZnJvbnRJbWFnZUJ5dGVz" +const backImage = "YmFja0ltYWdlQnl0ZXM=" + +var frontImageBytes = []byte("frontImageBytes") +var backImageBytes = []byte("backImageBytes") + +var studio models.Studio = models.Studio{ + Name: modelstest.NullString(studioName), +} + +var createTime time.Time = time.Date(2001, 01, 01, 0, 0, 0, 0, time.UTC) +var updateTime time.Time = time.Date(2002, 01, 01, 0, 0, 0, 0, time.UTC) + +func createFullMovie(id int, studioID int) models.Movie { + return models.Movie{ + ID: id, + Name: modelstest.NullString(movieName), + Aliases: modelstest.NullString(movieAliases), + Date: date, + Rating: sql.NullInt64{ + Int64: rating, + Valid: true, + }, + Duration: sql.NullInt64{ + Int64: duration, + Valid: true, + }, + Director: modelstest.NullString(director), + Synopsis: modelstest.NullString(synopsis), + URL: modelstest.NullString(url), + StudioID: sql.NullInt64{ + Int64: int64(studioID), + Valid: true, + }, + CreatedAt: models.SQLiteTimestamp{ + Timestamp: createTime, + }, + UpdatedAt: models.SQLiteTimestamp{ + Timestamp: updateTime, + }, + } +} + +func createEmptyMovie(id int) models.Movie { + return models.Movie{ + ID: id, + CreatedAt: models.SQLiteTimestamp{ + Timestamp: createTime, + }, + UpdatedAt: models.SQLiteTimestamp{ + Timestamp: updateTime, + }, + } +} + +func createFullJSONMovie(studio, frontImage, backImage string) *jsonschema.Movie { + return &jsonschema.Movie{ + Name: movieName, + Aliases: movieAliases, + Date: date.String, + Rating: rating, + Duration: duration, + Director: director, + Synopsis: synopsis, + URL: url, + Studio: studio, + FrontImage: frontImage, + BackImage: backImage, + CreatedAt: models.JSONTime{ + Time: createTime, + }, + UpdatedAt: models.JSONTime{ + Time: updateTime, + }, + } +} + +func createEmptyJSONMovie() *jsonschema.Movie { + return &jsonschema.Movie{ + CreatedAt: models.JSONTime{ + Time: createTime, + }, + UpdatedAt: models.JSONTime{ + Time: updateTime, + }, + } +} + +type testScenario struct { + movie models.Movie + expected *jsonschema.Movie + err bool +} + +var scenarios []testScenario + +func initTestTable() { + scenarios = []testScenario{ + testScenario{ + createFullMovie(movieID, studioID), + createFullJSONMovie(studioName, frontImage, backImage), + false, + }, + testScenario{ + createEmptyMovie(emptyID), + createEmptyJSONMovie(), + false, + }, + testScenario{ + createFullMovie(errFrontImageID, studioID), + nil, + true, + }, + testScenario{ + createFullMovie(errBackImageID, studioID), + nil, + true, + }, + testScenario{ + createFullMovie(errStudioMovieID, errStudioID), + nil, + true, + }, + testScenario{ + createFullMovie(missingStudioMovieID, missingStudioID), + createFullJSONMovie("", frontImage, backImage), + false, + }, + } +} + +func TestToJSON(t *testing.T) { + initTestTable() + + mockMovieReader := &mocks.MovieReaderWriter{} + + imageErr := errors.New("error getting image") + + mockMovieReader.On("GetFrontImage", movieID).Return(frontImageBytes, nil).Once() + mockMovieReader.On("GetFrontImage", missingStudioMovieID).Return(frontImageBytes, nil).Once() + mockMovieReader.On("GetFrontImage", emptyID).Return(nil, nil).Once().Maybe() + mockMovieReader.On("GetFrontImage", errFrontImageID).Return(nil, imageErr).Once() + mockMovieReader.On("GetFrontImage", errBackImageID).Return(frontImageBytes, nil).Once() + + mockMovieReader.On("GetBackImage", movieID).Return(backImageBytes, nil).Once() + mockMovieReader.On("GetBackImage", missingStudioMovieID).Return(backImageBytes, nil).Once() + mockMovieReader.On("GetBackImage", emptyID).Return(nil, nil).Once() + mockMovieReader.On("GetBackImage", errBackImageID).Return(nil, imageErr).Once() + mockMovieReader.On("GetBackImage", errFrontImageID).Return(backImageBytes, nil).Maybe() + mockMovieReader.On("GetBackImage", errStudioMovieID).Return(backImageBytes, nil).Maybe() + + mockStudioReader := &mocks.StudioReaderWriter{} + + studioErr := errors.New("error getting studio") + + mockStudioReader.On("Find", studioID).Return(&studio, nil) + mockStudioReader.On("Find", missingStudioID).Return(nil, nil) + mockStudioReader.On("Find", errStudioID).Return(nil, studioErr) + + for i, s := range scenarios { + movie := s.movie + json, err := ToJSON(mockMovieReader, mockStudioReader, &movie) + + if !s.err && err != nil { + t.Errorf("[%d] unexpected error: %s", i, err.Error()) + } else if s.err && err == nil { + t.Errorf("[%d] expected error not returned", i) + } else { + assert.Equal(t, s.expected, json, "[%d]", i) + } + } + + mockMovieReader.AssertExpectations(t) + mockStudioReader.AssertExpectations(t) +} diff --git a/pkg/movie/import.go b/pkg/movie/import.go new file mode 100644 index 000000000..7e1065df6 --- /dev/null +++ b/pkg/movie/import.go @@ -0,0 +1,166 @@ +package movie + +import ( + "database/sql" + "fmt" + + "github.com/stashapp/stash/pkg/manager/jsonschema" + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/utils" +) + +type Importer struct { + ReaderWriter models.MovieReaderWriter + StudioWriter models.StudioReaderWriter + Input jsonschema.Movie + MissingRefBehaviour models.ImportMissingRefEnum + + movie models.Movie + frontImageData []byte + backImageData []byte +} + +func (i *Importer) PreImport() error { + i.movie = i.movieJSONToMovie(i.Input) + + if err := i.populateStudio(); err != nil { + return err + } + + var err error + if len(i.Input.FrontImage) > 0 { + _, i.frontImageData, err = utils.ProcessBase64Image(i.Input.FrontImage) + if err != nil { + return fmt.Errorf("invalid front_image: %s", err.Error()) + } + } + if len(i.Input.BackImage) > 0 { + _, i.backImageData, err = utils.ProcessBase64Image(i.Input.BackImage) + if err != nil { + return fmt.Errorf("invalid back_image: %s", err.Error()) + } + } + + return nil +} + +func (i *Importer) movieJSONToMovie(movieJSON jsonschema.Movie) models.Movie { + checksum := utils.MD5FromString(movieJSON.Name) + + newMovie := models.Movie{ + Checksum: checksum, + Name: sql.NullString{String: movieJSON.Name, Valid: true}, + Aliases: sql.NullString{String: movieJSON.Aliases, Valid: true}, + Date: models.SQLiteDate{String: movieJSON.Date, Valid: true}, + Director: sql.NullString{String: movieJSON.Director, Valid: true}, + Synopsis: sql.NullString{String: movieJSON.Synopsis, Valid: true}, + URL: sql.NullString{String: movieJSON.URL, Valid: true}, + CreatedAt: models.SQLiteTimestamp{Timestamp: movieJSON.CreatedAt.GetTime()}, + UpdatedAt: models.SQLiteTimestamp{Timestamp: movieJSON.UpdatedAt.GetTime()}, + } + + if movieJSON.Rating != 0 { + newMovie.Rating = sql.NullInt64{Int64: int64(movieJSON.Rating), Valid: true} + } + + if movieJSON.Duration != 0 { + newMovie.Duration = sql.NullInt64{Int64: int64(movieJSON.Duration), Valid: true} + } + + return newMovie +} + +func (i *Importer) populateStudio() error { + if i.Input.Studio != "" { + studio, err := i.StudioWriter.FindByName(i.Input.Studio, false) + if err != nil { + return fmt.Errorf("error finding studio by name: %s", err.Error()) + } + + if studio == nil { + if i.MissingRefBehaviour == models.ImportMissingRefEnumFail { + return fmt.Errorf("movie studio '%s' not found", i.Input.Studio) + } + + if i.MissingRefBehaviour == models.ImportMissingRefEnumIgnore { + return nil + } + + if i.MissingRefBehaviour == models.ImportMissingRefEnumCreate { + studioID, err := i.createStudio(i.Input.Studio) + if err != nil { + return err + } + i.movie.StudioID = sql.NullInt64{ + Int64: int64(studioID), + Valid: true, + } + } + } else { + i.movie.StudioID = sql.NullInt64{Int64: int64(studio.ID), Valid: true} + } + } + + return nil +} + +func (i *Importer) createStudio(name string) (int, error) { + newStudio := *models.NewStudio(name) + + created, err := i.StudioWriter.Create(newStudio) + if err != nil { + return 0, err + } + + return created.ID, nil +} + +func (i *Importer) PostImport(id int) error { + if len(i.frontImageData) > 0 { + if err := i.ReaderWriter.UpdateMovieImages(id, i.frontImageData, i.backImageData); err != nil { + return fmt.Errorf("error setting movie images: %s", err.Error()) + } + } + + return nil +} + +func (i *Importer) Name() string { + return i.Input.Name +} + +func (i *Importer) FindExistingID() (*int, error) { + const nocase = false + existing, err := i.ReaderWriter.FindByName(i.Name(), nocase) + if err != nil { + return nil, err + } + + if existing != nil { + id := existing.ID + return &id, nil + } + + return nil, nil +} + +func (i *Importer) Create() (*int, error) { + created, err := i.ReaderWriter.Create(i.movie) + if err != nil { + return nil, fmt.Errorf("error creating movie: %s", err.Error()) + } + + id := created.ID + return &id, nil +} + +func (i *Importer) Update(id int) error { + movie := i.movie + movie.ID = id + _, err := i.ReaderWriter.UpdateFull(movie) + if err != nil { + return fmt.Errorf("error updating existing movie: %s", err.Error()) + } + + return nil +} diff --git a/pkg/movie/import_test.go b/pkg/movie/import_test.go new file mode 100644 index 000000000..afdc484d1 --- /dev/null +++ b/pkg/movie/import_test.go @@ -0,0 +1,278 @@ +package movie + +import ( + "errors" + "testing" + + "github.com/stashapp/stash/pkg/manager/jsonschema" + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/models/mocks" + "github.com/stashapp/stash/pkg/models/modelstest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +const invalidImage = "aW1hZ2VCeXRlcw&&" + +const ( + movieNameErr = "movieNameErr" + existingMovieName = "existingMovieName" + + existingMovieID = 100 + existingStudioID = 101 + + existingStudioName = "existingStudioName" + existingStudioErr = "existingStudioErr" + missingStudioName = "existingStudioName" + + errImageID = 3 +) + +func TestImporterName(t *testing.T) { + i := Importer{ + Input: jsonschema.Movie{ + Name: movieName, + }, + } + + assert.Equal(t, movieName, i.Name()) +} + +func TestImporterPreImport(t *testing.T) { + i := Importer{ + Input: jsonschema.Movie{ + Name: movieName, + FrontImage: invalidImage, + }, + } + + err := i.PreImport() + assert.NotNil(t, err) + + i.Input.FrontImage = frontImage + i.Input.BackImage = invalidImage + + err = i.PreImport() + assert.NotNil(t, err) + + i.Input.BackImage = "" + + err = i.PreImport() + assert.Nil(t, err) + + i.Input.BackImage = backImage + + err = i.PreImport() + assert.Nil(t, err) +} + +func TestImporterPreImportWithStudio(t *testing.T) { + studioReaderWriter := &mocks.StudioReaderWriter{} + + i := Importer{ + StudioWriter: studioReaderWriter, + Input: jsonschema.Movie{ + Name: movieName, + FrontImage: frontImage, + Studio: existingStudioName, + Rating: 5, + Duration: 10, + }, + } + + studioReaderWriter.On("FindByName", existingStudioName, false).Return(&models.Studio{ + ID: existingStudioID, + }, nil).Once() + studioReaderWriter.On("FindByName", existingStudioErr, false).Return(nil, errors.New("FindByName error")).Once() + + err := i.PreImport() + assert.Nil(t, err) + assert.Equal(t, int64(existingStudioID), i.movie.StudioID.Int64) + + i.Input.Studio = existingStudioErr + err = i.PreImport() + assert.NotNil(t, err) + + studioReaderWriter.AssertExpectations(t) +} + +func TestImporterPreImportWithMissingStudio(t *testing.T) { + studioReaderWriter := &mocks.StudioReaderWriter{} + + i := Importer{ + StudioWriter: studioReaderWriter, + Input: jsonschema.Movie{ + Name: movieName, + FrontImage: frontImage, + Studio: missingStudioName, + }, + MissingRefBehaviour: models.ImportMissingRefEnumFail, + } + + studioReaderWriter.On("FindByName", missingStudioName, false).Return(nil, nil).Times(3) + studioReaderWriter.On("Create", mock.AnythingOfType("models.Studio")).Return(&models.Studio{ + ID: existingStudioID, + }, nil) + + err := i.PreImport() + assert.NotNil(t, err) + + i.MissingRefBehaviour = models.ImportMissingRefEnumIgnore + err = i.PreImport() + assert.Nil(t, err) + + i.MissingRefBehaviour = models.ImportMissingRefEnumCreate + err = i.PreImport() + assert.Nil(t, err) + assert.Equal(t, int64(existingStudioID), i.movie.StudioID.Int64) + + studioReaderWriter.AssertExpectations(t) +} + +func TestImporterPreImportWithMissingStudioCreateErr(t *testing.T) { + studioReaderWriter := &mocks.StudioReaderWriter{} + + i := Importer{ + StudioWriter: studioReaderWriter, + Input: jsonschema.Movie{ + Name: movieName, + FrontImage: frontImage, + Studio: missingStudioName, + }, + MissingRefBehaviour: models.ImportMissingRefEnumCreate, + } + + studioReaderWriter.On("FindByName", missingStudioName, false).Return(nil, nil).Once() + studioReaderWriter.On("Create", mock.AnythingOfType("models.Studio")).Return(nil, errors.New("Create error")) + + err := i.PreImport() + assert.NotNil(t, err) +} + +func TestImporterPostImport(t *testing.T) { + readerWriter := &mocks.MovieReaderWriter{} + + i := Importer{ + ReaderWriter: readerWriter, + frontImageData: frontImageBytes, + backImageData: backImageBytes, + } + + updateMovieImageErr := errors.New("UpdateMovieImage error") + + readerWriter.On("UpdateMovieImages", movieID, frontImageBytes, backImageBytes).Return(nil).Once() + readerWriter.On("UpdateMovieImages", errImageID, frontImageBytes, backImageBytes).Return(updateMovieImageErr).Once() + + err := i.PostImport(movieID) + assert.Nil(t, err) + + err = i.PostImport(errImageID) + assert.NotNil(t, err) + + readerWriter.AssertExpectations(t) +} + +func TestImporterFindExistingID(t *testing.T) { + readerWriter := &mocks.MovieReaderWriter{} + + i := Importer{ + ReaderWriter: readerWriter, + Input: jsonschema.Movie{ + Name: movieName, + }, + } + + errFindByName := errors.New("FindByName error") + readerWriter.On("FindByName", movieName, false).Return(nil, nil).Once() + readerWriter.On("FindByName", existingMovieName, false).Return(&models.Movie{ + ID: existingMovieID, + }, nil).Once() + readerWriter.On("FindByName", movieNameErr, false).Return(nil, errFindByName).Once() + + id, err := i.FindExistingID() + assert.Nil(t, id) + assert.Nil(t, err) + + i.Input.Name = existingMovieName + id, err = i.FindExistingID() + assert.Equal(t, existingMovieID, *id) + assert.Nil(t, err) + + i.Input.Name = movieNameErr + id, err = i.FindExistingID() + assert.Nil(t, id) + assert.NotNil(t, err) + + readerWriter.AssertExpectations(t) +} + +func TestCreate(t *testing.T) { + readerWriter := &mocks.MovieReaderWriter{} + + movie := models.Movie{ + Name: modelstest.NullString(movieName), + } + + movieErr := models.Movie{ + Name: modelstest.NullString(movieNameErr), + } + + i := Importer{ + ReaderWriter: readerWriter, + movie: movie, + } + + errCreate := errors.New("Create error") + readerWriter.On("Create", movie).Return(&models.Movie{ + ID: movieID, + }, nil).Once() + readerWriter.On("Create", movieErr).Return(nil, errCreate).Once() + + id, err := i.Create() + assert.Equal(t, movieID, *id) + assert.Nil(t, err) + + i.movie = movieErr + id, err = i.Create() + assert.Nil(t, id) + assert.NotNil(t, err) + + readerWriter.AssertExpectations(t) +} + +func TestUpdate(t *testing.T) { + readerWriter := &mocks.MovieReaderWriter{} + + movie := models.Movie{ + Name: modelstest.NullString(movieName), + } + + movieErr := models.Movie{ + Name: modelstest.NullString(movieNameErr), + } + + i := Importer{ + ReaderWriter: readerWriter, + movie: movie, + } + + errUpdate := errors.New("Update error") + + // id needs to be set for the mock input + movie.ID = movieID + readerWriter.On("UpdateFull", movie).Return(nil, nil).Once() + + err := i.Update(movieID) + assert.Nil(t, err) + + i.movie = movieErr + + // need to set id separately + movieErr.ID = errImageID + readerWriter.On("UpdateFull", movieErr).Return(nil, errUpdate).Once() + + err = i.Update(errImageID) + assert.NotNil(t, err) + + readerWriter.AssertExpectations(t) +} diff --git a/pkg/performer/export.go b/pkg/performer/export.go new file mode 100644 index 000000000..6f9b44ee8 --- /dev/null +++ b/pkg/performer/export.go @@ -0,0 +1,100 @@ +package performer + +import ( + "fmt" + + "github.com/stashapp/stash/pkg/manager/jsonschema" + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/utils" +) + +// ToJSON converts a Performer object into its JSON equivalent. +func ToJSON(reader models.PerformerReader, performer *models.Performer) (*jsonschema.Performer, error) { + newPerformerJSON := jsonschema.Performer{ + CreatedAt: models.JSONTime{Time: performer.CreatedAt.Timestamp}, + UpdatedAt: models.JSONTime{Time: performer.UpdatedAt.Timestamp}, + } + + if performer.Name.Valid { + newPerformerJSON.Name = performer.Name.String + } + if performer.Gender.Valid { + newPerformerJSON.Gender = performer.Gender.String + } + if performer.URL.Valid { + newPerformerJSON.URL = performer.URL.String + } + if performer.Birthdate.Valid { + newPerformerJSON.Birthdate = utils.GetYMDFromDatabaseDate(performer.Birthdate.String) + } + if performer.Ethnicity.Valid { + newPerformerJSON.Ethnicity = performer.Ethnicity.String + } + if performer.Country.Valid { + newPerformerJSON.Country = performer.Country.String + } + if performer.EyeColor.Valid { + newPerformerJSON.EyeColor = performer.EyeColor.String + } + if performer.Height.Valid { + newPerformerJSON.Height = performer.Height.String + } + if performer.Measurements.Valid { + newPerformerJSON.Measurements = performer.Measurements.String + } + if performer.FakeTits.Valid { + newPerformerJSON.FakeTits = performer.FakeTits.String + } + if performer.CareerLength.Valid { + newPerformerJSON.CareerLength = performer.CareerLength.String + } + if performer.Tattoos.Valid { + newPerformerJSON.Tattoos = performer.Tattoos.String + } + if performer.Piercings.Valid { + newPerformerJSON.Piercings = performer.Piercings.String + } + if performer.Aliases.Valid { + newPerformerJSON.Aliases = performer.Aliases.String + } + if performer.Twitter.Valid { + newPerformerJSON.Twitter = performer.Twitter.String + } + if performer.Instagram.Valid { + newPerformerJSON.Instagram = performer.Instagram.String + } + if performer.Favorite.Valid { + newPerformerJSON.Favorite = performer.Favorite.Bool + } + + image, err := reader.GetPerformerImage(performer.ID) + if err != nil { + return nil, fmt.Errorf("error getting performers image: %s", err.Error()) + } + + if len(image) > 0 { + newPerformerJSON.Image = utils.GetBase64StringFromData(image) + } + + return &newPerformerJSON, nil +} + +func GetIDs(performers []*models.Performer) []int { + var results []int + for _, performer := range performers { + results = append(results, performer.ID) + } + + return results +} + +func GetNames(performers []*models.Performer) []string { + var results []string + for _, performer := range performers { + if performer.Name.Valid { + results = append(results, performer.Name.String) + } + } + + return results +} diff --git a/pkg/performer/export_test.go b/pkg/performer/export_test.go new file mode 100644 index 000000000..6df042341 --- /dev/null +++ b/pkg/performer/export_test.go @@ -0,0 +1,191 @@ +package performer + +import ( + "database/sql" + "errors" + + "github.com/stashapp/stash/pkg/manager/jsonschema" + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/models/mocks" + "github.com/stashapp/stash/pkg/models/modelstest" + "github.com/stashapp/stash/pkg/utils" + "github.com/stretchr/testify/assert" + + "testing" + "time" +) + +const ( + performerID = 1 + noImageID = 2 + errImageID = 3 +) + +const ( + performerName = "testPerformer" + url = "url" + aliases = "aliases" + careerLength = "careerLength" + country = "country" + ethnicity = "ethnicity" + eyeColor = "eyeColor" + fakeTits = "fakeTits" + gender = "gender" + height = "height" + instagram = "instagram" + measurements = "measurements" + piercings = "piercings" + tattoos = "tattoos" + twitter = "twitter" +) + +var imageBytes = []byte("imageBytes") + +const image = "aW1hZ2VCeXRlcw==" + +var birthDate = models.SQLiteDate{ + String: "2001-01-01", + Valid: true, +} +var createTime time.Time = time.Date(2001, 01, 01, 0, 0, 0, 0, time.Local) +var updateTime time.Time = time.Date(2002, 01, 01, 0, 0, 0, 0, time.Local) + +func createFullPerformer(id int, name string) *models.Performer { + return &models.Performer{ + ID: id, + Name: modelstest.NullString(name), + Checksum: utils.MD5FromString(name), + URL: modelstest.NullString(url), + Aliases: modelstest.NullString(aliases), + Birthdate: birthDate, + CareerLength: modelstest.NullString(careerLength), + Country: modelstest.NullString(country), + Ethnicity: modelstest.NullString(ethnicity), + EyeColor: modelstest.NullString(eyeColor), + FakeTits: modelstest.NullString(fakeTits), + Favorite: sql.NullBool{ + Bool: true, + Valid: true, + }, + Gender: modelstest.NullString(gender), + Height: modelstest.NullString(height), + Instagram: modelstest.NullString(instagram), + Measurements: modelstest.NullString(measurements), + Piercings: modelstest.NullString(piercings), + Tattoos: modelstest.NullString(tattoos), + Twitter: modelstest.NullString(twitter), + CreatedAt: models.SQLiteTimestamp{ + Timestamp: createTime, + }, + UpdatedAt: models.SQLiteTimestamp{ + Timestamp: updateTime, + }, + } +} + +func createEmptyPerformer(id int) models.Performer { + return models.Performer{ + ID: id, + CreatedAt: models.SQLiteTimestamp{ + Timestamp: createTime, + }, + UpdatedAt: models.SQLiteTimestamp{ + Timestamp: updateTime, + }, + } +} + +func createFullJSONPerformer(name string, image string) *jsonschema.Performer { + return &jsonschema.Performer{ + Name: name, + URL: url, + Aliases: aliases, + Birthdate: birthDate.String, + CareerLength: careerLength, + Country: country, + Ethnicity: ethnicity, + EyeColor: eyeColor, + FakeTits: fakeTits, + Favorite: true, + Gender: gender, + Height: height, + Instagram: instagram, + Measurements: measurements, + Piercings: piercings, + Tattoos: tattoos, + Twitter: twitter, + CreatedAt: models.JSONTime{ + Time: createTime, + }, + UpdatedAt: models.JSONTime{ + Time: updateTime, + }, + Image: image, + } +} + +func createEmptyJSONPerformer() *jsonschema.Performer { + return &jsonschema.Performer{ + CreatedAt: models.JSONTime{ + Time: createTime, + }, + UpdatedAt: models.JSONTime{ + Time: updateTime, + }, + } +} + +type testScenario struct { + input models.Performer + expected *jsonschema.Performer + err bool +} + +var scenarios []testScenario + +func initTestTable() { + scenarios = []testScenario{ + testScenario{ + *createFullPerformer(performerID, performerName), + createFullJSONPerformer(performerName, image), + false, + }, + testScenario{ + createEmptyPerformer(noImageID), + createEmptyJSONPerformer(), + false, + }, + testScenario{ + *createFullPerformer(errImageID, performerName), + nil, + true, + }, + } +} + +func TestToJSON(t *testing.T) { + initTestTable() + + mockPerformerReader := &mocks.PerformerReaderWriter{} + + imageErr := errors.New("error getting image") + + mockPerformerReader.On("GetPerformerImage", performerID).Return(imageBytes, nil).Once() + mockPerformerReader.On("GetPerformerImage", noImageID).Return(nil, nil).Once() + mockPerformerReader.On("GetPerformerImage", errImageID).Return(nil, imageErr).Once() + + for i, s := range scenarios { + tag := s.input + json, err := ToJSON(mockPerformerReader, &tag) + + if !s.err && err != nil { + t.Errorf("[%d] unexpected error: %s", i, err.Error()) + } else if s.err && err == nil { + t.Errorf("[%d] expected error not returned", i) + } else { + assert.Equal(t, s.expected, json, "[%d]", i) + } + } + + mockPerformerReader.AssertExpectations(t) +} diff --git a/pkg/performer/import.go b/pkg/performer/import.go new file mode 100644 index 000000000..0dae51776 --- /dev/null +++ b/pkg/performer/import.go @@ -0,0 +1,144 @@ +package performer + +import ( + "database/sql" + "fmt" + + "github.com/stashapp/stash/pkg/manager/jsonschema" + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/utils" +) + +type Importer struct { + ReaderWriter models.PerformerReaderWriter + Input jsonschema.Performer + + performer models.Performer + imageData []byte +} + +func (i *Importer) PreImport() error { + i.performer = performerJSONToPerformer(i.Input) + + var err error + if len(i.Input.Image) > 0 { + _, i.imageData, err = utils.ProcessBase64Image(i.Input.Image) + if err != nil { + return fmt.Errorf("invalid image: %s", err.Error()) + } + } + + return nil +} + +func (i *Importer) PostImport(id int) error { + if len(i.imageData) > 0 { + if err := i.ReaderWriter.UpdatePerformerImage(id, i.imageData); err != nil { + return fmt.Errorf("error setting performer image: %s", err.Error()) + } + } + + return nil +} + +func (i *Importer) Name() string { + return i.Input.Name +} + +func (i *Importer) FindExistingID() (*int, error) { + const nocase = false + existing, err := i.ReaderWriter.FindByNames([]string{i.Name()}, nocase) + if err != nil { + return nil, err + } + + if len(existing) > 0 { + id := existing[0].ID + return &id, nil + } + + return nil, nil +} + +func (i *Importer) Create() (*int, error) { + created, err := i.ReaderWriter.Create(i.performer) + if err != nil { + return nil, fmt.Errorf("error creating performer: %s", err.Error()) + } + + id := created.ID + return &id, nil +} + +func (i *Importer) Update(id int) error { + performer := i.performer + performer.ID = id + _, err := i.ReaderWriter.Update(performer) + if err != nil { + return fmt.Errorf("error updating existing performer: %s", err.Error()) + } + + return nil +} + +func performerJSONToPerformer(performerJSON jsonschema.Performer) models.Performer { + checksum := utils.MD5FromString(performerJSON.Name) + + newPerformer := models.Performer{ + Checksum: checksum, + Favorite: sql.NullBool{Bool: performerJSON.Favorite, Valid: true}, + CreatedAt: models.SQLiteTimestamp{Timestamp: performerJSON.CreatedAt.GetTime()}, + UpdatedAt: models.SQLiteTimestamp{Timestamp: performerJSON.UpdatedAt.GetTime()}, + } + + if performerJSON.Name != "" { + newPerformer.Name = sql.NullString{String: performerJSON.Name, Valid: true} + } + if performerJSON.Gender != "" { + newPerformer.Gender = sql.NullString{String: performerJSON.Gender, Valid: true} + } + if performerJSON.URL != "" { + newPerformer.URL = sql.NullString{String: performerJSON.URL, Valid: true} + } + if performerJSON.Birthdate != "" { + newPerformer.Birthdate = models.SQLiteDate{String: performerJSON.Birthdate, Valid: true} + } + if performerJSON.Ethnicity != "" { + newPerformer.Ethnicity = sql.NullString{String: performerJSON.Ethnicity, Valid: true} + } + if performerJSON.Country != "" { + newPerformer.Country = sql.NullString{String: performerJSON.Country, Valid: true} + } + if performerJSON.EyeColor != "" { + newPerformer.EyeColor = sql.NullString{String: performerJSON.EyeColor, Valid: true} + } + if performerJSON.Height != "" { + newPerformer.Height = sql.NullString{String: performerJSON.Height, Valid: true} + } + if performerJSON.Measurements != "" { + newPerformer.Measurements = sql.NullString{String: performerJSON.Measurements, Valid: true} + } + if performerJSON.FakeTits != "" { + newPerformer.FakeTits = sql.NullString{String: performerJSON.FakeTits, Valid: true} + } + if performerJSON.CareerLength != "" { + newPerformer.CareerLength = sql.NullString{String: performerJSON.CareerLength, Valid: true} + } + if performerJSON.Tattoos != "" { + newPerformer.Tattoos = sql.NullString{String: performerJSON.Tattoos, Valid: true} + } + if performerJSON.Piercings != "" { + newPerformer.Piercings = sql.NullString{String: performerJSON.Piercings, Valid: true} + } + if performerJSON.Aliases != "" { + newPerformer.Aliases = sql.NullString{String: performerJSON.Aliases, Valid: true} + } + if performerJSON.Twitter != "" { + newPerformer.Twitter = sql.NullString{String: performerJSON.Twitter, Valid: true} + } + if performerJSON.Instagram != "" { + newPerformer.Instagram = sql.NullString{String: performerJSON.Instagram, Valid: true} + } + + return newPerformer +} diff --git a/pkg/performer/import_test.go b/pkg/performer/import_test.go new file mode 100644 index 000000000..d58f91265 --- /dev/null +++ b/pkg/performer/import_test.go @@ -0,0 +1,184 @@ +package performer + +import ( + "errors" + + "github.com/stashapp/stash/pkg/manager/jsonschema" + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/models/mocks" + "github.com/stashapp/stash/pkg/models/modelstest" + "github.com/stashapp/stash/pkg/utils" + "github.com/stretchr/testify/assert" + + "testing" +) + +const invalidImage = "aW1hZ2VCeXRlcw&&" + +const ( + existingPerformerID = 100 + + existingPerformerName = "existingPerformerName" + performerNameErr = "performerNameErr" +) + +func TestImporterName(t *testing.T) { + i := Importer{ + Input: jsonschema.Performer{ + Name: performerName, + }, + } + + assert.Equal(t, performerName, i.Name()) +} + +func TestImporterPreImport(t *testing.T) { + i := Importer{ + Input: jsonschema.Performer{ + Name: performerName, + Image: invalidImage, + }, + } + + err := i.PreImport() + + assert.NotNil(t, err) + + i.Input = *createFullJSONPerformer(performerName, image) + + err = i.PreImport() + + assert.Nil(t, err) + expectedPerformer := *createFullPerformer(0, performerName) + expectedPerformer.Checksum = utils.MD5FromString(performerName) + assert.Equal(t, expectedPerformer, i.performer) +} + +func TestImporterPostImport(t *testing.T) { + readerWriter := &mocks.PerformerReaderWriter{} + + i := Importer{ + ReaderWriter: readerWriter, + imageData: imageBytes, + } + + updatePerformerImageErr := errors.New("UpdatePerformerImage error") + + readerWriter.On("UpdatePerformerImage", performerID, imageBytes).Return(nil).Once() + readerWriter.On("UpdatePerformerImage", errImageID, imageBytes).Return(updatePerformerImageErr).Once() + + err := i.PostImport(performerID) + assert.Nil(t, err) + + err = i.PostImport(errImageID) + assert.NotNil(t, err) + + readerWriter.AssertExpectations(t) +} + +func TestImporterFindExistingID(t *testing.T) { + readerWriter := &mocks.PerformerReaderWriter{} + + i := Importer{ + ReaderWriter: readerWriter, + Input: jsonschema.Performer{ + Name: performerName, + }, + } + + errFindByNames := errors.New("FindByNames error") + readerWriter.On("FindByNames", []string{performerName}, false).Return(nil, nil).Once() + readerWriter.On("FindByNames", []string{existingPerformerName}, false).Return([]*models.Performer{ + { + ID: existingPerformerID, + }, + }, nil).Once() + readerWriter.On("FindByNames", []string{performerNameErr}, false).Return(nil, errFindByNames).Once() + + id, err := i.FindExistingID() + assert.Nil(t, id) + assert.Nil(t, err) + + i.Input.Name = existingPerformerName + id, err = i.FindExistingID() + assert.Equal(t, existingPerformerID, *id) + assert.Nil(t, err) + + i.Input.Name = performerNameErr + id, err = i.FindExistingID() + assert.Nil(t, id) + assert.NotNil(t, err) + + readerWriter.AssertExpectations(t) +} + +func TestCreate(t *testing.T) { + readerWriter := &mocks.PerformerReaderWriter{} + + performer := models.Performer{ + Name: modelstest.NullString(performerName), + } + + performerErr := models.Performer{ + Name: modelstest.NullString(performerNameErr), + } + + i := Importer{ + ReaderWriter: readerWriter, + performer: performer, + } + + errCreate := errors.New("Create error") + readerWriter.On("Create", performer).Return(&models.Performer{ + ID: performerID, + }, nil).Once() + readerWriter.On("Create", performerErr).Return(nil, errCreate).Once() + + id, err := i.Create() + assert.Equal(t, performerID, *id) + assert.Nil(t, err) + + i.performer = performerErr + id, err = i.Create() + assert.Nil(t, id) + assert.NotNil(t, err) + + readerWriter.AssertExpectations(t) +} + +func TestUpdate(t *testing.T) { + readerWriter := &mocks.PerformerReaderWriter{} + + performer := models.Performer{ + Name: modelstest.NullString(performerName), + } + + performerErr := models.Performer{ + Name: modelstest.NullString(performerNameErr), + } + + i := Importer{ + ReaderWriter: readerWriter, + performer: performer, + } + + errUpdate := errors.New("Update error") + + // id needs to be set for the mock input + performer.ID = performerID + readerWriter.On("Update", performer).Return(nil, nil).Once() + + err := i.Update(performerID) + assert.Nil(t, err) + + i.performer = performerErr + + // need to set id separately + performerErr.ID = errImageID + readerWriter.On("Update", performerErr).Return(nil, errUpdate).Once() + + err = i.Update(errImageID) + assert.NotNil(t, err) + + readerWriter.AssertExpectations(t) +} diff --git a/pkg/plugin/common/log/log.go b/pkg/plugin/common/log/log.go index 33bf5f3cb..d4ebc0876 100644 --- a/pkg/plugin/common/log/log.go +++ b/pkg/plugin/common/log/log.go @@ -52,6 +52,7 @@ var ( } ProgressLevel = Level{ char: 'p', + Name: "progress", } NoneLevel = Level{ Name: "none", diff --git a/pkg/plugin/examples/python/log.py b/pkg/plugin/examples/python/log.py new file mode 100644 index 000000000..cd5c3a78d --- /dev/null +++ b/pkg/plugin/examples/python/log.py @@ -0,0 +1,44 @@ +import sys + +# Log messages sent from a plugin instance are transmitted via stderr and are +# encoded with a prefix consisting of special character SOH, then the log +# level (one of t, d, i, w, e, or p - corresponding to trace, debug, info, +# warning, error and progress levels respectively), then special character +# STX. +# +# The LogTrace, LogDebug, LogInfo, LogWarning, and LogError methods, and their equivalent +# formatted methods are intended for use by plugin instances to transmit log +# messages. The LogProgress method is also intended for sending progress data. +# + +def __prefix(levelChar): + startLevelChar = b'\x01' + endLevelChar = b'\x02' + + ret = startLevelChar + levelChar + endLevelChar + return ret.decode() + +def __log(levelChar, s): + if levelChar == "": + return + + print(__prefix(levelChar) + s + "\n", file=sys.stderr, flush=True) + +def LogTrace(s): + __log(b't', s) + +def LogDebug(s): + __log(b'd', s) + +def LogInfo(s): + __log(b'i', s) + +def LogWarning(s): + __log(b'w', s) + +def LogError(s): + __log(b'e', s) + +def LogProgress(p): + progress = min(max(0, p), 1) + __log(b'p', str(progress)) diff --git a/pkg/plugin/examples/python/pyplugin.py b/pkg/plugin/examples/python/pyplugin.py new file mode 100644 index 000000000..91d09d9d9 --- /dev/null +++ b/pkg/plugin/examples/python/pyplugin.py @@ -0,0 +1,123 @@ +import json +import sys +import time + +import log +from stash_interface import StashInterface + +# raw plugins may accept the plugin input from stdin, or they can elect +# to ignore it entirely. In this case it optionally reads from the +# command-line parameters. +def main(): + input = None + + if len(sys.argv) < 2: + input = readJSONInput() + log.LogDebug("Raw input: %s" % json.dumps(input)) + else: + log.LogDebug("Using command line inputs") + mode = sys.argv[1] + log.LogDebug("Command line inputs: {}".format(sys.argv[1:])) + + input = {} + input['args'] = { + "mode": mode + } + + # just some hard-coded values + input['server_connection'] = { + "Scheme": "http", + "Port": 9999, + } + + output = {} + run(input, output) + + out = json.dumps(output) + print(out + "\n") + +def readJSONInput(): + input = sys.stdin.read() + return json.loads(input) + +def run(input, output): + modeArg = input['args']["mode"] + + try: + if modeArg == "" or modeArg == "add": + client = StashInterface(input["server_connection"]) + addTag(client) + elif modeArg == "remove": + client = StashInterface(input["server_connection"]) + removeTag(client) + elif modeArg == "long": + doLongTask() + elif modeArg == "indef": + doIndefiniteTask() + except Exception as e: + raise + #output["error"] = str(e) + #return + + output["output"] = "ok" + +def doLongTask(): + total = 100 + upTo = 0 + + log.LogInfo("Doing long task") + while upTo < total: + time.sleep(1) + + log.LogProgress(float(upTo) / float(total)) + upTo = upTo + 1 + +def doIndefiniteTask(): + log.LogWarning("Sleeping indefinitely") + while True: + time.sleep(1) + +def addTag(client): + tagName = "Hawwwwt" + tagID = client.findTagIdWithName(tagName) + + if tagID == None: + tagID = client.createTagWithName(tagName) + + scene = client.findRandomSceneId() + + if scene == None: + raise Exception("no scenes to add tag to") + + tagIds = [] + for t in scene["tags"]: + tagIds.append(t["id"]) + + # remove first to ensure we don't re-add the same id + try: + tagIds.remove(tagID) + except ValueError: + pass + + tagIds.append(tagID) + + input = { + "id": scene["id"], + "tag_ids": tagIds + } + + log.LogInfo("Adding tag to scene {}".format(scene["id"])) + client.updateScene(input) + +def removeTag(client): + tagName = "Hawwwwt" + tagID = client.findTagIdWithName(tagName) + + if tagID == None: + log.LogInfo("Tag does not exist. Nothing to remove") + return + + log.LogInfo("Destroying tag") + client.destroyTag(tagID) + +main() \ No newline at end of file diff --git a/pkg/plugin/examples/python/pyraw.yml b/pkg/plugin/examples/python/pyraw.yml new file mode 100644 index 000000000..71c963b6f --- /dev/null +++ b/pkg/plugin/examples/python/pyraw.yml @@ -0,0 +1,29 @@ +# example plugin config +name: Hawwwwt Tagger (Raw Python edition) +description: Python Hawwwwt tagging utility (using raw interface). +version: 1.0 +url: http://www.github.com/stashapp/stash +exec: + - python + - "{pluginDir}/pyplugin.py" +interface: raw +tasks: + - name: Add hawwwwt tag to random scene + description: Creates a "Hawwwwt" tag if not present and adds to a random scene. + defaultArgs: + mode: add + - name: Remove hawwwwt tag from system + description: Removes the "Hawwwwt" tag from all scenes and deletes the tag. + defaultArgs: + mode: remove + - name: Indefinite task + description: Sleeps indefinitely - interruptable + # we'll try command-line argument for this one + execArgs: + - indef + - "{pluginDir}" + - name: Long task + description: Sleeps for 100 seconds - interruptable + defaultArgs: + mode: long + diff --git a/pkg/plugin/examples/python/stash_interface.py b/pkg/plugin/examples/python/stash_interface.py new file mode 100644 index 000000000..e05d27ddc --- /dev/null +++ b/pkg/plugin/examples/python/stash_interface.py @@ -0,0 +1,122 @@ +import requests + +class StashInterface: + port = "" + url = "" + headers = { + "Accept-Encoding": "gzip, deflate, br", + "Content-Type": "application/json", + "Accept": "application/json", + "Connection": "keep-alive", + "DNT": "1" + } + + def __init__(self, conn): + self.port = conn['Port'] + scheme = conn['Scheme'] + + self.url = scheme + "://localhost:" + str(self.port) + "/graphql" + + # TODO - cookies + + def __callGraphQL(self, query, variables = None): + json = {} + json['query'] = query + if variables != None: + json['variables'] = variables + + # handle cookies + response = requests.post(self.url, json=json, headers=self.headers) + + if response.status_code == 200: + result = response.json() + if result.get("error", None): + for error in result["error"]["errors"]: + raise Exception("GraphQL error: {}".format(error)) + if result.get("data", None): + return result.get("data") + else: + raise Exception("GraphQL query failed:{} - {}. Query: {}. Variables: {}".format(response.status_code, response.content, query, variables)) + + def findTagIdWithName(self, name): + query = """ +query { + allTags { + id + name + } +} + """ + + result = self.__callGraphQL(query) + + for tag in result["allTags"]: + if tag["name"] == name: + return tag["id"] + return None + + def createTagWithName(self, name): + query = """ +mutation tagCreate($input:TagCreateInput!) { + tagCreate(input: $input){ + id + } +} +""" + variables = {'input': { + 'name': name + }} + + result = self.__callGraphQL(query, variables) + return result["tagCreate"]["id"] + + def destroyTag(self, id): + query = """ +mutation tagDestroy($input: TagDestroyInput!) { + tagDestroy(input: $input) +} +""" + variables = {'input': { + 'id': id + }} + + self.__callGraphQL(query, variables) + + def findRandomSceneId(self): + query = """ +query findScenes($filter: FindFilterType!) { + findScenes(filter: $filter) { + count + scenes { + id + tags { + id + } + } + } +} +""" + + variables = {'filter': { + 'per_page': 1, + 'sort': 'random' + }} + + result = self.__callGraphQL(query, variables) + + if result["findScenes"]["count"] == 0: + return None + + return result["findScenes"]["scenes"][0] + + def updateScene(self, sceneData): + query = """ +mutation sceneUpdate($input:SceneUpdateInput!) { + sceneUpdate(input: $input) { + id + } +} +""" + variables = {'input': sceneData} + + self.__callGraphQL(query, variables) \ No newline at end of file diff --git a/pkg/scene/export.go b/pkg/scene/export.go new file mode 100644 index 000000000..3b0f37f5f --- /dev/null +++ b/pkg/scene/export.go @@ -0,0 +1,302 @@ +package scene + +import ( + "fmt" + "math" + "strconv" + + "github.com/stashapp/stash/pkg/manager/jsonschema" + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/utils" +) + +// ToBasicJSON converts a scene object into its JSON object equivalent. It +// does not convert the relationships to other objects, with the exception +// of cover image. +func ToBasicJSON(reader models.SceneReader, scene *models.Scene) (*jsonschema.Scene, error) { + newSceneJSON := jsonschema.Scene{ + CreatedAt: models.JSONTime{Time: scene.CreatedAt.Timestamp}, + UpdatedAt: models.JSONTime{Time: scene.UpdatedAt.Timestamp}, + } + + if scene.Checksum.Valid { + newSceneJSON.Checksum = scene.Checksum.String + } + + if scene.OSHash.Valid { + newSceneJSON.OSHash = scene.OSHash.String + } + + if scene.Title.Valid { + newSceneJSON.Title = scene.Title.String + } + + if scene.URL.Valid { + newSceneJSON.URL = scene.URL.String + } + + if scene.Date.Valid { + newSceneJSON.Date = utils.GetYMDFromDatabaseDate(scene.Date.String) + } + + if scene.Rating.Valid { + newSceneJSON.Rating = int(scene.Rating.Int64) + } + + newSceneJSON.OCounter = scene.OCounter + + if scene.Details.Valid { + newSceneJSON.Details = scene.Details.String + } + + newSceneJSON.File = getSceneFileJSON(scene) + + cover, err := reader.GetSceneCover(scene.ID) + if err != nil { + return nil, fmt.Errorf("error getting scene cover: %s", err.Error()) + } + + if len(cover) > 0 { + newSceneJSON.Cover = utils.GetBase64StringFromData(cover) + } + + return &newSceneJSON, nil +} + +func getSceneFileJSON(scene *models.Scene) *jsonschema.SceneFile { + ret := &jsonschema.SceneFile{} + + if scene.FileModTime.Valid { + ret.ModTime = models.JSONTime{Time: scene.FileModTime.Timestamp} + } + + if scene.Size.Valid { + ret.Size = scene.Size.String + } + + if scene.Duration.Valid { + ret.Duration = getDecimalString(scene.Duration.Float64) + } + + if scene.VideoCodec.Valid { + ret.VideoCodec = scene.VideoCodec.String + } + + if scene.AudioCodec.Valid { + ret.AudioCodec = scene.AudioCodec.String + } + + if scene.Format.Valid { + ret.Format = scene.Format.String + } + + if scene.Width.Valid { + ret.Width = int(scene.Width.Int64) + } + + if scene.Height.Valid { + ret.Height = int(scene.Height.Int64) + } + + if scene.Framerate.Valid { + ret.Framerate = getDecimalString(scene.Framerate.Float64) + } + + if scene.Bitrate.Valid { + ret.Bitrate = int(scene.Bitrate.Int64) + } + + return ret +} + +// GetStudioName returns the name of the provided scene's studio. It returns an +// empty string if there is no studio assigned to the scene. +func GetStudioName(reader models.StudioReader, scene *models.Scene) (string, error) { + if scene.StudioID.Valid { + studio, err := reader.Find(int(scene.StudioID.Int64)) + if err != nil { + return "", err + } + + if studio != nil { + return studio.Name.String, nil + } + } + + return "", nil +} + +// GetGalleryChecksum returns the checksum of the provided gallery. It returns an +// empty string if there is no gallery assigned to the scene. +func GetGalleryChecksum(reader models.GalleryReader, scene *models.Scene) (string, error) { + gallery, err := reader.FindBySceneID(scene.ID) + if err != nil { + return "", fmt.Errorf("error getting scene gallery: %s", err.Error()) + } + + if gallery != nil { + return gallery.Checksum, nil + } + + return "", nil +} + +// GetTagNames returns a slice of tag names corresponding to the provided +// scene's tags. +func GetTagNames(reader models.TagReader, scene *models.Scene) ([]string, error) { + tags, err := reader.FindBySceneID(scene.ID) + if err != nil { + return nil, fmt.Errorf("error getting scene tags: %s", err.Error()) + } + + return getTagNames(tags), nil +} + +func getTagNames(tags []*models.Tag) []string { + var results []string + for _, tag := range tags { + if tag.Name != "" { + results = append(results, tag.Name) + } + } + + return results +} + +// GetDependentTagIDs returns a slice of unique tag IDs that this scene references. +func GetDependentTagIDs(tags models.TagReader, joins models.JoinReader, markerReader models.SceneMarkerReader, scene *models.Scene) ([]int, error) { + var ret []int + + t, err := tags.FindBySceneID(scene.ID) + if err != nil { + return nil, err + } + + for _, tt := range t { + ret = utils.IntAppendUnique(ret, tt.ID) + } + + sm, err := markerReader.FindBySceneID(scene.ID) + if err != nil { + return nil, err + } + + for _, smm := range sm { + ret = utils.IntAppendUnique(ret, smm.PrimaryTagID) + smmt, err := tags.FindBySceneMarkerID(smm.ID) + if err != nil { + return nil, fmt.Errorf("invalid tags for scene marker: %s", err.Error()) + } + + for _, smmtt := range smmt { + ret = utils.IntAppendUnique(ret, smmtt.ID) + } + } + + return ret, nil +} + +// GetSceneMoviesJSON returns a slice of SceneMovie JSON representation objects +// corresponding to the provided scene's scene movie relationships. +func GetSceneMoviesJSON(movieReader models.MovieReader, joinReader models.JoinReader, scene *models.Scene) ([]jsonschema.SceneMovie, error) { + sceneMovies, err := joinReader.GetSceneMovies(scene.ID) + if err != nil { + return nil, fmt.Errorf("error getting scene movies: %s", err.Error()) + } + + var results []jsonschema.SceneMovie + for _, sceneMovie := range sceneMovies { + movie, err := movieReader.Find(sceneMovie.MovieID) + if err != nil { + return nil, fmt.Errorf("error getting movie: %s", err.Error()) + } + + if movie.Name.Valid { + sceneMovieJSON := jsonschema.SceneMovie{ + MovieName: movie.Name.String, + SceneIndex: int(sceneMovie.SceneIndex.Int64), + } + results = append(results, sceneMovieJSON) + } + } + + return results, nil +} + +// GetDependentMovieIDs returns a slice of movie IDs that this scene references. +func GetDependentMovieIDs(joins models.JoinReader, scene *models.Scene) ([]int, error) { + var ret []int + + m, err := joins.GetSceneMovies(scene.ID) + if err != nil { + return nil, err + } + + for _, mm := range m { + ret = append(ret, mm.MovieID) + } + + return ret, nil +} + +// GetSceneMarkersJSON returns a slice of SceneMarker JSON representation +// objects corresponding to the provided scene's markers. +func GetSceneMarkersJSON(markerReader models.SceneMarkerReader, tagReader models.TagReader, scene *models.Scene) ([]jsonschema.SceneMarker, error) { + sceneMarkers, err := markerReader.FindBySceneID(scene.ID) + if err != nil { + return nil, fmt.Errorf("error getting scene markers: %s", err.Error()) + } + + var results []jsonschema.SceneMarker + + for _, sceneMarker := range sceneMarkers { + primaryTag, err := tagReader.Find(sceneMarker.PrimaryTagID) + if err != nil { + return nil, fmt.Errorf("invalid primary tag for scene marker: %s", err.Error()) + } + + sceneMarkerTags, err := tagReader.FindBySceneMarkerID(sceneMarker.ID) + if err != nil { + return nil, fmt.Errorf("invalid tags for scene marker: %s", err.Error()) + } + + sceneMarkerJSON := jsonschema.SceneMarker{ + Title: sceneMarker.Title, + Seconds: getDecimalString(sceneMarker.Seconds), + PrimaryTag: primaryTag.Name, + Tags: getTagNames(sceneMarkerTags), + CreatedAt: models.JSONTime{Time: sceneMarker.CreatedAt.Timestamp}, + UpdatedAt: models.JSONTime{Time: sceneMarker.UpdatedAt.Timestamp}, + } + + results = append(results, sceneMarkerJSON) + } + + return results, nil +} + +func getDecimalString(num float64) string { + if num == 0 { + return "" + } + + precision := getPrecision(num) + if precision == 0 { + precision = 1 + } + return fmt.Sprintf("%."+strconv.Itoa(precision)+"f", num) +} + +func getPrecision(num float64) int { + if num == 0 { + return 0 + } + + e := 1.0 + p := 0 + for (math.Round(num*e) / e) != num { + e *= 10 + p++ + } + return p +} diff --git a/pkg/scene/export_test.go b/pkg/scene/export_test.go new file mode 100644 index 000000000..3c5919686 --- /dev/null +++ b/pkg/scene/export_test.go @@ -0,0 +1,667 @@ +package scene + +import ( + "database/sql" + "errors" + + "github.com/stashapp/stash/pkg/manager/jsonschema" + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/models/mocks" + "github.com/stashapp/stash/pkg/models/modelstest" + "github.com/stretchr/testify/assert" + + "testing" + "time" +) + +const ( + sceneID = 1 + noImageID = 2 + errImageID = 3 + + studioID = 4 + missingStudioID = 5 + errStudioID = 6 + + noGalleryID = 7 + errGalleryID = 8 + + noTagsID = 11 + errTagsID = 12 + + noMoviesID = 13 + errMoviesID = 14 + errFindMovieID = 15 + + noMarkersID = 16 + errMarkersID = 17 + errFindPrimaryTagID = 18 + errFindByMarkerID = 19 +) + +const ( + url = "url" + checksum = "checksum" + oshash = "oshash" + title = "title" + date = "2001-01-01" + rating = 5 + ocounter = 2 + details = "details" + size = "size" + duration = 1.23 + durationStr = "1.23" + videoCodec = "videoCodec" + audioCodec = "audioCodec" + format = "format" + width = 100 + height = 100 + framerate = 3.21 + framerateStr = "3.21" + bitrate = 1 +) + +const ( + studioName = "studioName" + galleryChecksum = "galleryChecksum" + + validMovie1 = 1 + validMovie2 = 2 + invalidMovie = 3 + + movie1Name = "movie1Name" + movie2Name = "movie2Name" + + movie1Scene = 1 + movie2Scene = 2 +) + +var names = []string{ + "name1", + "name2", +} + +var imageBytes = []byte("imageBytes") + +const image = "aW1hZ2VCeXRlcw==" + +var createTime time.Time = time.Date(2001, 01, 01, 0, 0, 0, 0, time.UTC) +var updateTime time.Time = time.Date(2002, 01, 01, 0, 0, 0, 0, time.UTC) + +func createFullScene(id int) models.Scene { + return models.Scene{ + ID: id, + Title: modelstest.NullString(title), + AudioCodec: modelstest.NullString(audioCodec), + Bitrate: modelstest.NullInt64(bitrate), + Checksum: modelstest.NullString(checksum), + Date: models.SQLiteDate{ + String: date, + Valid: true, + }, + Details: modelstest.NullString(details), + Duration: sql.NullFloat64{ + Float64: duration, + Valid: true, + }, + Format: modelstest.NullString(format), + Framerate: sql.NullFloat64{ + Float64: framerate, + Valid: true, + }, + Height: modelstest.NullInt64(height), + OCounter: ocounter, + OSHash: modelstest.NullString(oshash), + Rating: modelstest.NullInt64(rating), + Size: modelstest.NullString(size), + VideoCodec: modelstest.NullString(videoCodec), + Width: modelstest.NullInt64(width), + URL: modelstest.NullString(url), + CreatedAt: models.SQLiteTimestamp{ + Timestamp: createTime, + }, + UpdatedAt: models.SQLiteTimestamp{ + Timestamp: updateTime, + }, + } +} + +func createEmptyScene(id int) models.Scene { + return models.Scene{ + ID: id, + CreatedAt: models.SQLiteTimestamp{ + Timestamp: createTime, + }, + UpdatedAt: models.SQLiteTimestamp{ + Timestamp: updateTime, + }, + } +} + +func createFullJSONScene(image string) *jsonschema.Scene { + return &jsonschema.Scene{ + Title: title, + Checksum: checksum, + Date: date, + Details: details, + OCounter: ocounter, + OSHash: oshash, + Rating: rating, + URL: url, + File: &jsonschema.SceneFile{ + AudioCodec: audioCodec, + Bitrate: bitrate, + Duration: durationStr, + Format: format, + Framerate: framerateStr, + Height: height, + Size: size, + VideoCodec: videoCodec, + Width: width, + }, + CreatedAt: models.JSONTime{ + Time: createTime, + }, + UpdatedAt: models.JSONTime{ + Time: updateTime, + }, + Cover: image, + } +} + +func createEmptyJSONScene() *jsonschema.Scene { + return &jsonschema.Scene{ + File: &jsonschema.SceneFile{}, + CreatedAt: models.JSONTime{ + Time: createTime, + }, + UpdatedAt: models.JSONTime{ + Time: updateTime, + }, + } +} + +type basicTestScenario struct { + input models.Scene + expected *jsonschema.Scene + err bool +} + +var scenarios = []basicTestScenario{ + { + createFullScene(sceneID), + createFullJSONScene(image), + false, + }, + { + createEmptyScene(noImageID), + createEmptyJSONScene(), + false, + }, + { + createFullScene(errImageID), + nil, + true, + }, +} + +func TestToJSON(t *testing.T) { + mockSceneReader := &mocks.SceneReaderWriter{} + + imageErr := errors.New("error getting image") + + mockSceneReader.On("GetSceneCover", sceneID).Return(imageBytes, nil).Once() + mockSceneReader.On("GetSceneCover", noImageID).Return(nil, nil).Once() + mockSceneReader.On("GetSceneCover", errImageID).Return(nil, imageErr).Once() + + for i, s := range scenarios { + scene := s.input + json, err := ToBasicJSON(mockSceneReader, &scene) + + if !s.err && err != nil { + t.Errorf("[%d] unexpected error: %s", i, err.Error()) + } else if s.err && err == nil { + t.Errorf("[%d] expected error not returned", i) + } else { + assert.Equal(t, s.expected, json, "[%d]", i) + } + } + + mockSceneReader.AssertExpectations(t) +} + +func createStudioScene(studioID int) models.Scene { + return models.Scene{ + StudioID: modelstest.NullInt64(int64(studioID)), + } +} + +type stringTestScenario struct { + input models.Scene + expected string + err bool +} + +var getStudioScenarios = []stringTestScenario{ + { + createStudioScene(studioID), + studioName, + false, + }, + { + createStudioScene(missingStudioID), + "", + false, + }, + { + createStudioScene(errStudioID), + "", + true, + }, +} + +func TestGetStudioName(t *testing.T) { + mockStudioReader := &mocks.StudioReaderWriter{} + + studioErr := errors.New("error getting image") + + mockStudioReader.On("Find", studioID).Return(&models.Studio{ + Name: modelstest.NullString(studioName), + }, nil).Once() + mockStudioReader.On("Find", missingStudioID).Return(nil, nil).Once() + mockStudioReader.On("Find", errStudioID).Return(nil, studioErr).Once() + + for i, s := range getStudioScenarios { + scene := s.input + json, err := GetStudioName(mockStudioReader, &scene) + + if !s.err && err != nil { + t.Errorf("[%d] unexpected error: %s", i, err.Error()) + } else if s.err && err == nil { + t.Errorf("[%d] expected error not returned", i) + } else { + assert.Equal(t, s.expected, json, "[%d]", i) + } + } + + mockStudioReader.AssertExpectations(t) +} + +var getGalleryChecksumScenarios = []stringTestScenario{ + { + createEmptyScene(sceneID), + galleryChecksum, + false, + }, + { + createEmptyScene(noGalleryID), + "", + false, + }, + { + createEmptyScene(errGalleryID), + "", + true, + }, +} + +func TestGetGalleryChecksum(t *testing.T) { + mockGalleryReader := &mocks.GalleryReaderWriter{} + + galleryErr := errors.New("error getting gallery") + + mockGalleryReader.On("FindBySceneID", sceneID).Return(&models.Gallery{ + Checksum: galleryChecksum, + }, nil).Once() + mockGalleryReader.On("FindBySceneID", noGalleryID).Return(nil, nil).Once() + mockGalleryReader.On("FindBySceneID", errGalleryID).Return(nil, galleryErr).Once() + + for i, s := range getGalleryChecksumScenarios { + scene := s.input + json, err := GetGalleryChecksum(mockGalleryReader, &scene) + + if !s.err && err != nil { + t.Errorf("[%d] unexpected error: %s", i, err.Error()) + } else if s.err && err == nil { + t.Errorf("[%d] expected error not returned", i) + } else { + assert.Equal(t, s.expected, json, "[%d]", i) + } + } + + mockGalleryReader.AssertExpectations(t) +} + +type stringSliceTestScenario struct { + input models.Scene + expected []string + err bool +} + +var getTagNamesScenarios = []stringSliceTestScenario{ + { + createEmptyScene(sceneID), + names, + false, + }, + { + createEmptyScene(noTagsID), + nil, + false, + }, + { + createEmptyScene(errTagsID), + nil, + true, + }, +} + +func getTags(names []string) []*models.Tag { + var ret []*models.Tag + for _, n := range names { + ret = append(ret, &models.Tag{ + Name: n, + }) + } + + return ret +} + +func TestGetTagNames(t *testing.T) { + mockTagReader := &mocks.TagReaderWriter{} + + tagErr := errors.New("error getting tag") + + mockTagReader.On("FindBySceneID", sceneID).Return(getTags(names), nil).Once() + mockTagReader.On("FindBySceneID", noTagsID).Return(nil, nil).Once() + mockTagReader.On("FindBySceneID", errTagsID).Return(nil, tagErr).Once() + + for i, s := range getTagNamesScenarios { + scene := s.input + json, err := GetTagNames(mockTagReader, &scene) + + if !s.err && err != nil { + t.Errorf("[%d] unexpected error: %s", i, err.Error()) + } else if s.err && err == nil { + t.Errorf("[%d] expected error not returned", i) + } else { + assert.Equal(t, s.expected, json, "[%d]", i) + } + } + + mockTagReader.AssertExpectations(t) +} + +type sceneMoviesTestScenario struct { + input models.Scene + expected []jsonschema.SceneMovie + err bool +} + +var getSceneMoviesJSONScenarios = []sceneMoviesTestScenario{ + { + createEmptyScene(sceneID), + []jsonschema.SceneMovie{ + { + MovieName: movie1Name, + SceneIndex: movie1Scene, + }, + { + MovieName: movie2Name, + SceneIndex: movie2Scene, + }, + }, + false, + }, + { + createEmptyScene(noMoviesID), + nil, + false, + }, + { + createEmptyScene(errMoviesID), + nil, + true, + }, + { + createEmptyScene(errFindMovieID), + nil, + true, + }, +} + +var validMovies = []models.MoviesScenes{ + { + MovieID: validMovie1, + SceneIndex: modelstest.NullInt64(movie1Scene), + }, + { + MovieID: validMovie2, + SceneIndex: modelstest.NullInt64(movie2Scene), + }, +} + +var invalidMovies = []models.MoviesScenes{ + { + MovieID: invalidMovie, + SceneIndex: modelstest.NullInt64(movie1Scene), + }, +} + +func TestGetSceneMoviesJSON(t *testing.T) { + mockMovieReader := &mocks.MovieReaderWriter{} + mockJoinReader := &mocks.JoinReaderWriter{} + + joinErr := errors.New("error getting scene movies") + movieErr := errors.New("error getting movie") + + mockJoinReader.On("GetSceneMovies", sceneID).Return(validMovies, nil).Once() + mockJoinReader.On("GetSceneMovies", noMoviesID).Return(nil, nil).Once() + mockJoinReader.On("GetSceneMovies", errMoviesID).Return(nil, joinErr).Once() + mockJoinReader.On("GetSceneMovies", errFindMovieID).Return(invalidMovies, nil).Once() + + mockMovieReader.On("Find", validMovie1).Return(&models.Movie{ + Name: modelstest.NullString(movie1Name), + }, nil).Once() + mockMovieReader.On("Find", validMovie2).Return(&models.Movie{ + Name: modelstest.NullString(movie2Name), + }, nil).Once() + mockMovieReader.On("Find", invalidMovie).Return(nil, movieErr).Once() + + for i, s := range getSceneMoviesJSONScenarios { + scene := s.input + json, err := GetSceneMoviesJSON(mockMovieReader, mockJoinReader, &scene) + + if !s.err && err != nil { + t.Errorf("[%d] unexpected error: %s", i, err.Error()) + } else if s.err && err == nil { + t.Errorf("[%d] expected error not returned", i) + } else { + assert.Equal(t, s.expected, json, "[%d]", i) + } + } + + mockMovieReader.AssertExpectations(t) +} + +const ( + validMarkerID1 = 1 + validMarkerID2 = 2 + + invalidMarkerID1 = 3 + invalidMarkerID2 = 4 + + validTagID1 = 1 + validTagID2 = 2 + + validTagName1 = "validTagName1" + validTagName2 = "validTagName2" + + invalidTagID = 3 + + markerTitle1 = "markerTitle1" + markerTitle2 = "markerTitle2" + + markerSeconds1 = 1.0 + markerSeconds2 = 2.3 + + markerSeconds1Str = "1.0" + markerSeconds2Str = "2.3" +) + +type sceneMarkersTestScenario struct { + input models.Scene + expected []jsonschema.SceneMarker + err bool +} + +var getSceneMarkersJSONScenarios = []sceneMarkersTestScenario{ + { + createEmptyScene(sceneID), + []jsonschema.SceneMarker{ + { + Title: markerTitle1, + PrimaryTag: validTagName1, + Seconds: markerSeconds1Str, + Tags: []string{ + validTagName1, + validTagName2, + }, + CreatedAt: models.JSONTime{ + Time: createTime, + }, + UpdatedAt: models.JSONTime{ + Time: updateTime, + }, + }, + { + Title: markerTitle2, + PrimaryTag: validTagName2, + Seconds: markerSeconds2Str, + Tags: []string{ + validTagName2, + }, + CreatedAt: models.JSONTime{ + Time: createTime, + }, + UpdatedAt: models.JSONTime{ + Time: updateTime, + }, + }, + }, + false, + }, + { + createEmptyScene(noMarkersID), + nil, + false, + }, + { + createEmptyScene(errMarkersID), + nil, + true, + }, + { + createEmptyScene(errFindPrimaryTagID), + nil, + true, + }, + { + createEmptyScene(errFindByMarkerID), + nil, + true, + }, +} + +var validMarkers = []*models.SceneMarker{ + { + ID: validMarkerID1, + Title: markerTitle1, + PrimaryTagID: validTagID1, + Seconds: markerSeconds1, + CreatedAt: models.SQLiteTimestamp{ + Timestamp: createTime, + }, + UpdatedAt: models.SQLiteTimestamp{ + Timestamp: updateTime, + }, + }, + { + ID: validMarkerID2, + Title: markerTitle2, + PrimaryTagID: validTagID2, + Seconds: markerSeconds2, + CreatedAt: models.SQLiteTimestamp{ + Timestamp: createTime, + }, + UpdatedAt: models.SQLiteTimestamp{ + Timestamp: updateTime, + }, + }, +} + +var invalidMarkers1 = []*models.SceneMarker{ + { + ID: invalidMarkerID1, + PrimaryTagID: invalidTagID, + }, +} + +var invalidMarkers2 = []*models.SceneMarker{ + { + ID: invalidMarkerID2, + PrimaryTagID: validTagID1, + }, +} + +func TestGetSceneMarkersJSON(t *testing.T) { + mockTagReader := &mocks.TagReaderWriter{} + mockMarkerReader := &mocks.SceneMarkerReaderWriter{} + + markersErr := errors.New("error getting scene markers") + tagErr := errors.New("error getting tags") + + mockMarkerReader.On("FindBySceneID", sceneID).Return(validMarkers, nil).Once() + mockMarkerReader.On("FindBySceneID", noMarkersID).Return(nil, nil).Once() + mockMarkerReader.On("FindBySceneID", errMarkersID).Return(nil, markersErr).Once() + mockMarkerReader.On("FindBySceneID", errFindPrimaryTagID).Return(invalidMarkers1, nil).Once() + mockMarkerReader.On("FindBySceneID", errFindByMarkerID).Return(invalidMarkers2, nil).Once() + + mockTagReader.On("Find", validTagID1).Return(&models.Tag{ + Name: validTagName1, + }, nil) + mockTagReader.On("Find", validTagID2).Return(&models.Tag{ + Name: validTagName2, + }, nil) + mockTagReader.On("Find", invalidTagID).Return(nil, tagErr) + + mockTagReader.On("FindBySceneMarkerID", validMarkerID1).Return([]*models.Tag{ + { + Name: validTagName1, + }, + { + Name: validTagName2, + }, + }, nil) + mockTagReader.On("FindBySceneMarkerID", validMarkerID2).Return([]*models.Tag{ + { + Name: validTagName2, + }, + }, nil) + mockTagReader.On("FindBySceneMarkerID", invalidMarkerID2).Return(nil, tagErr).Once() + + for i, s := range getSceneMarkersJSONScenarios { + scene := s.input + json, err := GetSceneMarkersJSON(mockMarkerReader, mockTagReader, &scene) + + if !s.err && err != nil { + t.Errorf("[%d] unexpected error: %s", i, err.Error()) + } else if s.err && err == nil { + t.Errorf("[%d] expected error not returned", i) + } else { + assert.Equal(t, s.expected, json, "[%d]", i) + } + } + + mockTagReader.AssertExpectations(t) +} diff --git a/pkg/scene/import.go b/pkg/scene/import.go new file mode 100644 index 000000000..a843646c2 --- /dev/null +++ b/pkg/scene/import.go @@ -0,0 +1,483 @@ +package scene + +import ( + "database/sql" + "fmt" + "strconv" + "strings" + + "github.com/stashapp/stash/pkg/manager/jsonschema" + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/utils" +) + +type Importer struct { + ReaderWriter models.SceneReaderWriter + StudioWriter models.StudioReaderWriter + GalleryWriter models.GalleryReaderWriter + PerformerWriter models.PerformerReaderWriter + MovieWriter models.MovieReaderWriter + TagWriter models.TagReaderWriter + JoinWriter models.JoinReaderWriter + Input jsonschema.Scene + Path string + MissingRefBehaviour models.ImportMissingRefEnum + FileNamingAlgorithm models.HashAlgorithm + + ID int + scene models.Scene + gallery *models.Gallery + performers []*models.Performer + movies []models.MoviesScenes + tags []*models.Tag + coverImageData []byte +} + +func (i *Importer) PreImport() error { + i.scene = i.sceneJSONToScene(i.Input) + + if err := i.populateStudio(); err != nil { + return err + } + + if err := i.populateGallery(); err != nil { + return err + } + + if err := i.populatePerformers(); err != nil { + return err + } + + if err := i.populateTags(); err != nil { + return err + } + + if err := i.populateMovies(); err != nil { + return err + } + + var err error + if len(i.Input.Cover) > 0 { + _, i.coverImageData, err = utils.ProcessBase64Image(i.Input.Cover) + if err != nil { + return fmt.Errorf("invalid cover image: %s", err.Error()) + } + } + + return nil +} + +func (i *Importer) sceneJSONToScene(sceneJSON jsonschema.Scene) models.Scene { + newScene := models.Scene{ + Checksum: sql.NullString{String: sceneJSON.Checksum, Valid: sceneJSON.Checksum != ""}, + OSHash: sql.NullString{String: sceneJSON.OSHash, Valid: sceneJSON.OSHash != ""}, + Path: i.Path, + } + + if sceneJSON.Title != "" { + newScene.Title = sql.NullString{String: sceneJSON.Title, Valid: true} + } + if sceneJSON.Details != "" { + newScene.Details = sql.NullString{String: sceneJSON.Details, Valid: true} + } + if sceneJSON.URL != "" { + newScene.URL = sql.NullString{String: sceneJSON.URL, Valid: true} + } + if sceneJSON.Date != "" { + newScene.Date = models.SQLiteDate{String: sceneJSON.Date, Valid: true} + } + if sceneJSON.Rating != 0 { + newScene.Rating = sql.NullInt64{Int64: int64(sceneJSON.Rating), Valid: true} + } + + newScene.OCounter = sceneJSON.OCounter + newScene.CreatedAt = models.SQLiteTimestamp{Timestamp: sceneJSON.CreatedAt.GetTime()} + newScene.UpdatedAt = models.SQLiteTimestamp{Timestamp: sceneJSON.UpdatedAt.GetTime()} + + if sceneJSON.File != nil { + if sceneJSON.File.Size != "" { + newScene.Size = sql.NullString{String: sceneJSON.File.Size, Valid: true} + } + if sceneJSON.File.Duration != "" { + duration, _ := strconv.ParseFloat(sceneJSON.File.Duration, 64) + newScene.Duration = sql.NullFloat64{Float64: duration, Valid: true} + } + if sceneJSON.File.VideoCodec != "" { + newScene.VideoCodec = sql.NullString{String: sceneJSON.File.VideoCodec, Valid: true} + } + if sceneJSON.File.AudioCodec != "" { + newScene.AudioCodec = sql.NullString{String: sceneJSON.File.AudioCodec, Valid: true} + } + if sceneJSON.File.Format != "" { + newScene.Format = sql.NullString{String: sceneJSON.File.Format, Valid: true} + } + if sceneJSON.File.Width != 0 { + newScene.Width = sql.NullInt64{Int64: int64(sceneJSON.File.Width), Valid: true} + } + if sceneJSON.File.Height != 0 { + newScene.Height = sql.NullInt64{Int64: int64(sceneJSON.File.Height), Valid: true} + } + if sceneJSON.File.Framerate != "" { + framerate, _ := strconv.ParseFloat(sceneJSON.File.Framerate, 64) + newScene.Framerate = sql.NullFloat64{Float64: framerate, Valid: true} + } + if sceneJSON.File.Bitrate != 0 { + newScene.Bitrate = sql.NullInt64{Int64: int64(sceneJSON.File.Bitrate), Valid: true} + } + } + + return newScene +} + +func (i *Importer) populateStudio() error { + if i.Input.Studio != "" { + studio, err := i.StudioWriter.FindByName(i.Input.Studio, false) + if err != nil { + return fmt.Errorf("error finding studio by name: %s", err.Error()) + } + + if studio == nil { + if i.MissingRefBehaviour == models.ImportMissingRefEnumFail { + return fmt.Errorf("scene studio '%s' not found", i.Input.Studio) + } + + if i.MissingRefBehaviour == models.ImportMissingRefEnumIgnore { + return nil + } + + if i.MissingRefBehaviour == models.ImportMissingRefEnumCreate { + studioID, err := i.createStudio(i.Input.Studio) + if err != nil { + return err + } + i.scene.StudioID = sql.NullInt64{ + Int64: int64(studioID), + Valid: true, + } + } + } else { + i.scene.StudioID = sql.NullInt64{Int64: int64(studio.ID), Valid: true} + } + } + + return nil +} + +func (i *Importer) createStudio(name string) (int, error) { + newStudio := *models.NewStudio(name) + + created, err := i.StudioWriter.Create(newStudio) + if err != nil { + return 0, err + } + + return created.ID, nil +} + +func (i *Importer) populateGallery() error { + if i.Input.Gallery != "" { + gallery, err := i.GalleryWriter.FindByChecksum(i.Input.Gallery) + if err != nil { + return fmt.Errorf("error finding gallery: %s", err.Error()) + } + + if gallery == nil { + if i.MissingRefBehaviour == models.ImportMissingRefEnumFail { + return fmt.Errorf("scene gallery '%s' not found", i.Input.Studio) + } + + // we don't create galleries - just ignore + if i.MissingRefBehaviour == models.ImportMissingRefEnumIgnore || i.MissingRefBehaviour == models.ImportMissingRefEnumCreate { + return nil + } + } else { + i.gallery = gallery + } + } + + return nil +} + +func (i *Importer) populatePerformers() error { + if len(i.Input.Performers) > 0 { + names := i.Input.Performers + performers, err := i.PerformerWriter.FindByNames(names, false) + if err != nil { + return err + } + + var pluckedNames []string + for _, performer := range performers { + if !performer.Name.Valid { + continue + } + pluckedNames = append(pluckedNames, performer.Name.String) + } + + missingPerformers := utils.StrFilter(names, func(name string) bool { + return !utils.StrInclude(pluckedNames, name) + }) + + if len(missingPerformers) > 0 { + if i.MissingRefBehaviour == models.ImportMissingRefEnumFail { + return fmt.Errorf("scene performers [%s] not found", strings.Join(missingPerformers, ", ")) + } + + if i.MissingRefBehaviour == models.ImportMissingRefEnumCreate { + createdPerformers, err := i.createPerformers(missingPerformers) + if err != nil { + return fmt.Errorf("error creating scene performers: %s", err.Error()) + } + + performers = append(performers, createdPerformers...) + } + + // ignore if MissingRefBehaviour set to Ignore + } + + i.performers = performers + } + + return nil +} + +func (i *Importer) createPerformers(names []string) ([]*models.Performer, error) { + var ret []*models.Performer + for _, name := range names { + newPerformer := *models.NewPerformer(name) + + created, err := i.PerformerWriter.Create(newPerformer) + if err != nil { + return nil, err + } + + ret = append(ret, created) + } + + return ret, nil +} + +func (i *Importer) populateMovies() error { + if len(i.Input.Movies) > 0 { + for _, inputMovie := range i.Input.Movies { + movie, err := i.MovieWriter.FindByName(inputMovie.MovieName, false) + if err != nil { + return fmt.Errorf("error finding scene movie: %s", err.Error()) + } + + if movie == nil { + if i.MissingRefBehaviour == models.ImportMissingRefEnumFail { + return fmt.Errorf("scene movie [%s] not found", inputMovie.MovieName) + } + + if i.MissingRefBehaviour == models.ImportMissingRefEnumCreate { + movie, err = i.createMovie(inputMovie.MovieName) + if err != nil { + return fmt.Errorf("error creating scene movie: %s", err.Error()) + } + } + + // ignore if MissingRefBehaviour set to Ignore + if i.MissingRefBehaviour == models.ImportMissingRefEnumIgnore { + continue + } + } + + toAdd := models.MoviesScenes{ + MovieID: movie.ID, + } + + if inputMovie.SceneIndex != 0 { + toAdd.SceneIndex = sql.NullInt64{ + Int64: int64(inputMovie.SceneIndex), + Valid: true, + } + } + + i.movies = append(i.movies, toAdd) + } + } + + return nil +} + +func (i *Importer) createMovie(name string) (*models.Movie, error) { + newMovie := *models.NewMovie(name) + + created, err := i.MovieWriter.Create(newMovie) + if err != nil { + return nil, err + } + + return created, nil +} + +func (i *Importer) populateTags() error { + if len(i.Input.Tags) > 0 { + + tags, err := importTags(i.TagWriter, i.Input.Tags, i.MissingRefBehaviour) + if err != nil { + return err + } + + i.tags = tags + } + + return nil +} + +func (i *Importer) PostImport(id int) error { + if len(i.coverImageData) > 0 { + if err := i.ReaderWriter.UpdateSceneCover(id, i.coverImageData); err != nil { + return fmt.Errorf("error setting scene images: %s", err.Error()) + } + } + + if i.gallery != nil { + i.gallery.SceneID = sql.NullInt64{Int64: int64(id), Valid: true} + _, err := i.GalleryWriter.Update(*i.gallery) + if err != nil { + return fmt.Errorf("failed to update gallery: %s", err.Error()) + } + } + + if len(i.performers) > 0 { + var performerJoins []models.PerformersScenes + for _, performer := range i.performers { + join := models.PerformersScenes{ + PerformerID: performer.ID, + SceneID: id, + } + performerJoins = append(performerJoins, join) + } + if err := i.JoinWriter.UpdatePerformersScenes(id, performerJoins); err != nil { + return fmt.Errorf("failed to associate performers: %s", err.Error()) + } + } + + if len(i.movies) > 0 { + for index := range i.movies { + i.movies[index].SceneID = id + } + if err := i.JoinWriter.UpdateMoviesScenes(id, i.movies); err != nil { + return fmt.Errorf("failed to associate movies: %s", err.Error()) + } + } + + if len(i.tags) > 0 { + var tagJoins []models.ScenesTags + for _, tag := range i.tags { + join := models.ScenesTags{ + SceneID: id, + TagID: tag.ID, + } + tagJoins = append(tagJoins, join) + } + if err := i.JoinWriter.UpdateScenesTags(id, tagJoins); err != nil { + return fmt.Errorf("failed to associate tags: %s", err.Error()) + } + } + + return nil +} + +func (i *Importer) Name() string { + return i.Path +} + +func (i *Importer) FindExistingID() (*int, error) { + var existing *models.Scene + var err error + if i.FileNamingAlgorithm == models.HashAlgorithmMd5 { + existing, err = i.ReaderWriter.FindByChecksum(i.Input.Checksum) + } else if i.FileNamingAlgorithm == models.HashAlgorithmOshash { + existing, err = i.ReaderWriter.FindByOSHash(i.Input.OSHash) + } else { + panic("unknown file naming algorithm") + } + + if err != nil { + return nil, err + } + + if existing != nil { + id := existing.ID + return &id, nil + } + + return nil, nil +} + +func (i *Importer) Create() (*int, error) { + created, err := i.ReaderWriter.Create(i.scene) + if err != nil { + return nil, fmt.Errorf("error creating scene: %s", err.Error()) + } + + id := created.ID + i.ID = id + return &id, nil +} + +func (i *Importer) Update(id int) error { + scene := i.scene + scene.ID = id + i.ID = id + _, err := i.ReaderWriter.UpdateFull(scene) + if err != nil { + return fmt.Errorf("error updating existing scene: %s", err.Error()) + } + + return nil +} + +func importTags(tagWriter models.TagReaderWriter, names []string, missingRefBehaviour models.ImportMissingRefEnum) ([]*models.Tag, error) { + tags, err := tagWriter.FindByNames(names, false) + if err != nil { + return nil, err + } + + var pluckedNames []string + for _, tag := range tags { + pluckedNames = append(pluckedNames, tag.Name) + } + + missingTags := utils.StrFilter(names, func(name string) bool { + return !utils.StrInclude(pluckedNames, name) + }) + + if len(missingTags) > 0 { + if missingRefBehaviour == models.ImportMissingRefEnumFail { + return nil, fmt.Errorf("tags [%s] not found", strings.Join(missingTags, ", ")) + } + + if missingRefBehaviour == models.ImportMissingRefEnumCreate { + createdTags, err := createTags(tagWriter, missingTags) + if err != nil { + return nil, fmt.Errorf("error creating tags: %s", err.Error()) + } + + tags = append(tags, createdTags...) + } + + // ignore if MissingRefBehaviour set to Ignore + } + + return tags, nil +} + +func createTags(tagWriter models.TagWriter, names []string) ([]*models.Tag, error) { + var ret []*models.Tag + for _, name := range names { + newTag := *models.NewTag(name) + + created, err := tagWriter.Create(newTag) + if err != nil { + return nil, err + } + + ret = append(ret, created) + } + + return ret, nil +} diff --git a/pkg/scene/import_test.go b/pkg/scene/import_test.go new file mode 100644 index 000000000..e43e0ff43 --- /dev/null +++ b/pkg/scene/import_test.go @@ -0,0 +1,761 @@ +package scene + +import ( + "errors" + "testing" + + "github.com/stashapp/stash/pkg/manager/jsonschema" + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/models/mocks" + "github.com/stashapp/stash/pkg/models/modelstest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +const invalidImage = "aW1hZ2VCeXRlcw&&" + +const ( + path = "path" + + sceneNameErr = "sceneNameErr" + existingSceneName = "existingSceneName" + + existingSceneID = 100 + existingStudioID = 101 + existingGalleryID = 102 + existingPerformerID = 103 + existingMovieID = 104 + existingTagID = 105 + + existingStudioName = "existingStudioName" + existingStudioErr = "existingStudioErr" + missingStudioName = "missingStudioName" + + existingGalleryChecksum = "existingGalleryChecksum" + existingGalleryErr = "existingGalleryErr" + missingGalleryChecksum = "missingGalleryChecksum" + + existingPerformerName = "existingPerformerName" + existingPerformerErr = "existingPerformerErr" + missingPerformerName = "missingPerformerName" + + existingMovieName = "existingMovieName" + existingMovieErr = "existingMovieErr" + missingMovieName = "missingMovieName" + + existingTagName = "existingTagName" + existingTagErr = "existingTagErr" + missingTagName = "missingTagName" + + errPerformersID = 200 + + missingChecksum = "missingChecksum" + missingOSHash = "missingOSHash" + errChecksum = "errChecksum" + errOSHash = "errOSHash" +) + +func TestImporterName(t *testing.T) { + i := Importer{ + Path: path, + Input: jsonschema.Scene{}, + } + + assert.Equal(t, path, i.Name()) +} + +func TestImporterPreImport(t *testing.T) { + i := Importer{ + Path: path, + Input: jsonschema.Scene{ + Cover: invalidImage, + }, + } + + err := i.PreImport() + assert.NotNil(t, err) + + i.Input.Cover = image + + err = i.PreImport() + assert.Nil(t, err) +} + +func TestImporterPreImportWithStudio(t *testing.T) { + studioReaderWriter := &mocks.StudioReaderWriter{} + + i := Importer{ + StudioWriter: studioReaderWriter, + Path: path, + Input: jsonschema.Scene{ + Studio: existingStudioName, + }, + } + + studioReaderWriter.On("FindByName", existingStudioName, false).Return(&models.Studio{ + ID: existingStudioID, + }, nil).Once() + studioReaderWriter.On("FindByName", existingStudioErr, false).Return(nil, errors.New("FindByName error")).Once() + + err := i.PreImport() + assert.Nil(t, err) + assert.Equal(t, int64(existingStudioID), i.scene.StudioID.Int64) + + i.Input.Studio = existingStudioErr + err = i.PreImport() + assert.NotNil(t, err) + + studioReaderWriter.AssertExpectations(t) +} + +func TestImporterPreImportWithMissingStudio(t *testing.T) { + studioReaderWriter := &mocks.StudioReaderWriter{} + + i := Importer{ + Path: path, + StudioWriter: studioReaderWriter, + Input: jsonschema.Scene{ + Studio: missingStudioName, + }, + MissingRefBehaviour: models.ImportMissingRefEnumFail, + } + + studioReaderWriter.On("FindByName", missingStudioName, false).Return(nil, nil).Times(3) + studioReaderWriter.On("Create", mock.AnythingOfType("models.Studio")).Return(&models.Studio{ + ID: existingStudioID, + }, nil) + + err := i.PreImport() + assert.NotNil(t, err) + + i.MissingRefBehaviour = models.ImportMissingRefEnumIgnore + err = i.PreImport() + assert.Nil(t, err) + + i.MissingRefBehaviour = models.ImportMissingRefEnumCreate + err = i.PreImport() + assert.Nil(t, err) + assert.Equal(t, int64(existingStudioID), i.scene.StudioID.Int64) + + studioReaderWriter.AssertExpectations(t) +} + +func TestImporterPreImportWithMissingStudioCreateErr(t *testing.T) { + studioReaderWriter := &mocks.StudioReaderWriter{} + + i := Importer{ + StudioWriter: studioReaderWriter, + Path: path, + Input: jsonschema.Scene{ + Studio: missingStudioName, + }, + MissingRefBehaviour: models.ImportMissingRefEnumCreate, + } + + studioReaderWriter.On("FindByName", missingStudioName, false).Return(nil, nil).Once() + studioReaderWriter.On("Create", mock.AnythingOfType("models.Studio")).Return(nil, errors.New("Create error")) + + err := i.PreImport() + assert.NotNil(t, err) +} + +func TestImporterPreImportWithGallery(t *testing.T) { + galleryReaderWriter := &mocks.GalleryReaderWriter{} + + i := Importer{ + GalleryWriter: galleryReaderWriter, + Path: path, + Input: jsonschema.Scene{ + Gallery: existingGalleryChecksum, + }, + } + + galleryReaderWriter.On("FindByChecksum", existingGalleryChecksum).Return(&models.Gallery{ + ID: existingGalleryID, + }, nil).Once() + galleryReaderWriter.On("FindByChecksum", existingGalleryErr).Return(nil, errors.New("FindByChecksum error")).Once() + + err := i.PreImport() + assert.Nil(t, err) + assert.Equal(t, existingGalleryID, i.gallery.ID) + + i.Input.Gallery = existingGalleryErr + err = i.PreImport() + assert.NotNil(t, err) + + galleryReaderWriter.AssertExpectations(t) +} + +func TestImporterPreImportWithMissingGallery(t *testing.T) { + galleryReaderWriter := &mocks.GalleryReaderWriter{} + + i := Importer{ + Path: path, + GalleryWriter: galleryReaderWriter, + Input: jsonschema.Scene{ + Gallery: missingGalleryChecksum, + }, + MissingRefBehaviour: models.ImportMissingRefEnumFail, + } + + galleryReaderWriter.On("FindByChecksum", missingGalleryChecksum).Return(nil, nil).Times(3) + + err := i.PreImport() + assert.NotNil(t, err) + + i.MissingRefBehaviour = models.ImportMissingRefEnumIgnore + err = i.PreImport() + assert.Nil(t, err) + assert.Nil(t, i.gallery) + + i.MissingRefBehaviour = models.ImportMissingRefEnumCreate + err = i.PreImport() + assert.Nil(t, err) + assert.Nil(t, i.gallery) + + galleryReaderWriter.AssertExpectations(t) +} + +func TestImporterPreImportWithPerformer(t *testing.T) { + performerReaderWriter := &mocks.PerformerReaderWriter{} + + i := Importer{ + PerformerWriter: performerReaderWriter, + Path: path, + MissingRefBehaviour: models.ImportMissingRefEnumFail, + Input: jsonschema.Scene{ + Performers: []string{ + existingPerformerName, + }, + }, + } + + performerReaderWriter.On("FindByNames", []string{existingPerformerName}, false).Return([]*models.Performer{ + { + ID: existingPerformerID, + Name: modelstest.NullString(existingPerformerName), + }, + }, nil).Once() + performerReaderWriter.On("FindByNames", []string{existingPerformerErr}, false).Return(nil, errors.New("FindByNames error")).Once() + + err := i.PreImport() + assert.Nil(t, err) + assert.Equal(t, existingPerformerID, i.performers[0].ID) + + i.Input.Performers = []string{existingPerformerErr} + err = i.PreImport() + assert.NotNil(t, err) + + performerReaderWriter.AssertExpectations(t) +} + +func TestImporterPreImportWithMissingPerformer(t *testing.T) { + performerReaderWriter := &mocks.PerformerReaderWriter{} + + i := Importer{ + Path: path, + PerformerWriter: performerReaderWriter, + Input: jsonschema.Scene{ + Performers: []string{ + missingPerformerName, + }, + }, + MissingRefBehaviour: models.ImportMissingRefEnumFail, + } + + performerReaderWriter.On("FindByNames", []string{missingPerformerName}, false).Return(nil, nil).Times(3) + performerReaderWriter.On("Create", mock.AnythingOfType("models.Performer")).Return(&models.Performer{ + ID: existingPerformerID, + }, nil) + + err := i.PreImport() + assert.NotNil(t, err) + + i.MissingRefBehaviour = models.ImportMissingRefEnumIgnore + err = i.PreImport() + assert.Nil(t, err) + + i.MissingRefBehaviour = models.ImportMissingRefEnumCreate + err = i.PreImport() + assert.Nil(t, err) + assert.Equal(t, existingPerformerID, i.performers[0].ID) + + performerReaderWriter.AssertExpectations(t) +} + +func TestImporterPreImportWithMissingPerformerCreateErr(t *testing.T) { + performerReaderWriter := &mocks.PerformerReaderWriter{} + + i := Importer{ + PerformerWriter: performerReaderWriter, + Path: path, + Input: jsonschema.Scene{ + Performers: []string{ + missingPerformerName, + }, + }, + MissingRefBehaviour: models.ImportMissingRefEnumCreate, + } + + performerReaderWriter.On("FindByNames", []string{missingPerformerName}, false).Return(nil, nil).Once() + performerReaderWriter.On("Create", mock.AnythingOfType("models.Performer")).Return(nil, errors.New("Create error")) + + err := i.PreImport() + assert.NotNil(t, err) +} + +func TestImporterPreImportWithMovie(t *testing.T) { + movieReaderWriter := &mocks.MovieReaderWriter{} + + i := Importer{ + MovieWriter: movieReaderWriter, + Path: path, + MissingRefBehaviour: models.ImportMissingRefEnumFail, + Input: jsonschema.Scene{ + Movies: []jsonschema.SceneMovie{ + { + MovieName: existingMovieName, + SceneIndex: 1, + }, + }, + }, + } + + movieReaderWriter.On("FindByName", existingMovieName, false).Return(&models.Movie{ + ID: existingMovieID, + Name: modelstest.NullString(existingMovieName), + }, nil).Once() + movieReaderWriter.On("FindByName", existingMovieErr, false).Return(nil, errors.New("FindByName error")).Once() + + err := i.PreImport() + assert.Nil(t, err) + assert.Equal(t, existingMovieID, i.movies[0].MovieID) + + i.Input.Movies[0].MovieName = existingMovieErr + err = i.PreImport() + assert.NotNil(t, err) + + movieReaderWriter.AssertExpectations(t) +} + +func TestImporterPreImportWithMissingMovie(t *testing.T) { + movieReaderWriter := &mocks.MovieReaderWriter{} + + i := Importer{ + Path: path, + MovieWriter: movieReaderWriter, + Input: jsonschema.Scene{ + Movies: []jsonschema.SceneMovie{ + { + MovieName: missingMovieName, + }, + }, + }, + MissingRefBehaviour: models.ImportMissingRefEnumFail, + } + + movieReaderWriter.On("FindByName", missingMovieName, false).Return(nil, nil).Times(3) + movieReaderWriter.On("Create", mock.AnythingOfType("models.Movie")).Return(&models.Movie{ + ID: existingMovieID, + }, nil) + + err := i.PreImport() + assert.NotNil(t, err) + + i.MissingRefBehaviour = models.ImportMissingRefEnumIgnore + err = i.PreImport() + assert.Nil(t, err) + + i.MissingRefBehaviour = models.ImportMissingRefEnumCreate + err = i.PreImport() + assert.Nil(t, err) + assert.Equal(t, existingMovieID, i.movies[0].MovieID) + + movieReaderWriter.AssertExpectations(t) +} + +func TestImporterPreImportWithMissingMovieCreateErr(t *testing.T) { + movieReaderWriter := &mocks.MovieReaderWriter{} + + i := Importer{ + MovieWriter: movieReaderWriter, + Path: path, + Input: jsonschema.Scene{ + Movies: []jsonschema.SceneMovie{ + { + MovieName: missingMovieName, + }, + }, + }, + MissingRefBehaviour: models.ImportMissingRefEnumCreate, + } + + movieReaderWriter.On("FindByName", missingMovieName, false).Return(nil, nil).Once() + movieReaderWriter.On("Create", mock.AnythingOfType("models.Movie")).Return(nil, errors.New("Create error")) + + err := i.PreImport() + assert.NotNil(t, err) +} + +func TestImporterPreImportWithTag(t *testing.T) { + tagReaderWriter := &mocks.TagReaderWriter{} + + i := Importer{ + TagWriter: tagReaderWriter, + Path: path, + MissingRefBehaviour: models.ImportMissingRefEnumFail, + Input: jsonschema.Scene{ + Tags: []string{ + existingTagName, + }, + }, + } + + tagReaderWriter.On("FindByNames", []string{existingTagName}, false).Return([]*models.Tag{ + { + ID: existingTagID, + Name: existingTagName, + }, + }, nil).Once() + tagReaderWriter.On("FindByNames", []string{existingTagErr}, false).Return(nil, errors.New("FindByNames error")).Once() + + err := i.PreImport() + assert.Nil(t, err) + assert.Equal(t, existingTagID, i.tags[0].ID) + + i.Input.Tags = []string{existingTagErr} + err = i.PreImport() + assert.NotNil(t, err) + + tagReaderWriter.AssertExpectations(t) +} + +func TestImporterPreImportWithMissingTag(t *testing.T) { + tagReaderWriter := &mocks.TagReaderWriter{} + + i := Importer{ + Path: path, + TagWriter: tagReaderWriter, + Input: jsonschema.Scene{ + Tags: []string{ + missingTagName, + }, + }, + MissingRefBehaviour: models.ImportMissingRefEnumFail, + } + + tagReaderWriter.On("FindByNames", []string{missingTagName}, false).Return(nil, nil).Times(3) + tagReaderWriter.On("Create", mock.AnythingOfType("models.Tag")).Return(&models.Tag{ + ID: existingTagID, + }, nil) + + err := i.PreImport() + assert.NotNil(t, err) + + i.MissingRefBehaviour = models.ImportMissingRefEnumIgnore + err = i.PreImport() + assert.Nil(t, err) + + i.MissingRefBehaviour = models.ImportMissingRefEnumCreate + err = i.PreImport() + assert.Nil(t, err) + assert.Equal(t, existingTagID, i.tags[0].ID) + + tagReaderWriter.AssertExpectations(t) +} + +func TestImporterPreImportWithMissingTagCreateErr(t *testing.T) { + tagReaderWriter := &mocks.TagReaderWriter{} + + i := Importer{ + TagWriter: tagReaderWriter, + Path: path, + Input: jsonschema.Scene{ + Tags: []string{ + missingTagName, + }, + }, + MissingRefBehaviour: models.ImportMissingRefEnumCreate, + } + + tagReaderWriter.On("FindByNames", []string{missingTagName}, false).Return(nil, nil).Once() + tagReaderWriter.On("Create", mock.AnythingOfType("models.Tag")).Return(nil, errors.New("Create error")) + + err := i.PreImport() + assert.NotNil(t, err) +} + +func TestImporterPostImport(t *testing.T) { + readerWriter := &mocks.SceneReaderWriter{} + + i := Importer{ + ReaderWriter: readerWriter, + coverImageData: imageBytes, + } + + updateSceneImageErr := errors.New("UpdateSceneCover error") + + readerWriter.On("UpdateSceneCover", sceneID, imageBytes).Return(nil).Once() + readerWriter.On("UpdateSceneCover", errImageID, imageBytes).Return(updateSceneImageErr).Once() + + err := i.PostImport(sceneID) + assert.Nil(t, err) + + err = i.PostImport(errImageID) + assert.NotNil(t, err) + + readerWriter.AssertExpectations(t) +} + +func TestImporterPostImportUpdateGallery(t *testing.T) { + galleryReaderWriter := &mocks.GalleryReaderWriter{} + + i := Importer{ + GalleryWriter: galleryReaderWriter, + gallery: &models.Gallery{ + ID: existingGalleryID, + }, + } + + updateErr := errors.New("Update error") + + updateArg := *i.gallery + updateArg.SceneID = modelstest.NullInt64(sceneID) + + galleryReaderWriter.On("Update", updateArg).Return(nil, nil).Once() + + updateArg.SceneID = modelstest.NullInt64(errGalleryID) + galleryReaderWriter.On("Update", updateArg).Return(nil, updateErr).Once() + + err := i.PostImport(sceneID) + assert.Nil(t, err) + + err = i.PostImport(errGalleryID) + assert.NotNil(t, err) + + galleryReaderWriter.AssertExpectations(t) +} + +func TestImporterPostImportUpdatePerformers(t *testing.T) { + joinReaderWriter := &mocks.JoinReaderWriter{} + + i := Importer{ + JoinWriter: joinReaderWriter, + performers: []*models.Performer{ + { + ID: existingPerformerID, + }, + }, + } + + updateErr := errors.New("UpdatePerformersScenes error") + + joinReaderWriter.On("UpdatePerformersScenes", sceneID, []models.PerformersScenes{ + { + PerformerID: existingPerformerID, + SceneID: sceneID, + }, + }).Return(nil).Once() + joinReaderWriter.On("UpdatePerformersScenes", errPerformersID, mock.AnythingOfType("[]models.PerformersScenes")).Return(updateErr).Once() + + err := i.PostImport(sceneID) + assert.Nil(t, err) + + err = i.PostImport(errPerformersID) + assert.NotNil(t, err) + + joinReaderWriter.AssertExpectations(t) +} + +func TestImporterPostImportUpdateMovies(t *testing.T) { + joinReaderWriter := &mocks.JoinReaderWriter{} + + i := Importer{ + JoinWriter: joinReaderWriter, + movies: []models.MoviesScenes{ + { + MovieID: existingMovieID, + }, + }, + } + + updateErr := errors.New("UpdateMoviesScenes error") + + joinReaderWriter.On("UpdateMoviesScenes", sceneID, []models.MoviesScenes{ + { + MovieID: existingMovieID, + SceneID: sceneID, + }, + }).Return(nil).Once() + joinReaderWriter.On("UpdateMoviesScenes", errMoviesID, mock.AnythingOfType("[]models.MoviesScenes")).Return(updateErr).Once() + + err := i.PostImport(sceneID) + assert.Nil(t, err) + + err = i.PostImport(errMoviesID) + assert.NotNil(t, err) + + joinReaderWriter.AssertExpectations(t) +} + +func TestImporterPostImportUpdateTags(t *testing.T) { + joinReaderWriter := &mocks.JoinReaderWriter{} + + i := Importer{ + JoinWriter: joinReaderWriter, + tags: []*models.Tag{ + { + ID: existingTagID, + }, + }, + } + + updateErr := errors.New("UpdateScenesTags error") + + joinReaderWriter.On("UpdateScenesTags", sceneID, []models.ScenesTags{ + { + TagID: existingTagID, + SceneID: sceneID, + }, + }).Return(nil).Once() + joinReaderWriter.On("UpdateScenesTags", errTagsID, mock.AnythingOfType("[]models.ScenesTags")).Return(updateErr).Once() + + err := i.PostImport(sceneID) + assert.Nil(t, err) + + err = i.PostImport(errTagsID) + assert.NotNil(t, err) + + joinReaderWriter.AssertExpectations(t) +} + +func TestImporterFindExistingID(t *testing.T) { + readerWriter := &mocks.SceneReaderWriter{} + + i := Importer{ + ReaderWriter: readerWriter, + Path: path, + Input: jsonschema.Scene{ + Checksum: missingChecksum, + OSHash: missingOSHash, + }, + FileNamingAlgorithm: models.HashAlgorithmMd5, + } + + expectedErr := errors.New("FindBy* error") + readerWriter.On("FindByChecksum", missingChecksum).Return(nil, nil).Once() + readerWriter.On("FindByChecksum", checksum).Return(&models.Scene{ + ID: existingSceneID, + }, nil).Once() + readerWriter.On("FindByChecksum", errChecksum).Return(nil, expectedErr).Once() + + readerWriter.On("FindByOSHash", missingOSHash).Return(nil, nil).Once() + readerWriter.On("FindByOSHash", oshash).Return(&models.Scene{ + ID: existingSceneID, + }, nil).Once() + readerWriter.On("FindByOSHash", errOSHash).Return(nil, expectedErr).Once() + + id, err := i.FindExistingID() + assert.Nil(t, id) + assert.Nil(t, err) + + i.Input.Checksum = checksum + id, err = i.FindExistingID() + assert.Equal(t, existingSceneID, *id) + assert.Nil(t, err) + + i.Input.Checksum = errChecksum + id, err = i.FindExistingID() + assert.Nil(t, id) + assert.NotNil(t, err) + + i.FileNamingAlgorithm = models.HashAlgorithmOshash + id, err = i.FindExistingID() + assert.Nil(t, id) + assert.Nil(t, err) + + i.Input.OSHash = oshash + id, err = i.FindExistingID() + assert.Equal(t, existingSceneID, *id) + assert.Nil(t, err) + + i.Input.OSHash = errOSHash + id, err = i.FindExistingID() + assert.Nil(t, id) + assert.NotNil(t, err) + + readerWriter.AssertExpectations(t) +} + +func TestCreate(t *testing.T) { + readerWriter := &mocks.SceneReaderWriter{} + + scene := models.Scene{ + Title: modelstest.NullString(title), + } + + sceneErr := models.Scene{ + Title: modelstest.NullString(sceneNameErr), + } + + i := Importer{ + ReaderWriter: readerWriter, + scene: scene, + } + + errCreate := errors.New("Create error") + readerWriter.On("Create", scene).Return(&models.Scene{ + ID: sceneID, + }, nil).Once() + readerWriter.On("Create", sceneErr).Return(nil, errCreate).Once() + + id, err := i.Create() + assert.Equal(t, sceneID, *id) + assert.Nil(t, err) + assert.Equal(t, sceneID, i.ID) + + i.scene = sceneErr + id, err = i.Create() + assert.Nil(t, id) + assert.NotNil(t, err) + + readerWriter.AssertExpectations(t) +} + +func TestUpdate(t *testing.T) { + readerWriter := &mocks.SceneReaderWriter{} + + scene := models.Scene{ + Title: modelstest.NullString(title), + } + + sceneErr := models.Scene{ + Title: modelstest.NullString(sceneNameErr), + } + + i := Importer{ + ReaderWriter: readerWriter, + scene: scene, + } + + errUpdate := errors.New("Update error") + + // id needs to be set for the mock input + scene.ID = sceneID + readerWriter.On("UpdateFull", scene).Return(nil, nil).Once() + + err := i.Update(sceneID) + assert.Nil(t, err) + assert.Equal(t, sceneID, i.ID) + + i.scene = sceneErr + + // need to set id separately + sceneErr.ID = errImageID + readerWriter.On("UpdateFull", sceneErr).Return(nil, errUpdate).Once() + + err = i.Update(errImageID) + assert.NotNil(t, err) + + readerWriter.AssertExpectations(t) +} diff --git a/pkg/scene/marker_import.go b/pkg/scene/marker_import.go new file mode 100644 index 000000000..9a559b384 --- /dev/null +++ b/pkg/scene/marker_import.go @@ -0,0 +1,125 @@ +package scene + +import ( + "database/sql" + "fmt" + "strconv" + + "github.com/stashapp/stash/pkg/manager/jsonschema" + "github.com/stashapp/stash/pkg/models" +) + +type MarkerImporter struct { + SceneID int + ReaderWriter models.SceneMarkerReaderWriter + TagWriter models.TagReaderWriter + JoinWriter models.JoinReaderWriter + Input jsonschema.SceneMarker + MissingRefBehaviour models.ImportMissingRefEnum + + tags []*models.Tag + marker models.SceneMarker +} + +func (i *MarkerImporter) PreImport() error { + seconds, _ := strconv.ParseFloat(i.Input.Seconds, 64) + i.marker = models.SceneMarker{ + Title: i.Input.Title, + Seconds: seconds, + SceneID: sql.NullInt64{Int64: int64(i.SceneID), Valid: true}, + CreatedAt: models.SQLiteTimestamp{Timestamp: i.Input.CreatedAt.GetTime()}, + UpdatedAt: models.SQLiteTimestamp{Timestamp: i.Input.UpdatedAt.GetTime()}, + } + + if err := i.populateTags(); err != nil { + return err + } + + return nil +} + +func (i *MarkerImporter) populateTags() error { + // primary tag cannot be ignored + mrb := i.MissingRefBehaviour + if mrb == models.ImportMissingRefEnumIgnore { + mrb = models.ImportMissingRefEnumFail + } + + primaryTag, err := importTags(i.TagWriter, []string{i.Input.PrimaryTag}, mrb) + if err != nil { + return err + } + + i.marker.PrimaryTagID = primaryTag[0].ID + + if len(i.Input.Tags) > 0 { + tags, err := importTags(i.TagWriter, i.Input.Tags, i.MissingRefBehaviour) + if err != nil { + return err + } + + i.tags = tags + } + + return nil +} + +func (i *MarkerImporter) PostImport(id int) error { + if len(i.tags) > 0 { + var tagJoins []models.SceneMarkersTags + for _, tag := range i.tags { + join := models.SceneMarkersTags{ + SceneMarkerID: id, + TagID: tag.ID, + } + tagJoins = append(tagJoins, join) + } + if err := i.JoinWriter.UpdateSceneMarkersTags(id, tagJoins); err != nil { + return fmt.Errorf("failed to associate tags: %s", err.Error()) + } + } + + return nil +} + +func (i *MarkerImporter) Name() string { + return fmt.Sprintf("%s (%s)", i.Input.Title, i.Input.Seconds) +} + +func (i *MarkerImporter) FindExistingID() (*int, error) { + existingMarkers, err := i.ReaderWriter.FindBySceneID(i.SceneID) + + if err != nil { + return nil, err + } + + for _, m := range existingMarkers { + if m.Seconds == i.marker.Seconds { + id := m.ID + return &id, nil + } + } + + return nil, nil +} + +func (i *MarkerImporter) Create() (*int, error) { + created, err := i.ReaderWriter.Create(i.marker) + if err != nil { + return nil, fmt.Errorf("error creating marker: %s", err.Error()) + } + + id := created.ID + return &id, nil +} + +func (i *MarkerImporter) Update(id int) error { + marker := i.marker + marker.ID = id + _, err := i.ReaderWriter.Update(marker) + if err != nil { + return fmt.Errorf("error updating existing marker: %s", err.Error()) + } + + return nil +} diff --git a/pkg/scene/marker_import_test.go b/pkg/scene/marker_import_test.go new file mode 100644 index 000000000..23d0d7cf6 --- /dev/null +++ b/pkg/scene/marker_import_test.go @@ -0,0 +1,210 @@ +package scene + +import ( + "errors" + "testing" + + "github.com/stashapp/stash/pkg/manager/jsonschema" + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/models/mocks" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +const ( + seconds = "5" + secondsFloat = 5.0 + errSceneID = 999 +) + +func TestMarkerImporterName(t *testing.T) { + i := MarkerImporter{ + Input: jsonschema.SceneMarker{ + Title: title, + Seconds: seconds, + }, + } + + assert.Equal(t, title+" (5)", i.Name()) +} + +func TestMarkerImporterPreImportWithTag(t *testing.T) { + tagReaderWriter := &mocks.TagReaderWriter{} + + i := MarkerImporter{ + TagWriter: tagReaderWriter, + MissingRefBehaviour: models.ImportMissingRefEnumFail, + Input: jsonschema.SceneMarker{ + PrimaryTag: existingTagName, + }, + } + + tagReaderWriter.On("FindByNames", []string{existingTagName}, false).Return([]*models.Tag{ + { + ID: existingTagID, + Name: existingTagName, + }, + }, nil).Times(4) + tagReaderWriter.On("FindByNames", []string{existingTagErr}, false).Return(nil, errors.New("FindByNames error")).Times(2) + + err := i.PreImport() + assert.Nil(t, err) + assert.Equal(t, existingTagID, i.marker.PrimaryTagID) + + i.Input.PrimaryTag = existingTagErr + err = i.PreImport() + assert.NotNil(t, err) + + i.Input.PrimaryTag = existingTagName + i.Input.Tags = []string{ + existingTagName, + } + err = i.PreImport() + assert.Nil(t, err) + assert.Equal(t, existingTagID, i.tags[0].ID) + + i.Input.Tags[0] = existingTagErr + err = i.PreImport() + assert.NotNil(t, err) + + tagReaderWriter.AssertExpectations(t) +} + +func TestMarkerImporterPostImportUpdateTags(t *testing.T) { + joinReaderWriter := &mocks.JoinReaderWriter{} + + i := MarkerImporter{ + JoinWriter: joinReaderWriter, + tags: []*models.Tag{ + { + ID: existingTagID, + }, + }, + } + + updateErr := errors.New("UpdateSceneMarkersTags error") + + joinReaderWriter.On("UpdateSceneMarkersTags", sceneID, []models.SceneMarkersTags{ + { + TagID: existingTagID, + SceneMarkerID: sceneID, + }, + }).Return(nil).Once() + joinReaderWriter.On("UpdateSceneMarkersTags", errTagsID, mock.AnythingOfType("[]models.SceneMarkersTags")).Return(updateErr).Once() + + err := i.PostImport(sceneID) + assert.Nil(t, err) + + err = i.PostImport(errTagsID) + assert.NotNil(t, err) + + joinReaderWriter.AssertExpectations(t) +} + +func TestMarkerImporterFindExistingID(t *testing.T) { + readerWriter := &mocks.SceneMarkerReaderWriter{} + + i := MarkerImporter{ + ReaderWriter: readerWriter, + SceneID: sceneID, + marker: models.SceneMarker{ + Seconds: secondsFloat, + }, + } + + expectedErr := errors.New("FindBy* error") + readerWriter.On("FindBySceneID", sceneID).Return([]*models.SceneMarker{ + { + ID: existingSceneID, + Seconds: secondsFloat, + }, + }, nil).Times(2) + readerWriter.On("FindBySceneID", errSceneID).Return(nil, expectedErr).Once() + + id, err := i.FindExistingID() + assert.Equal(t, existingSceneID, *id) + assert.Nil(t, err) + + i.marker.Seconds++ + id, err = i.FindExistingID() + assert.Nil(t, id) + assert.Nil(t, err) + + i.SceneID = errSceneID + id, err = i.FindExistingID() + assert.Nil(t, id) + assert.NotNil(t, err) + + readerWriter.AssertExpectations(t) +} + +func TestMarkerImporterCreate(t *testing.T) { + readerWriter := &mocks.SceneMarkerReaderWriter{} + + scene := models.SceneMarker{ + Title: title, + } + + sceneErr := models.SceneMarker{ + Title: sceneNameErr, + } + + i := MarkerImporter{ + ReaderWriter: readerWriter, + marker: scene, + } + + errCreate := errors.New("Create error") + readerWriter.On("Create", scene).Return(&models.SceneMarker{ + ID: sceneID, + }, nil).Once() + readerWriter.On("Create", sceneErr).Return(nil, errCreate).Once() + + id, err := i.Create() + assert.Equal(t, sceneID, *id) + assert.Nil(t, err) + + i.marker = sceneErr + id, err = i.Create() + assert.Nil(t, id) + assert.NotNil(t, err) + + readerWriter.AssertExpectations(t) +} + +func TestMarkerImporterUpdate(t *testing.T) { + readerWriter := &mocks.SceneMarkerReaderWriter{} + + scene := models.SceneMarker{ + Title: title, + } + + sceneErr := models.SceneMarker{ + Title: sceneNameErr, + } + + i := MarkerImporter{ + ReaderWriter: readerWriter, + marker: scene, + } + + errUpdate := errors.New("Update error") + + // id needs to be set for the mock input + scene.ID = sceneID + readerWriter.On("Update", scene).Return(nil, nil).Once() + + err := i.Update(sceneID) + assert.Nil(t, err) + + i.marker = sceneErr + + // need to set id separately + sceneErr.ID = errImageID + readerWriter.On("Update", sceneErr).Return(nil, errUpdate).Once() + + err = i.Update(errImageID) + assert.NotNil(t, err) + + readerWriter.AssertExpectations(t) +} diff --git a/pkg/scraper/action.go b/pkg/scraper/action.go index 8156fb6ce..d3937ea8b 100644 --- a/pkg/scraper/action.go +++ b/pkg/scraper/action.go @@ -40,6 +40,9 @@ type scraper interface { scrapeSceneByFragment(scene models.SceneUpdateInput) (*models.ScrapedScene, error) scrapeSceneByURL(url string) (*models.ScrapedScene, error) + scrapeGalleryByFragment(scene models.GalleryUpdateInput) (*models.ScrapedGallery, error) + scrapeGalleryByURL(url string) (*models.ScrapedGallery, error) + scrapeMovieByURL(url string) (*models.ScrapedMovie, error) } diff --git a/pkg/scraper/config.go b/pkg/scraper/config.go index fad5e04e4..4dca0f58e 100644 --- a/pkg/scraper/config.go +++ b/pkg/scraper/config.go @@ -32,9 +32,15 @@ type config struct { // Configuration for querying scenes by a Scene fragment SceneByFragment *scraperTypeConfig `yaml:"sceneByFragment"` + // Configuration for querying gallery by a Gallery fragment + GalleryByFragment *scraperTypeConfig `yaml:"galleryByFragment"` + // Configuration for querying a scene by a URL SceneByURL []*scrapeByURLConfig `yaml:"sceneByURL"` + // Configuration for querying a gallery by a URL + GalleryByURL []*scrapeByURLConfig `yaml:"galleryByURL"` + // Configuration for querying a movie by a URL MovieByURL []*scrapeByURLConfig `yaml:"movieByURL"` @@ -108,7 +114,8 @@ type scraperTypeConfig struct { Scraper string `yaml:"scraper"` // for xpath name scraper only - QueryURL string `yaml:"queryURL"` + QueryURL string `yaml:"queryURL"` + QueryURLReplacements queryURLReplacements `yaml:"queryURLReplace"` } func (c scraperTypeConfig) validate() error { @@ -234,6 +241,21 @@ func (c config) toScraper() *models.Scraper { ret.Scene = &scene } + gallery := models.ScraperSpec{} + if c.GalleryByFragment != nil { + gallery.SupportedScrapes = append(gallery.SupportedScrapes, models.ScrapeTypeFragment) + } + if len(c.GalleryByURL) > 0 { + gallery.SupportedScrapes = append(gallery.SupportedScrapes, models.ScrapeTypeURL) + for _, v := range c.GalleryByURL { + gallery.Urls = append(gallery.Urls, v.URL...) + } + } + + if len(gallery.SupportedScrapes) > 0 { + ret.Gallery = &gallery + } + movie := models.ScraperSpec{} if len(c.MovieByURL) > 0 { movie.SupportedScrapes = append(movie.SupportedScrapes, models.ScrapeTypeURL) @@ -308,6 +330,10 @@ func (c config) supportsScenes() bool { return c.SceneByFragment != nil || len(c.SceneByURL) > 0 } +func (c config) supportsGalleries() bool { + return c.GalleryByFragment != nil || len(c.GalleryByURL) > 0 +} + func (c config) matchesSceneURL(url string) bool { for _, scraper := range c.SceneByURL { if scraper.matchesURL(url) { @@ -318,6 +344,15 @@ func (c config) matchesSceneURL(url string) bool { return false } +func (c config) matchesGalleryURL(url string) bool { + for _, scraper := range c.GalleryByURL { + if scraper.matchesURL(url) { + return true + } + } + return false +} + func (c config) supportsMovies() bool { return len(c.MovieByURL) > 0 } @@ -359,6 +394,33 @@ func (c config) ScrapeSceneURL(url string, globalConfig GlobalConfig) (*models.S return nil, nil } +func (c config) ScrapeGallery(gallery models.GalleryUpdateInput, globalConfig GlobalConfig) (*models.ScrapedGallery, error) { + if c.GalleryByFragment != nil { + s := getScraper(*c.GalleryByFragment, c, globalConfig) + return s.scrapeGalleryByFragment(gallery) + } + + return nil, nil +} + +func (c config) ScrapeGalleryURL(url string, globalConfig GlobalConfig) (*models.ScrapedGallery, error) { + for _, scraper := range c.GalleryByURL { + if scraper.matchesURL(url) { + s := getScraper(scraper.scraperTypeConfig, c, globalConfig) + ret, err := s.scrapeGalleryByURL(url) + if err != nil { + return nil, err + } + + if ret != nil { + return ret, nil + } + } + } + + return nil, nil +} + func (c config) ScrapeMovieURL(url string, globalConfig GlobalConfig) (*models.ScrapedMovie, error) { for _, scraper := range c.MovieByURL { if scraper.matchesURL(url) { diff --git a/pkg/scraper/freeones.go b/pkg/scraper/freeones.go index 8b7277ba2..dd39517dd 100644 --- a/pkg/scraper/freeones.go +++ b/pkg/scraper/freeones.go @@ -14,12 +14,13 @@ const freeonesScraperConfig = ` name: Freeones performerByName: action: scrapeXPath - queryURL: https://www.freeones.xxx/babes?q={}&v=teasers&s=relevance&l=96&m%5BcanPreviewFeatures%5D=0 + queryURL: https://www.freeones.com/babes?q={}&v=teasers&s=relevance&l=96&m%5BcanPreviewFeatures%5D=0 scraper: performerSearch performerByURL: - action: scrapeXPath url: - - https://www.freeones.xxx + - freeones.xxx + - freeones.com scraper: performerScraper xPathScrapers: @@ -28,80 +29,78 @@ xPathScrapers: Name: //div[@id="search-result"]//p[@data-test="subject-name"]/text() URL: selector: //div[@id="search-result"]//div[@data-test="teaser-subject"]/a/@href - replace: - - regex: ^ - with: https://www.freeones.xxx - - regex: $ - with: /profile + postProcess: + - replace: + - regex: ^ + with: https://www.freeones.com + - regex: $ + with: /profile performerScraper: performer: - Name: //h1 + Name: + selector: //h1 + postProcess: + - replace: + - regex: \sBio\s*$ + with: "" URL: selector: //a[span[text()="Profile"]]/@href - replace: - - regex: ^ - with: https://www.freeones.xxx - Twitter: //div[p[text()='Follow On']]//div//a[@class='d-flex align-items-center justify-content-center m-2 social-icons color-twitter']/@href - Instagram: //div[p[text()='Follow On']]//div//a[@class='d-flex align-items-center justify-content-center m-2 social-icons color-telegram']/@href + postProcess: + - replace: + - regex: ^ + with: https://www.freeones.com + Twitter: //a[contains(@href,'twitter.com/')]/@href + Instagram: //a[contains(@href,'instagram.com/')]/@href Birthdate: - selector: //div[p[text()='Personal Information']]//div//p/a/span[contains(text(),'Born On')] - replace: - - regex: Born On - with: - - regex: "," - with: - parseDate: January 2 2006 + selector: //div[p[text()='Personal Information']]//span[contains(text(),'Born On')] + postProcess: + - replace: + - regex: Born On + with: + - parseDate: January 2, 2006 Ethnicity: - selector: //div[p[text()='Ethnicity']]//div//p[@class='mb-0 text-center'] - replace: - - regex: Asian - with: "asian" - - regex: Caucasian - with: "white" - - regex: Black - with: "black" - - regex: Latin - with: "hispanic" - Country: //div[p[text()='Personal Information']]//div//p//a[@data-test="link-country"] - EyeColor: //span[@data-test="link_span_eye_color"] + selector: //div[p[text()='Ethnicity']]//a[@data-test="link_ethnicity"] + postProcess: + - map: + Asian: asian + Caucasian: white + Black: black + Latin: hispanic + Country: //div[p[text()='Personal Information']]//a[@data-test="link-country"] + EyeColor: //span[text()='Eye Color']/following-sibling::span/a Height: - selector: //span[@data-test="link_span_height"] - replace: - - regex: \D+[\s\S]+ - with: "" + selector: //span[text()='Height']/following-sibling::span/a + postProcess: + - replace: + - regex: \D+[\s\S]+ + with: "" + - map: + Unknown: "" Measurements: - selector: //span[@data-test="p-measurements"]//a/span + selector: //span[text()='Measurements']/following-sibling::span/span/a concat: " - " - replace: - - regex: Unknown - with: + postProcess: + - map: + Unknown: "" FakeTits: - selector: //span[@data-test='link_span_boobs'] - replace: - - regex: Unknown - with: - - regex: Fake - with: "Yes" - - regex: Natural - with: "No" + selector: //span[text()='Boobs']/following-sibling::span/a + postProcess: + - map: + Unknown: "" + Fake: Yes + Natural: No CareerLength: - selector: //div[p[text()='career']]//div//div[@class='timeline-horizontal mb-3']//div//p[@class='m-0'] + selector: //div[p[text()='career']]//div[contains(@class,'timeline-horizontal')]//p[@class='m-0'] concat: "-" - replace: - - regex: -\w+-\w+-\w+-\w+-\w+$ - with: "" - Aliases: //div[p[text()='Aliases']]//div//p[@class='mb-0 text-center'] - Tattoos: //span[@data-test="p_has_tattoos"]|//span[@cdata-test="p_has_tattoos"] - Piercings: //span[@data-test="p_has_piercings"] + Aliases: //p[text()='Aliases']/following-sibling::div/p + Tattoos: //span[text()='Tattoos']/following-sibling::span/span + Piercings: //span[text()='Piercings']/following-sibling::span/span Image: - selector: //div[@class='profile-image-container']//a/img/@src + selector: //div[contains(@class,'image-container')]//a/img/@src Gender: - selector: //meta[@name="language"]/@name - replace: - - regex: language - with: "Female" -# Last updated June 15, 2020 + fixed: "Female" +# Last updated November 06, 2020 ` func getFreeonesScraper() config { diff --git a/pkg/scraper/json.go b/pkg/scraper/json.go index 7fb7522a3..00590f5fe 100644 --- a/pkg/scraper/json.go +++ b/pkg/scraper/json.go @@ -88,6 +88,16 @@ func (s *jsonScraper) scrapeSceneByURL(url string) (*models.ScrapedScene, error) return scraper.scrapeScene(q) } +func (s *jsonScraper) scrapeGalleryByURL(url string) (*models.ScrapedGallery, error) { + doc, scraper, err := s.scrapeURL(url) + if err != nil { + return nil, err + } + + q := s.getJsonQuery(doc) + return scraper.scrapeGallery(q) +} + func (s *jsonScraper) scrapeMovieByURL(url string) (*models.ScrapedMovie, error) { doc, scraper, err := s.scrapeURL(url) if err != nil { @@ -138,7 +148,11 @@ func (s *jsonScraper) scrapeSceneByFragment(scene models.SceneUpdateInput) (*mod } // construct the URL - url := constructSceneURL(s.scraper.QueryURL, storedScene) + queryURL := queryURLParametersFromScene(storedScene) + if s.scraper.QueryURLReplacements != nil { + queryURL.applyReplacements(s.scraper.QueryURLReplacements) + } + url := queryURL.constructURL(s.scraper.QueryURL) scraper := s.getJsonScraper() @@ -156,6 +170,39 @@ func (s *jsonScraper) scrapeSceneByFragment(scene models.SceneUpdateInput) (*mod return scraper.scrapeScene(q) } +func (s *jsonScraper) scrapeGalleryByFragment(gallery models.GalleryUpdateInput) (*models.ScrapedGallery, error) { + storedGallery, err := galleryFromUpdateFragment(gallery) + if err != nil { + return nil, err + } + + if storedGallery == nil { + return nil, errors.New("no scene found") + } + + // construct the URL + queryURL := queryURLParametersFromGallery(storedGallery) + if s.scraper.QueryURLReplacements != nil { + queryURL.applyReplacements(s.scraper.QueryURLReplacements) + } + url := queryURL.constructURL(s.scraper.QueryURL) + + scraper := s.getJsonScraper() + + if scraper == nil { + return nil, errors.New("json scraper with name " + s.scraper.Scraper + " not found in config") + } + + doc, err := s.loadURL(url) + + if err != nil { + return nil, err + } + + q := s.getJsonQuery(doc) + return scraper.scrapeGallery(q) +} + func (s *jsonScraper) getJsonQuery(doc string) *jsonQuery { return &jsonQuery{ doc: doc, diff --git a/pkg/scraper/mapped.go b/pkg/scraper/mapped.go index 540fcd9a5..98f4896e4 100644 --- a/pkg/scraper/mapped.go +++ b/pkg/scraper/mapped.go @@ -155,6 +155,60 @@ func (s *mappedSceneScraperConfig) UnmarshalYAML(unmarshal func(interface{}) err return nil } +type mappedGalleryScraperConfig struct { + mappedConfig + + Tags mappedConfig `yaml:"Tags"` + Performers mappedConfig `yaml:"Performers"` + Studio mappedConfig `yaml:"Studio"` +} +type _mappedGalleryScraperConfig mappedGalleryScraperConfig + +func (s *mappedGalleryScraperConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { + // HACK - unmarshal to map first, then remove known scene sub-fields, then + // remarshal to yaml and pass that down to the base map + parentMap := make(map[string]interface{}) + if err := unmarshal(parentMap); err != nil { + return err + } + + // move the known sub-fields to a separate map + thisMap := make(map[string]interface{}) + + thisMap[mappedScraperConfigSceneTags] = parentMap[mappedScraperConfigSceneTags] + thisMap[mappedScraperConfigScenePerformers] = parentMap[mappedScraperConfigScenePerformers] + thisMap[mappedScraperConfigSceneStudio] = parentMap[mappedScraperConfigSceneStudio] + + delete(parentMap, mappedScraperConfigSceneTags) + delete(parentMap, mappedScraperConfigScenePerformers) + delete(parentMap, mappedScraperConfigSceneStudio) + + // re-unmarshal the sub-fields + yml, err := yaml.Marshal(thisMap) + if err != nil { + return err + } + + // needs to be a different type to prevent infinite recursion + c := _mappedGalleryScraperConfig{} + if err := yaml.Unmarshal(yml, &c); err != nil { + return err + } + + *s = mappedGalleryScraperConfig(c) + + yml, err = yaml.Marshal(parentMap) + if err != nil { + return err + } + + if err := yaml.Unmarshal(yml, &s.mappedConfig); err != nil { + return err + } + + return nil +} + type mappedPerformerScraperConfig struct { mappedConfig } @@ -540,6 +594,7 @@ type mappedScrapers map[string]*mappedScraper type mappedScraper struct { Common commonMappedConfig `yaml:"common"` Scene *mappedSceneScraperConfig `yaml:"scene"` + Gallery *mappedGalleryScraperConfig `yaml:"gallery"` Performer *mappedPerformerScraperConfig `yaml:"performer"` Movie *mappedMovieScraperConfig `yaml:"movie"` } @@ -687,6 +742,62 @@ func (s mappedScraper) scrapeScene(q mappedQuery) (*models.ScrapedScene, error) return &ret, nil } +func (s mappedScraper) scrapeGallery(q mappedQuery) (*models.ScrapedGallery, error) { + var ret models.ScrapedGallery + + galleryScraperConfig := s.Gallery + galleryMap := galleryScraperConfig.mappedConfig + if galleryMap == nil { + return nil, nil + } + + galleryPerformersMap := galleryScraperConfig.Performers + galleryTagsMap := galleryScraperConfig.Tags + galleryStudioMap := galleryScraperConfig.Studio + + logger.Debug(`Processing gallery:`) + results := galleryMap.process(q, s.Common) + if len(results) > 0 { + results[0].apply(&ret) + + // now apply the performers and tags + if galleryPerformersMap != nil { + logger.Debug(`Processing gallery performers:`) + performerResults := galleryPerformersMap.process(q, s.Common) + + for _, p := range performerResults { + performer := &models.ScrapedScenePerformer{} + p.apply(performer) + ret.Performers = append(ret.Performers, performer) + } + } + + if galleryTagsMap != nil { + logger.Debug(`Processing gallery tags:`) + tagResults := galleryTagsMap.process(q, s.Common) + + for _, p := range tagResults { + tag := &models.ScrapedSceneTag{} + p.apply(tag) + ret.Tags = append(ret.Tags, tag) + } + } + + if galleryStudioMap != nil { + logger.Debug(`Processing gallery studio:`) + studioResults := galleryStudioMap.process(q, s.Common) + + if len(studioResults) > 0 { + studio := &models.ScrapedSceneStudio{} + studioResults[0].apply(studio) + ret.Studio = studio + } + } + } + + return &ret, nil +} + func (s mappedScraper) scrapeMovie(q mappedQuery) (*models.ScrapedMovie, error) { var ret models.ScrapedMovie diff --git a/pkg/scraper/query_url.go b/pkg/scraper/query_url.go new file mode 100644 index 000000000..517df5ac2 --- /dev/null +++ b/pkg/scraper/query_url.go @@ -0,0 +1,51 @@ +package scraper + +import ( + "path/filepath" + "strings" + + "github.com/stashapp/stash/pkg/models" +) + +type queryURLReplacements map[string]mappedRegexConfigs + +type queryURLParameters map[string]string + +func queryURLParametersFromScene(scene *models.Scene) queryURLParameters { + ret := make(queryURLParameters) + ret["checksum"] = scene.Checksum.String + ret["oshash"] = scene.OSHash.String + ret["filename"] = filepath.Base(scene.Path) + ret["title"] = scene.Title.String + return ret +} + +func queryURLParametersFromGallery(gallery *models.Gallery) queryURLParameters { + ret := make(queryURLParameters) + ret["checksum"] = gallery.Checksum + + if gallery.Path.Valid { + ret["filename"] = filepath.Base(gallery.Path.String) + } + ret["title"] = gallery.Title.String + + return ret +} + +func (p queryURLParameters) applyReplacements(r queryURLReplacements) { + for k, v := range p { + rpl, found := r[k] + if found { + p[k] = rpl.apply(v) + } + } +} + +func (p queryURLParameters) constructURL(url string) string { + ret := url + for k, v := range p { + ret = strings.Replace(ret, "{"+k+"}", v, -1) + } + + return ret +} diff --git a/pkg/scraper/scrapers.go b/pkg/scraper/scrapers.go index 64788bccc..23918d684 100644 --- a/pkg/scraper/scrapers.go +++ b/pkg/scraper/scrapers.go @@ -9,6 +9,7 @@ import ( "github.com/stashapp/stash/pkg/logger" "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/utils" ) // GlobalConfig contains the global scraper options. @@ -58,7 +59,7 @@ func loadScrapers(path string) ([]config, error) { logger.Debugf("Reading scraper configs from %s", path) scraperFiles := []string{} - err := filepath.Walk(path, func(fp string, f os.FileInfo, err error) error { + err := utils.SymWalk(path, func(fp string, f os.FileInfo, err error) error { if filepath.Ext(fp) == ".yml" { scraperFiles = append(scraperFiles, fp) } @@ -132,6 +133,20 @@ func (c Cache) ListSceneScrapers() []*models.Scraper { return ret } +// ListGalleryScrapers returns a list of scrapers that are capable of +// scraping galleries. +func (c Cache) ListGalleryScrapers() []*models.Scraper { + var ret []*models.Scraper + for _, s := range c.scrapers { + // filter on type + if s.supportsGalleries() { + ret = append(ret, s.toScraper()) + } + } + + return ret +} + // ListMovieScrapers returns a list of scrapers that are capable of // scraping scenes. func (c Cache) ListMovieScrapers() []*models.Scraper { @@ -214,106 +229,30 @@ func (c Cache) ScrapePerformerURL(url string) (*models.ScrapedPerformer, error) return nil, nil } -func matchPerformer(p *models.ScrapedScenePerformer) error { - qb := models.NewPerformerQueryBuilder() - - performers, err := qb.FindByNames([]string{p.Name}, nil, true) - - if err != nil { - return err - } - - if len(performers) != 1 { - // ignore - cannot match - return nil - } - - id := strconv.Itoa(performers[0].ID) - p.ID = &id - return nil -} - -func matchStudio(s *models.ScrapedSceneStudio) error { - qb := models.NewStudioQueryBuilder() - - studio, err := qb.FindByName(s.Name, nil, true) - - if err != nil { - return err - } - - if studio == nil { - // ignore - cannot match - return nil - } - - id := strconv.Itoa(studio.ID) - s.ID = &id - return nil -} - -func matchMovie(m *models.ScrapedSceneMovie) error { - qb := models.NewMovieQueryBuilder() - - movies, err := qb.FindByNames([]string{m.Name}, nil, true) - - if err != nil { - return err - } - - if len(movies) != 1 { - // ignore - cannot match - return nil - } - - id := strconv.Itoa(movies[0].ID) - m.ID = &id - return nil -} - -func matchTag(s *models.ScrapedSceneTag) error { - qb := models.NewTagQueryBuilder() - - tag, err := qb.FindByName(s.Name, nil, true) - - if err != nil { - return err - } - - if tag == nil { - // ignore - cannot match - return nil - } - - id := strconv.Itoa(tag.ID) - s.ID = &id - return nil -} - func (c Cache) postScrapeScene(ret *models.ScrapedScene) error { for _, p := range ret.Performers { - err := matchPerformer(p) + err := models.MatchScrapedScenePerformer(p) if err != nil { return err } } for _, p := range ret.Movies { - err := matchMovie(p) + err := models.MatchScrapedSceneMovie(p) if err != nil { return err } } for _, t := range ret.Tags { - err := matchTag(t) + err := models.MatchScrapedSceneTag(t) if err != nil { return err } } if ret.Studio != nil { - err := matchStudio(ret.Studio) + err := models.MatchScrapedSceneStudio(ret.Studio) if err != nil { return err } @@ -327,6 +266,31 @@ func (c Cache) postScrapeScene(ret *models.ScrapedScene) error { return nil } +func (c Cache) postScrapeGallery(ret *models.ScrapedGallery) error { + for _, p := range ret.Performers { + err := models.MatchScrapedScenePerformer(p) + if err != nil { + return err + } + } + + for _, t := range ret.Tags { + err := models.MatchScrapedSceneTag(t) + if err != nil { + return err + } + } + + if ret.Studio != nil { + err := models.MatchScrapedSceneStudio(ret.Studio) + if err != nil { + return err + } + } + + return nil +} + // ScrapeScene uses the scraper with the provided ID to scrape a scene. func (c Cache) ScrapeScene(scraperID string, scene models.SceneUpdateInput) (*models.ScrapedScene, error) { // find scraper with the provided id @@ -375,6 +339,53 @@ func (c Cache) ScrapeSceneURL(url string) (*models.ScrapedScene, error) { return nil, nil } +// ScrapeGallery uses the scraper with the provided ID to scrape a scene. +func (c Cache) ScrapeGallery(scraperID string, gallery models.GalleryUpdateInput) (*models.ScrapedGallery, error) { + s := c.findScraper(scraperID) + if s != nil { + ret, err := s.ScrapeGallery(gallery, c.globalConfig) + + if err != nil { + return nil, err + } + + if ret != nil { + err = c.postScrapeGallery(ret) + if err != nil { + return nil, err + } + } + + return ret, nil + } + + return nil, errors.New("Scraped with ID " + scraperID + " not found") +} + +// ScrapeGalleryURL uses the first scraper it finds that matches the URL +// provided to scrape a scene. If no scrapers are found that matches +// the URL, then nil is returned. +func (c Cache) ScrapeGalleryURL(url string) (*models.ScrapedGallery, error) { + for _, s := range c.scrapers { + if s.matchesGalleryURL(url) { + ret, err := s.ScrapeGalleryURL(url, c.globalConfig) + + if err != nil { + return nil, err + } + + err = c.postScrapeGallery(ret) + if err != nil { + return nil, err + } + + return ret, nil + } + } + + return nil, nil +} + func matchMovieStudio(s *models.ScrapedMovieStudio) error { qb := models.NewStudioQueryBuilder() diff --git a/pkg/scraper/script.go b/pkg/scraper/script.go index 91a754f7c..e8a1ee0af 100644 --- a/pkg/scraper/script.go +++ b/pkg/scraper/script.go @@ -137,6 +137,20 @@ func (s *scriptScraper) scrapeSceneByFragment(scene models.SceneUpdateInput) (*m return &ret, err } +func (s *scriptScraper) scrapeGalleryByFragment(gallery models.GalleryUpdateInput) (*models.ScrapedGallery, error) { + inString, err := json.Marshal(gallery) + + if err != nil { + return nil, err + } + + var ret models.ScrapedGallery + + err = s.runScraperScript(string(inString), &ret) + + return &ret, err +} + func (s *scriptScraper) scrapeSceneByURL(url string) (*models.ScrapedScene, error) { inString := `{"url": "` + url + `"}` @@ -147,6 +161,16 @@ func (s *scriptScraper) scrapeSceneByURL(url string) (*models.ScrapedScene, erro return &ret, err } +func (s *scriptScraper) scrapeGalleryByURL(url string) (*models.ScrapedGallery, error) { + inString := `{"url": "` + url + `"}` + + var ret models.ScrapedGallery + + err := s.runScraperScript(string(inString), &ret) + + return &ret, err +} + func (s *scriptScraper) scrapeMovieByURL(url string) (*models.ScrapedMovie, error) { inString := `{"url": "` + url + `"}` diff --git a/pkg/scraper/stash.go b/pkg/scraper/stash.go index d14122760..873828e54 100644 --- a/pkg/scraper/stash.go +++ b/pkg/scraper/stash.go @@ -184,6 +184,68 @@ func (s *stashScraper) scrapeSceneByFragment(scene models.SceneUpdateInput) (*mo return &ret, nil } +func (s *stashScraper) scrapeGalleryByFragment(scene models.GalleryUpdateInput) (*models.ScrapedGallery, error) { + // query by MD5 + // assumes that the gallery exists in the database + qb := models.NewGalleryQueryBuilder() + id, err := strconv.Atoi(scene.ID) + if err != nil { + return nil, err + } + + storedGallery, err := qb.Find(id, nil) + + if err != nil { + return nil, err + } + + var q struct { + FindGallery *models.ScrapedGalleryStash `graphql:"findGalleryByHash(input: $c)"` + } + + type GalleryHashInput struct { + Checksum *string `graphql:"checksum" json:"checksum"` + } + + input := GalleryHashInput{ + Checksum: &storedGallery.Checksum, + } + + vars := map[string]interface{}{ + "c": &input, + } + + client := s.getStashClient() + err = client.Query(context.Background(), &q, vars) + if err != nil { + return nil, err + } + + if q.FindGallery != nil { + // the ids of the studio, performers and tags must be nilled + if q.FindGallery.Studio != nil { + q.FindGallery.Studio.ID = nil + } + + for _, p := range q.FindGallery.Performers { + p.ID = nil + } + + for _, t := range q.FindGallery.Tags { + t.ID = nil + } + } + + // need to copy back to a scraped scene + ret := models.ScrapedGallery{} + err = copier.Copy(&ret, q.FindGallery) + if err != nil { + return nil, err + } + + return &ret, nil +} + func (s *stashScraper) scrapePerformerByURL(url string) (*models.ScrapedPerformer, error) { return nil, errors.New("scrapePerformerByURL not supported for stash scraper") } @@ -192,6 +254,10 @@ func (s *stashScraper) scrapeSceneByURL(url string) (*models.ScrapedScene, error return nil, errors.New("scrapeSceneByURL not supported for stash scraper") } +func (s *stashScraper) scrapeGalleryByURL(url string) (*models.ScrapedGallery, error) { + return nil, errors.New("scrapeGalleryByURL not supported for stash scraper") +} + func (s *stashScraper) scrapeMovieByURL(url string) (*models.ScrapedMovie, error) { return nil, errors.New("scrapeMovieByURL not supported for stash scraper") } @@ -206,3 +272,13 @@ func sceneFromUpdateFragment(scene models.SceneUpdateInput) (*models.Scene, erro // TODO - should we modify it with the input? return qb.Find(id) } + +func galleryFromUpdateFragment(gallery models.GalleryUpdateInput) (*models.Gallery, error) { + qb := models.NewGalleryQueryBuilder() + id, err := strconv.Atoi(gallery.ID) + if err != nil { + return nil, err + } + + return qb.Find(id, nil) +} diff --git a/pkg/scraper/stashbox/graphql/generated_client.go b/pkg/scraper/stashbox/graphql/generated_client.go new file mode 100644 index 000000000..26e4c7e39 --- /dev/null +++ b/pkg/scraper/stashbox/graphql/generated_client.go @@ -0,0 +1,718 @@ +// Code generated by github.com/Yamashou/gqlgenc, DO NOT EDIT. + +package graphql + +import ( + "context" + "net/http" + + "github.com/Yamashou/gqlgenc/client" +) + +type Client struct { + Client *client.Client +} + +func NewClient(cli *http.Client, baseURL string, options ...client.HTTPRequestOption) *Client { + return &Client{Client: client.NewClient(cli, baseURL, options...)} +} + +type Query struct { + FindPerformer *Performer "json:\"findPerformer\" graphql:\"findPerformer\"" + QueryPerformers QueryPerformersResultType "json:\"queryPerformers\" graphql:\"queryPerformers\"" + FindStudio *Studio "json:\"findStudio\" graphql:\"findStudio\"" + QueryStudios QueryStudiosResultType "json:\"queryStudios\" graphql:\"queryStudios\"" + FindTag *Tag "json:\"findTag\" graphql:\"findTag\"" + QueryTags QueryTagsResultType "json:\"queryTags\" graphql:\"queryTags\"" + FindScene *Scene "json:\"findScene\" graphql:\"findScene\"" + FindSceneByFingerprint []*Scene "json:\"findSceneByFingerprint\" graphql:\"findSceneByFingerprint\"" + FindScenesByFingerprints []*Scene "json:\"findScenesByFingerprints\" graphql:\"findScenesByFingerprints\"" + QueryScenes QueryScenesResultType "json:\"queryScenes\" graphql:\"queryScenes\"" + FindEdit *Edit "json:\"findEdit\" graphql:\"findEdit\"" + QueryEdits QueryEditsResultType "json:\"queryEdits\" graphql:\"queryEdits\"" + FindUser *User "json:\"findUser\" graphql:\"findUser\"" + QueryUsers QueryUsersResultType "json:\"queryUsers\" graphql:\"queryUsers\"" + Me *User "json:\"me\" graphql:\"me\"" + SearchPerformer []*Performer "json:\"searchPerformer\" graphql:\"searchPerformer\"" + SearchScene []*Scene "json:\"searchScene\" graphql:\"searchScene\"" + Version Version "json:\"version\" graphql:\"version\"" +} + +type Mutation struct { + SceneCreate *Scene "json:\"sceneCreate\" graphql:\"sceneCreate\"" + SceneUpdate *Scene "json:\"sceneUpdate\" graphql:\"sceneUpdate\"" + SceneDestroy bool "json:\"sceneDestroy\" graphql:\"sceneDestroy\"" + PerformerCreate *Performer "json:\"performerCreate\" graphql:\"performerCreate\"" + PerformerUpdate *Performer "json:\"performerUpdate\" graphql:\"performerUpdate\"" + PerformerDestroy bool "json:\"performerDestroy\" graphql:\"performerDestroy\"" + StudioCreate *Studio "json:\"studioCreate\" graphql:\"studioCreate\"" + StudioUpdate *Studio "json:\"studioUpdate\" graphql:\"studioUpdate\"" + StudioDestroy bool "json:\"studioDestroy\" graphql:\"studioDestroy\"" + TagCreate *Tag "json:\"tagCreate\" graphql:\"tagCreate\"" + TagUpdate *Tag "json:\"tagUpdate\" graphql:\"tagUpdate\"" + TagDestroy bool "json:\"tagDestroy\" graphql:\"tagDestroy\"" + UserCreate *User "json:\"userCreate\" graphql:\"userCreate\"" + UserUpdate *User "json:\"userUpdate\" graphql:\"userUpdate\"" + UserDestroy bool "json:\"userDestroy\" graphql:\"userDestroy\"" + ImageCreate *Image "json:\"imageCreate\" graphql:\"imageCreate\"" + ImageUpdate *Image "json:\"imageUpdate\" graphql:\"imageUpdate\"" + ImageDestroy bool "json:\"imageDestroy\" graphql:\"imageDestroy\"" + RegenerateAPIKey string "json:\"regenerateAPIKey\" graphql:\"regenerateAPIKey\"" + ChangePassword bool "json:\"changePassword\" graphql:\"changePassword\"" + SceneEdit Edit "json:\"sceneEdit\" graphql:\"sceneEdit\"" + PerformerEdit Edit "json:\"performerEdit\" graphql:\"performerEdit\"" + StudioEdit Edit "json:\"studioEdit\" graphql:\"studioEdit\"" + TagEdit Edit "json:\"tagEdit\" graphql:\"tagEdit\"" + EditVote Edit "json:\"editVote\" graphql:\"editVote\"" + EditComment Edit "json:\"editComment\" graphql:\"editComment\"" + ApplyEdit Edit "json:\"applyEdit\" graphql:\"applyEdit\"" + CancelEdit Edit "json:\"cancelEdit\" graphql:\"cancelEdit\"" + SubmitFingerprint bool "json:\"submitFingerprint\" graphql:\"submitFingerprint\"" +} +type URLFragment struct { + URL string "json:\"url\" graphql:\"url\"" + Type string "json:\"type\" graphql:\"type\"" +} +type ImageFragment struct { + ID string "json:\"id\" graphql:\"id\"" + URL string "json:\"url\" graphql:\"url\"" + Width *int "json:\"width\" graphql:\"width\"" + Height *int "json:\"height\" graphql:\"height\"" +} +type StudioFragment struct { + Name string "json:\"name\" graphql:\"name\"" + ID string "json:\"id\" graphql:\"id\"" + Urls []*URLFragment "json:\"urls\" graphql:\"urls\"" + Images []*ImageFragment "json:\"images\" graphql:\"images\"" +} +type TagFragment struct { + Name string "json:\"name\" graphql:\"name\"" + ID string "json:\"id\" graphql:\"id\"" +} +type FuzzyDateFragment struct { + Date string "json:\"date\" graphql:\"date\"" + Accuracy DateAccuracyEnum "json:\"accuracy\" graphql:\"accuracy\"" +} +type MeasurementsFragment struct { + BandSize *int "json:\"band_size\" graphql:\"band_size\"" + CupSize *string "json:\"cup_size\" graphql:\"cup_size\"" + Waist *int "json:\"waist\" graphql:\"waist\"" + Hip *int "json:\"hip\" graphql:\"hip\"" +} +type BodyModificationFragment struct { + Location string "json:\"location\" graphql:\"location\"" + Description *string "json:\"description\" graphql:\"description\"" +} +type PerformerFragment struct { + ID string "json:\"id\" graphql:\"id\"" + Name string "json:\"name\" graphql:\"name\"" + Disambiguation *string "json:\"disambiguation\" graphql:\"disambiguation\"" + Aliases []string "json:\"aliases\" graphql:\"aliases\"" + Gender *GenderEnum "json:\"gender\" graphql:\"gender\"" + Urls []*URLFragment "json:\"urls\" graphql:\"urls\"" + Images []*ImageFragment "json:\"images\" graphql:\"images\"" + Birthdate *FuzzyDateFragment "json:\"birthdate\" graphql:\"birthdate\"" + Ethnicity *EthnicityEnum "json:\"ethnicity\" graphql:\"ethnicity\"" + Country *string "json:\"country\" graphql:\"country\"" + EyeColor *EyeColorEnum "json:\"eye_color\" graphql:\"eye_color\"" + HairColor *HairColorEnum "json:\"hair_color\" graphql:\"hair_color\"" + Height *int "json:\"height\" graphql:\"height\"" + Measurements MeasurementsFragment "json:\"measurements\" graphql:\"measurements\"" + BreastType *BreastTypeEnum "json:\"breast_type\" graphql:\"breast_type\"" + CareerStartYear *int "json:\"career_start_year\" graphql:\"career_start_year\"" + CareerEndYear *int "json:\"career_end_year\" graphql:\"career_end_year\"" + Tattoos []*BodyModificationFragment "json:\"tattoos\" graphql:\"tattoos\"" + Piercings []*BodyModificationFragment "json:\"piercings\" graphql:\"piercings\"" +} +type PerformerAppearanceFragment struct { + As *string "json:\"as\" graphql:\"as\"" + Performer PerformerFragment "json:\"performer\" graphql:\"performer\"" +} +type FingerprintFragment struct { + Algorithm FingerprintAlgorithm "json:\"algorithm\" graphql:\"algorithm\"" + Hash string "json:\"hash\" graphql:\"hash\"" + Duration int "json:\"duration\" graphql:\"duration\"" +} +type SceneFragment struct { + ID string "json:\"id\" graphql:\"id\"" + Title *string "json:\"title\" graphql:\"title\"" + Details *string "json:\"details\" graphql:\"details\"" + Duration *int "json:\"duration\" graphql:\"duration\"" + Date *string "json:\"date\" graphql:\"date\"" + Urls []*URLFragment "json:\"urls\" graphql:\"urls\"" + Images []*ImageFragment "json:\"images\" graphql:\"images\"" + Studio *StudioFragment "json:\"studio\" graphql:\"studio\"" + Tags []*TagFragment "json:\"tags\" graphql:\"tags\"" + Performers []*PerformerAppearanceFragment "json:\"performers\" graphql:\"performers\"" + Fingerprints []*FingerprintFragment "json:\"fingerprints\" graphql:\"fingerprints\"" +} +type GalleryFragment struct { + ID string "json:\"id\" graphql:\"id\"" + Title *string "json:\"title\" graphql:\"title\"" + Details *string "json:\"details\" graphql:\"details\"" + Duration *int "json:\"duration\" graphql:\"duration\"" + Date *string "json:\"date\" graphql:\"date\"" + Urls []*URLFragment "json:\"urls\" graphql:\"urls\"" + Images []*ImageFragment "json:\"images\" graphql:\"images\"" + Studio *StudioFragment "json:\"studio\" graphql:\"studio\"" + Tags []*TagFragment "json:\"tags\" graphql:\"tags\"" + Performers []*PerformerAppearanceFragment "json:\"performers\" graphql:\"performers\"" + Fingerprints []*FingerprintFragment "json:\"fingerprints\" graphql:\"fingerprints\"" +} +type FindSceneByFingerprint struct { + FindSceneByFingerprint []*SceneFragment "json:\"findSceneByFingerprint\" graphql:\"findSceneByFingerprint\"" +} +type FindScenesByFingerprints struct { + FindScenesByFingerprints []*SceneFragment "json:\"findScenesByFingerprints\" graphql:\"findScenesByFingerprints\"" +} +type FindGalleriesByFingerprints struct { + FindGalleriesByFingerprints []*GalleryFragment `json:"findGalleriesByFingerprints" graphql:"findGalleriesByFingerprints"` +} +type SearchScene struct { + SearchScene []*SceneFragment "json:\"searchScene\" graphql:\"searchScene\"" +} +type SearchGallery struct { + SearchGallery []*GalleryFragment `json:"searchScene" graphql:"searchScene"` +} +type SubmitFingerprintPayload struct { + SubmitFingerprint bool "json:\"submitFingerprint\" graphql:\"submitFingerprint\"" +} + +const FindSceneByFingerprintQuery = `query FindSceneByFingerprint ($fingerprint: FingerprintQueryInput!) { + findSceneByFingerprint(fingerprint: $fingerprint) { + ... SceneFragment + } +} +fragment SceneFragment on Scene { + id + title + details + duration + date + urls { + ... URLFragment + } + images { + ... ImageFragment + } + studio { + ... StudioFragment + } + tags { + ... TagFragment + } + performers { + ... PerformerAppearanceFragment + } + fingerprints { + ... FingerprintFragment + } +} +fragment TagFragment on Tag { + name + id +} +fragment PerformerFragment on Performer { + id + name + disambiguation + aliases + gender + urls { + ... URLFragment + } + images { + ... ImageFragment + } + birthdate { + ... FuzzyDateFragment + } + ethnicity + country + eye_color + hair_color + height + measurements { + ... MeasurementsFragment + } + breast_type + career_start_year + career_end_year + tattoos { + ... BodyModificationFragment + } + piercings { + ... BodyModificationFragment + } +} +fragment BodyModificationFragment on BodyModification { + location + description +} +fragment MeasurementsFragment on Measurements { + band_size + cup_size + waist + hip +} +fragment FingerprintFragment on Fingerprint { + algorithm + hash + duration +} +fragment URLFragment on URL { + url + type +} +fragment ImageFragment on Image { + id + url + width + height +} +fragment StudioFragment on Studio { + name + id + urls { + ... URLFragment + } + images { + ... ImageFragment + } +} +fragment PerformerAppearanceFragment on PerformerAppearance { + as + performer { + ... PerformerFragment + } +} +fragment FuzzyDateFragment on FuzzyDate { + date + accuracy +} +` + +func (c *Client) FindSceneByFingerprint(ctx context.Context, fingerprint FingerprintQueryInput, httpRequestOptions ...client.HTTPRequestOption) (*FindSceneByFingerprint, error) { + vars := map[string]interface{}{ + "fingerprint": fingerprint, + } + + var res FindSceneByFingerprint + if err := c.Client.Post(ctx, FindSceneByFingerprintQuery, &res, vars, httpRequestOptions...); err != nil { + return nil, err + } + + return &res, nil +} + +const FindScenesByFingerprintsQuery = `query FindScenesByFingerprints ($fingerprints: [String!]!) { + findScenesByFingerprints(fingerprints: $fingerprints) { + ... SceneFragment + } +} +fragment ImageFragment on Image { + id + url + width + height +} +fragment StudioFragment on Studio { + name + id + urls { + ... URLFragment + } + images { + ... ImageFragment + } +} +fragment TagFragment on Tag { + name + id +} +fragment MeasurementsFragment on Measurements { + band_size + cup_size + waist + hip +} +fragment BodyModificationFragment on BodyModification { + location + description +} +fragment SceneFragment on Scene { + id + title + details + duration + date + urls { + ... URLFragment + } + images { + ... ImageFragment + } + studio { + ... StudioFragment + } + tags { + ... TagFragment + } + performers { + ... PerformerAppearanceFragment + } + fingerprints { + ... FingerprintFragment + } +} +fragment PerformerAppearanceFragment on PerformerAppearance { + as + performer { + ... PerformerFragment + } +} +fragment PerformerFragment on Performer { + id + name + disambiguation + aliases + gender + urls { + ... URLFragment + } + images { + ... ImageFragment + } + birthdate { + ... FuzzyDateFragment + } + ethnicity + country + eye_color + hair_color + height + measurements { + ... MeasurementsFragment + } + breast_type + career_start_year + career_end_year + tattoos { + ... BodyModificationFragment + } + piercings { + ... BodyModificationFragment + } +} +fragment FuzzyDateFragment on FuzzyDate { + date + accuracy +} +fragment FingerprintFragment on Fingerprint { + algorithm + hash + duration +} +fragment URLFragment on URL { + url + type +} +` + +func (c *Client) FindScenesByFingerprints(ctx context.Context, fingerprints []string, httpRequestOptions ...client.HTTPRequestOption) (*FindScenesByFingerprints, error) { + vars := map[string]interface{}{ + "fingerprints": fingerprints, + } + + var res FindScenesByFingerprints + if err := c.Client.Post(ctx, FindScenesByFingerprintsQuery, &res, vars, httpRequestOptions...); err != nil { + return nil, err + } + + return &res, nil +} + +const SearchSceneQuery = `query SearchScene ($term: String!) { + searchScene(term: $term) { + ... SceneFragment + } +} +fragment FuzzyDateFragment on FuzzyDate { + date + accuracy +} +fragment MeasurementsFragment on Measurements { + band_size + cup_size + waist + hip +} +fragment FingerprintFragment on Fingerprint { + algorithm + hash + duration +} +fragment SceneFragment on Scene { + id + title + details + duration + date + urls { + ... URLFragment + } + images { + ... ImageFragment + } + studio { + ... StudioFragment + } + tags { + ... TagFragment + } + performers { + ... PerformerAppearanceFragment + } + fingerprints { + ... FingerprintFragment + } +} +fragment TagFragment on Tag { + name + id +} +fragment PerformerAppearanceFragment on PerformerAppearance { + as + performer { + ... PerformerFragment + } +} +fragment PerformerFragment on Performer { + id + name + disambiguation + aliases + gender + urls { + ... URLFragment + } + images { + ... ImageFragment + } + birthdate { + ... FuzzyDateFragment + } + ethnicity + country + eye_color + hair_color + height + measurements { + ... MeasurementsFragment + } + breast_type + career_start_year + career_end_year + tattoos { + ... BodyModificationFragment + } + piercings { + ... BodyModificationFragment + } +} +fragment URLFragment on URL { + url + type +} +fragment ImageFragment on Image { + id + url + width + height +} +fragment StudioFragment on Studio { + name + id + urls { + ... URLFragment + } + images { + ... ImageFragment + } +} +fragment BodyModificationFragment on BodyModification { + location + description +} +` + +func (c *Client) FindGalleriesByFingerprints(ctx context.Context, fingerprints []string, httpRequestOptions ...client.HTTPRequestOption) (*FindGalleriesByFingerprints, error) { + vars := map[string]interface{}{ + "fingerprints": fingerprints, + } + + var res FindGalleriesByFingerprints + if err := c.Client.Post(ctx, FindScenesByFingerprintsQuery, &res, vars, httpRequestOptions...); err != nil { + return nil, err + } + + return &res, nil +} + +const SearchGalleryQuery = `query SearchGallery ($term: String!) { + searchGallery(term: $term) { + ... GalleryFragment + } +} +fragment FuzzyDateFragment on FuzzyDate { + date + accuracy +} +fragment MeasurementsFragment on Measurements { + band_size + cup_size + waist + hip +} +fragment FingerprintFragment on Fingerprint { + algorithm + hash + duration +} +fragment GalleryFragment on Gallery { + id + title + details + duration + date + urls { + ... URLFragment + } + images { + ... ImageFragment + } + studio { + ... StudioFragment + } + tags { + ... TagFragment + } + performers { + ... PerformerAppearanceFragment + } + fingerprints { + ... FingerprintFragment + } +} +fragment TagFragment on Tag { + name + id +} +fragment PerformerAppearanceFragment on PerformerAppearance { + as + performer { + ... PerformerFragment + } +} +fragment PerformerFragment on Performer { + id + name + disambiguation + aliases + gender + urls { + ... URLFragment + } + images { + ... ImageFragment + } + birthdate { + ... FuzzyDateFragment + } + ethnicity + country + eye_color + hair_color + height + measurements { + ... MeasurementsFragment + } + breast_type + career_start_year + career_end_year + tattoos { + ... BodyModificationFragment + } + piercings { + ... BodyModificationFragment + } +} +fragment URLFragment on URL { + url + type +} +fragment ImageFragment on Image { + id + url + width + height +} +fragment StudioFragment on Studio { + name + id + urls { + ... URLFragment + } + images { + ... ImageFragment + } +} +fragment BodyModificationFragment on BodyModification { + location + description +} +` + +func (c *Client) SearchScene(ctx context.Context, term string, httpRequestOptions ...client.HTTPRequestOption) (*SearchScene, error) { + vars := map[string]interface{}{ + "term": term, + } + + var res SearchScene + if err := c.Client.Post(ctx, SearchSceneQuery, &res, vars, httpRequestOptions...); err != nil { + return nil, err + } + + return &res, nil +} + +func (c *Client) SearchGallery(ctx context.Context, term string, httpRequestOptions ...client.HTTPRequestOption) (*SearchGallery, error) { + vars := map[string]interface{}{ + "term": term, + } + + var res SearchGallery + if err := c.Client.Post(ctx, SearchGalleryQuery, &res, vars, httpRequestOptions...); err != nil { + return nil, err + } + + return &res, nil +} + +const SubmitFingerprintQuery = `mutation SubmitFingerprint ($input: FingerprintSubmission!) { + submitFingerprint(input: $input) +} +` + +func (c *Client) SubmitFingerprint(ctx context.Context, input FingerprintSubmission, httpRequestOptions ...client.HTTPRequestOption) (*SubmitFingerprintPayload, error) { + vars := map[string]interface{}{ + "input": input, + } + + var res SubmitFingerprintPayload + if err := c.Client.Post(ctx, SubmitFingerprintQuery, &res, vars, httpRequestOptions...); err != nil { + return nil, err + } + + return &res, nil +} diff --git a/pkg/scraper/stashbox/graphql/generated_models.go b/pkg/scraper/stashbox/graphql/generated_models.go new file mode 100644 index 000000000..a8715092b --- /dev/null +++ b/pkg/scraper/stashbox/graphql/generated_models.go @@ -0,0 +1,1427 @@ +// Code generated by github.com/99designs/gqlgen, DO NOT EDIT. + +package graphql + +import ( + "fmt" + "io" + "strconv" + "time" +) + +type EditDetails interface { + IsEditDetails() +} + +type EditTarget interface { + IsEditTarget() +} + +type ApplyEditInput struct { + ID string `json:"id"` +} + +type BodyModification struct { + Location string `json:"location"` + Description *string `json:"description"` +} + +type BodyModificationCriterionInput struct { + Location *string `json:"location"` + Description *string `json:"description"` + Modifier CriterionModifier `json:"modifier"` +} + +type BodyModificationInput struct { + Location string `json:"location"` + Description *string `json:"description"` +} + +type BreastTypeCriterionInput struct { + Value *BreastTypeEnum `json:"value"` + Modifier CriterionModifier `json:"modifier"` +} + +type CancelEditInput struct { + ID string `json:"id"` +} + +type DateCriterionInput struct { + Value string `json:"value"` + Modifier CriterionModifier `json:"modifier"` +} + +type Edit struct { + ID string `json:"id"` + User *User `json:"user"` + // Object being edited - null if creating a new object + Target EditTarget `json:"target"` + TargetType TargetTypeEnum `json:"target_type"` + // Objects to merge with the target. Only applicable to merges + MergeSources []EditTarget `json:"merge_sources"` + Operation OperationEnum `json:"operation"` + Details EditDetails `json:"details"` + Comments []*EditComment `json:"comments"` + Votes []*VoteComment `json:"votes"` + // = Accepted - Rejected + VoteCount int `json:"vote_count"` + Status VoteStatusEnum `json:"status"` + Applied bool `json:"applied"` + Created time.Time `json:"created"` +} + +type EditComment struct { + User *User `json:"user"` + Date time.Time `json:"date"` + Comment string `json:"comment"` +} + +type EditCommentInput struct { + ID string `json:"id"` + Comment string `json:"comment"` +} + +type EditFilterType struct { + // Filter by user id + UserID *string `json:"user_id"` + // Filter by status + Status *VoteStatusEnum `json:"status"` + // Filter by operation + Operation *OperationEnum `json:"operation"` + // Filter by vote count + VoteCount *IntCriterionInput `json:"vote_count"` + // Filter by applied status + Applied *bool `json:"applied"` + // Filter by target type + TargetType *TargetTypeEnum `json:"target_type"` + // Filter by target id + TargetID *string `json:"target_id"` +} + +type EditInput struct { + // Not required for create type + ID *string `json:"id"` + Operation OperationEnum `json:"operation"` + // Required for amending an existing edit + EditID *string `json:"edit_id"` + // Only required for merge type + MergeSourceIds []string `json:"merge_source_ids"` + Comment *string `json:"comment"` +} + +type EditVoteInput struct { + ID string `json:"id"` + Comment *string `json:"comment"` + Type VoteTypeEnum `json:"type"` +} + +type EthnicityCriterionInput struct { + Value *EthnicityEnum `json:"value"` + Modifier CriterionModifier `json:"modifier"` +} + +type EyeColorCriterionInput struct { + Value *EyeColorEnum `json:"value"` + Modifier CriterionModifier `json:"modifier"` +} + +type Fingerprint struct { + Hash string `json:"hash"` + Algorithm FingerprintAlgorithm `json:"algorithm"` + Duration int `json:"duration"` +} + +type FingerprintInput struct { + Hash string `json:"hash"` + Algorithm FingerprintAlgorithm `json:"algorithm"` + Duration int `json:"duration"` +} + +type FingerprintQueryInput struct { + Hash string `json:"hash"` + Algorithm FingerprintAlgorithm `json:"algorithm"` +} + +type FingerprintSubmission struct { + SceneID string `json:"scene_id"` + Fingerprint *FingerprintInput `json:"fingerprint"` +} + +type FuzzyDate struct { + Date string `json:"date"` + Accuracy DateAccuracyEnum `json:"accuracy"` +} + +type FuzzyDateInput struct { + Date string `json:"date"` + Accuracy DateAccuracyEnum `json:"accuracy"` +} + +type HairColorCriterionInput struct { + Value *HairColorEnum `json:"value"` + Modifier CriterionModifier `json:"modifier"` +} + +type IDCriterionInput struct { + Value []string `json:"value"` + Modifier CriterionModifier `json:"modifier"` +} + +type Image struct { + ID string `json:"id"` + URL string `json:"url"` + Width *int `json:"width"` + Height *int `json:"height"` +} + +type ImageCreateInput struct { + URL string `json:"url"` +} + +type ImageDestroyInput struct { + ID string `json:"id"` +} + +type ImageUpdateInput struct { + ID string `json:"id"` + URL string `json:"url"` +} + +type IntCriterionInput struct { + Value int `json:"value"` + Modifier CriterionModifier `json:"modifier"` +} + +type Measurements struct { + CupSize *string `json:"cup_size"` + BandSize *int `json:"band_size"` + Waist *int `json:"waist"` + Hip *int `json:"hip"` +} + +type MeasurementsInput struct { + CupSize *string `json:"cup_size"` + BandSize *int `json:"band_size"` + Waist *int `json:"waist"` + Hip *int `json:"hip"` +} + +type MultiIDCriterionInput struct { + Value []string `json:"value"` + Modifier CriterionModifier `json:"modifier"` +} + +type Performer struct { + ID string `json:"id"` + Name string `json:"name"` + Disambiguation *string `json:"disambiguation"` + Aliases []string `json:"aliases"` + Gender *GenderEnum `json:"gender"` + Urls []*URL `json:"urls"` + Birthdate *FuzzyDate `json:"birthdate"` + Age *int `json:"age"` + Ethnicity *EthnicityEnum `json:"ethnicity"` + Country *string `json:"country"` + EyeColor *EyeColorEnum `json:"eye_color"` + HairColor *HairColorEnum `json:"hair_color"` + // Height in cm + Height *int `json:"height"` + Measurements *Measurements `json:"measurements"` + BreastType *BreastTypeEnum `json:"breast_type"` + CareerStartYear *int `json:"career_start_year"` + CareerEndYear *int `json:"career_end_year"` + Tattoos []*BodyModification `json:"tattoos"` + Piercings []*BodyModification `json:"piercings"` + Images []*Image `json:"images"` + Deleted bool `json:"deleted"` +} + +func (Performer) IsEditTarget() {} + +type PerformerAppearance struct { + Performer *Performer `json:"performer"` + // Performing as alias + As *string `json:"as"` +} + +type PerformerAppearanceInput struct { + PerformerID string `json:"performer_id"` + // Performing as alias + As *string `json:"as"` +} + +type PerformerCreateInput struct { + Name string `json:"name"` + Disambiguation *string `json:"disambiguation"` + Aliases []string `json:"aliases"` + Gender *GenderEnum `json:"gender"` + Urls []*URLInput `json:"urls"` + Birthdate *FuzzyDateInput `json:"birthdate"` + Ethnicity *EthnicityEnum `json:"ethnicity"` + Country *string `json:"country"` + EyeColor *EyeColorEnum `json:"eye_color"` + HairColor *HairColorEnum `json:"hair_color"` + Height *int `json:"height"` + Measurements *MeasurementsInput `json:"measurements"` + BreastType *BreastTypeEnum `json:"breast_type"` + CareerStartYear *int `json:"career_start_year"` + CareerEndYear *int `json:"career_end_year"` + Tattoos []*BodyModificationInput `json:"tattoos"` + Piercings []*BodyModificationInput `json:"piercings"` + ImageIds []string `json:"image_ids"` +} + +type PerformerDestroyInput struct { + ID string `json:"id"` +} + +type PerformerEdit struct { + Name *string `json:"name"` + Disambiguation *string `json:"disambiguation"` + AddedAliases []string `json:"added_aliases"` + RemovedAliases []string `json:"removed_aliases"` + Gender *GenderEnum `json:"gender"` + AddedUrls []*URL `json:"added_urls"` + RemovedUrls []*URL `json:"removed_urls"` + Birthdate *FuzzyDate `json:"birthdate"` + Ethnicity *EthnicityEnum `json:"ethnicity"` + Country *string `json:"country"` + EyeColor *EyeColorEnum `json:"eye_color"` + HairColor *HairColorEnum `json:"hair_color"` + // Height in cm + Height *int `json:"height"` + Measurements *Measurements `json:"measurements"` + BreastType *BreastTypeEnum `json:"breast_type"` + CareerStartYear *int `json:"career_start_year"` + CareerEndYear *int `json:"career_end_year"` + AddedTattoos []*BodyModification `json:"added_tattoos"` + RemovedTattoos []*BodyModification `json:"removed_tattoos"` + AddedPiercings []*BodyModification `json:"added_piercings"` + RemovedPiercings []*BodyModification `json:"removed_piercings"` + AddedImages []*Image `json:"added_images"` + RemovedImages []*Image `json:"removed_images"` +} + +func (PerformerEdit) IsEditDetails() {} + +type PerformerEditDetailsInput struct { + Name *string `json:"name"` + Disambiguation *string `json:"disambiguation"` + Aliases []string `json:"aliases"` + Gender *GenderEnum `json:"gender"` + Urls []*URLInput `json:"urls"` + Birthdate *FuzzyDateInput `json:"birthdate"` + Ethnicity *EthnicityEnum `json:"ethnicity"` + Country *string `json:"country"` + EyeColor *EyeColorEnum `json:"eye_color"` + HairColor *HairColorEnum `json:"hair_color"` + Height *int `json:"height"` + Measurements *MeasurementsInput `json:"measurements"` + BreastType *BreastTypeEnum `json:"breast_type"` + CareerStartYear *int `json:"career_start_year"` + CareerEndYear *int `json:"career_end_year"` + Tattoos []*BodyModificationInput `json:"tattoos"` + Piercings []*BodyModificationInput `json:"piercings"` + ImageIds []string `json:"image_ids"` +} + +type PerformerEditInput struct { + Edit *EditInput `json:"edit"` + // Not required for destroy type + Details *PerformerEditDetailsInput `json:"details"` +} + +type PerformerFilterType struct { + // Searches name and aliases - assumes like query unless quoted + Names *string `json:"names"` + // Searches name only - assumes like query unless quoted + Name *string `json:"name"` + // Search aliases only - assumes like query unless quoted + Alias *string `json:"alias"` + Disambiguation *StringCriterionInput `json:"disambiguation"` + Gender *GenderEnum `json:"gender"` + // Filter to search urls - assumes like query unless quoted + URL *string `json:"url"` + Birthdate *DateCriterionInput `json:"birthdate"` + BirthYear *IntCriterionInput `json:"birth_year"` + Age *IntCriterionInput `json:"age"` + Ethnicity *EthnicityCriterionInput `json:"ethnicity"` + Country *StringCriterionInput `json:"country"` + EyeColor *EyeColorCriterionInput `json:"eye_color"` + HairColor *HairColorCriterionInput `json:"hair_color"` + Height *IntCriterionInput `json:"height"` + CupSize *StringCriterionInput `json:"cup_size"` + BandSize *IntCriterionInput `json:"band_size"` + WaistSize *IntCriterionInput `json:"waist_size"` + HipSize *IntCriterionInput `json:"hip_size"` + BreastType *BreastTypeCriterionInput `json:"breast_type"` + CareerStartYear *IntCriterionInput `json:"career_start_year"` + CareerEndYear *IntCriterionInput `json:"career_end_year"` + Tattoos *BodyModificationCriterionInput `json:"tattoos"` + Piercings *BodyModificationCriterionInput `json:"piercings"` +} + +type PerformerUpdateInput struct { + ID string `json:"id"` + Name *string `json:"name"` + Disambiguation *string `json:"disambiguation"` + Aliases []string `json:"aliases"` + Gender *GenderEnum `json:"gender"` + Urls []*URLInput `json:"urls"` + Birthdate *FuzzyDateInput `json:"birthdate"` + Ethnicity *EthnicityEnum `json:"ethnicity"` + Country *string `json:"country"` + EyeColor *EyeColorEnum `json:"eye_color"` + HairColor *HairColorEnum `json:"hair_color"` + Height *int `json:"height"` + Measurements *MeasurementsInput `json:"measurements"` + BreastType *BreastTypeEnum `json:"breast_type"` + CareerStartYear *int `json:"career_start_year"` + CareerEndYear *int `json:"career_end_year"` + Tattoos []*BodyModificationInput `json:"tattoos"` + Piercings []*BodyModificationInput `json:"piercings"` + ImageIds []string `json:"image_ids"` +} + +type QueryEditsResultType struct { + Count int `json:"count"` + Edits []*Edit `json:"edits"` +} + +type QueryPerformersResultType struct { + Count int `json:"count"` + Performers []*Performer `json:"performers"` +} + +type QueryScenesResultType struct { + Count int `json:"count"` + Scenes []*Scene `json:"scenes"` +} + +type QuerySpec struct { + Page *int `json:"page"` + PerPage *int `json:"per_page"` + Sort *string `json:"sort"` + Direction *SortDirectionEnum `json:"direction"` +} + +type QueryStudiosResultType struct { + Count int `json:"count"` + Studios []*Studio `json:"studios"` +} + +type QueryTagsResultType struct { + Count int `json:"count"` + Tags []*Tag `json:"tags"` +} + +type QueryUsersResultType struct { + Count int `json:"count"` + Users []*User `json:"users"` +} + +type RoleCriterionInput struct { + Value []RoleEnum `json:"value"` + Modifier CriterionModifier `json:"modifier"` +} + +type Scene struct { + ID string `json:"id"` + Title *string `json:"title"` + Details *string `json:"details"` + Date *string `json:"date"` + Urls []*URL `json:"urls"` + Studio *Studio `json:"studio"` + Tags []*Tag `json:"tags"` + Images []*Image `json:"images"` + Performers []*PerformerAppearance `json:"performers"` + Fingerprints []*Fingerprint `json:"fingerprints"` + Duration *int `json:"duration"` + Director *string `json:"director"` + Deleted bool `json:"deleted"` +} + +func (Scene) IsEditTarget() {} + +type SceneCreateInput struct { + Title *string `json:"title"` + Details *string `json:"details"` + Urls []*URLInput `json:"urls"` + Date *string `json:"date"` + StudioID *string `json:"studio_id"` + Performers []*PerformerAppearanceInput `json:"performers"` + TagIds []string `json:"tag_ids"` + ImageIds []string `json:"image_ids"` + Fingerprints []*FingerprintInput `json:"fingerprints"` + Duration *int `json:"duration"` + Director *string `json:"director"` +} + +type SceneDestroyInput struct { + ID string `json:"id"` +} + +type SceneEdit struct { + Title *string `json:"title"` + Details *string `json:"details"` + AddedUrls []*URL `json:"added_urls"` + RemovedUrls []*URL `json:"removed_urls"` + Date *string `json:"date"` + StudioID *string `json:"studio_id"` + // Added or modified performer appearance entries + AddedPerformers []*PerformerAppearance `json:"added_performers"` + RemovedPerformers []*PerformerAppearance `json:"removed_performers"` + AddedTags []*Tag `json:"added_tags"` + RemovedTags []*Tag `json:"removed_tags"` + AddedImages []*Image `json:"added_images"` + RemovedImages []*Image `json:"removed_images"` + AddedFingerprints []*Fingerprint `json:"added_fingerprints"` + RemovedFingerprints []*Fingerprint `json:"removed_fingerprints"` + Duration *int `json:"duration"` + Director *string `json:"director"` +} + +func (SceneEdit) IsEditDetails() {} + +type SceneEditDetailsInput struct { + Title *string `json:"title"` + Details *string `json:"details"` + Urls []*URLInput `json:"urls"` + Date *string `json:"date"` + StudioID *string `json:"studio_id"` + Performers []*PerformerAppearanceInput `json:"performers"` + TagIds []string `json:"tag_ids"` + ImageIds []string `json:"image_ids"` + Fingerprints []*FingerprintInput `json:"fingerprints"` + Duration *int `json:"duration"` + Director *string `json:"director"` +} + +type SceneEditInput struct { + Edit *EditInput `json:"edit"` + // Not required for destroy type + Details *SceneEditDetailsInput `json:"details"` + Duration *int `json:"duration"` +} + +type SceneFilterType struct { + // Filter to search title and details - assumes like query unless quoted + Text *string `json:"text"` + // Filter to search title - assumes like query unless quoted + Title *string `json:"title"` + // Filter to search urls - assumes like query unless quoted + URL *string `json:"url"` + // Filter by date + Date *DateCriterionInput `json:"date"` + // Filter to only include scenes with this studio + Studios *MultiIDCriterionInput `json:"studios"` + // Filter to only include scenes with these tags + Tags *MultiIDCriterionInput `json:"tags"` + // Filter to only include scenes with these performers + Performers *MultiIDCriterionInput `json:"performers"` + // Filter to include scenes with performer appearing as alias + Alias *StringCriterionInput `json:"alias"` +} + +type SceneUpdateInput struct { + ID string `json:"id"` + Title *string `json:"title"` + Details *string `json:"details"` + Urls []*URLInput `json:"urls"` + Date *string `json:"date"` + StudioID *string `json:"studio_id"` + Performers []*PerformerAppearanceInput `json:"performers"` + TagIds []string `json:"tag_ids"` + ImageIds []string `json:"image_ids"` + Fingerprints []*FingerprintInput `json:"fingerprints"` + Duration *int `json:"duration"` + Director *string `json:"director"` +} + +type StringCriterionInput struct { + Value string `json:"value"` + Modifier CriterionModifier `json:"modifier"` +} + +type Studio struct { + ID string `json:"id"` + Name string `json:"name"` + Urls []*URL `json:"urls"` + Parent *Studio `json:"parent"` + ChildStudios []*Studio `json:"child_studios"` + Images []*Image `json:"images"` + Deleted bool `json:"deleted"` +} + +func (Studio) IsEditTarget() {} + +type StudioCreateInput struct { + Name string `json:"name"` + Urls []*URLInput `json:"urls"` + ParentID *string `json:"parent_id"` + ChildStudioIds []string `json:"child_studio_ids"` + ImageIds []string `json:"image_ids"` +} + +type StudioDestroyInput struct { + ID string `json:"id"` +} + +type StudioEdit struct { + Name *string `json:"name"` + // Added and modified URLs + AddedUrls []*URL `json:"added_urls"` + RemovedUrls []*URL `json:"removed_urls"` + Parent *Studio `json:"parent"` + AddedChildStudios []*Studio `json:"added_child_studios"` + RemovedChildStudios []*Studio `json:"removed_child_studios"` + AddedImages []*Image `json:"added_images"` + RemovedImages []*Image `json:"removed_images"` +} + +func (StudioEdit) IsEditDetails() {} + +type StudioEditDetailsInput struct { + Name *string `json:"name"` + Urls []*URLInput `json:"urls"` + ParentID *string `json:"parent_id"` + ChildStudioIds []string `json:"child_studio_ids"` + ImageIds []string `json:"image_ids"` +} + +type StudioEditInput struct { + Edit *EditInput `json:"edit"` + // Not required for destroy type + Details *StudioEditDetailsInput `json:"details"` +} + +type StudioFilterType struct { + // Filter to search name - assumes like query unless quoted + Name *string `json:"name"` + // Filter to search url - assumes like query unless quoted + URL *string `json:"url"` + Parent *IDCriterionInput `json:"parent"` +} + +type StudioUpdateInput struct { + ID string `json:"id"` + Name *string `json:"name"` + Urls []*URLInput `json:"urls"` + ParentID *string `json:"parent_id"` + ChildStudioIds []string `json:"child_studio_ids"` + ImageIds []string `json:"image_ids"` +} + +type Tag struct { + ID string `json:"id"` + Name string `json:"name"` + Description *string `json:"description"` + Aliases []string `json:"aliases"` + Deleted bool `json:"deleted"` + Edits []*Edit `json:"edits"` +} + +func (Tag) IsEditTarget() {} + +type TagCreateInput struct { + Name string `json:"name"` + Description *string `json:"description"` + Aliases []string `json:"aliases"` +} + +type TagDestroyInput struct { + ID string `json:"id"` +} + +type TagEdit struct { + Name *string `json:"name"` + Description *string `json:"description"` + AddedAliases []string `json:"added_aliases"` + RemovedAliases []string `json:"removed_aliases"` +} + +func (TagEdit) IsEditDetails() {} + +type TagEditDetailsInput struct { + Name *string `json:"name"` + Description *string `json:"description"` + Aliases []string `json:"aliases"` +} + +type TagEditInput struct { + Edit *EditInput `json:"edit"` + // Not required for destroy type + Details *TagEditDetailsInput `json:"details"` +} + +type TagFilterType struct { + // Filter to search name, aliases and description - assumes like query unless quoted + Text *string `json:"text"` + // Searches name and aliases - assumes like query unless quoted + Names *string `json:"names"` + // Filter to search name - assumes like query unless quoted + Name *string `json:"name"` +} + +type TagUpdateInput struct { + ID string `json:"id"` + Name *string `json:"name"` + Description *string `json:"description"` + Aliases []string `json:"aliases"` +} + +type URL struct { + URL string `json:"url"` + Type string `json:"type"` +} + +type URLInput struct { + URL string `json:"url"` + Type string `json:"type"` +} + +type User struct { + ID string `json:"id"` + Name string `json:"name"` + // Should not be visible to other users + Roles []RoleEnum `json:"roles"` + // Should not be visible to other users + Email *string `json:"email"` + // Should not be visible to other users + APIKey *string `json:"api_key"` + SuccessfulEdits int `json:"successful_edits"` + UnsuccessfulEdits int `json:"unsuccessful_edits"` + SuccessfulVotes int `json:"successful_votes"` + // Votes on unsuccessful edits + UnsuccessfulVotes int `json:"unsuccessful_votes"` + // Calls to the API from this user over a configurable time period + APICalls int `json:"api_calls"` +} + +type UserChangePasswordInput struct { + // Password in plain text + ExistingPassword string `json:"existing_password"` + NewPassword string `json:"new_password"` +} + +type UserCreateInput struct { + Name string `json:"name"` + // Password in plain text + Password string `json:"password"` + Roles []RoleEnum `json:"roles"` + Email string `json:"email"` +} + +type UserDestroyInput struct { + ID string `json:"id"` +} + +type UserFilterType struct { + // Filter to search user name - assumes like query unless quoted + Name *string `json:"name"` + // Filter to search email - assumes like query unless quoted + Email *string `json:"email"` + // Filter by roles + Roles *RoleCriterionInput `json:"roles"` + // Filter by api key + APIKey *string `json:"apiKey"` + // Filter by successful edits + SuccessfulEdits *IntCriterionInput `json:"successful_edits"` + // Filter by unsuccessful edits + UnsuccessfulEdits *IntCriterionInput `json:"unsuccessful_edits"` + // Filter by votes on successful edits + SuccessfulVotes *IntCriterionInput `json:"successful_votes"` + // Filter by votes on unsuccessful edits + UnsuccessfulVotes *IntCriterionInput `json:"unsuccessful_votes"` + // Filter by number of API calls + APICalls *IntCriterionInput `json:"api_calls"` +} + +type UserUpdateInput struct { + ID string `json:"id"` + Name *string `json:"name"` + // Password in plain text + Password *string `json:"password"` + Roles []RoleEnum `json:"roles"` + Email *string `json:"email"` +} + +type Version struct { + Hash string `json:"hash"` + BuildTime string `json:"build_time"` + Version string `json:"version"` +} + +type VoteComment struct { + User *User `json:"user"` + Date *string `json:"date"` + Comment *string `json:"comment"` + Type *VoteTypeEnum `json:"type"` +} + +type BreastTypeEnum string + +const ( + BreastTypeEnumNatural BreastTypeEnum = "NATURAL" + BreastTypeEnumFake BreastTypeEnum = "FAKE" + BreastTypeEnumNa BreastTypeEnum = "NA" +) + +var AllBreastTypeEnum = []BreastTypeEnum{ + BreastTypeEnumNatural, + BreastTypeEnumFake, + BreastTypeEnumNa, +} + +func (e BreastTypeEnum) IsValid() bool { + switch e { + case BreastTypeEnumNatural, BreastTypeEnumFake, BreastTypeEnumNa: + return true + } + return false +} + +func (e BreastTypeEnum) String() string { + return string(e) +} + +func (e *BreastTypeEnum) UnmarshalGQL(v interface{}) error { + str, ok := v.(string) + if !ok { + return fmt.Errorf("enums must be strings") + } + + *e = BreastTypeEnum(str) + if !e.IsValid() { + return fmt.Errorf("%s is not a valid BreastTypeEnum", str) + } + return nil +} + +func (e BreastTypeEnum) MarshalGQL(w io.Writer) { + fmt.Fprint(w, strconv.Quote(e.String())) +} + +type CriterionModifier string + +const ( + // = + CriterionModifierEquals CriterionModifier = "EQUALS" + // != + CriterionModifierNotEquals CriterionModifier = "NOT_EQUALS" + // > + CriterionModifierGreaterThan CriterionModifier = "GREATER_THAN" + // < + CriterionModifierLessThan CriterionModifier = "LESS_THAN" + // IS NULL + CriterionModifierIsNull CriterionModifier = "IS_NULL" + // IS NOT NULL + CriterionModifierNotNull CriterionModifier = "NOT_NULL" + // INCLUDES ALL + CriterionModifierIncludesAll CriterionModifier = "INCLUDES_ALL" + CriterionModifierIncludes CriterionModifier = "INCLUDES" + CriterionModifierExcludes CriterionModifier = "EXCLUDES" +) + +var AllCriterionModifier = []CriterionModifier{ + CriterionModifierEquals, + CriterionModifierNotEquals, + CriterionModifierGreaterThan, + CriterionModifierLessThan, + CriterionModifierIsNull, + CriterionModifierNotNull, + CriterionModifierIncludesAll, + CriterionModifierIncludes, + CriterionModifierExcludes, +} + +func (e CriterionModifier) IsValid() bool { + switch e { + case CriterionModifierEquals, CriterionModifierNotEquals, CriterionModifierGreaterThan, CriterionModifierLessThan, CriterionModifierIsNull, CriterionModifierNotNull, CriterionModifierIncludesAll, CriterionModifierIncludes, CriterionModifierExcludes: + return true + } + return false +} + +func (e CriterionModifier) String() string { + return string(e) +} + +func (e *CriterionModifier) UnmarshalGQL(v interface{}) error { + str, ok := v.(string) + if !ok { + return fmt.Errorf("enums must be strings") + } + + *e = CriterionModifier(str) + if !e.IsValid() { + return fmt.Errorf("%s is not a valid CriterionModifier", str) + } + return nil +} + +func (e CriterionModifier) MarshalGQL(w io.Writer) { + fmt.Fprint(w, strconv.Quote(e.String())) +} + +type DateAccuracyEnum string + +const ( + DateAccuracyEnumYear DateAccuracyEnum = "YEAR" + DateAccuracyEnumMonth DateAccuracyEnum = "MONTH" + DateAccuracyEnumDay DateAccuracyEnum = "DAY" +) + +var AllDateAccuracyEnum = []DateAccuracyEnum{ + DateAccuracyEnumYear, + DateAccuracyEnumMonth, + DateAccuracyEnumDay, +} + +func (e DateAccuracyEnum) IsValid() bool { + switch e { + case DateAccuracyEnumYear, DateAccuracyEnumMonth, DateAccuracyEnumDay: + return true + } + return false +} + +func (e DateAccuracyEnum) String() string { + return string(e) +} + +func (e *DateAccuracyEnum) UnmarshalGQL(v interface{}) error { + str, ok := v.(string) + if !ok { + return fmt.Errorf("enums must be strings") + } + + *e = DateAccuracyEnum(str) + if !e.IsValid() { + return fmt.Errorf("%s is not a valid DateAccuracyEnum", str) + } + return nil +} + +func (e DateAccuracyEnum) MarshalGQL(w io.Writer) { + fmt.Fprint(w, strconv.Quote(e.String())) +} + +type EthnicityEnum string + +const ( + EthnicityEnumCaucasian EthnicityEnum = "CAUCASIAN" + EthnicityEnumBlack EthnicityEnum = "BLACK" + EthnicityEnumAsian EthnicityEnum = "ASIAN" + EthnicityEnumIndian EthnicityEnum = "INDIAN" + EthnicityEnumLatin EthnicityEnum = "LATIN" + EthnicityEnumMiddleEastern EthnicityEnum = "MIDDLE_EASTERN" + EthnicityEnumMixed EthnicityEnum = "MIXED" + EthnicityEnumOther EthnicityEnum = "OTHER" +) + +var AllEthnicityEnum = []EthnicityEnum{ + EthnicityEnumCaucasian, + EthnicityEnumBlack, + EthnicityEnumAsian, + EthnicityEnumIndian, + EthnicityEnumLatin, + EthnicityEnumMiddleEastern, + EthnicityEnumMixed, + EthnicityEnumOther, +} + +func (e EthnicityEnum) IsValid() bool { + switch e { + case EthnicityEnumCaucasian, EthnicityEnumBlack, EthnicityEnumAsian, EthnicityEnumIndian, EthnicityEnumLatin, EthnicityEnumMiddleEastern, EthnicityEnumMixed, EthnicityEnumOther: + return true + } + return false +} + +func (e EthnicityEnum) String() string { + return string(e) +} + +func (e *EthnicityEnum) UnmarshalGQL(v interface{}) error { + str, ok := v.(string) + if !ok { + return fmt.Errorf("enums must be strings") + } + + *e = EthnicityEnum(str) + if !e.IsValid() { + return fmt.Errorf("%s is not a valid EthnicityEnum", str) + } + return nil +} + +func (e EthnicityEnum) MarshalGQL(w io.Writer) { + fmt.Fprint(w, strconv.Quote(e.String())) +} + +type EyeColorEnum string + +const ( + EyeColorEnumBlue EyeColorEnum = "BLUE" + EyeColorEnumBrown EyeColorEnum = "BROWN" + EyeColorEnumGrey EyeColorEnum = "GREY" + EyeColorEnumGreen EyeColorEnum = "GREEN" + EyeColorEnumHazel EyeColorEnum = "HAZEL" + EyeColorEnumRed EyeColorEnum = "RED" +) + +var AllEyeColorEnum = []EyeColorEnum{ + EyeColorEnumBlue, + EyeColorEnumBrown, + EyeColorEnumGrey, + EyeColorEnumGreen, + EyeColorEnumHazel, + EyeColorEnumRed, +} + +func (e EyeColorEnum) IsValid() bool { + switch e { + case EyeColorEnumBlue, EyeColorEnumBrown, EyeColorEnumGrey, EyeColorEnumGreen, EyeColorEnumHazel, EyeColorEnumRed: + return true + } + return false +} + +func (e EyeColorEnum) String() string { + return string(e) +} + +func (e *EyeColorEnum) UnmarshalGQL(v interface{}) error { + str, ok := v.(string) + if !ok { + return fmt.Errorf("enums must be strings") + } + + *e = EyeColorEnum(str) + if !e.IsValid() { + return fmt.Errorf("%s is not a valid EyeColorEnum", str) + } + return nil +} + +func (e EyeColorEnum) MarshalGQL(w io.Writer) { + fmt.Fprint(w, strconv.Quote(e.String())) +} + +type FingerprintAlgorithm string + +const ( + FingerprintAlgorithmMd5 FingerprintAlgorithm = "MD5" + FingerprintAlgorithmOshash FingerprintAlgorithm = "OSHASH" +) + +var AllFingerprintAlgorithm = []FingerprintAlgorithm{ + FingerprintAlgorithmMd5, + FingerprintAlgorithmOshash, +} + +func (e FingerprintAlgorithm) IsValid() bool { + switch e { + case FingerprintAlgorithmMd5, FingerprintAlgorithmOshash: + return true + } + return false +} + +func (e FingerprintAlgorithm) String() string { + return string(e) +} + +func (e *FingerprintAlgorithm) UnmarshalGQL(v interface{}) error { + str, ok := v.(string) + if !ok { + return fmt.Errorf("enums must be strings") + } + + *e = FingerprintAlgorithm(str) + if !e.IsValid() { + return fmt.Errorf("%s is not a valid FingerprintAlgorithm", str) + } + return nil +} + +func (e FingerprintAlgorithm) MarshalGQL(w io.Writer) { + fmt.Fprint(w, strconv.Quote(e.String())) +} + +type GenderEnum string + +const ( + GenderEnumMale GenderEnum = "MALE" + GenderEnumFemale GenderEnum = "FEMALE" + GenderEnumTransgenderMale GenderEnum = "TRANSGENDER_MALE" + GenderEnumTransgenderFemale GenderEnum = "TRANSGENDER_FEMALE" + GenderEnumIntersex GenderEnum = "INTERSEX" +) + +var AllGenderEnum = []GenderEnum{ + GenderEnumMale, + GenderEnumFemale, + GenderEnumTransgenderMale, + GenderEnumTransgenderFemale, + GenderEnumIntersex, +} + +func (e GenderEnum) IsValid() bool { + switch e { + case GenderEnumMale, GenderEnumFemale, GenderEnumTransgenderMale, GenderEnumTransgenderFemale, GenderEnumIntersex: + return true + } + return false +} + +func (e GenderEnum) String() string { + return string(e) +} + +func (e *GenderEnum) UnmarshalGQL(v interface{}) error { + str, ok := v.(string) + if !ok { + return fmt.Errorf("enums must be strings") + } + + *e = GenderEnum(str) + if !e.IsValid() { + return fmt.Errorf("%s is not a valid GenderEnum", str) + } + return nil +} + +func (e GenderEnum) MarshalGQL(w io.Writer) { + fmt.Fprint(w, strconv.Quote(e.String())) +} + +type HairColorEnum string + +const ( + HairColorEnumBlonde HairColorEnum = "BLONDE" + HairColorEnumBrunette HairColorEnum = "BRUNETTE" + HairColorEnumBlack HairColorEnum = "BLACK" + HairColorEnumRed HairColorEnum = "RED" + HairColorEnumAuburn HairColorEnum = "AUBURN" + HairColorEnumGrey HairColorEnum = "GREY" + HairColorEnumBald HairColorEnum = "BALD" + HairColorEnumVarious HairColorEnum = "VARIOUS" + HairColorEnumOther HairColorEnum = "OTHER" +) + +var AllHairColorEnum = []HairColorEnum{ + HairColorEnumBlonde, + HairColorEnumBrunette, + HairColorEnumBlack, + HairColorEnumRed, + HairColorEnumAuburn, + HairColorEnumGrey, + HairColorEnumBald, + HairColorEnumVarious, + HairColorEnumOther, +} + +func (e HairColorEnum) IsValid() bool { + switch e { + case HairColorEnumBlonde, HairColorEnumBrunette, HairColorEnumBlack, HairColorEnumRed, HairColorEnumAuburn, HairColorEnumGrey, HairColorEnumBald, HairColorEnumVarious, HairColorEnumOther: + return true + } + return false +} + +func (e HairColorEnum) String() string { + return string(e) +} + +func (e *HairColorEnum) UnmarshalGQL(v interface{}) error { + str, ok := v.(string) + if !ok { + return fmt.Errorf("enums must be strings") + } + + *e = HairColorEnum(str) + if !e.IsValid() { + return fmt.Errorf("%s is not a valid HairColorEnum", str) + } + return nil +} + +func (e HairColorEnum) MarshalGQL(w io.Writer) { + fmt.Fprint(w, strconv.Quote(e.String())) +} + +type OperationEnum string + +const ( + OperationEnumCreate OperationEnum = "CREATE" + OperationEnumModify OperationEnum = "MODIFY" + OperationEnumDestroy OperationEnum = "DESTROY" + OperationEnumMerge OperationEnum = "MERGE" +) + +var AllOperationEnum = []OperationEnum{ + OperationEnumCreate, + OperationEnumModify, + OperationEnumDestroy, + OperationEnumMerge, +} + +func (e OperationEnum) IsValid() bool { + switch e { + case OperationEnumCreate, OperationEnumModify, OperationEnumDestroy, OperationEnumMerge: + return true + } + return false +} + +func (e OperationEnum) String() string { + return string(e) +} + +func (e *OperationEnum) UnmarshalGQL(v interface{}) error { + str, ok := v.(string) + if !ok { + return fmt.Errorf("enums must be strings") + } + + *e = OperationEnum(str) + if !e.IsValid() { + return fmt.Errorf("%s is not a valid OperationEnum", str) + } + return nil +} + +func (e OperationEnum) MarshalGQL(w io.Writer) { + fmt.Fprint(w, strconv.Quote(e.String())) +} + +type RoleEnum string + +const ( + RoleEnumRead RoleEnum = "READ" + RoleEnumVote RoleEnum = "VOTE" + RoleEnumEdit RoleEnum = "EDIT" + RoleEnumModify RoleEnum = "MODIFY" + RoleEnumAdmin RoleEnum = "ADMIN" +) + +var AllRoleEnum = []RoleEnum{ + RoleEnumRead, + RoleEnumVote, + RoleEnumEdit, + RoleEnumModify, + RoleEnumAdmin, +} + +func (e RoleEnum) IsValid() bool { + switch e { + case RoleEnumRead, RoleEnumVote, RoleEnumEdit, RoleEnumModify, RoleEnumAdmin: + return true + } + return false +} + +func (e RoleEnum) String() string { + return string(e) +} + +func (e *RoleEnum) UnmarshalGQL(v interface{}) error { + str, ok := v.(string) + if !ok { + return fmt.Errorf("enums must be strings") + } + + *e = RoleEnum(str) + if !e.IsValid() { + return fmt.Errorf("%s is not a valid RoleEnum", str) + } + return nil +} + +func (e RoleEnum) MarshalGQL(w io.Writer) { + fmt.Fprint(w, strconv.Quote(e.String())) +} + +type SortDirectionEnum string + +const ( + SortDirectionEnumAsc SortDirectionEnum = "ASC" + SortDirectionEnumDesc SortDirectionEnum = "DESC" +) + +var AllSortDirectionEnum = []SortDirectionEnum{ + SortDirectionEnumAsc, + SortDirectionEnumDesc, +} + +func (e SortDirectionEnum) IsValid() bool { + switch e { + case SortDirectionEnumAsc, SortDirectionEnumDesc: + return true + } + return false +} + +func (e SortDirectionEnum) String() string { + return string(e) +} + +func (e *SortDirectionEnum) UnmarshalGQL(v interface{}) error { + str, ok := v.(string) + if !ok { + return fmt.Errorf("enums must be strings") + } + + *e = SortDirectionEnum(str) + if !e.IsValid() { + return fmt.Errorf("%s is not a valid SortDirectionEnum", str) + } + return nil +} + +func (e SortDirectionEnum) MarshalGQL(w io.Writer) { + fmt.Fprint(w, strconv.Quote(e.String())) +} + +type TargetTypeEnum string + +const ( + TargetTypeEnumScene TargetTypeEnum = "SCENE" + TargetTypeEnumStudio TargetTypeEnum = "STUDIO" + TargetTypeEnumPerformer TargetTypeEnum = "PERFORMER" + TargetTypeEnumTag TargetTypeEnum = "TAG" +) + +var AllTargetTypeEnum = []TargetTypeEnum{ + TargetTypeEnumScene, + TargetTypeEnumStudio, + TargetTypeEnumPerformer, + TargetTypeEnumTag, +} + +func (e TargetTypeEnum) IsValid() bool { + switch e { + case TargetTypeEnumScene, TargetTypeEnumStudio, TargetTypeEnumPerformer, TargetTypeEnumTag: + return true + } + return false +} + +func (e TargetTypeEnum) String() string { + return string(e) +} + +func (e *TargetTypeEnum) UnmarshalGQL(v interface{}) error { + str, ok := v.(string) + if !ok { + return fmt.Errorf("enums must be strings") + } + + *e = TargetTypeEnum(str) + if !e.IsValid() { + return fmt.Errorf("%s is not a valid TargetTypeEnum", str) + } + return nil +} + +func (e TargetTypeEnum) MarshalGQL(w io.Writer) { + fmt.Fprint(w, strconv.Quote(e.String())) +} + +type VoteStatusEnum string + +const ( + VoteStatusEnumAccepted VoteStatusEnum = "ACCEPTED" + VoteStatusEnumRejected VoteStatusEnum = "REJECTED" + VoteStatusEnumPending VoteStatusEnum = "PENDING" + VoteStatusEnumImmediateAccepted VoteStatusEnum = "IMMEDIATE_ACCEPTED" + VoteStatusEnumImmediateRejected VoteStatusEnum = "IMMEDIATE_REJECTED" +) + +var AllVoteStatusEnum = []VoteStatusEnum{ + VoteStatusEnumAccepted, + VoteStatusEnumRejected, + VoteStatusEnumPending, + VoteStatusEnumImmediateAccepted, + VoteStatusEnumImmediateRejected, +} + +func (e VoteStatusEnum) IsValid() bool { + switch e { + case VoteStatusEnumAccepted, VoteStatusEnumRejected, VoteStatusEnumPending, VoteStatusEnumImmediateAccepted, VoteStatusEnumImmediateRejected: + return true + } + return false +} + +func (e VoteStatusEnum) String() string { + return string(e) +} + +func (e *VoteStatusEnum) UnmarshalGQL(v interface{}) error { + str, ok := v.(string) + if !ok { + return fmt.Errorf("enums must be strings") + } + + *e = VoteStatusEnum(str) + if !e.IsValid() { + return fmt.Errorf("%s is not a valid VoteStatusEnum", str) + } + return nil +} + +func (e VoteStatusEnum) MarshalGQL(w io.Writer) { + fmt.Fprint(w, strconv.Quote(e.String())) +} + +type VoteTypeEnum string + +const ( + VoteTypeEnumComment VoteTypeEnum = "COMMENT" + VoteTypeEnumAccept VoteTypeEnum = "ACCEPT" + VoteTypeEnumReject VoteTypeEnum = "REJECT" + // Immediately accepts the edit - bypassing the vote + VoteTypeEnumImmediateAccept VoteTypeEnum = "IMMEDIATE_ACCEPT" + // Immediately rejects the edit - bypassing the vote + VoteTypeEnumImmediateReject VoteTypeEnum = "IMMEDIATE_REJECT" +) + +var AllVoteTypeEnum = []VoteTypeEnum{ + VoteTypeEnumComment, + VoteTypeEnumAccept, + VoteTypeEnumReject, + VoteTypeEnumImmediateAccept, + VoteTypeEnumImmediateReject, +} + +func (e VoteTypeEnum) IsValid() bool { + switch e { + case VoteTypeEnumComment, VoteTypeEnumAccept, VoteTypeEnumReject, VoteTypeEnumImmediateAccept, VoteTypeEnumImmediateReject: + return true + } + return false +} + +func (e VoteTypeEnum) String() string { + return string(e) +} + +func (e *VoteTypeEnum) UnmarshalGQL(v interface{}) error { + str, ok := v.(string) + if !ok { + return fmt.Errorf("enums must be strings") + } + + *e = VoteTypeEnum(str) + if !e.IsValid() { + return fmt.Errorf("%s is not a valid VoteTypeEnum", str) + } + return nil +} + +func (e VoteTypeEnum) MarshalGQL(w io.Writer) { + fmt.Fprint(w, strconv.Quote(e.String())) +} diff --git a/pkg/scraper/stashbox/stash_box.go b/pkg/scraper/stashbox/stash_box.go new file mode 100644 index 000000000..4b99d557d --- /dev/null +++ b/pkg/scraper/stashbox/stash_box.go @@ -0,0 +1,417 @@ +package stashbox + +import ( + "context" + "fmt" + "io/ioutil" + "net/http" + "strconv" + "strings" + "time" + + "github.com/Yamashou/gqlgenc/client" + + "github.com/stashapp/stash/pkg/logger" + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/scraper/stashbox/graphql" + "github.com/stashapp/stash/pkg/utils" +) + +// Timeout to get the image. Includes transfer time. May want to make this +// configurable at some point. +const imageGetTimeout = time.Second * 30 + +// Client represents the client interface to a stash-box server instance. +type Client struct { + client *graphql.Client +} + +// NewClient returns a new instance of a stash-box client. +func NewClient(box models.StashBox) *Client { + authHeader := func(req *http.Request) { + req.Header.Set("ApiKey", box.APIKey) + } + + client := &graphql.Client{ + Client: client.NewClient(http.DefaultClient, box.Endpoint, authHeader), + } + + return &Client{ + client: client, + } +} + +// QueryStashBoxScene queries stash-box for scenes using a query string. +func (c Client) QueryStashBoxScene(queryStr string) ([]*models.ScrapedScene, error) { + scenes, err := c.client.SearchScene(context.TODO(), queryStr) + if err != nil { + return nil, err + } + + sceneFragments := scenes.SearchScene + + var ret []*models.ScrapedScene + for _, s := range sceneFragments { + ss, err := sceneFragmentToScrapedScene(s) + if err != nil { + return nil, err + } + ret = append(ret, ss) + } + + return ret, nil +} + +// FindStashBoxScenesByFingerprints queries stash-box for scenes using every +// scene's MD5 checksum and/or oshash. +func (c Client) FindStashBoxScenesByFingerprints(sceneIDs []string) ([]*models.ScrapedScene, error) { + qb := models.NewSceneQueryBuilder() + + var fingerprints []string + + for _, sceneID := range sceneIDs { + idInt, _ := strconv.Atoi(sceneID) + scene, err := qb.Find(idInt) + if err != nil { + return nil, err + } + + if scene == nil { + return nil, fmt.Errorf("scene with id %d not found", idInt) + } + + if scene.Checksum.Valid { + fingerprints = append(fingerprints, scene.Checksum.String) + } + + if scene.OSHash.Valid { + fingerprints = append(fingerprints, scene.OSHash.String) + } + } + + return c.findStashBoxScenesByFingerprints(fingerprints) +} + +func (c Client) findStashBoxScenesByFingerprints(fingerprints []string) ([]*models.ScrapedScene, error) { + var ret []*models.ScrapedScene + for i := 0; i < len(fingerprints); i += 100 { + end := i + 100 + if end > len(fingerprints) { + end = len(fingerprints) + } + scenes, err := c.client.FindScenesByFingerprints(context.TODO(), fingerprints[i:end]) + + if err != nil { + return nil, err + } + + sceneFragments := scenes.FindScenesByFingerprints + + for _, s := range sceneFragments { + ss, err := sceneFragmentToScrapedScene(s) + if err != nil { + return nil, err + } + ret = append(ret, ss) + } + } + + return ret, nil +} + +func (c Client) SubmitStashBoxFingerprints(sceneIDs []string, endpoint string) (bool, error) { + qb := models.NewSceneQueryBuilder() + jqb := models.NewJoinsQueryBuilder() + + var fingerprints []graphql.FingerprintSubmission + + for _, sceneID := range sceneIDs { + idInt, _ := strconv.Atoi(sceneID) + scene, err := qb.Find(idInt) + if err != nil { + return false, err + } + + if scene == nil { + continue + } + + stashIDs, err := jqb.GetSceneStashIDs(idInt) + if err != nil { + return false, err + } + + sceneStashID := "" + for _, stashID := range stashIDs { + if stashID.Endpoint == endpoint { + sceneStashID = stashID.StashID + } + } + + if sceneStashID != "" { + if scene.Checksum.Valid && scene.Duration.Valid { + fingerprint := graphql.FingerprintInput{ + Hash: scene.Checksum.String, + Algorithm: graphql.FingerprintAlgorithmMd5, + Duration: int(scene.Duration.Float64), + } + fingerprints = append(fingerprints, graphql.FingerprintSubmission{ + SceneID: sceneStashID, + Fingerprint: &fingerprint, + }) + } + + if scene.OSHash.Valid && scene.Duration.Valid { + fingerprint := graphql.FingerprintInput{ + Hash: scene.OSHash.String, + Algorithm: graphql.FingerprintAlgorithmOshash, + Duration: int(scene.Duration.Float64), + } + fingerprints = append(fingerprints, graphql.FingerprintSubmission{ + SceneID: sceneStashID, + Fingerprint: &fingerprint, + }) + } + } + } + + return c.submitStashBoxFingerprints(fingerprints) +} + +func (c Client) submitStashBoxFingerprints(fingerprints []graphql.FingerprintSubmission) (bool, error) { + for _, fingerprint := range fingerprints { + _, err := c.client.SubmitFingerprint(context.TODO(), fingerprint) + if err != nil { + return false, err + } + } + + return true, nil +} + +func findURL(urls []*graphql.URLFragment, urlType string) *string { + for _, u := range urls { + if u.Type == urlType { + ret := u.URL + return &ret + } + } + + return nil +} + +func enumToStringPtr(e fmt.Stringer) *string { + if e != nil { + ret := e.String() + return &ret + } + + return nil +} + +func formatMeasurements(m graphql.MeasurementsFragment) *string { + if m.BandSize != nil && m.CupSize != nil && m.Hip != nil && m.Waist != nil { + ret := fmt.Sprintf("%d%s-%d-%d", *m.BandSize, *m.CupSize, *m.Waist, *m.Hip) + return &ret + } + + return nil +} + +func formatCareerLength(start, end *int) *string { + if start == nil && end == nil { + return nil + } + + var ret string + if end == nil { + ret = fmt.Sprintf("%d -", *start) + } else { + ret = fmt.Sprintf("%d - %d", *start, *end) + } + + return &ret +} + +func formatBodyModifications(m []*graphql.BodyModificationFragment) *string { + if len(m) == 0 { + return nil + } + + var retSlice []string + for _, f := range m { + if f.Description == nil { + retSlice = append(retSlice, f.Location) + } else { + retSlice = append(retSlice, fmt.Sprintf("%s, %s", f.Location, *f.Description)) + } + } + + ret := strings.Join(retSlice, "; ") + return &ret +} + +func fetchImage(url string) (*string, error) { + client := &http.Client{ + Timeout: imageGetTimeout, + } + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil, err + } + + resp, err := client.Do(req) + + if err != nil { + return nil, err + } + + defer resp.Body.Close() + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + // determine the image type and set the base64 type + contentType := resp.Header.Get("Content-Type") + if contentType == "" { + contentType = http.DetectContentType(body) + } + + img := "data:" + contentType + ";base64," + utils.GetBase64StringFromData(body) + return &img, nil +} + +func performerFragmentToScrapedScenePerformer(p graphql.PerformerFragment) *models.ScrapedScenePerformer { + id := p.ID + images := []string{} + for _, image := range p.Images { + images = append(images, image.URL) + } + sp := &models.ScrapedScenePerformer{ + Name: p.Name, + Country: p.Country, + Measurements: formatMeasurements(p.Measurements), + CareerLength: formatCareerLength(p.CareerStartYear, p.CareerEndYear), + Tattoos: formatBodyModifications(p.Tattoos), + Piercings: formatBodyModifications(p.Piercings), + Twitter: findURL(p.Urls, "TWITTER"), + RemoteSiteID: &id, + Images: images, + // TODO - Image - should be returned as a set of URLs. Will need a + // graphql schema change to accommodate this. Leave off for now. + } + + if p.Height != nil && *p.Height > 0 { + hs := strconv.Itoa(*p.Height) + sp.Height = &hs + } + + if p.Birthdate != nil { + b := p.Birthdate.Date + sp.Birthdate = &b + } + + if p.Gender != nil { + sp.Gender = enumToStringPtr(p.Gender) + } + + if p.Ethnicity != nil { + sp.Ethnicity = enumToStringPtr(p.Ethnicity) + } + + if p.EyeColor != nil { + sp.EyeColor = enumToStringPtr(p.EyeColor) + } + + if p.BreastType != nil { + sp.FakeTits = enumToStringPtr(p.BreastType) + } + + return sp +} + +func getFirstImage(images []*graphql.ImageFragment) *string { + ret, err := fetchImage(images[0].URL) + if err != nil { + logger.Warnf("Error fetching image %s: %s", images[0].URL, err.Error()) + } + + return ret +} + +func getFingerprints(scene *graphql.SceneFragment) []*models.StashBoxFingerprint { + fingerprints := []*models.StashBoxFingerprint{} + for _, fp := range scene.Fingerprints { + fingerprint := models.StashBoxFingerprint{ + Algorithm: fp.Algorithm.String(), + Hash: fp.Hash, + Duration: fp.Duration, + } + fingerprints = append(fingerprints, &fingerprint) + } + return fingerprints +} + +func sceneFragmentToScrapedScene(s *graphql.SceneFragment) (*models.ScrapedScene, error) { + stashID := s.ID + ss := &models.ScrapedScene{ + Title: s.Title, + Date: s.Date, + Details: s.Details, + URL: findURL(s.Urls, "STUDIO"), + Duration: s.Duration, + RemoteSiteID: &stashID, + Fingerprints: getFingerprints(s), + // Image + // stash_id + } + + if len(s.Images) > 0 { + // TODO - #454 code sorts images by aspect ratio according to a wanted + // orientation. I'm just grabbing the first for now + ss.Image = getFirstImage(s.Images) + } + + if s.Studio != nil { + studioID := s.Studio.ID + ss.Studio = &models.ScrapedSceneStudio{ + Name: s.Studio.Name, + URL: findURL(s.Studio.Urls, "HOME"), + RemoteSiteID: &studioID, + } + + err := models.MatchScrapedSceneStudio(ss.Studio) + if err != nil { + return nil, err + } + } + + for _, p := range s.Performers { + sp := performerFragmentToScrapedScenePerformer(p.Performer) + + err := models.MatchScrapedScenePerformer(sp) + if err != nil { + return nil, err + } + + ss.Performers = append(ss.Performers, sp) + } + + for _, t := range s.Tags { + st := &models.ScrapedSceneTag{ + Name: t.Name, + } + + err := models.MatchScrapedSceneTag(st) + if err != nil { + return nil, err + } + + ss.Tags = append(ss.Tags, st) + } + + return ss, nil +} diff --git a/pkg/scraper/url.go b/pkg/scraper/url.go index 5b99f6a25..fa4ae44c4 100644 --- a/pkg/scraper/url.go +++ b/pkg/scraper/url.go @@ -10,7 +10,6 @@ import ( "net/http" "net/http/cookiejar" "os" - "path/filepath" "strings" "time" @@ -19,7 +18,6 @@ import ( "github.com/chromedp/chromedp" jsoniter "github.com/json-iterator/go" "github.com/stashapp/stash/pkg/logger" - "github.com/stashapp/stash/pkg/models" "golang.org/x/net/html/charset" "golang.org/x/net/publicsuffix" ) @@ -28,16 +26,6 @@ import ( // configurable at some point. const scrapeGetTimeout = time.Second * 30 -func constructSceneURL(url string, scene *models.Scene) string { - // support checksum, title and filename - ret := strings.Replace(url, "{checksum}", scene.Checksum.String, -1) - ret = strings.Replace(url, "{oshash}", scene.OSHash.String, -1) - ret = strings.Replace(ret, "{filename}", filepath.Base(scene.Path), -1) - ret = strings.Replace(ret, "{title}", scene.Title.String, -1) - - return ret -} - func loadURL(url string, scraperConfig config, globalConfig GlobalConfig) (io.Reader, error) { driverOptions := scraperConfig.DriverOptions if driverOptions != nil && driverOptions.UseCDP { diff --git a/pkg/scraper/xpath.go b/pkg/scraper/xpath.go index 6219b10e8..787187bd0 100644 --- a/pkg/scraper/xpath.go +++ b/pkg/scraper/xpath.go @@ -69,6 +69,16 @@ func (s *xpathScraper) scrapeSceneByURL(url string) (*models.ScrapedScene, error return scraper.scrapeScene(q) } +func (s *xpathScraper) scrapeGalleryByURL(url string) (*models.ScrapedGallery, error) { + doc, scraper, err := s.scrapeURL(url) + if err != nil { + return nil, err + } + + q := s.getXPathQuery(doc) + return scraper.scrapeGallery(q) +} + func (s *xpathScraper) scrapeMovieByURL(url string) (*models.ScrapedMovie, error) { doc, scraper, err := s.scrapeURL(url) if err != nil { @@ -119,7 +129,11 @@ func (s *xpathScraper) scrapeSceneByFragment(scene models.SceneUpdateInput) (*mo } // construct the URL - url := constructSceneURL(s.scraper.QueryURL, storedScene) + queryURL := queryURLParametersFromScene(storedScene) + if s.scraper.QueryURLReplacements != nil { + queryURL.applyReplacements(s.scraper.QueryURLReplacements) + } + url := queryURL.constructURL(s.scraper.QueryURL) scraper := s.getXpathScraper() @@ -137,6 +151,39 @@ func (s *xpathScraper) scrapeSceneByFragment(scene models.SceneUpdateInput) (*mo return scraper.scrapeScene(q) } +func (s *xpathScraper) scrapeGalleryByFragment(gallery models.GalleryUpdateInput) (*models.ScrapedGallery, error) { + storedGallery, err := galleryFromUpdateFragment(gallery) + if err != nil { + return nil, err + } + + if storedGallery == nil { + return nil, errors.New("no scene found") + } + + // construct the URL + queryURL := queryURLParametersFromGallery(storedGallery) + if s.scraper.QueryURLReplacements != nil { + queryURL.applyReplacements(s.scraper.QueryURLReplacements) + } + url := queryURL.constructURL(s.scraper.QueryURL) + + scraper := s.getXpathScraper() + + if scraper == nil { + return nil, errors.New("xpath scraper with name " + s.scraper.Scraper + " not found in config") + } + + doc, err := s.loadURL(url) + + if err != nil { + return nil, err + } + + q := s.getXPathQuery(doc) + return scraper.scrapeGallery(q) +} + func (s *xpathScraper) loadURL(url string) (*html.Node, error) { r, err := loadURL(url, s.config, s.globalConfig) if err != nil { diff --git a/pkg/studio/export.go b/pkg/studio/export.go new file mode 100644 index 000000000..7779a0be0 --- /dev/null +++ b/pkg/studio/export.go @@ -0,0 +1,47 @@ +package studio + +import ( + "fmt" + + "github.com/stashapp/stash/pkg/manager/jsonschema" + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/utils" +) + +// ToJSON converts a Studio object into its JSON equivalent. +func ToJSON(reader models.StudioReader, studio *models.Studio) (*jsonschema.Studio, error) { + newStudioJSON := jsonschema.Studio{ + CreatedAt: models.JSONTime{Time: studio.CreatedAt.Timestamp}, + UpdatedAt: models.JSONTime{Time: studio.UpdatedAt.Timestamp}, + } + + if studio.Name.Valid { + newStudioJSON.Name = studio.Name.String + } + + if studio.URL.Valid { + newStudioJSON.URL = studio.URL.String + } + + if studio.ParentID.Valid { + parent, err := reader.Find(int(studio.ParentID.Int64)) + if err != nil { + return nil, fmt.Errorf("error getting parent studio: %s", err.Error()) + } + + if parent != nil { + newStudioJSON.ParentStudio = parent.Name.String + } + } + + image, err := reader.GetStudioImage(studio.ID) + if err != nil { + return nil, fmt.Errorf("error getting studio image: %s", err.Error()) + } + + if len(image) > 0 { + newStudioJSON.Image = utils.GetBase64StringFromData(image) + } + + return &newStudioJSON, nil +} diff --git a/pkg/studio/export_test.go b/pkg/studio/export_test.go new file mode 100644 index 000000000..b8802bcbc --- /dev/null +++ b/pkg/studio/export_test.go @@ -0,0 +1,168 @@ +package studio + +import ( + "errors" + + "github.com/stashapp/stash/pkg/manager/jsonschema" + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/models/mocks" + "github.com/stashapp/stash/pkg/models/modelstest" + "github.com/stretchr/testify/assert" + + "testing" + "time" +) + +const ( + studioID = 1 + noImageID = 2 + errImageID = 3 + missingParentStudioID = 4 + errStudioID = 5 + + parentStudioID = 10 + missingStudioID = 11 + errParentStudioID = 12 +) + +const studioName = "testStudio" +const url = "url" + +const parentStudioName = "parentStudio" + +var parentStudio models.Studio = models.Studio{ + Name: modelstest.NullString(parentStudioName), +} + +var imageBytes = []byte("imageBytes") + +const image = "aW1hZ2VCeXRlcw==" + +var createTime time.Time = time.Date(2001, 01, 01, 0, 0, 0, 0, time.UTC) +var updateTime time.Time = time.Date(2002, 01, 01, 0, 0, 0, 0, time.UTC) + +func createFullStudio(id int, parentID int) models.Studio { + return models.Studio{ + ID: id, + Name: modelstest.NullString(studioName), + URL: modelstest.NullString(url), + ParentID: modelstest.NullInt64(int64(parentID)), + CreatedAt: models.SQLiteTimestamp{ + Timestamp: createTime, + }, + UpdatedAt: models.SQLiteTimestamp{ + Timestamp: updateTime, + }, + } +} + +func createEmptyStudio(id int) models.Studio { + return models.Studio{ + ID: id, + CreatedAt: models.SQLiteTimestamp{ + Timestamp: createTime, + }, + UpdatedAt: models.SQLiteTimestamp{ + Timestamp: updateTime, + }, + } +} + +func createFullJSONStudio(parentStudio, image string) *jsonschema.Studio { + return &jsonschema.Studio{ + Name: studioName, + URL: url, + CreatedAt: models.JSONTime{ + Time: createTime, + }, + UpdatedAt: models.JSONTime{ + Time: updateTime, + }, + ParentStudio: parentStudio, + Image: image, + } +} + +func createEmptyJSONStudio() *jsonschema.Studio { + return &jsonschema.Studio{ + CreatedAt: models.JSONTime{ + Time: createTime, + }, + UpdatedAt: models.JSONTime{ + Time: updateTime, + }, + } +} + +type testScenario struct { + input models.Studio + expected *jsonschema.Studio + err bool +} + +var scenarios []testScenario + +func initTestTable() { + scenarios = []testScenario{ + testScenario{ + createFullStudio(studioID, parentStudioID), + createFullJSONStudio(parentStudioName, image), + false, + }, + testScenario{ + createEmptyStudio(noImageID), + createEmptyJSONStudio(), + false, + }, + testScenario{ + createFullStudio(errImageID, parentStudioID), + nil, + true, + }, + testScenario{ + createFullStudio(missingParentStudioID, missingStudioID), + createFullJSONStudio("", image), + false, + }, + testScenario{ + createFullStudio(errStudioID, errParentStudioID), + nil, + true, + }, + } +} + +func TestToJSON(t *testing.T) { + initTestTable() + + mockStudioReader := &mocks.StudioReaderWriter{} + + imageErr := errors.New("error getting image") + + mockStudioReader.On("GetStudioImage", studioID).Return(imageBytes, nil).Once() + mockStudioReader.On("GetStudioImage", noImageID).Return(nil, nil).Once() + mockStudioReader.On("GetStudioImage", errImageID).Return(nil, imageErr).Once() + mockStudioReader.On("GetStudioImage", missingParentStudioID).Return(imageBytes, nil).Maybe() + mockStudioReader.On("GetStudioImage", errStudioID).Return(imageBytes, nil).Maybe() + + parentStudioErr := errors.New("error getting parent studio") + + mockStudioReader.On("Find", parentStudioID).Return(&parentStudio, nil) + mockStudioReader.On("Find", missingStudioID).Return(nil, nil) + mockStudioReader.On("Find", errParentStudioID).Return(nil, parentStudioErr) + + for i, s := range scenarios { + studio := s.input + json, err := ToJSON(mockStudioReader, &studio) + + if !s.err && err != nil { + t.Errorf("[%d] unexpected error: %s", i, err.Error()) + } else if s.err && err == nil { + t.Errorf("[%d] expected error not returned", i) + } else { + assert.Equal(t, s.expected, json, "[%d]", i) + } + } + + mockStudioReader.AssertExpectations(t) +} diff --git a/pkg/studio/import.go b/pkg/studio/import.go new file mode 100644 index 000000000..64924f475 --- /dev/null +++ b/pkg/studio/import.go @@ -0,0 +1,143 @@ +package studio + +import ( + "database/sql" + "errors" + "fmt" + + "github.com/stashapp/stash/pkg/manager/jsonschema" + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/utils" +) + +var ErrParentStudioNotExist = errors.New("parent studio does not exist") + +type Importer struct { + ReaderWriter models.StudioReaderWriter + Input jsonschema.Studio + MissingRefBehaviour models.ImportMissingRefEnum + + studio models.Studio + imageData []byte +} + +func (i *Importer) PreImport() error { + checksum := utils.MD5FromString(i.Input.Name) + + i.studio = models.Studio{ + Checksum: checksum, + Name: sql.NullString{String: i.Input.Name, Valid: true}, + URL: sql.NullString{String: i.Input.URL, Valid: true}, + CreatedAt: models.SQLiteTimestamp{Timestamp: i.Input.CreatedAt.GetTime()}, + UpdatedAt: models.SQLiteTimestamp{Timestamp: i.Input.UpdatedAt.GetTime()}, + } + + if err := i.populateParentStudio(); err != nil { + return err + } + + var err error + if len(i.Input.Image) > 0 { + _, i.imageData, err = utils.ProcessBase64Image(i.Input.Image) + if err != nil { + return fmt.Errorf("invalid image: %s", err.Error()) + } + } + + return nil +} + +func (i *Importer) populateParentStudio() error { + if i.Input.ParentStudio != "" { + studio, err := i.ReaderWriter.FindByName(i.Input.ParentStudio, false) + if err != nil { + return fmt.Errorf("error finding studio by name: %s", err.Error()) + } + + if studio == nil { + if i.MissingRefBehaviour == models.ImportMissingRefEnumFail { + return ErrParentStudioNotExist + } + + if i.MissingRefBehaviour == models.ImportMissingRefEnumIgnore { + return nil + } + + if i.MissingRefBehaviour == models.ImportMissingRefEnumCreate { + parentID, err := i.createParentStudio(i.Input.ParentStudio) + if err != nil { + return err + } + i.studio.ParentID = sql.NullInt64{ + Int64: int64(parentID), + Valid: true, + } + } + } else { + i.studio.ParentID = sql.NullInt64{Int64: int64(studio.ID), Valid: true} + } + } + + return nil +} + +func (i *Importer) createParentStudio(name string) (int, error) { + newStudio := *models.NewStudio(name) + + created, err := i.ReaderWriter.Create(newStudio) + if err != nil { + return 0, err + } + + return created.ID, nil +} + +func (i *Importer) PostImport(id int) error { + if len(i.imageData) > 0 { + if err := i.ReaderWriter.UpdateStudioImage(id, i.imageData); err != nil { + return fmt.Errorf("error setting studio image: %s", err.Error()) + } + } + + return nil +} + +func (i *Importer) Name() string { + return i.Input.Name +} + +func (i *Importer) FindExistingID() (*int, error) { + const nocase = false + existing, err := i.ReaderWriter.FindByName(i.Name(), nocase) + if err != nil { + return nil, err + } + + if existing != nil { + id := existing.ID + return &id, nil + } + + return nil, nil +} + +func (i *Importer) Create() (*int, error) { + created, err := i.ReaderWriter.Create(i.studio) + if err != nil { + return nil, fmt.Errorf("error creating studio: %s", err.Error()) + } + + id := created.ID + return &id, nil +} + +func (i *Importer) Update(id int) error { + studio := i.studio + studio.ID = id + _, err := i.ReaderWriter.UpdateFull(studio) + if err != nil { + return fmt.Errorf("error updating existing studio: %s", err.Error()) + } + + return nil +} diff --git a/pkg/studio/import_test.go b/pkg/studio/import_test.go new file mode 100644 index 000000000..bc71c8b10 --- /dev/null +++ b/pkg/studio/import_test.go @@ -0,0 +1,263 @@ +package studio + +import ( + "errors" + "testing" + + "github.com/stashapp/stash/pkg/manager/jsonschema" + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/models/mocks" + "github.com/stashapp/stash/pkg/models/modelstest" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +const invalidImage = "aW1hZ2VCeXRlcw&&" + +const ( + studioNameErr = "studioNameErr" + existingStudioName = "existingTagName" + + existingStudioID = 100 + + existingParentStudioName = "existingParentStudioName" + existingParentStudioErr = "existingParentStudioErr" + missingParentStudioName = "existingParentStudioName" +) + +func TestImporterName(t *testing.T) { + i := Importer{ + Input: jsonschema.Studio{ + Name: studioName, + }, + } + + assert.Equal(t, studioName, i.Name()) +} + +func TestImporterPreImport(t *testing.T) { + i := Importer{ + Input: jsonschema.Studio{ + Name: studioName, + Image: invalidImage, + }, + } + + err := i.PreImport() + + assert.NotNil(t, err) + + i.Input.Image = image + + err = i.PreImport() + + assert.Nil(t, err) +} + +func TestImporterPreImportWithParent(t *testing.T) { + readerWriter := &mocks.StudioReaderWriter{} + + i := Importer{ + ReaderWriter: readerWriter, + Input: jsonschema.Studio{ + Name: studioName, + Image: image, + ParentStudio: existingParentStudioName, + }, + } + + readerWriter.On("FindByName", existingParentStudioName, false).Return(&models.Studio{ + ID: existingStudioID, + }, nil).Once() + readerWriter.On("FindByName", existingParentStudioErr, false).Return(nil, errors.New("FindByName error")).Once() + + err := i.PreImport() + assert.Nil(t, err) + assert.Equal(t, int64(existingStudioID), i.studio.ParentID.Int64) + + i.Input.ParentStudio = existingParentStudioErr + err = i.PreImport() + assert.NotNil(t, err) + + readerWriter.AssertExpectations(t) +} + +func TestImporterPreImportWithMissingParent(t *testing.T) { + readerWriter := &mocks.StudioReaderWriter{} + + i := Importer{ + ReaderWriter: readerWriter, + Input: jsonschema.Studio{ + Name: studioName, + Image: image, + ParentStudio: missingParentStudioName, + }, + MissingRefBehaviour: models.ImportMissingRefEnumFail, + } + + readerWriter.On("FindByName", missingParentStudioName, false).Return(nil, nil).Times(3) + readerWriter.On("Create", mock.AnythingOfType("models.Studio")).Return(&models.Studio{ + ID: existingStudioID, + }, nil) + + err := i.PreImport() + assert.NotNil(t, err) + + i.MissingRefBehaviour = models.ImportMissingRefEnumIgnore + err = i.PreImport() + assert.Nil(t, err) + + i.MissingRefBehaviour = models.ImportMissingRefEnumCreate + err = i.PreImport() + assert.Nil(t, err) + assert.Equal(t, int64(existingStudioID), i.studio.ParentID.Int64) + + readerWriter.AssertExpectations(t) +} + +func TestImporterPreImportWithMissingParentCreateErr(t *testing.T) { + readerWriter := &mocks.StudioReaderWriter{} + + i := Importer{ + ReaderWriter: readerWriter, + Input: jsonschema.Studio{ + Name: studioName, + Image: image, + ParentStudio: missingParentStudioName, + }, + MissingRefBehaviour: models.ImportMissingRefEnumCreate, + } + + readerWriter.On("FindByName", missingParentStudioName, false).Return(nil, nil).Once() + readerWriter.On("Create", mock.AnythingOfType("models.Studio")).Return(nil, errors.New("Create error")) + + err := i.PreImport() + assert.NotNil(t, err) +} + +func TestImporterPostImport(t *testing.T) { + readerWriter := &mocks.StudioReaderWriter{} + + i := Importer{ + ReaderWriter: readerWriter, + imageData: imageBytes, + } + + updateStudioImageErr := errors.New("UpdateStudioImage error") + + readerWriter.On("UpdateStudioImage", studioID, imageBytes).Return(nil).Once() + readerWriter.On("UpdateStudioImage", errImageID, imageBytes).Return(updateStudioImageErr).Once() + + err := i.PostImport(studioID) + assert.Nil(t, err) + + err = i.PostImport(errImageID) + assert.NotNil(t, err) + + readerWriter.AssertExpectations(t) +} + +func TestImporterFindExistingID(t *testing.T) { + readerWriter := &mocks.StudioReaderWriter{} + + i := Importer{ + ReaderWriter: readerWriter, + Input: jsonschema.Studio{ + Name: studioName, + }, + } + + errFindByName := errors.New("FindByName error") + readerWriter.On("FindByName", studioName, false).Return(nil, nil).Once() + readerWriter.On("FindByName", existingStudioName, false).Return(&models.Studio{ + ID: existingStudioID, + }, nil).Once() + readerWriter.On("FindByName", studioNameErr, false).Return(nil, errFindByName).Once() + + id, err := i.FindExistingID() + assert.Nil(t, id) + assert.Nil(t, err) + + i.Input.Name = existingStudioName + id, err = i.FindExistingID() + assert.Equal(t, existingStudioID, *id) + assert.Nil(t, err) + + i.Input.Name = studioNameErr + id, err = i.FindExistingID() + assert.Nil(t, id) + assert.NotNil(t, err) + + readerWriter.AssertExpectations(t) +} + +func TestCreate(t *testing.T) { + readerWriter := &mocks.StudioReaderWriter{} + + studio := models.Studio{ + Name: modelstest.NullString(studioName), + } + + studioErr := models.Studio{ + Name: modelstest.NullString(studioNameErr), + } + + i := Importer{ + ReaderWriter: readerWriter, + studio: studio, + } + + errCreate := errors.New("Create error") + readerWriter.On("Create", studio).Return(&models.Studio{ + ID: studioID, + }, nil).Once() + readerWriter.On("Create", studioErr).Return(nil, errCreate).Once() + + id, err := i.Create() + assert.Equal(t, studioID, *id) + assert.Nil(t, err) + + i.studio = studioErr + id, err = i.Create() + assert.Nil(t, id) + assert.NotNil(t, err) + + readerWriter.AssertExpectations(t) +} + +func TestUpdate(t *testing.T) { + readerWriter := &mocks.StudioReaderWriter{} + + studio := models.Studio{ + Name: modelstest.NullString(studioName), + } + + studioErr := models.Studio{ + Name: modelstest.NullString(studioNameErr), + } + + i := Importer{ + ReaderWriter: readerWriter, + studio: studio, + } + + errUpdate := errors.New("Update error") + + // id needs to be set for the mock input + studio.ID = studioID + readerWriter.On("UpdateFull", studio).Return(nil, nil).Once() + + err := i.Update(studioID) + assert.Nil(t, err) + + i.studio = studioErr + + // need to set id separately + studioErr.ID = errImageID + readerWriter.On("UpdateFull", studioErr).Return(nil, errUpdate).Once() + + err = i.Update(errImageID) + assert.NotNil(t, err) + + readerWriter.AssertExpectations(t) +} diff --git a/pkg/tag/export.go b/pkg/tag/export.go new file mode 100644 index 000000000..9671ee869 --- /dev/null +++ b/pkg/tag/export.go @@ -0,0 +1,47 @@ +package tag + +import ( + "fmt" + + "github.com/stashapp/stash/pkg/manager/jsonschema" + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/utils" +) + +// ToJSON converts a Tag object into its JSON equivalent. +func ToJSON(reader models.TagReader, tag *models.Tag) (*jsonschema.Tag, error) { + newTagJSON := jsonschema.Tag{ + Name: tag.Name, + CreatedAt: models.JSONTime{Time: tag.CreatedAt.Timestamp}, + UpdatedAt: models.JSONTime{Time: tag.UpdatedAt.Timestamp}, + } + + image, err := reader.GetTagImage(tag.ID) + if err != nil { + return nil, fmt.Errorf("error getting tag image: %s", err.Error()) + } + + if len(image) > 0 { + newTagJSON.Image = utils.GetBase64StringFromData(image) + } + + return &newTagJSON, nil +} + +func GetIDs(tags []*models.Tag) []int { + var results []int + for _, tag := range tags { + results = append(results, tag.ID) + } + + return results +} + +func GetNames(tags []*models.Tag) []string { + var results []string + for _, tag := range tags { + results = append(results, tag.Name) + } + + return results +} diff --git a/pkg/tag/export_test.go b/pkg/tag/export_test.go new file mode 100644 index 000000000..a494aebf4 --- /dev/null +++ b/pkg/tag/export_test.go @@ -0,0 +1,105 @@ +package tag + +import ( + "errors" + + "github.com/stashapp/stash/pkg/manager/jsonschema" + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/models/mocks" + "github.com/stretchr/testify/assert" + + "testing" + "time" +) + +const ( + tagID = 1 + noImageID = 2 + errImageID = 3 +) + +const tagName = "testTag" + +var createTime time.Time = time.Date(2001, 01, 01, 0, 0, 0, 0, time.UTC) +var updateTime time.Time = time.Date(2002, 01, 01, 0, 0, 0, 0, time.UTC) + +func createTag(id int) models.Tag { + return models.Tag{ + ID: id, + Name: tagName, + CreatedAt: models.SQLiteTimestamp{ + Timestamp: createTime, + }, + UpdatedAt: models.SQLiteTimestamp{ + Timestamp: updateTime, + }, + } +} + +func createJSONTag(image string) *jsonschema.Tag { + return &jsonschema.Tag{ + Name: tagName, + CreatedAt: models.JSONTime{ + Time: createTime, + }, + UpdatedAt: models.JSONTime{ + Time: updateTime, + }, + Image: image, + } +} + +type testScenario struct { + tag models.Tag + expected *jsonschema.Tag + err bool +} + +var scenarios []testScenario + +func initTestTable() { + scenarios = []testScenario{ + testScenario{ + createTag(tagID), + createJSONTag("PHN2ZwogICB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgIHhtbG5zOmNjPSJodHRwOi8vY3JlYXRpdmVjb21tb25zLm9yZy9ucyMiCiAgIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyIKICAgeG1sbnM6c3ZnPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIKICAgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIgogICB4bWxuczpzb2RpcG9kaT0iaHR0cDovL3NvZGlwb2RpLnNvdXJjZWZvcmdlLm5ldC9EVEQvc29kaXBvZGktMC5kdGQiCiAgIHhtbG5zOmlua3NjYXBlPSJodHRwOi8vd3d3Lmlua3NjYXBlLm9yZy9uYW1lc3BhY2VzL2lua3NjYXBlIgogICB3aWR0aD0iMjAwIgogICBoZWlnaHQ9IjIwMCIKICAgaWQ9InN2ZzIiCiAgIHZlcnNpb249IjEuMSIKICAgaW5rc2NhcGU6dmVyc2lvbj0iMC40OC40IHI5OTM5IgogICBzb2RpcG9kaTpkb2NuYW1lPSJ0YWcuc3ZnIj4KICA8ZGVmcwogICAgIGlkPSJkZWZzNCIgLz4KICA8c29kaXBvZGk6bmFtZWR2aWV3CiAgICAgaWQ9ImJhc2UiCiAgICAgcGFnZWNvbG9yPSIjMDAwMDAwIgogICAgIGJvcmRlcmNvbG9yPSIjNjY2NjY2IgogICAgIGJvcmRlcm9wYWNpdHk9IjEuMCIKICAgICBpbmtzY2FwZTpwYWdlb3BhY2l0eT0iMSIKICAgICBpbmtzY2FwZTpwYWdlc2hhZG93PSIyIgogICAgIGlua3NjYXBlOnpvb209IjEiCiAgICAgaW5rc2NhcGU6Y3g9IjE4MS43Nzc3MSIKICAgICBpbmtzY2FwZTpjeT0iMjc5LjcyMzc2IgogICAgIGlua3NjYXBlOmRvY3VtZW50LXVuaXRzPSJweCIKICAgICBpbmtzY2FwZTpjdXJyZW50LWxheWVyPSJsYXllcjEiCiAgICAgc2hvd2dyaWQ9ImZhbHNlIgogICAgIGZpdC1tYXJnaW4tdG9wPSIwIgogICAgIGZpdC1tYXJnaW4tbGVmdD0iMCIKICAgICBmaXQtbWFyZ2luLXJpZ2h0PSIwIgogICAgIGZpdC1tYXJnaW4tYm90dG9tPSIwIgogICAgIGlua3NjYXBlOndpbmRvdy13aWR0aD0iMTkyMCIKICAgICBpbmtzY2FwZTp3aW5kb3ctaGVpZ2h0PSIxMDE3IgogICAgIGlua3NjYXBlOndpbmRvdy14PSItOCIKICAgICBpbmtzY2FwZTp3aW5kb3cteT0iLTgiCiAgICAgaW5rc2NhcGU6d2luZG93LW1heGltaXplZD0iMSIgLz4KICA8bWV0YWRhdGEKICAgICBpZD0ibWV0YWRhdGE3Ij4KICAgIDxyZGY6UkRGPgogICAgICA8Y2M6V29yawogICAgICAgICByZGY6YWJvdXQ9IiI+CiAgICAgICAgPGRjOmZvcm1hdD5pbWFnZS9zdmcreG1sPC9kYzpmb3JtYXQ+CiAgICAgICAgPGRjOnR5cGUKICAgICAgICAgICByZGY6cmVzb3VyY2U9Imh0dHA6Ly9wdXJsLm9yZy9kYy9kY21pdHlwZS9TdGlsbEltYWdlIiAvPgogICAgICAgIDxkYzp0aXRsZT48L2RjOnRpdGxlPgogICAgICA8L2NjOldvcms+CiAgICA8L3JkZjpSREY+CiAgPC9tZXRhZGF0YT4KICA8ZwogICAgIGlua3NjYXBlOmxhYmVsPSJMYXllciAxIgogICAgIGlua3NjYXBlOmdyb3VwbW9kZT0ibGF5ZXIiCiAgICAgaWQ9ImxheWVyMSIKICAgICB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtMTU3Ljg0MzU4LC01MjQuNjk1MjIpIj4KICAgIDxwYXRoCiAgICAgICBpZD0icGF0aDI5ODciCiAgICAgICBkPSJtIDIyOS45NDMxNCw2NjkuMjY1NDkgLTM2LjA4NDY2LC0zNi4wODQ2NiBjIC00LjY4NjUzLC00LjY4NjUzIC00LjY4NjUzLC0xMi4yODQ2OCAwLC0xNi45NzEyMSBsIDM2LjA4NDY2LC0zNi4wODQ2NyBhIDEyLjAwMDQ1MywxMi4wMDA0NTMgMCAwIDEgOC40ODU2LC0zLjUxNDggbCA3NC45MTQ0MywwIGMgNi42Mjc2MSwwIDEyLjAwMDQxLDUuMzcyOCAxMi4wMDA0MSwxMi4wMDA0MSBsIDAsNzIuMTY5MzMgYyAwLDYuNjI3NjEgLTUuMzcyOCwxMi4wMDA0MSAtMTIuMDAwNDEsMTIuMDAwNDEgbCAtNzQuOTE0NDMsMCBhIDEyLjAwMDQ1MywxMi4wMDA0NTMgMCAwIDEgLTguNDg1NiwtMy41MTQ4MSB6IG0gLTEzLjQ1NjM5LC01My4wNTU4NyBjIC00LjY4NjUzLDQuNjg2NTMgLTQuNjg2NTMsMTIuMjg0NjggMCwxNi45NzEyMSA0LjY4NjUyLDQuNjg2NTIgMTIuMjg0NjcsNC42ODY1MiAxNi45NzEyLDAgNC42ODY1MywtNC42ODY1MyA0LjY4NjUzLC0xMi4yODQ2OCAwLC0xNi45NzEyMSAtNC42ODY1MywtNC42ODY1MiAtMTIuMjg0NjgsLTQuNjg2NTIgLTE2Ljk3MTIsMCB6IgogICAgICAgaW5rc2NhcGU6Y29ubmVjdG9yLWN1cnZhdHVyZT0iMCIKICAgICAgIHN0eWxlPSJmaWxsOiNmZmZmZmY7ZmlsbC1vcGFjaXR5OjEiIC8+CiAgPC9nPgo8L3N2Zz4="), + false, + }, + testScenario{ + createTag(noImageID), + createJSONTag(""), + false, + }, + testScenario{ + createTag(errImageID), + nil, + true, + }, + } +} + +func TestToJSON(t *testing.T) { + initTestTable() + + mockTagReader := &mocks.TagReaderWriter{} + + imageErr := errors.New("error getting image") + + mockTagReader.On("GetTagImage", tagID).Return(models.DefaultTagImage, nil).Once() + mockTagReader.On("GetTagImage", noImageID).Return(nil, nil).Once() + mockTagReader.On("GetTagImage", errImageID).Return(nil, imageErr).Once() + + for i, s := range scenarios { + tag := s.tag + json, err := ToJSON(mockTagReader, &tag) + + if !s.err && err != nil { + t.Errorf("[%d] unexpected error: %s", i, err.Error()) + } else if s.err && err == nil { + t.Errorf("[%d] expected error not returned", i) + } else { + assert.Equal(t, s.expected, json, "[%d]", i) + } + } + + mockTagReader.AssertExpectations(t) +} diff --git a/pkg/tag/import.go b/pkg/tag/import.go new file mode 100644 index 000000000..5985253e6 --- /dev/null +++ b/pkg/tag/import.go @@ -0,0 +1,85 @@ +package tag + +import ( + "fmt" + + "github.com/stashapp/stash/pkg/manager/jsonschema" + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/utils" +) + +type Importer struct { + ReaderWriter models.TagReaderWriter + Input jsonschema.Tag + + tag models.Tag + imageData []byte +} + +func (i *Importer) PreImport() error { + i.tag = models.Tag{ + Name: i.Input.Name, + CreatedAt: models.SQLiteTimestamp{Timestamp: i.Input.CreatedAt.GetTime()}, + UpdatedAt: models.SQLiteTimestamp{Timestamp: i.Input.UpdatedAt.GetTime()}, + } + + var err error + if len(i.Input.Image) > 0 { + _, i.imageData, err = utils.ProcessBase64Image(i.Input.Image) + if err != nil { + return fmt.Errorf("invalid image: %s", err.Error()) + } + } + + return nil +} + +func (i *Importer) PostImport(id int) error { + if len(i.imageData) > 0 { + if err := i.ReaderWriter.UpdateTagImage(id, i.imageData); err != nil { + return fmt.Errorf("error setting tag image: %s", err.Error()) + } + } + + return nil +} + +func (i *Importer) Name() string { + return i.Input.Name +} + +func (i *Importer) FindExistingID() (*int, error) { + const nocase = false + existing, err := i.ReaderWriter.FindByName(i.Name(), nocase) + if err != nil { + return nil, err + } + + if existing != nil { + id := existing.ID + return &id, nil + } + + return nil, nil +} + +func (i *Importer) Create() (*int, error) { + created, err := i.ReaderWriter.Create(i.tag) + if err != nil { + return nil, fmt.Errorf("error creating tag: %s", err.Error()) + } + + id := created.ID + return &id, nil +} + +func (i *Importer) Update(id int) error { + tag := i.tag + tag.ID = id + _, err := i.ReaderWriter.Update(tag) + if err != nil { + return fmt.Errorf("error updating existing tag: %s", err.Error()) + } + + return nil +} diff --git a/pkg/tag/import_test.go b/pkg/tag/import_test.go new file mode 100644 index 000000000..b99dd012c --- /dev/null +++ b/pkg/tag/import_test.go @@ -0,0 +1,179 @@ +package tag + +import ( + "errors" + "testing" + + "github.com/stashapp/stash/pkg/manager/jsonschema" + "github.com/stashapp/stash/pkg/models" + "github.com/stashapp/stash/pkg/models/mocks" + "github.com/stretchr/testify/assert" +) + +const image = "aW1hZ2VCeXRlcw==" +const invalidImage = "aW1hZ2VCeXRlcw&&" + +var imageBytes = []byte("imageBytes") + +const ( + tagNameErr = "tagNameErr" + existingTagName = "existingTagName" + + existingTagID = 100 +) + +func TestImporterName(t *testing.T) { + i := Importer{ + Input: jsonschema.Tag{ + Name: tagName, + }, + } + + assert.Equal(t, tagName, i.Name()) +} + +func TestImporterPreImport(t *testing.T) { + i := Importer{ + Input: jsonschema.Tag{ + Name: tagName, + Image: invalidImage, + }, + } + + err := i.PreImport() + + assert.NotNil(t, err) + + i.Input.Image = image + + err = i.PreImport() + + assert.Nil(t, err) +} + +func TestImporterPostImport(t *testing.T) { + readerWriter := &mocks.TagReaderWriter{} + + i := Importer{ + ReaderWriter: readerWriter, + imageData: imageBytes, + } + + updateTagImageErr := errors.New("UpdateTagImage error") + + readerWriter.On("UpdateTagImage", tagID, imageBytes).Return(nil).Once() + readerWriter.On("UpdateTagImage", errImageID, imageBytes).Return(updateTagImageErr).Once() + + err := i.PostImport(tagID) + assert.Nil(t, err) + + err = i.PostImport(errImageID) + assert.NotNil(t, err) + + readerWriter.AssertExpectations(t) +} + +func TestImporterFindExistingID(t *testing.T) { + readerWriter := &mocks.TagReaderWriter{} + + i := Importer{ + ReaderWriter: readerWriter, + Input: jsonschema.Tag{ + Name: tagName, + }, + } + + errFindByName := errors.New("FindByName error") + readerWriter.On("FindByName", tagName, false).Return(nil, nil).Once() + readerWriter.On("FindByName", existingTagName, false).Return(&models.Tag{ + ID: existingTagID, + }, nil).Once() + readerWriter.On("FindByName", tagNameErr, false).Return(nil, errFindByName).Once() + + id, err := i.FindExistingID() + assert.Nil(t, id) + assert.Nil(t, err) + + i.Input.Name = existingTagName + id, err = i.FindExistingID() + assert.Equal(t, existingTagID, *id) + assert.Nil(t, err) + + i.Input.Name = tagNameErr + id, err = i.FindExistingID() + assert.Nil(t, id) + assert.NotNil(t, err) + + readerWriter.AssertExpectations(t) +} + +func TestCreate(t *testing.T) { + readerWriter := &mocks.TagReaderWriter{} + + tag := models.Tag{ + Name: tagName, + } + + tagErr := models.Tag{ + Name: tagNameErr, + } + + i := Importer{ + ReaderWriter: readerWriter, + tag: tag, + } + + errCreate := errors.New("Create error") + readerWriter.On("Create", tag).Return(&models.Tag{ + ID: tagID, + }, nil).Once() + readerWriter.On("Create", tagErr).Return(nil, errCreate).Once() + + id, err := i.Create() + assert.Equal(t, tagID, *id) + assert.Nil(t, err) + + i.tag = tagErr + id, err = i.Create() + assert.Nil(t, id) + assert.NotNil(t, err) + + readerWriter.AssertExpectations(t) +} + +func TestUpdate(t *testing.T) { + readerWriter := &mocks.TagReaderWriter{} + + tag := models.Tag{ + Name: tagName, + } + + tagErr := models.Tag{ + Name: tagNameErr, + } + + i := Importer{ + ReaderWriter: readerWriter, + tag: tag, + } + + errUpdate := errors.New("Update error") + + // id needs to be set for the mock input + tag.ID = tagID + readerWriter.On("Update", tag).Return(nil, nil).Once() + + err := i.Update(tagID) + assert.Nil(t, err) + + i.tag = tagErr + + // need to set id separately + tagErr.ID = errImageID + readerWriter.On("Update", tagErr).Return(nil, errUpdate).Once() + + err = i.Update(errImageID) + assert.NotNil(t, err) + + readerWriter.AssertExpectations(t) +} diff --git a/pkg/utils/crypto.go b/pkg/utils/crypto.go index 06e11c9f9..8f20ad9b2 100644 --- a/pkg/utils/crypto.go +++ b/pkg/utils/crypto.go @@ -26,8 +26,12 @@ func MD5FromFilePath(filePath string) (string, error) { } defer f.Close() + return MD5FromReader(f) +} + +func MD5FromReader(src io.Reader) (string, error) { h := md5.New() - if _, err := io.Copy(h, f); err != nil { + if _, err := io.Copy(h, src); err != nil { return "", err } checksum := h.Sum(nil) diff --git a/pkg/utils/file.go b/pkg/utils/file.go index f4d947408..196f0d047 100644 --- a/pkg/utils/file.go +++ b/pkg/utils/file.go @@ -5,7 +5,6 @@ import ( "fmt" "io" "io/ioutil" - "math" "net/http" "os" "os/user" @@ -115,11 +114,7 @@ func ListDir(path string) []string { if !file.IsDir() { continue } - abs, err := filepath.Abs(path) - if err != nil { - continue - } - dirPaths = append(dirPaths, filepath.Join(abs, file.Name())) + dirPaths = append(dirPaths, filepath.Join(path, file.Name())) } return dirPaths } @@ -188,29 +183,6 @@ func IsZipFileUncompressed(path string) (bool, error) { return false, nil } -// humanize code taken from https://github.com/dustin/go-humanize and adjusted - -func logn(n, b float64) float64 { - return math.Log(n) / math.Log(b) -} - -// HumanizeBytes returns a human readable bytes string of a uint -func HumanizeBytes(s uint64) string { - sizes := []string{"B", "KB", "MB", "GB", "TB", "PB", "EB"} - if s < 10 { - return fmt.Sprintf("%d B", s) - } - e := math.Floor(logn(float64(s), 1024)) - suffix := sizes[int(e)] - val := math.Floor(float64(s)/math.Pow(1024, e)*10+0.5) / 10 - f := "%.0f %s" - if val < 10 { - f = "%.1f %s" - } - - return fmt.Sprintf(f, val, suffix) -} - // WriteFile writes file to path creating parent directories if needed func WriteFile(path string, file []byte) error { pathErr := EnsureDirAll(filepath.Dir(path)) @@ -244,11 +216,7 @@ func GetDir(path string) string { path = GetHomeDirectory() } - absolutePath, err := filepath.Abs(path) - if err == nil { - path = absolutePath - } - return absolutePath + return path } func GetParent(path string) *string { diff --git a/pkg/utils/int_collections.go b/pkg/utils/int_collections.go new file mode 100644 index 000000000..5b59a81f7 --- /dev/null +++ b/pkg/utils/int_collections.go @@ -0,0 +1,39 @@ +package utils + +// IntIndex returns the first index of the provided int value in the provided +// int slice. It returns -1 if it is not found. +func IntIndex(vs []int, t int) int { + for i, v := range vs { + if v == t { + return i + } + } + return -1 +} + +// IntInclude returns true if the provided int value exists in the provided int +// slice. +func IntInclude(vs []int, t int) bool { + return IntIndex(vs, t) >= 0 +} + +// IntAppendUnique appends toAdd to the vs int slice if toAdd does not already +// exist in the slice. It returns the new or unchanged int slice. +func IntAppendUnique(vs []int, toAdd int) []int { + if IntInclude(vs, toAdd) { + return vs + } + + return append(vs, toAdd) +} + +// IntAppendUniques appends a slice of int values to the vs int slice. It only +// appends values that do not already exist in the slice. It returns the new or +// unchanged int slice. +func IntAppendUniques(vs []int, toAdd []int) []int { + for _, v := range toAdd { + vs = IntAppendUnique(vs, v) + } + + return vs +} diff --git a/pkg/utils/symwalk.go b/pkg/utils/symwalk.go new file mode 100644 index 000000000..ccb0f02a7 --- /dev/null +++ b/pkg/utils/symwalk.go @@ -0,0 +1,80 @@ +// Modified from github.com/facebookgo/symwalk + +// BSD License + +// For symwalk software + +// Copyright (c) 2015, Facebook, Inc. All rights reserved. + +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: + +// * Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. + +// * Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. + +// * Neither the name Facebook nor the names of its contributors may be used to +// endorse or promote products derived from this software without specific +// prior written permission. + +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +// ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package utils + +import ( + "os" + "path/filepath" +) + +// symwalkFunc calls the provided WalkFn for regular files. +// However, when it encounters a symbolic link, it resolves the link fully using the +// filepath.EvalSymlinks function and recursively calls symwalk.Walk on the resolved path. +// This ensures that unlink filepath.Walk, traversal does not stop at symbolic links. +// +// Note that symwalk.Walk does not terminate if there are any non-terminating loops in +// the file structure. +func walk(filename string, linkDirname string, walkFn filepath.WalkFunc) error { + symWalkFunc := func(path string, info os.FileInfo, err error) error { + + if fname, err := filepath.Rel(filename, path); err == nil { + path = filepath.Join(linkDirname, fname) + } else { + return err + } + + if err == nil && info.Mode()&os.ModeSymlink == os.ModeSymlink { + finalPath, err := filepath.EvalSymlinks(path) + if err != nil { + // don't bail out if symlink is invalid + return walkFn(path, info, err) + } + info, err := os.Lstat(finalPath) + if err != nil { + return walkFn(path, info, err) + } + if info.IsDir() { + return walk(finalPath, path, walkFn) + } + } + + return walkFn(path, info, err) + } + return filepath.Walk(filename, symWalkFunc) +} + +// SymWalk extends filepath.Walk to also follow symlinks +func SymWalk(path string, walkFn filepath.WalkFunc) error { + return walk(path, path, walkFn) +} diff --git a/scripts/cross-compile.sh b/scripts/cross-compile.sh index 4521e0814..e8df66e13 100755 --- a/scripts/cross-compile.sh +++ b/scripts/cross-compile.sh @@ -7,11 +7,11 @@ SETENV="BUILD_DATE=\"$BUILD_DATE\" GITHASH=$GITHASH STASH_VERSION=\"$STASH_VERSI SETUP="export GO111MODULE=on; export CGO_ENABLED=1; set -e; echo '=== Running packr ==='; make packr;" WINDOWS="echo '=== Building Windows binary ==='; $SETENV GOOS=windows GOARCH=amd64 CC=x86_64-w64-mingw32-gcc CXX=x86_64-w64-mingw32-g++ LDFLAGS=\"-extldflags '-static' \" OUTPUT=\"dist/stash-win.exe\" make build-release;" DARWIN="echo '=== Building OSX binary ==='; $SETENV GOOS=darwin GOARCH=amd64 CC=o64-clang CXX=o64-clang++ OUTPUT=\"dist/stash-osx\" make build-release;" -LINUX_AMD64="echo '=== Building Linux (amd64) binary ==='; $SETENV GOOS=linux GOARCH=amd64 OUTPUT=\"dist/stash-linux\" make build-release;" -LINUX_ARM64v8="echo '=== Building Linux (armv8/arm64) binary ==='; $SETENV GOOS=linux GOARCH=arm64 CC=aarch64-linux-gnu-gcc OUTPUT=\"dist/stash-linux-arm64v8\" make build-release;" -LINUX_ARM32v7="echo '=== Building Linux (armv7/armhf) binary ==='; $SETENV GOOS=linux GOARCH=arm GOARM=7 CC=arm-linux-gnueabihf-gcc OUTPUT=\"dist/stash-linux-arm32v7\" make build-release;" -LINUX_ARM32v6="echo '=== Building Linux (armv6 | Raspberry Pi 1) binary ==='; $SETENV GOOS=linux GOARCH=arm GOARM=6 CC=arm-linux-gnueabi-gcc OUTPUT=\"dist/stash-pi\" make build-release;" +LINUX_AMD64="echo '=== Building Linux (amd64) binary ==='; $SETENV GOOS=linux GOARCH=amd64 OUTPUT=\"dist/stash-linux\" make build-release-static;" +LINUX_ARM64v8="echo '=== Building Linux (armv8/arm64) binary ==='; $SETENV GOOS=linux GOARCH=arm64 CC=aarch64-linux-gnu-gcc OUTPUT=\"dist/stash-linux-arm64v8\" make build-release-static;" +LINUX_ARM32v7="echo '=== Building Linux (armv7/armhf) binary ==='; $SETENV GOOS=linux GOARCH=arm GOARM=7 CC=arm-linux-gnueabihf-gcc OUTPUT=\"dist/stash-linux-arm32v7\" make build-release-static;" +LINUX_ARM32v6="echo '=== Building Linux (armv6 | Raspberry Pi 1) binary ==='; $SETENV GOOS=linux GOARCH=arm GOARM=6 CC=arm-linux-gnueabi-gcc OUTPUT=\"dist/stash-pi\" make build-release-static;" COMMAND="$SETUP $WINDOWS $DARWIN $LINUX_AMD64 $LINUX_ARM64v8 $LINUX_ARM32v7 $LINUX_ARM32v6 echo '=== Build complete ==='" -docker run --rm --mount type=bind,source="$(pwd)",target=/stash -w /stash stashapp/compiler:3 /bin/bash -c "$COMMAND" +docker run --rm --mount type=bind,source="$(pwd)",target=/stash -w /stash stashapp/compiler:4 /bin/bash -c "$COMMAND" diff --git a/scripts/upload-pull-request.sh b/scripts/upload-pull-request.sh index 54807b6d7..10523c903 100644 --- a/scripts/upload-pull-request.sh +++ b/scripts/upload-pull-request.sh @@ -5,9 +5,29 @@ uploadFile() { FILE=$1 BASENAME="$(basename "${FILE}")" + + # get available server from gofile api + serverApi=$(curl -m 15 https://apiv2.gofile.io/getServer) + resp=$(echo "$serverApi" | cut -d "\"" -f 4) + + # if no server is available abort + if [ $resp != "ok" ] ; then + echo "Upload of $BASENAME failed! Server not available." + echo + return + fi + server=$(echo "$serverApi" | cut -d "," -f 2 | cut -d "\"" -f 6) + # abort if it takes more than two minutes to upload - uploadedTo=`curl -m 120 --upload-file $FILE "https://transfer.sh/$BASENAME"` - echo "$BASENAME uploaded to url: $uploadedTo" + uploadedTo=$(curl -m 120 -F "email=stash@stashapp.cc" -F "file=@$FILE" "https://$server.gofile.io/uploadFile") + resp=$(echo "$uploadedTo" | cut -d "\"" -f 4) + if [ $resp = "ok" ] ; then + URL=$(echo "$uploadedTo"|cut -d "," -f 2 | cut -d "\"" -f 6) + echo "$BASENAME uploaded to url: \"https://gofile.io/d/$URL\"" + fi + # print an extra newline + echo + } uploadFile "dist/stash-osx" diff --git a/tools.go b/tools.go index 09a5657f4..9bc7b212d 100644 --- a/tools.go +++ b/tools.go @@ -4,4 +4,6 @@ package main import ( _ "github.com/99designs/gqlgen" + _ "github.com/Yamashou/gqlgenc" + _ "github.com/vektra/mockery/v2" ) diff --git a/ui/v2.5/.editorconfig b/ui/v2.5/.editorconfig index 86a63dc0f..8c52ff937 100644 --- a/ui/v2.5/.editorconfig +++ b/ui/v2.5/.editorconfig @@ -7,3 +7,6 @@ indent_style = space indent_size = 2 insert_final_newline = true trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false diff --git a/ui/v2.5/.eslintrc.json b/ui/v2.5/.eslintrc.json index f26d60fca..07792d77a 100644 --- a/ui/v2.5/.eslintrc.json +++ b/ui/v2.5/.eslintrc.json @@ -1,4 +1,10 @@ { + "env": { + "browser": true + }, + "globals": { + "Mousetrap": "readonly" + }, "parser": "@typescript-eslint/parser", "parserOptions": { "project": "./tsconfig.json" @@ -16,11 +22,18 @@ "rules": { "@typescript-eslint/explicit-function-return-type": "off", "@typescript-eslint/no-explicit-any": 2, - "lines-between-class-members": "off", - "@typescript-eslint/interface-name-prefix": [ - "warn", - { "prefixWithI": "always" } + "@typescript-eslint/naming-convention": [ + "error", + { + "selector": "interface", + "format": ["PascalCase"], + "custom": { + "regex": "^I[A-Z]", + "match": true + } + } ], + "lines-between-class-members": "off", "import/extensions": [ "error", "ignorePackages", @@ -46,6 +59,7 @@ "@typescript-eslint/indent": "off", "react/prop-types": "off", "react/destructuring-assignment": "off", + "react/require-default-props": "off", "react/jsx-props-no-spreading": "off", "react/style-prop-object": ["error", { "allow": ["FormattedNumber"] diff --git a/ui/v2.5/.stylelintrc b/ui/v2.5/.stylelintrc index 5f5be26f0..7442e1729 100644 --- a/ui/v2.5/.stylelintrc +++ b/ui/v2.5/.stylelintrc @@ -4,7 +4,7 @@ ], "extends": "stylelint-config-prettier", "rules": { - "indentation": 2, + "indentation": null, "at-rule-empty-line-before": [ "always", { except: ["after-same-name", "first-nested" ], ignore: ["after-comment"], diff --git a/ui/v2.5/codegen.yml b/ui/v2.5/codegen.yml index dbab06402..d648406a9 100644 --- a/ui/v2.5/codegen.yml +++ b/ui/v2.5/codegen.yml @@ -3,13 +3,12 @@ schema: "../../graphql/schema/**/*.graphql" documents: "../../graphql/documents/**/*.graphql" generates: src/core/generated-graphql.tsx: - config: - withHooks: true - withHOC: false - withComponents: false plugins: - - add: "/* eslint-disable */" + - add: + content: "/* eslint-disable */" - time - typescript - typescript-operations - typescript-react-apollo + config: + withRefetchFn: true diff --git a/ui/v2.5/package.json b/ui/v2.5/package.json index 164a6834d..dd744b2a2 100644 --- a/ui/v2.5/package.json +++ b/ui/v2.5/package.json @@ -25,66 +25,66 @@ "not op_mini all" ], "dependencies": { - "@apollo/react-hooks": "^3.1.5", - "@formatjs/intl-numberformat": "^4.2.1", - "@fortawesome/fontawesome-svg-core": "^1.2.28", - "@fortawesome/free-regular-svg-icons": "^5.13.0", - "@fortawesome/free-solid-svg-icons": "^5.13.0", - "@fortawesome/react-fontawesome": "^0.1.9", + "@apollo/client": "^3.1.4", + "@formatjs/intl-numberformat": "^5.6.0", + "@fortawesome/fontawesome-svg-core": "^1.2.30", + "@fortawesome/free-regular-svg-icons": "^5.14.0", + "@fortawesome/free-solid-svg-icons": "^5.14.0", + "@fortawesome/react-fontawesome": "^0.1.11", + "@types/apollo-upload-client": "^14.1.0", "@types/mousetrap": "^1.6.3", - "apollo-cache": "^1.3.4", - "apollo-cache-inmemory": "^1.6.5", - "apollo-client": "^2.6.8", - "apollo-link": "^1.2.14", - "apollo-link-error": "^1.1.13", - "apollo-link-http": "^1.5.17", - "apollo-link-ws": "^1.0.20", - "apollo-utilities": "^1.3.3", - "axios": "0.19.2", - "bootstrap": "^4.4.1", + "apollo-upload-client": "^14.1.2", + "axios": "0.20.0", + "base64-blob": "^1.4.1", + "bootstrap": "^4.5.2", "classnames": "^2.2.6", - "flag-icon-css": "^3.4.6", - "formik": "^2.1.4", - "graphql": "^14.5.8", - "graphql-tag": "^2.10.3", - "i18n-iso-countries": "^5.2.0", - "jimp": "^0.12.1", - "localforage": "1.7.3", - "lodash": "^4.17.15", + "flag-icon-css": "^3.5.0", + "flexbin": "^0.2.0", + "formik": "^2.1.5", + "fslightbox-react": "^1.5.0", + "graphql": "^15.3.0", + "graphql-tag": "^2.11.0", + "i18n-iso-countries": "^6.0.0", + "jimp": "^0.16.1", + "localforage": "1.9.0", + "lodash": "^4.17.20", "mousetrap": "^1.6.5", - "query-string": "6.12.1", + "mousetrap-pause": "^1.0.0", + "query-string": "6.13.1", "react": "16.13.1", - "react-apollo": "^3.1.5", - "react-bootstrap": "1.0.1", + "react-bootstrap": "1.3.0", "react-dom": "16.13.1", "react-images": "0.5.19", - "react-intl": "^4.5.1", + "react-intl": "^5.8.0", "react-jw-player": "1.19.1", "react-markdown": "^4.3.1", "react-photo-gallery": "^8.0.0", "react-router-bootstrap": "^0.25.0", - "react-router-dom": "^5.1.2", + "react-router-dom": "^5.2.0", + "react-router-hash-link": "^2.1.0", "react-select": "^3.1.0", - "subscriptions-transport-ws": "^0.9.16", + "string.prototype.replaceall": "^1.0.3", + "subscriptions-transport-ws": "^0.9.18", "universal-cookie": "^4.0.3" }, "devDependencies": { - "@graphql-codegen/add": "^1.13.5", - "@graphql-codegen/cli": "^1.13.5", - "@graphql-codegen/time": "^1.13.5", - "@graphql-codegen/typescript": "^1.13.5", - "@graphql-codegen/typescript-compatibility": "^1.13.5", - "@graphql-codegen/typescript-operations": "^1.13.5", - "@graphql-codegen/typescript-react-apollo": "^1.13.5", + "@graphql-codegen/add": "^2.0.1", + "@graphql-codegen/cli": "^1.17.8", + "@graphql-codegen/time": "^2.0.1", + "@graphql-codegen/typescript": "^1.17.9", + "@graphql-codegen/typescript-operations": "^1.17.8", + "@graphql-codegen/typescript-react-apollo": "^2.0.6", "@types/classnames": "^2.2.10", - "@types/lodash": "^4.14.150", - "@types/node": "13.13.4", - "@types/react": "16.9.34", - "@types/react-dom": "^16.9.7", - "@types/react-images": "^0.5.1", + "@types/fslightbox-react": "^1.4.0", + "@types/lodash": "^4.14.161", + "@types/node": "14.6.4", + "@types/react": "16.9.43", + "@types/react-dom": "^16.9.8", + "@types/react-images": "^0.5.3", "@types/react-router-bootstrap": "^0.24.5", "@types/react-router-dom": "5.1.5", - "@types/react-select": "^3.0.12", + "@types/react-router-hash-link": "^1.2.1", + "@types/react-select": "3.0.19", "@typescript-eslint/eslint-plugin": "^2.30.0", "@typescript-eslint/parser": "^2.30.0", "eslint": "^6.8.0", @@ -95,13 +95,13 @@ "eslint-plugin-react": "^7.19.0", "eslint-plugin-react-hooks": "^4.0.0", "extract-react-intl-messages": "^4.1.1", - "node-sass": "4.14.0", + "node-sass": "4.14.1", "postcss-safe-parser": "^4.0.2", - "prettier": "2.0.5", - "react-scripts": "^3.4.1", + "prettier": "2.1.1", + "react-scripts": "^3.4.3", "stylelint": "^13.3.3", "stylelint-config-prettier": "^8.0.1", "stylelint-order": "^4.0.0", - "typescript": "^3.8.3" + "typescript": "^3.9.7" } } diff --git a/ui/v2.5/src/App.tsx b/ui/v2.5/src/App.tsx index 92299389b..29f2dda77 100755 --- a/ui/v2.5/src/App.tsx +++ b/ui/v2.5/src/App.tsx @@ -5,12 +5,15 @@ import { ToastProvider } from "src/hooks/Toast"; import { library } from "@fortawesome/fontawesome-svg-core"; import { fas } from "@fortawesome/free-solid-svg-icons"; import "@formatjs/intl-numberformat/polyfill"; -import "@formatjs/intl-numberformat/dist/locale-data/en"; -import "@formatjs/intl-numberformat/dist/locale-data/en-GB"; +import "@formatjs/intl-numberformat/locale-data/en"; +import "@formatjs/intl-numberformat/locale-data/en-GB"; +import replaceAll from "string.prototype.replaceall"; import locales from "src/locale"; import { useConfiguration } from "src/core/StashService"; import { flattenMessages } from "src/utils"; +import Mousetrap from "mousetrap"; +import MousetrapPause from "mousetrap-pause"; import { ErrorBoundary } from "./components/ErrorBoundary"; import Galleries from "./components/Galleries/Galleries"; import { MainNavbar } from "./components/MainNavbar"; @@ -23,6 +26,12 @@ import Studios from "./components/Studios/Studios"; import { SceneFilenameParser } from "./components/SceneFilenameParser/SceneFilenameParser"; import Movies from "./components/Movies/Movies"; import Tags from "./components/Tags/Tags"; +import Images from "./components/Images/Images"; + +MousetrapPause(Mousetrap); + +// Required for browsers older than August 2020ish. Can be removed at some point. +replaceAll.shim(); // Set fontawesome/free-solid-svg as default fontawesome icons library.add(fas); @@ -35,8 +44,8 @@ const intlFormats = { export const App: React.FC = () => { const config = useConfiguration(); - const language = config.data?.configuration?.interface?.language ?? "en-US"; - const messageLanguage = language.slice(0, 2); + const language = config.data?.configuration?.interface?.language ?? "en-GB"; + const messageLanguage = language.replace(/-/, ""); // eslint-disable-next-line @typescript-eslint/no-explicit-any const messages = flattenMessages((locales as any)[messageLanguage]); @@ -49,6 +58,7 @@ export const App: React.FC = () => { + diff --git a/ui/v2.5/src/components/Changelog/Changelog.tsx b/ui/v2.5/src/components/Changelog/Changelog.tsx index d35f01a09..fb955ccd1 100644 --- a/ui/v2.5/src/components/Changelog/Changelog.tsx +++ b/ui/v2.5/src/components/Changelog/Changelog.tsx @@ -1,7 +1,13 @@ import React from "react"; import { useChangelogStorage } from "src/hooks"; import Version from "./Version"; -import { V010, V011, V020, V021, V030 } from "./versions"; +import V010 from "./versions/v010.md"; +import V011 from "./versions/v011.md"; +import V020 from "./versions/v020.md"; +import V021 from "./versions/v021.md"; +import V030 from "./versions/v030.md"; +import V040 from "./versions/v040.md"; +import { MarkdownPage } from "../Shared/MarkdownPage"; const Changelog: React.FC = () => { const [{ data, loading }, setOpenState] = useChangelogStorage(); @@ -30,13 +36,21 @@ const Changelog: React.FC = () => { <>

Changelog:

- + + + + { openState={openState} setOpenState={setVersionOpenState} > - + { openState={openState} setOpenState={setVersionOpenState} > - + { openState={openState} setOpenState={setVersionOpenState} > - + { openState={openState} setOpenState={setVersionOpenState} > - + ); diff --git a/ui/v2.5/src/components/Changelog/versions/index.ts b/ui/v2.5/src/components/Changelog/versions/index.ts deleted file mode 100644 index 1d3060a63..000000000 --- a/ui/v2.5/src/components/Changelog/versions/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { default as V010 } from "./v010"; -export { default as V011 } from "./v011"; -export { default as V020 } from "./v020"; -export { default as V021 } from "./v021"; -export { default as V030 } from "./v030"; diff --git a/ui/v2.5/src/components/Changelog/versions/v010.tsx b/ui/v2.5/src/components/Changelog/versions/v010.md similarity index 91% rename from ui/v2.5/src/components/Changelog/versions/v010.tsx rename to ui/v2.5/src/components/Changelog/versions/v010.md index b440f5bd3..78134ab82 100644 --- a/ui/v2.5/src/components/Changelog/versions/v010.tsx +++ b/ui/v2.5/src/components/Changelog/versions/v010.md @@ -1,7 +1,3 @@ -import React from "react"; -import ReactMarkdown from "react-markdown"; - -const markup = ` ### ✨ New Features * Configurable custom performer scrapers @@ -50,6 +46,3 @@ const markup = ` * Fix input fields losing focus when switching between windows * Fix VTT for chapter display in scene players * Fix usage of Box.Bytes causing depreciation message -`; - -export default () => ; diff --git a/ui/v2.5/src/components/Changelog/versions/v011.md b/ui/v2.5/src/components/Changelog/versions/v011.md new file mode 100644 index 000000000..8e0e81edf --- /dev/null +++ b/ui/v2.5/src/components/Changelog/versions/v011.md @@ -0,0 +1,2 @@ +### 🐛 Bug fixes +* Fix version checking. diff --git a/ui/v2.5/src/components/Changelog/versions/v011.tsx b/ui/v2.5/src/components/Changelog/versions/v011.tsx deleted file mode 100644 index 06867d0cd..000000000 --- a/ui/v2.5/src/components/Changelog/versions/v011.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from "react"; -import ReactMarkdown from "react-markdown"; - -const markup = ` -### 🐛 Bug fixes -Fix version checking. -`; - -export default () => ; diff --git a/ui/v2.5/src/components/Changelog/versions/v020.tsx b/ui/v2.5/src/components/Changelog/versions/v020.md similarity index 95% rename from ui/v2.5/src/components/Changelog/versions/v020.tsx rename to ui/v2.5/src/components/Changelog/versions/v020.md index 6848d6827..edfef4f47 100644 --- a/ui/v2.5/src/components/Changelog/versions/v020.tsx +++ b/ui/v2.5/src/components/Changelog/versions/v020.md @@ -1,7 +1,3 @@ -import React from "react"; -import ReactMarkdown from "react-markdown"; - -const markup = ` #### 💥 **Note: After upgrading performance will be degraded until a full [scan](/settings?tab=tasks) has been completed.** #### 💥 **Note: [Language](/settings?tab=interface) has been set to \`English (United States)\` by default, which affects number and date formatting.** @@ -61,7 +57,3 @@ const markup = ` * Fix redirect loops in login, migrate and setup pages. * Make studio, movies, tag, performers scrape/parser matching case insensitive. * Fix files with special characters in filename not being scanned. - -`; - -export default () => ; diff --git a/ui/v2.5/src/components/Changelog/versions/v021.md b/ui/v2.5/src/components/Changelog/versions/v021.md new file mode 100644 index 000000000..7ffb68080 --- /dev/null +++ b/ui/v2.5/src/components/Changelog/versions/v021.md @@ -0,0 +1,3 @@ +### 🐛 Bug fixes +* Fix max loop duration not working. +* Fix URL sanitization on non-Chrome browsers. diff --git a/ui/v2.5/src/components/Changelog/versions/v021.tsx b/ui/v2.5/src/components/Changelog/versions/v021.tsx deleted file mode 100644 index 1886d1df5..000000000 --- a/ui/v2.5/src/components/Changelog/versions/v021.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from "react"; -import ReactMarkdown from "react-markdown"; - -const markup = ` -### 🐛 Bug fixes -* Fix max loop duration not working. -* Fix URL sanitization on non-Chrome browsers. - -`; - -export default () => ; diff --git a/ui/v2.5/src/components/Changelog/versions/v030.tsx b/ui/v2.5/src/components/Changelog/versions/v030.md similarity index 93% rename from ui/v2.5/src/components/Changelog/versions/v030.tsx rename to ui/v2.5/src/components/Changelog/versions/v030.md index fa445bf52..d81c00d18 100644 --- a/ui/v2.5/src/components/Changelog/versions/v030.tsx +++ b/ui/v2.5/src/components/Changelog/versions/v030.md @@ -1,7 +1,3 @@ -import React from "react"; -import ReactMarkdown from "react-markdown"; - -const markup = ` #### 💥 **Note: After upgrading, the next scan will populate all scenes with oshash hashes. MD5 calculation can be disabled after populating the oshash for all scenes. See \`Hashing Algorithms\` in the \`Configuration\` section of the manual for details. ** ### ✨ New Features @@ -49,7 +45,3 @@ const markup = ` * Fix directories with video name extensions being detected as files to be scanned. * Fix issues moving generated files between file systems. * Fix formatted dates using incorrect timezone. - -`; - -export default () => ; diff --git a/ui/v2.5/src/components/Changelog/versions/v040.md b/ui/v2.5/src/components/Changelog/versions/v040.md new file mode 100644 index 000000000..bc25aa9c6 --- /dev/null +++ b/ui/v2.5/src/components/Changelog/versions/v040.md @@ -0,0 +1,40 @@ +#### 💥 **Note: After upgrading, please [verify your stash library settings](/settings?tab=configuration) and perform a [scan](/settings?tab=tasks) to populate gallery images and the file modification times in the database. ** + +### ✨ New Features +* Add selective scan. +* Add selective export of all objects. +* Add stash-box tagger to scenes page. +* Add filters tab in scene page. +* Add selectable streaming quality profiles in the scene player. +* Add gallery metadata scraping. +* Add scrapers list setting page. +* Add support for individual images and manual creation of galleries. +* Add various fields to galleries. +* Add partial import from zip file. + +### 🎨 Improvements +* Add equals/not equals string criteria. +* Increase page size limit to 1000 and add new page size options. +* Add support for query URL parameter regex replacement when scraping by query URL. +* Include empty fields in isMissing filter +* Show static image on scene wall if preview video is missing. +* Add path filter to scene and gallery query. +* Add button to hide left panel on scene page. +* Add link to parent studio in studio page. +* Add missing scenes movie filter. +* Add gallery icon to scene cards. +* Add country query link to performer flag. +* Improved gallery layout. +* Add hover delay before scene preview is played. +* Re-show preview thumbnail when mousing away from scene card. + +### 🐛 Bug fixes +* Changed startup behaviour to only set libraries from `STASH_STASH` environment variable if not already set. +* Don't set default studio image during studio creation. +* Update Freeones scraper for website update. +* Fix invalid date tag preventing video file from being scanned. +* Fix error when creating movie from scene scrape dialog. +* Fix incorrect date timezone. +* Fix search filters not persisting for studios, markers and galleries. +* Fix pending thumbnail on wall items on mobile platforms. +* Fix downloading and permissions for ffmpeg/ffprobe. diff --git a/ui/v2.5/src/components/Galleries/DeleteGalleriesDialog.tsx b/ui/v2.5/src/components/Galleries/DeleteGalleriesDialog.tsx new file mode 100644 index 000000000..3f84649cc --- /dev/null +++ b/ui/v2.5/src/components/Galleries/DeleteGalleriesDialog.tsx @@ -0,0 +1,93 @@ +import React, { useState } from "react"; +import { Form } from "react-bootstrap"; +import { useGalleryDestroy } from "src/core/StashService"; +import * as GQL from "src/core/generated-graphql"; +import { Modal } from "src/components/Shared"; +import { useToast } from "src/hooks"; +import { FormattedMessage } from "react-intl"; + +interface IDeleteGalleryDialogProps { + selected: Partial[]; + onClose: (confirmed: boolean) => void; +} + +export const DeleteGalleriesDialog: React.FC = ( + props: IDeleteGalleryDialogProps +) => { + const plural = props.selected.length > 1; + + const singleMessageId = "deleteGalleryText"; + const pluralMessageId = "deleteGallerysText"; + + const singleMessage = + "Are you sure you want to delete this gallery? Galleries for zip files will be re-added during the next scan unless the zip file is also deleted."; + const pluralMessage = + "Are you sure you want to delete these galleries? Galleries for zip files will be re-added during the next scan unless the zip files are also deleted."; + + const header = plural ? "Delete Galleries" : "Delete Gallery"; + const toastMessage = plural ? "Deleted galleries" : "Deleted gallery"; + const messageId = plural ? pluralMessageId : singleMessageId; + const message = plural ? pluralMessage : singleMessage; + + const [deleteFile, setDeleteFile] = useState(false); + const [deleteGenerated, setDeleteGenerated] = useState(true); + + const Toast = useToast(); + const [deleteGallery] = useGalleryDestroy(getGalleriesDeleteInput()); + + // Network state + const [isDeleting, setIsDeleting] = useState(false); + + function getGalleriesDeleteInput(): GQL.GalleryDestroyInput { + return { + ids: props.selected.map((gallery) => gallery.id!), + delete_file: deleteFile, + delete_generated: deleteGenerated, + }; + } + + async function onDelete() { + setIsDeleting(true); + try { + await deleteGallery(); + Toast.success({ content: toastMessage }); + } catch (e) { + Toast.error(e); + } + setIsDeleting(false); + props.onClose(true); + } + + return ( + props.onClose(false), + text: "Cancel", + variant: "secondary", + }} + isRunning={isDeleting} + > +

+ +

+
+ setDeleteFile(!deleteFile)} + /> + setDeleteGenerated(!deleteGenerated)} + /> + +
+ ); +}; diff --git a/ui/v2.5/src/components/Galleries/EditGalleriesDialog.tsx b/ui/v2.5/src/components/Galleries/EditGalleriesDialog.tsx new file mode 100644 index 000000000..af3ea3365 --- /dev/null +++ b/ui/v2.5/src/components/Galleries/EditGalleriesDialog.tsx @@ -0,0 +1,368 @@ +import React, { useEffect, useState } from "react"; +import { Form, Col, Row } from "react-bootstrap"; +import _ from "lodash"; +import { useBulkGalleryUpdate } from "src/core/StashService"; +import * as GQL from "src/core/generated-graphql"; +import { StudioSelect, Modal } from "src/components/Shared"; +import { useToast } from "src/hooks"; +import { FormUtils } from "src/utils"; +import MultiSet from "../Shared/MultiSet"; +import { RatingStars } from "../Scenes/SceneDetails/RatingStars"; + +interface IListOperationProps { + selected: GQL.GallerySlimDataFragment[]; + onClose: (applied: boolean) => void; +} + +export const EditGalleriesDialog: React.FC = ( + props: IListOperationProps +) => { + const Toast = useToast(); + const [rating, setRating] = useState(); + const [studioId, setStudioId] = useState(); + const [performerMode, setPerformerMode] = React.useState< + GQL.BulkUpdateIdMode + >(GQL.BulkUpdateIdMode.Add); + const [performerIds, setPerformerIds] = useState(); + const [tagMode, setTagMode] = React.useState( + GQL.BulkUpdateIdMode.Add + ); + const [tagIds, setTagIds] = useState(); + + const [updateGalleries] = useBulkGalleryUpdate(getGalleryInput()); + + // Network state + const [isUpdating, setIsUpdating] = useState(false); + + function makeBulkUpdateIds( + ids: string[], + mode: GQL.BulkUpdateIdMode + ): GQL.BulkUpdateIds { + return { + mode, + ids, + }; + } + + function getGalleryInput(): GQL.BulkGalleryUpdateInput { + // need to determine what we are actually setting on each gallery + const aggregateRating = getRating(props.selected); + const aggregateStudioId = getStudioId(props.selected); + const aggregatePerformerIds = getPerformerIds(props.selected); + const aggregateTagIds = getTagIds(props.selected); + + const galleryInput: GQL.BulkGalleryUpdateInput = { + ids: props.selected.map((gallery) => { + return gallery.id; + }), + }; + + // if rating is undefined + if (rating === undefined) { + // and all galleries have the same rating, then we are unsetting the rating. + if (aggregateRating) { + // an undefined rating is ignored in the server, so set it to 0 instead + galleryInput.rating = 0; + } + // otherwise not setting the rating + } else { + // if rating is set, then we are setting the rating for all + galleryInput.rating = rating; + } + + // if studioId is undefined + if (studioId === undefined) { + // and all galleries have the same studioId, + // then unset the studioId, otherwise ignoring studioId + if (aggregateStudioId) { + // an undefined studio_id is ignored in the server, so set it to empty string instead + galleryInput.studio_id = ""; + } + } else { + // if studioId is set, then we are setting it + galleryInput.studio_id = studioId; + } + + // if performerIds are empty + if ( + performerMode === GQL.BulkUpdateIdMode.Set && + (!performerIds || performerIds.length === 0) + ) { + // and all galleries have the same ids, + if (aggregatePerformerIds.length > 0) { + // then unset the performerIds, otherwise ignore + galleryInput.performer_ids = makeBulkUpdateIds( + performerIds || [], + performerMode + ); + } + } else { + // if performerIds non-empty, then we are setting them + galleryInput.performer_ids = makeBulkUpdateIds( + performerIds || [], + performerMode + ); + } + + // if tagIds non-empty, then we are setting them + if ( + tagMode === GQL.BulkUpdateIdMode.Set && + (!tagIds || tagIds.length === 0) + ) { + // and all galleries have the same ids, + if (aggregateTagIds.length > 0) { + // then unset the tagIds, otherwise ignore + galleryInput.tag_ids = makeBulkUpdateIds(tagIds || [], tagMode); + } + } else { + // if tagIds non-empty, then we are setting them + galleryInput.tag_ids = makeBulkUpdateIds(tagIds || [], tagMode); + } + + return galleryInput; + } + + async function onSave() { + setIsUpdating(true); + try { + await updateGalleries(); + Toast.success({ content: "Updated galleries" }); + props.onClose(true); + } catch (e) { + Toast.error(e); + } + setIsUpdating(false); + } + + function getRating(state: GQL.GallerySlimDataFragment[]) { + let ret: number | undefined; + let first = true; + + state.forEach((gallery) => { + if (first) { + ret = gallery.rating ?? undefined; + first = false; + } else if (ret !== gallery.rating) { + ret = undefined; + } + }); + + return ret; + } + + function getStudioId(state: GQL.GallerySlimDataFragment[]) { + let ret: string | undefined; + let first = true; + + state.forEach((gallery) => { + if (first) { + ret = gallery?.studio?.id; + first = false; + } else { + const studio = gallery?.studio?.id; + if (ret !== studio) { + ret = undefined; + } + } + }); + + return ret; + } + + function getPerformerIds(state: GQL.GallerySlimDataFragment[]) { + let ret: string[] = []; + let first = true; + + state.forEach((gallery) => { + if (first) { + ret = gallery.performers + ? gallery.performers.map((p) => p.id).sort() + : []; + first = false; + } else { + const perfIds = gallery.performers + ? gallery.performers.map((p) => p.id).sort() + : []; + + if (!_.isEqual(ret, perfIds)) { + ret = []; + } + } + }); + + return ret; + } + + function getTagIds(state: GQL.GallerySlimDataFragment[]) { + let ret: string[] = []; + let first = true; + + state.forEach((gallery) => { + if (first) { + ret = gallery.tags ? gallery.tags.map((t) => t.id).sort() : []; + first = false; + } else { + const tIds = gallery.tags ? gallery.tags.map((t) => t.id).sort() : []; + + if (!_.isEqual(ret, tIds)) { + ret = []; + } + } + }); + + return ret; + } + + useEffect(() => { + const state = props.selected; + let updateRating: number | undefined; + let updateStudioID: string | undefined; + let updatePerformerIds: string[] = []; + let updateTagIds: string[] = []; + let first = true; + + state.forEach((gallery: GQL.GallerySlimDataFragment) => { + const galleryRating = gallery.rating; + const GalleriestudioID = gallery?.studio?.id; + const galleryPerformerIDs = (gallery.performers ?? []) + .map((p) => p.id) + .sort(); + const galleryTagIDs = (gallery.tags ?? []).map((p) => p.id).sort(); + + if (first) { + updateRating = galleryRating ?? undefined; + updateStudioID = GalleriestudioID; + updatePerformerIds = galleryPerformerIDs; + updateTagIds = galleryTagIDs; + first = false; + } else { + if (galleryRating !== updateRating) { + updateRating = undefined; + } + if (GalleriestudioID !== updateStudioID) { + updateStudioID = undefined; + } + if (!_.isEqual(galleryPerformerIDs, updatePerformerIds)) { + updatePerformerIds = []; + } + if (!_.isEqual(galleryTagIDs, updateTagIds)) { + updateTagIds = []; + } + } + }); + + setRating(updateRating); + setStudioId(updateStudioID); + if (performerMode === GQL.BulkUpdateIdMode.Set) { + setPerformerIds(updatePerformerIds); + } + + if (tagMode === GQL.BulkUpdateIdMode.Set) { + setTagIds(updateTagIds); + } + }, [props.selected, performerMode, tagMode]); + + function renderMultiSelect( + type: "performers" | "tags", + ids: string[] | undefined + ) { + let mode = GQL.BulkUpdateIdMode.Add; + switch (type) { + case "performers": + mode = performerMode; + break; + case "tags": + mode = tagMode; + break; + } + + return ( + { + const itemIDs = items.map((i) => i.id); + switch (type) { + case "performers": + setPerformerIds(itemIDs); + break; + case "tags": + setTagIds(itemIDs); + break; + } + }} + onSetMode={(newMode) => { + switch (type) { + case "performers": + setPerformerMode(newMode); + break; + case "tags": + setTagMode(newMode); + break; + } + }} + ids={ids ?? []} + mode={mode} + /> + ); + } + + function render() { + return ( + props.onClose(false), + text: "Cancel", + variant: "secondary", + }} + isRunning={isUpdating} + > +
+ + {FormUtils.renderLabel({ + title: "Rating", + })} + + setRating(value)} + disabled={isUpdating} + /> + + + + + {FormUtils.renderLabel({ + title: "Studio", + })} + + + setStudioId(items.length > 0 ? items[0]?.id : undefined) + } + ids={studioId ? [studioId] : []} + isDisabled={isUpdating} + /> + + + + + Performers + {renderMultiSelect("performers", performerIds)} + + + + Tags + {renderMultiSelect("tags", tagIds)} + +
+
+ ); + } + + return render(); +}; diff --git a/ui/v2.5/src/components/Galleries/Galleries.tsx b/ui/v2.5/src/components/Galleries/Galleries.tsx index aad72ddd5..8fa2c8bde 100644 --- a/ui/v2.5/src/components/Galleries/Galleries.tsx +++ b/ui/v2.5/src/components/Galleries/Galleries.tsx @@ -1,12 +1,16 @@ import React from "react"; import { Route, Switch } from "react-router-dom"; -import { Gallery } from "./Gallery"; +import { Gallery } from "./GalleryDetails/Gallery"; import { GalleryList } from "./GalleryList"; const Galleries = () => ( - - + } + /> + ); diff --git a/ui/v2.5/src/components/Galleries/Gallery.tsx b/ui/v2.5/src/components/Galleries/Gallery.tsx deleted file mode 100644 index f58a591ae..000000000 --- a/ui/v2.5/src/components/Galleries/Gallery.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React from "react"; -import { useParams } from "react-router-dom"; -import { useFindGallery } from "src/core/StashService"; -import { LoadingIndicator } from "src/components/Shared"; -import { GalleryViewer } from "./GalleryViewer"; - -export const Gallery: React.FC = () => { - const { id = "" } = useParams(); - - const { data, error, loading } = useFindGallery(id); - const gallery = data?.findGallery; - - if (loading || !gallery) return ; - if (error) return
{error.message}
; - - return ( -
- -
- ); -}; diff --git a/ui/v2.5/src/components/Galleries/GalleryCard.tsx b/ui/v2.5/src/components/Galleries/GalleryCard.tsx index cf72fe4c6..c73f5f9fb 100644 --- a/ui/v2.5/src/components/Galleries/GalleryCard.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryCard.tsx @@ -1,26 +1,35 @@ -import { Card, Button, ButtonGroup } from "react-bootstrap"; +import { Button, ButtonGroup } from "react-bootstrap"; import React from "react"; import { Link } from "react-router-dom"; import * as GQL from "src/core/generated-graphql"; import { FormattedPlural } from "react-intl"; +import { useConfiguration } from "src/core/StashService"; import { HoverPopover, Icon, TagLink } from "../Shared"; +import { BasicCard } from "../Shared/BasicCard"; interface IProps { - gallery: GQL.GalleryDataFragment; + gallery: GQL.GallerySlimDataFragment; + selecting?: boolean; + selected: boolean | undefined; zoomIndex: number; + onSelectedChanged: (selected: boolean, shiftKey: boolean) => void; } -export const GalleryCard: React.FC = ({ gallery, zoomIndex }) => { +export const GalleryCard: React.FC = (props) => { + const config = useConfiguration(); + const showStudioAsText = + config?.data?.configuration.interface.showStudioAsText ?? false; + function maybeRenderScenePopoverButton() { - if (!gallery.scene) return; + if (!props.gallery.scene) return; const popoverContent = ( - + ); return ( - + @@ -29,12 +38,84 @@ export const GalleryCard: React.FC = ({ gallery, zoomIndex }) => { ); } + function maybeRenderTagPopoverButton() { + if (props.gallery.tags.length <= 0) return; + + const popoverContent = props.gallery.tags.map((tag) => ( + + )); + + return ( + + + + ); + } + + function maybeRenderPerformerPopoverButton() { + if (props.gallery.performers.length <= 0) return; + + const popoverContent = props.gallery.performers.map((performer) => ( +
+ + {performer.name + + +
+ )); + + return ( + + + + ); + } + + function maybeRenderSceneStudioOverlay() { + if (!props.gallery.studio) return; + + return ( +
+ + {showStudioAsText ? ( + props.gallery.studio.name + ) : ( + {props.gallery.studio.name} + )} + +
+ ); + } + function maybeRenderPopoverButtonGroup() { - if (gallery.scene) { + if ( + props.gallery.scene || + props.gallery.performers.length > 0 || + props.gallery.tags.length > 0 + ) { return ( <>
+ {maybeRenderTagPopoverButton()} + {maybeRenderPerformerPopoverButton()} {maybeRenderScenePopoverButton()} @@ -42,30 +123,61 @@ export const GalleryCard: React.FC = ({ gallery, zoomIndex }) => { } } - return ( - - - {gallery.files.length > 0 ? ( - {gallery.path} - ) : undefined} - -
-
{gallery.path}
- - {gallery.files.length}  - - . - + function maybeRenderRatingBanner() { + if (!props.gallery.rating) { + return; + } + return ( +
+ RATING: {props.gallery.rating}
- {maybeRenderPopoverButtonGroup()} - + ); + } + + return ( + + {props.gallery.cover ? ( + {props.gallery.title + ) : undefined} + {maybeRenderRatingBanner()} + + } + overlays={maybeRenderSceneStudioOverlay()} + details={ + <> + +
+ {props.gallery.title ?? props.gallery.path} +
+ + + {props.gallery.image_count}  + + . + + + } + popovers={maybeRenderPopoverButtonGroup()} + selected={props.selected} + selecting={props.selecting} + onSelectedChanged={props.onSelectedChanged} + /> ); }; diff --git a/ui/v2.5/src/components/Galleries/GalleryDetails/Gallery.tsx b/ui/v2.5/src/components/Galleries/GalleryDetails/Gallery.tsx new file mode 100644 index 000000000..9b3db9a12 --- /dev/null +++ b/ui/v2.5/src/components/Galleries/GalleryDetails/Gallery.tsx @@ -0,0 +1,231 @@ +import { Tab, Nav, Dropdown } from "react-bootstrap"; +import React, { useEffect, useState } from "react"; +import { useParams, useHistory, Link } from "react-router-dom"; +import { useFindGallery } from "src/core/StashService"; +import { ErrorMessage, LoadingIndicator, Icon } from "src/components/Shared"; +import { TextUtils } from "src/utils"; +import * as Mousetrap from "mousetrap"; +import { GalleryEditPanel } from "./GalleryEditPanel"; +import { GalleryDetailPanel } from "./GalleryDetailPanel"; +import { DeleteGalleriesDialog } from "../DeleteGalleriesDialog"; +import { GalleryImagesPanel } from "./GalleryImagesPanel"; +import { GalleryAddPanel } from "./GalleryAddPanel"; +import { GalleryFileInfoPanel } from "./GalleryFileInfoPanel"; + +interface IGalleryParams { + id?: string; + tab?: string; +} + +export const Gallery: React.FC = () => { + const { tab = "images", id = "new" } = useParams(); + const history = useHistory(); + const isNew = id === "new"; + + const { data, error, loading } = useFindGallery(id); + const gallery = data?.findGallery; + + const [activeTabKey, setActiveTabKey] = useState("gallery-details-panel"); + const activeRightTabKey = tab === "images" || tab === "add" ? tab : "images"; + const setActiveRightTabKey = (newTab: string | null) => { + if (tab !== newTab) { + const tabParam = newTab === "images" ? "" : `/${newTab}`; + history.replace(`/galleries/${id}${tabParam}`); + } + }; + + const [isDeleteAlertOpen, setIsDeleteAlertOpen] = useState(false); + + function onDeleteDialogClosed(deleted: boolean) { + setIsDeleteAlertOpen(false); + if (deleted) { + history.push("/galleries"); + } + } + + function maybeRenderDeleteDialog() { + if (isDeleteAlertOpen && gallery) { + return ( + + ); + } + } + + function renderOperations() { + return ( + + + + + + setIsDeleteAlertOpen(true)} + > + Delete Gallery + + + + ); + } + + function renderTabs() { + if (!gallery) { + return; + } + + return ( + k && setActiveTabKey(k)} + > +
+ +
+ + + + + + + + + + setIsDeleteAlertOpen(true)} + /> + + +
+ ); + } + + function renderRightTabs() { + if (!gallery) { + return; + } + + return ( + k && setActiveRightTabKey(k)} + > +
+ +
+ + + + {/* */} + + + + + + +
+ ); + } + + // set up hotkeys + useEffect(() => { + Mousetrap.bind("a", () => setActiveTabKey("gallery-details-panel")); + Mousetrap.bind("e", () => setActiveTabKey("gallery-edit-panel")); + Mousetrap.bind("f", () => setActiveTabKey("gallery-file-info-panel")); + + return () => { + Mousetrap.unbind("a"); + Mousetrap.unbind("e"); + Mousetrap.unbind("f"); + }; + }); + + if (loading) { + return ; + } + + if (error) return ; + + if (isNew) + return ( +
+
+

Create Gallery

+ setIsDeleteAlertOpen(true)} + /> +
+
+ ); + + if (!gallery) + return ; + + return ( +
+ {maybeRenderDeleteDialog()} +
+
+ {gallery.studio && ( +

+ + {`${gallery.studio.name} + +

+ )} +

+ {gallery.title ?? TextUtils.fileNameFromPath(gallery.path ?? "")} +

+
+ {renderTabs()} +
+
{renderRightTabs()}
+
+ ); +}; diff --git a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryAddPanel.tsx b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryAddPanel.tsx new file mode 100644 index 000000000..f0e9e3adf --- /dev/null +++ b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryAddPanel.tsx @@ -0,0 +1,86 @@ +import React from "react"; +import * as GQL from "src/core/generated-graphql"; +import { GalleriesCriterion } from "src/models/list-filter/criteria/galleries"; +import { ListFilterModel } from "src/models/list-filter/filter"; +import { ImageList } from "src/components/Images/ImageList"; +import { showWhenSelected } from "src/hooks/ListHook"; +import { mutateAddGalleryImages } from "src/core/StashService"; +import { useToast } from "src/hooks"; + +interface IGalleryAddProps { + gallery: Partial; +} + +export const GalleryAddPanel: React.FC = ({ gallery }) => { + const Toast = useToast(); + + function filterHook(filter: ListFilterModel) { + const galleryValue = { + id: gallery.id!, + label: gallery.title ?? gallery.path ?? "", + }; + // if galleries is already present, then we modify it, otherwise add + let galleryCriterion = filter.criteria.find((c) => { + return c.type === "galleries"; + }) as GalleriesCriterion; + + if ( + galleryCriterion && + galleryCriterion.modifier === GQL.CriterionModifier.Excludes + ) { + // add the gallery if not present + if ( + !galleryCriterion.value.find((p) => { + return p.id === gallery.id; + }) + ) { + galleryCriterion.value.push(galleryValue); + } + + galleryCriterion.modifier = GQL.CriterionModifier.Excludes; + } else { + // overwrite + galleryCriterion = new GalleriesCriterion(); + galleryCriterion.modifier = GQL.CriterionModifier.Excludes; + galleryCriterion.value = [galleryValue]; + filter.criteria.push(galleryCriterion); + } + + return filter; + } + + async function addImages( + result: GQL.FindImagesQueryResult, + filter: ListFilterModel, + selectedIds: Set + ) { + try { + await mutateAddGalleryImages({ + gallery_id: gallery.id!, + image_ids: Array.from(selectedIds.values()), + }); + Toast.success({ + content: "Added images", + }); + } catch (e) { + Toast.error(e); + } + } + + const otherOperations = [ + { + text: "Add to Gallery", + onClick: addImages, + isDisplayed: showWhenSelected, + postRefetch: true, + }, + ]; + + return ( + + ); +}; diff --git a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryDetailPanel.tsx b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryDetailPanel.tsx new file mode 100644 index 000000000..a8901bb36 --- /dev/null +++ b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryDetailPanel.tsx @@ -0,0 +1,110 @@ +import React from "react"; +import { Link } from "react-router-dom"; +import { FormattedDate } from "react-intl"; +import * as GQL from "src/core/generated-graphql"; +import { TextUtils } from "src/utils"; +import { TagLink } from "src/components/Shared"; +import { PerformerCard } from "src/components/Performers/PerformerCard"; +import { RatingStars } from "src/components/Scenes/SceneDetails/RatingStars"; + +interface IGalleryDetailProps { + gallery: Partial; +} + +export const GalleryDetailPanel: React.FC = (props) => { + function renderDetails() { + if (!props.gallery.details || props.gallery.details === "") return; + return ( + <> +
Details
+

{props.gallery.details}

+ + ); + } + + function renderTags() { + if (!props.gallery.tags || props.gallery.tags.length === 0) return; + const tags = props.gallery.tags.map((tag) => ( + + )); + return ( + <> +
Tags
+ {tags} + + ); + } + + function renderPerformers() { + if (!props.gallery.performers || props.gallery.performers.length === 0) + return; + const cards = props.gallery.performers.map((performer) => ( + + )); + + return ( + <> +
Performers
+
+ {cards} +
+ + ); + } + + // filename should use entire row if there is no studio + const galleryDetailsWidth = props.gallery.studio ? "col-9" : "col-12"; + + return ( + <> +
+
+
+

+ {props.gallery.title ?? + TextUtils.fileNameFromPath(props.gallery.path ?? "")} +

+
+ {props.gallery.date ? ( +
+ +
+ ) : undefined} + {props.gallery.rating ? ( +
+ Rating: +
+ ) : ( + "" + )} +
+ {props.gallery.studio && ( +
+ + {`${props.gallery.studio.name} + +
+ )} +
+
+
+ {renderDetails()} + {renderTags()} + {renderPerformers()} +
+
+ + ); +}; diff --git a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryEditPanel.tsx b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryEditPanel.tsx new file mode 100644 index 000000000..77ed30225 --- /dev/null +++ b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryEditPanel.tsx @@ -0,0 +1,405 @@ +import React, { useEffect, useState } from "react"; +import { useHistory } from "react-router-dom"; +import { Button, Form, Col, Row } from "react-bootstrap"; +import * as GQL from "src/core/generated-graphql"; +import { + queryScrapeGalleryURL, + useGalleryCreate, + useGalleryUpdate, + useListGalleryScrapers, +} from "src/core/StashService"; +import { + PerformerSelect, + TagSelect, + StudioSelect, + Icon, + LoadingIndicator, +} from "src/components/Shared"; +import { useToast } from "src/hooks"; +import { FormUtils, EditableTextUtils } from "src/utils"; +import { RatingStars } from "src/components/Scenes/SceneDetails/RatingStars"; +import { GalleryScrapeDialog } from "./GalleryScrapeDialog"; + +interface IProps { + isVisible: boolean; + onDelete: () => void; +} + +interface INewProps { + isNew: true; + gallery: undefined; +} + +interface IExistingProps { + isNew: false; + gallery: GQL.GalleryDataFragment; +} + +export const GalleryEditPanel: React.FC< + IProps & (INewProps | IExistingProps) +> = (props) => { + const Toast = useToast(); + const history = useHistory(); + const [title, setTitle] = useState(); + const [details, setDetails] = useState(); + const [url, setUrl] = useState(); + const [date, setDate] = useState(); + const [rating, setRating] = useState(); + const [studioId, setStudioId] = useState(); + const [performerIds, setPerformerIds] = useState(); + const [tagIds, setTagIds] = useState(); + + const Scrapers = useListGalleryScrapers(); + + const [ + scrapedGallery, + setScrapedGallery, + ] = useState(); + + // Network state + const [isLoading, setIsLoading] = useState(true); + + const [createGallery] = useGalleryCreate( + getGalleryInput() as GQL.GalleryCreateInput + ); + const [updateGallery] = useGalleryUpdate( + getGalleryInput() as GQL.GalleryUpdateInput + ); + + useEffect(() => { + if (props.isVisible) { + Mousetrap.bind("s s", () => { + onSave(); + }); + Mousetrap.bind("d d", () => { + props.onDelete(); + }); + + // numeric keypresses get caught by jwplayer, so blur the element + // if the rating sequence is started + Mousetrap.bind("r", () => { + if (document.activeElement instanceof HTMLElement) { + document.activeElement.blur(); + } + + Mousetrap.bind("0", () => setRating(NaN)); + Mousetrap.bind("1", () => setRating(1)); + Mousetrap.bind("2", () => setRating(2)); + Mousetrap.bind("3", () => setRating(3)); + Mousetrap.bind("4", () => setRating(4)); + Mousetrap.bind("5", () => setRating(5)); + + setTimeout(() => { + Mousetrap.unbind("0"); + Mousetrap.unbind("1"); + Mousetrap.unbind("2"); + Mousetrap.unbind("3"); + Mousetrap.unbind("4"); + Mousetrap.unbind("5"); + }, 1000); + }); + + return () => { + Mousetrap.unbind("s s"); + Mousetrap.unbind("d d"); + + Mousetrap.unbind("r"); + }; + } + }); + + function updateGalleryEditState(state?: GQL.GalleryDataFragment) { + const perfIds = state?.performers?.map((performer) => performer.id); + const tIds = state?.tags ? state?.tags.map((tag) => tag.id) : undefined; + + setTitle(state?.title ?? undefined); + setDetails(state?.details ?? undefined); + setUrl(state?.url ?? undefined); + setDate(state?.date ?? undefined); + setRating(state?.rating === null ? NaN : state?.rating); + setStudioId(state?.studio?.id ?? undefined); + setPerformerIds(perfIds); + setTagIds(tIds); + } + + useEffect(() => { + updateGalleryEditState(props.gallery); + setIsLoading(false); + }, [props.gallery]); + + function getGalleryInput() { + return { + id: props.isNew ? undefined : props.gallery.id, + title, + details, + url, + date, + rating, + studio_id: studioId, + performer_ids: performerIds, + tag_ids: tagIds, + }; + } + + async function onSave() { + setIsLoading(true); + try { + if (props.isNew) { + const result = await createGallery(); + if (result.data?.galleryCreate) { + history.push(`/galleries/${result.data.galleryCreate.id}`); + Toast.success({ content: "Created gallery" }); + } + } else { + const result = await updateGallery(); + if (result.data?.galleryUpdate) { + Toast.success({ content: "Updated gallery" }); + } + } + } catch (e) { + Toast.error(e); + } + setIsLoading(false); + } + + function onScrapeDialogClosed(gallery?: GQL.ScrapedGalleryDataFragment) { + if (gallery) { + updateGalleryFromScrapedGallery(gallery); + } + setScrapedGallery(undefined); + } + + function maybeRenderScrapeDialog() { + if (!scrapedGallery) { + return; + } + + const currentGallery = getGalleryInput(); + + return ( + { + onScrapeDialogClosed(gallery); + }} + /> + ); + } + + function urlScrapable(scrapedUrl: string): boolean { + return (Scrapers?.data?.listGalleryScrapers ?? []).some((s) => + (s?.gallery?.urls ?? []).some((u) => scrapedUrl.includes(u)) + ); + } + + function updateGalleryFromScrapedGallery( + gallery: GQL.ScrapedGalleryDataFragment + ) { + if (gallery.title) { + setTitle(gallery.title); + } + + if (gallery.details) { + setDetails(gallery.details); + } + + if (gallery.date) { + setDate(gallery.date); + } + + if (gallery.url) { + setUrl(gallery.url); + } + + if (gallery.studio && gallery.studio.stored_id) { + setStudioId(gallery.studio.stored_id); + } + + if (gallery.performers && gallery.performers.length > 0) { + const idPerfs = gallery.performers.filter((p) => { + return p.stored_id !== undefined && p.stored_id !== null; + }); + + if (idPerfs.length > 0) { + const newIds = idPerfs.map((p) => p.stored_id); + setPerformerIds(newIds as string[]); + } + } + + if (gallery?.tags?.length) { + const idTags = gallery.tags.filter((p) => { + return p.stored_id !== undefined && p.stored_id !== null; + }); + + if (idTags.length > 0) { + const newIds = idTags.map((p) => p.stored_id); + setTagIds(newIds as string[]); + } + } + } + + async function onScrapeGalleryURL() { + if (!url) { + return; + } + setIsLoading(true); + try { + const result = await queryScrapeGalleryURL(url); + if (!result || !result.data || !result.data.scrapeGalleryURL) { + return; + } + setScrapedGallery(result.data.scrapeGalleryURL); + } catch (e) { + Toast.error(e); + } finally { + setIsLoading(false); + } + } + + function maybeRenderScrapeButton() { + if (!url || !urlScrapable(url)) { + return undefined; + } + return ( + + ); + } + + if (isLoading) return ; + + return ( + + ); +}; diff --git a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryFileInfoPanel.tsx b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryFileInfoPanel.tsx new file mode 100644 index 000000000..9bb00df2c --- /dev/null +++ b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryFileInfoPanel.tsx @@ -0,0 +1,40 @@ +import React from "react"; +import * as GQL from "src/core/generated-graphql"; + +interface IGalleryFileInfoPanelProps { + gallery: GQL.GalleryDataFragment; +} + +export const GalleryFileInfoPanel: React.FC = ( + props: IGalleryFileInfoPanelProps +) => { + function renderChecksum() { + return ( +
+ Checksum + {props.gallery.checksum} +
+ ); + } + + function renderPath() { + const { + gallery: { path }, + } = props; + return ( +
+ Path + + {`file://${props.gallery.path}`}{" "} + +
+ ); + } + + return ( +
+ {renderChecksum()} + {renderPath()} +
+ ); +}; diff --git a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryImagesPanel.tsx b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryImagesPanel.tsx new file mode 100644 index 000000000..d536eba83 --- /dev/null +++ b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryImagesPanel.tsx @@ -0,0 +1,88 @@ +import React from "react"; +import * as GQL from "src/core/generated-graphql"; +import { GalleriesCriterion } from "src/models/list-filter/criteria/galleries"; +import { ListFilterModel } from "src/models/list-filter/filter"; +import { ImageList } from "src/components/Images/ImageList"; +import { mutateRemoveGalleryImages } from "src/core/StashService"; +import { showWhenSelected } from "src/hooks/ListHook"; +import { useToast } from "src/hooks"; + +interface IGalleryDetailsProps { + gallery: GQL.GalleryDataFragment; +} + +export const GalleryImagesPanel: React.FC = ({ + gallery, +}) => { + const Toast = useToast(); + + function filterHook(filter: ListFilterModel) { + const galleryValue = { + id: gallery.id!, + label: gallery.title ?? gallery.path ?? "", + }; + // if galleries is already present, then we modify it, otherwise add + let galleryCriterion = filter.criteria.find((c) => { + return c.type === "galleries"; + }) as GalleriesCriterion; + + if ( + galleryCriterion && + (galleryCriterion.modifier === GQL.CriterionModifier.IncludesAll || + galleryCriterion.modifier === GQL.CriterionModifier.Includes) + ) { + // add the gallery if not present + if ( + !galleryCriterion.value.find((p) => { + return p.id === gallery.id; + }) + ) { + galleryCriterion.value.push(galleryValue); + } + + galleryCriterion.modifier = GQL.CriterionModifier.IncludesAll; + } else { + // overwrite + galleryCriterion = new GalleriesCriterion(); + galleryCriterion.value = [galleryValue]; + filter.criteria.push(galleryCriterion); + } + + return filter; + } + + async function removeImages( + result: GQL.FindImagesQueryResult, + filter: ListFilterModel, + selectedIds: Set + ) { + try { + await mutateRemoveGalleryImages({ + gallery_id: gallery.id!, + image_ids: Array.from(selectedIds.values()), + }); + Toast.success({ + content: "Added images", + }); + } catch (e) { + Toast.error(e); + } + } + + const otherOperations = [ + { + text: "Remove from Gallery", + onClick: removeImages, + isDisplayed: showWhenSelected, + postRefetch: true, + }, + ]; + + return ( + + ); +}; diff --git a/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryScrapeDialog.tsx b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryScrapeDialog.tsx new file mode 100644 index 000000000..9a71b33ad --- /dev/null +++ b/ui/v2.5/src/components/Galleries/GalleryDetails/GalleryScrapeDialog.tsx @@ -0,0 +1,451 @@ +import React, { useState } from "react"; +import { StudioSelect, PerformerSelect } from "src/components/Shared"; +import * as GQL from "src/core/generated-graphql"; +import { TagSelect } from "src/components/Shared/Select"; +import { + ScrapeDialog, + ScrapeDialogRow, + ScrapeResult, + ScrapedInputGroupRow, + ScrapedTextAreaRow, +} from "src/components/Shared/ScrapeDialog"; +import _ from "lodash"; +import { + useStudioCreate, + usePerformerCreate, + useTagCreate, +} from "src/core/StashService"; +import { useToast } from "src/hooks"; + +function renderScrapedStudio( + result: ScrapeResult, + isNew?: boolean, + onChange?: (value: string) => void +) { + const resultValue = isNew ? result.newValue : result.originalValue; + const value = resultValue ? [resultValue] : []; + + return ( + { + if (onChange) { + onChange(items[0]?.id); + } + }} + ids={value} + /> + ); +} + +function renderScrapedStudioRow( + result: ScrapeResult, + onChange: (value: ScrapeResult) => void, + newStudio?: GQL.ScrapedSceneStudio, + onCreateNew?: (value: GQL.ScrapedSceneStudio) => void +) { + return ( + renderScrapedStudio(result)} + renderNewField={() => + renderScrapedStudio(result, true, (value) => + onChange(result.cloneWithValue(value)) + ) + } + onChange={onChange} + newValues={newStudio ? [newStudio] : undefined} + onCreateNew={onCreateNew} + /> + ); +} + +function renderScrapedPerformers( + result: ScrapeResult, + isNew?: boolean, + onChange?: (value: string[]) => void +) { + const resultValue = isNew ? result.newValue : result.originalValue; + const value = resultValue ?? []; + + return ( + { + if (onChange) { + onChange(items.map((i) => i.id)); + } + }} + ids={value} + /> + ); +} + +function renderScrapedPerformersRow( + result: ScrapeResult, + onChange: (value: ScrapeResult) => void, + newPerformers: GQL.ScrapedScenePerformer[], + onCreateNew?: (value: GQL.ScrapedScenePerformer) => void +) { + return ( + renderScrapedPerformers(result)} + renderNewField={() => + renderScrapedPerformers(result, true, (value) => + onChange(result.cloneWithValue(value)) + ) + } + onChange={onChange} + newValues={newPerformers} + onCreateNew={onCreateNew} + /> + ); +} + +function renderScrapedTags( + result: ScrapeResult, + isNew?: boolean, + onChange?: (value: string[]) => void +) { + const resultValue = isNew ? result.newValue : result.originalValue; + const value = resultValue ?? []; + + return ( + { + if (onChange) { + onChange(items.map((i) => i.id)); + } + }} + ids={value} + /> + ); +} + +function renderScrapedTagsRow( + result: ScrapeResult, + onChange: (value: ScrapeResult) => void, + newTags: GQL.ScrapedSceneTag[], + onCreateNew?: (value: GQL.ScrapedSceneTag) => void +) { + return ( + renderScrapedTags(result)} + renderNewField={() => + renderScrapedTags(result, true, (value) => + onChange(result.cloneWithValue(value)) + ) + } + newValues={newTags} + onChange={onChange} + onCreateNew={onCreateNew} + /> + ); +} + +interface IGalleryScrapeDialogProps { + gallery: Partial; + scraped: GQL.ScrapedGallery; + + onClose: (scrapedGallery?: GQL.ScrapedGallery) => void; +} + +interface IHasStoredID { + stored_id?: string | null; +} + +export const GalleryScrapeDialog: React.FC = ( + props: IGalleryScrapeDialogProps +) => { + const [title, setTitle] = useState>( + new ScrapeResult(props.gallery.title, props.scraped.title) + ); + const [url, setURL] = useState>( + new ScrapeResult(props.gallery.url, props.scraped.url) + ); + const [date, setDate] = useState>( + new ScrapeResult(props.gallery.date, props.scraped.date) + ); + const [studio, setStudio] = useState>( + new ScrapeResult( + props.gallery.studio_id, + props.scraped.studio?.stored_id + ) + ); + const [newStudio, setNewStudio] = useState< + GQL.ScrapedSceneStudio | undefined + >( + props.scraped.studio && !props.scraped.studio.stored_id + ? props.scraped.studio + : undefined + ); + + function mapStoredIdObjects( + scrapedObjects?: IHasStoredID[] + ): string[] | undefined { + if (!scrapedObjects) { + return undefined; + } + const ret = scrapedObjects + .map((p) => p.stored_id) + .filter((p) => { + return p !== undefined && p !== null; + }) as string[]; + + if (ret.length === 0) { + return undefined; + } + + // sort by id numerically + ret.sort((a, b) => { + return parseInt(a, 10) - parseInt(b, 10); + }); + + return ret; + } + + function sortIdList(idList?: string[] | null) { + if (!idList) { + return; + } + + const ret = _.clone(idList); + // sort by id numerically + ret.sort((a, b) => { + return parseInt(a, 10) - parseInt(b, 10); + }); + + return ret; + } + + const [performers, setPerformers] = useState>( + new ScrapeResult( + sortIdList(props.gallery.performer_ids), + mapStoredIdObjects(props.scraped.performers ?? undefined) + ) + ); + const [newPerformers, setNewPerformers] = useState< + GQL.ScrapedScenePerformer[] + >(props.scraped.performers?.filter((t) => !t.stored_id) ?? []); + + const [tags, setTags] = useState>( + new ScrapeResult( + sortIdList(props.gallery.tag_ids), + mapStoredIdObjects(props.scraped.tags ?? undefined) + ) + ); + const [newTags, setNewTags] = useState( + props.scraped.tags?.filter((t) => !t.stored_id) ?? [] + ); + + const [details, setDetails] = useState>( + new ScrapeResult(props.gallery.details, props.scraped.details) + ); + + const [createStudio] = useStudioCreate({ name: "" }); + const [createPerformer] = usePerformerCreate(); + const [createTag] = useTagCreate({ name: "" }); + + const Toast = useToast(); + + // don't show the dialog if nothing was scraped + if ( + [title, url, date, studio, performers, tags, details].every( + (r) => !r.scraped + ) + ) { + props.onClose(); + return <>; + } + + async function createNewStudio(toCreate: GQL.ScrapedSceneStudio) { + try { + const result = await createStudio({ + variables: { + name: toCreate.name, + url: toCreate.url, + }, + }); + + // set the new studio as the value + setStudio(studio.cloneWithValue(result.data!.studioCreate!.id)); + setNewStudio(undefined); + + Toast.success({ + content: ( + + Created studio: {toCreate.name} + + ), + }); + } catch (e) { + Toast.error(e); + } + } + + async function createNewPerformer(toCreate: GQL.ScrapedScenePerformer) { + let performerInput: GQL.PerformerCreateInput = { name: "" }; + try { + performerInput = Object.assign(performerInput, toCreate); + const result = await createPerformer({ + variables: performerInput, + }); + + // add the new performer to the new performers value + const performerClone = performers.cloneWithValue(performers.newValue); + if (!performerClone.newValue) { + performerClone.newValue = []; + } + performerClone.newValue.push(result.data!.performerCreate!.id); + setPerformers(performerClone); + + // remove the performer from the list + const newPerformersClone = newPerformers.concat(); + const pIndex = newPerformersClone.indexOf(toCreate); + newPerformersClone.splice(pIndex, 1); + + setNewPerformers(newPerformersClone); + + Toast.success({ + content: ( + + Created performer: {toCreate.name} + + ), + }); + } catch (e) { + Toast.error(e); + } + } + + async function createNewTag(toCreate: GQL.ScrapedSceneTag) { + let tagInput: GQL.TagCreateInput = { name: "" }; + try { + tagInput = Object.assign(tagInput, toCreate); + const result = await createTag({ + variables: tagInput, + }); + + // add the new tag to the new tags value + const tagClone = tags.cloneWithValue(tags.newValue); + if (!tagClone.newValue) { + tagClone.newValue = []; + } + tagClone.newValue.push(result.data!.tagCreate!.id); + setTags(tagClone); + + // remove the tag from the list + const newTagsClone = newTags.concat(); + const pIndex = newTagsClone.indexOf(toCreate); + newTagsClone.splice(pIndex, 1); + + setNewTags(newTagsClone); + + Toast.success({ + content: ( + + Created tag: {toCreate.name} + + ), + }); + } catch (e) { + Toast.error(e); + } + } + + function makeNewScrapedItem(): GQL.ScrapedGalleryDataFragment { + const newStudioValue = studio.getNewValue(); + + return { + title: title.getNewValue(), + url: url.getNewValue(), + date: date.getNewValue(), + studio: newStudioValue + ? { + stored_id: newStudioValue, + name: "", + } + : undefined, + performers: performers.getNewValue()?.map((p) => { + return { + stored_id: p, + name: "", + }; + }), + tags: tags.getNewValue()?.map((m) => { + return { + stored_id: m, + name: "", + }; + }), + details: details.getNewValue(), + }; + } + + function renderScrapeRows() { + return ( + <> + setTitle(value)} + /> + setURL(value)} + /> + setDate(value)} + /> + {renderScrapedStudioRow( + studio, + (value) => setStudio(value), + newStudio, + createNewStudio + )} + {renderScrapedPerformersRow( + performers, + (value) => setPerformers(value), + newPerformers, + createNewPerformer + )} + {renderScrapedTagsRow( + tags, + (value) => setTags(value), + newTags, + createNewTag + )} + setDetails(value)} + /> + + ); + } + + return ( + { + props.onClose(apply ? makeNewScrapedItem() : undefined); + }} + /> + ); +}; diff --git a/ui/v2.5/src/components/Galleries/GalleryList.tsx b/ui/v2.5/src/components/Galleries/GalleryList.tsx index e3e31e926..59a545e03 100644 --- a/ui/v2.5/src/components/Galleries/GalleryList.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryList.tsx @@ -1,19 +1,154 @@ -import React from "react"; +import React, { useState } from "react"; +import _ from "lodash"; import { Table } from "react-bootstrap"; -import { Link } from "react-router-dom"; -import { FindGalleriesQueryResult } from "src/core/generated-graphql"; +import { Link, useHistory } from "react-router-dom"; +import { + FindGalleriesQueryResult, + GallerySlimDataFragment, +} from "src/core/generated-graphql"; import { useGalleriesList } from "src/hooks"; +import { showWhenSelected } from "src/hooks/ListHook"; import { ListFilterModel } from "src/models/list-filter/filter"; import { DisplayMode } from "src/models/list-filter/types"; +import { queryFindGalleries } from "src/core/StashService"; import { GalleryCard } from "./GalleryCard"; +import { EditGalleriesDialog } from "./EditGalleriesDialog"; +import { DeleteGalleriesDialog } from "./DeleteGalleriesDialog"; +import { ExportDialog } from "../Shared/ExportDialog"; + +interface IGalleryList { + filterHook?: (filter: ListFilterModel) => ListFilterModel; + persistState?: boolean; +} + +export const GalleryList: React.FC = ({ + filterHook, + persistState, +}) => { + const history = useHistory(); + const [isExportDialogOpen, setIsExportDialogOpen] = useState(false); + const [isExportAll, setIsExportAll] = useState(false); + + const otherOperations = [ + { + text: "View Random", + onClick: viewRandom, + }, + { + text: "Export...", + onClick: onExport, + isDisplayed: showWhenSelected, + }, + { + text: "Export all...", + onClick: onExportAll, + }, + ]; + + const addKeybinds = ( + result: FindGalleriesQueryResult, + filter: ListFilterModel + ) => { + Mousetrap.bind("p r", () => { + viewRandom(result, filter); + }); + + return () => { + Mousetrap.unbind("p r"); + }; + }; -export const GalleryList: React.FC = () => { const listData = useGalleriesList({ zoomable: true, + selectable: true, + otherOperations, renderContent, + renderEditDialog: renderEditGalleriesDialog, + renderDeleteDialog: renderDeleteGalleriesDialog, + filterHook, + addKeybinds, + persistState, }); - function renderContent( + async function viewRandom( + result: FindGalleriesQueryResult, + filter: ListFilterModel + ) { + // query for a random image + if (result.data && result.data.findGalleries) { + const { count } = result.data.findGalleries; + + const index = Math.floor(Math.random() * count); + const filterCopy = _.cloneDeep(filter); + filterCopy.itemsPerPage = 1; + filterCopy.currentPage = index + 1; + const singleResult = await queryFindGalleries(filterCopy); + if ( + singleResult && + singleResult.data && + singleResult.data.findGalleries && + singleResult.data.findGalleries.galleries.length === 1 + ) { + const { id } = singleResult!.data!.findGalleries!.galleries[0]; + // navigate to the image player page + history.push(`/galleries/${id}`); + } + } + } + + async function onExport() { + setIsExportAll(false); + setIsExportDialogOpen(true); + } + + async function onExportAll() { + setIsExportAll(true); + setIsExportDialogOpen(true); + } + + function maybeRenderGalleryExportDialog(selectedIds: Set) { + if (isExportDialogOpen) { + return ( + <> + { + setIsExportDialogOpen(false); + }} + /> + + ); + } + } + + function renderEditGalleriesDialog( + selectedImages: GallerySlimDataFragment[], + onClose: (applied: boolean) => void + ) { + return ( + <> + + + ); + } + + function renderDeleteGalleriesDialog( + selectedImages: GallerySlimDataFragment[], + onClose: (confirmed: boolean) => void + ) { + return ( + <> + + + ); + } + + function renderGalleries( result: FindGalleriesQueryResult, filter: ListFilterModel, selectedIds: Set, @@ -30,6 +165,11 @@ export const GalleryList: React.FC = () => { key={gallery.id} gallery={gallery} zoomIndex={zoomIndex} + selecting={selectedIds.size > 0} + selected={selectedIds.has(gallery.id)} + onSelectedChanged={(selected: boolean, shiftKey: boolean) => + listData.onSelectChange(gallery.id, selected, shiftKey) + } /> ))}
@@ -41,7 +181,7 @@ export const GalleryList: React.FC = () => { Preview - Path + Title @@ -49,19 +189,19 @@ export const GalleryList: React.FC = () => { - {gallery.files.length > 0 ? ( + {gallery.cover ? ( {gallery.title ) : undefined} - {gallery.path} ({gallery.files.length}{" "} - {gallery.files.length === 1 ? "image" : "images"}) + {gallery.title ?? gallery.path} ({gallery.image_count}{" "} + {gallery.image_count === 1 ? "image" : "images"}) @@ -75,5 +215,19 @@ export const GalleryList: React.FC = () => { } } + function renderContent( + result: FindGalleriesQueryResult, + filter: ListFilterModel, + selectedIds: Set, + zoomIndex: number + ) { + return ( + <> + {maybeRenderGalleryExportDialog(selectedIds)} + {renderGalleries(result, filter, selectedIds, zoomIndex)} + + ); + } + return listData.template; }; diff --git a/ui/v2.5/src/components/Galleries/GalleryViewer.tsx b/ui/v2.5/src/components/Galleries/GalleryViewer.tsx index 98785a2f9..0865f8735 100644 --- a/ui/v2.5/src/components/Galleries/GalleryViewer.tsx +++ b/ui/v2.5/src/components/Galleries/GalleryViewer.tsx @@ -1,58 +1,51 @@ -import React, { FunctionComponent, useState } from "react"; -import Lightbox from "react-images"; -import Gallery from "react-photo-gallery"; +import React, { useState } from "react"; import * as GQL from "src/core/generated-graphql"; +import FsLightbox from "fslightbox-react"; +import "flexbin/flexbin.css"; interface IProps { - gallery: GQL.GalleryDataFragment; + gallery: Partial; } -export const GalleryViewer: FunctionComponent = ({ gallery }) => { - const [currentImage, setCurrentImage] = useState(0); - const [lightboxIsOpen, setLightboxIsOpen] = useState(false); +export const GalleryViewer: React.FC = ({ gallery }) => { + const [lightboxToggle, setLightboxToggle] = useState(false); + const [currentIndex, setCurrentIndex] = useState(0); - function openLightbox( - _event: React.MouseEvent, - obj: { index: number } - ) { - setCurrentImage(obj.index); - setLightboxIsOpen(true); - } - function closeLightbox() { - setCurrentImage(0); - setLightboxIsOpen(false); - } - function gotoPrevious() { - setCurrentImage(currentImage - 1); - } - function gotoNext() { - setCurrentImage(currentImage + 1); - } + const openImage = (index: number) => { + setCurrentIndex(index); + setLightboxToggle(!lightboxToggle); + }; - const photos = gallery.files.map((file) => ({ - src: file.path ?? "", - caption: file.name ?? "", - })); - const thumbs = gallery.files.map((file) => ({ - src: `${file.path}?thumb=true` || "", - width: 1, - height: 1, - })); + const photos = !gallery.images + ? [] + : gallery.images.map((file) => file.paths.image ?? ""); + const thumbs = !gallery.images + ? [] + : gallery.images.map((file, index) => ( +
openImage(index)} + onKeyPress={() => openImage(index)} + > + {file.title +
+ )); return ( -
- - - window.open(photos[currentImage].src ?? "", "_blank") - } - isOpen={lightboxIsOpen} - width={9999} +
+
{thumbs}
+
); diff --git a/ui/v2.5/src/components/Galleries/styles.scss b/ui/v2.5/src/components/Galleries/styles.scss index 6d7e58f23..15e4c040a 100644 --- a/ui/v2.5/src/components/Galleries/styles.scss +++ b/ui/v2.5/src/components/Galleries/styles.scss @@ -1,16 +1,98 @@ -/* stylelint-disable selector-class-pattern */ -.react-photo-gallery--gallery { - img { - object-fit: contain; +.gallery-image { + &:hover { + cursor: pointer; + } +} + +.gallery-header { + flex-basis: auto; + margin-top: 30px; +} + +#gallery-details-container { + .tab-content { + min-height: 15rem; + } + + .gallery-description { + width: 100%; } } -/* stylelint-enable selector-class-pattern */ .gallery-card { - padding: 0.5rem; + &.card { + overflow: hidden; + padding: 0; + padding-bottom: 1rem; + } + + .card-section { + margin-top: auto; + + a:hover { + text-decoration: none; + } + } + + .card-section-title { + color: $text-color; + } &-image { object-fit: contain; - vertical-align: middle; + } +} + +.gallery-tabs { + max-height: calc(100vh - 4rem); + + overflow-wrap: break-word; + word-wrap: break-word; +} + +$galleryTabWidth: 450px; + +@media (min-width: 1200px) { + .gallery-tabs { + flex: 0 0 $galleryTabWidth; + max-width: $galleryTabWidth; + overflow: auto; + } + + .gallery-container { + flex: 0 0 calc(100% - #{$galleryTabWidth}); + max-width: calc(100% - #{$galleryTabWidth}); + } +} + +.gallery-tabs, +.gallery-container { + padding-left: 15px; + padding-right: 15px; + position: relative; + width: 100%; +} + +.gallery-container { + height: calc(100vh - 4rem); + overflow: auto; +} + +@media (min-width: 1200px), (max-width: 575px) { + .gallery-performers { + .performer-card { + width: 15rem; + + &-gallery { + height: 22.5rem; + } + } + } +} + +#gallery-edit-details { + .rating-stars { + font-size: 1.3em; + height: calc(1.5em + 0.75rem + 2px); } } diff --git a/ui/v2.5/src/components/Help/Manual.tsx b/ui/v2.5/src/components/Help/Manual.tsx index 038c31b52..c53405f61 100644 --- a/ui/v2.5/src/components/Help/Manual.tsx +++ b/ui/v2.5/src/components/Help/Manual.tsx @@ -9,18 +9,24 @@ import Interface from "src/docs/en/Interface.md"; import Galleries from "src/docs/en/Galleries.md"; import Scraping from "src/docs/en/Scraping.md"; import Plugins from "src/docs/en/Plugins.md"; +import Tagger from "src/docs/en/Tagger.md"; import Contributing from "src/docs/en/Contributing.md"; import SceneFilenameParser from "src/docs/en/SceneFilenameParser.md"; import KeyboardShortcuts from "src/docs/en/KeyboardShortcuts.md"; import Help from "src/docs/en/Help.md"; -import { Page } from "./Page"; +import { MarkdownPage } from "../Shared/MarkdownPage"; interface IManualProps { show: boolean; onClose: () => void; + defaultActiveTab?: string; } -export const Manual: React.FC = ({ show, onClose }) => { +export const Manual: React.FC = ({ + show, + onClose, + defaultActiveTab, +}) => { const content = [ { key: "Introduction.md", @@ -75,6 +81,11 @@ export const Manual: React.FC = ({ show, onClose }) => { title: "Plugins", content: Plugins, }, + { + key: "Tagger.md", + title: "Scene Tagger", + content: Tagger, + }, { key: "KeyboardShortcuts.md", title: "Keyboard Shortcuts", @@ -92,7 +103,9 @@ export const Manual: React.FC = ({ show, onClose }) => { }, ]; - const [activeTab, setActiveTab] = useState(content[0].key); + const [activeTab, setActiveTab] = useState( + defaultActiveTab ?? content[0].key + ); // links to other manual pages are specified as "/help/page.md" // intercept clicks to these pages and set the tab accordingly @@ -124,7 +137,7 @@ export const Manual: React.FC = ({ show, onClose }) => { setActiveTab(k)} + onSelect={(k) => k && setActiveTab(k)} id="manual-tabs" > @@ -151,7 +164,7 @@ export const Manual: React.FC = ({ show, onClose }) => { key={`${c.key}-pane`} onClick={interceptLinkClick} > - + ); })} diff --git a/ui/v2.5/src/components/Images/DeleteImagesDialog.tsx b/ui/v2.5/src/components/Images/DeleteImagesDialog.tsx new file mode 100644 index 000000000..3c3615018 --- /dev/null +++ b/ui/v2.5/src/components/Images/DeleteImagesDialog.tsx @@ -0,0 +1,91 @@ +import React, { useState } from "react"; +import { Form } from "react-bootstrap"; +import { useImagesDestroy } from "src/core/StashService"; +import * as GQL from "src/core/generated-graphql"; +import { Modal } from "src/components/Shared"; +import { useToast } from "src/hooks"; +import { FormattedMessage } from "react-intl"; + +interface IDeleteImageDialogProps { + selected: GQL.SlimImageDataFragment[]; + onClose: (confirmed: boolean) => void; +} + +export const DeleteImagesDialog: React.FC = ( + props: IDeleteImageDialogProps +) => { + const plural = props.selected.length > 1; + + const singleMessageId = "deleteImageText"; + const pluralMessageId = "deleteImagesText"; + + const singleMessage = + "Are you sure you want to delete this image? Unless the file is also deleted, this image will be re-added when scan is performed."; + const pluralMessage = + "Are you sure you want to delete these images? Unless the files are also deleted, these images will be re-added when scan is performed."; + + const header = plural ? "Delete Images" : "Delete Image"; + const toastMessage = plural ? "Deleted images" : "Deleted image"; + const messageId = plural ? pluralMessageId : singleMessageId; + const message = plural ? pluralMessage : singleMessage; + + const [deleteFile, setDeleteFile] = useState(false); + const [deleteGenerated, setDeleteGenerated] = useState(true); + + const Toast = useToast(); + const [deleteImage] = useImagesDestroy(getImagesDeleteInput()); + + // Network state + const [isDeleting, setIsDeleting] = useState(false); + + function getImagesDeleteInput(): GQL.ImagesDestroyInput { + return { + ids: props.selected.map((image) => image.id), + delete_file: deleteFile, + delete_generated: deleteGenerated, + }; + } + + async function onDelete() { + setIsDeleting(true); + try { + await deleteImage(); + Toast.success({ content: toastMessage }); + } catch (e) { + Toast.error(e); + } + setIsDeleting(false); + props.onClose(true); + } + + return ( + props.onClose(false), + text: "Cancel", + variant: "secondary", + }} + isRunning={isDeleting} + > +

+ +

+
+ setDeleteFile(!deleteFile)} + /> + setDeleteGenerated(!deleteGenerated)} + /> + +
+ ); +}; diff --git a/ui/v2.5/src/components/Images/EditImagesDialog.tsx b/ui/v2.5/src/components/Images/EditImagesDialog.tsx new file mode 100644 index 000000000..2fe85adbd --- /dev/null +++ b/ui/v2.5/src/components/Images/EditImagesDialog.tsx @@ -0,0 +1,366 @@ +import React, { useEffect, useState } from "react"; +import { Form, Col, Row } from "react-bootstrap"; +import _ from "lodash"; +import { useBulkImageUpdate } from "src/core/StashService"; +import * as GQL from "src/core/generated-graphql"; +import { StudioSelect, Modal } from "src/components/Shared"; +import { useToast } from "src/hooks"; +import { FormUtils } from "src/utils"; +import MultiSet from "../Shared/MultiSet"; +import { RatingStars } from "../Scenes/SceneDetails/RatingStars"; + +interface IListOperationProps { + selected: GQL.SlimImageDataFragment[]; + onClose: (applied: boolean) => void; +} + +export const EditImagesDialog: React.FC = ( + props: IListOperationProps +) => { + const Toast = useToast(); + const [rating, setRating] = useState(); + const [studioId, setStudioId] = useState(); + const [performerMode, setPerformerMode] = React.useState< + GQL.BulkUpdateIdMode + >(GQL.BulkUpdateIdMode.Add); + const [performerIds, setPerformerIds] = useState(); + const [tagMode, setTagMode] = React.useState( + GQL.BulkUpdateIdMode.Add + ); + const [tagIds, setTagIds] = useState(); + + const [updateImages] = useBulkImageUpdate(getImageInput()); + + // Network state + const [isUpdating, setIsUpdating] = useState(false); + + function makeBulkUpdateIds( + ids: string[], + mode: GQL.BulkUpdateIdMode + ): GQL.BulkUpdateIds { + return { + mode, + ids, + }; + } + + function getImageInput(): GQL.BulkImageUpdateInput { + // need to determine what we are actually setting on each image + const aggregateRating = getRating(props.selected); + const aggregateStudioId = getStudioId(props.selected); + const aggregatePerformerIds = getPerformerIds(props.selected); + const aggregateTagIds = getTagIds(props.selected); + + const imageInput: GQL.BulkImageUpdateInput = { + ids: props.selected.map((image) => { + return image.id; + }), + }; + + // if rating is undefined + if (rating === undefined) { + // and all images have the same rating, then we are unsetting the rating. + if (aggregateRating) { + // an undefined rating is ignored in the server, so set it to 0 instead + imageInput.rating = 0; + } + // otherwise not setting the rating + } else { + // if rating is set, then we are setting the rating for all + imageInput.rating = rating; + } + + // if studioId is undefined + if (studioId === undefined) { + // and all images have the same studioId, + // then unset the studioId, otherwise ignoring studioId + if (aggregateStudioId) { + // an undefined studio_id is ignored in the server, so set it to empty string instead + imageInput.studio_id = ""; + } + } else { + // if studioId is set, then we are setting it + imageInput.studio_id = studioId; + } + + // if performerIds are empty + if ( + performerMode === GQL.BulkUpdateIdMode.Set && + (!performerIds || performerIds.length === 0) + ) { + // and all images have the same ids, + if (aggregatePerformerIds.length > 0) { + // then unset the performerIds, otherwise ignore + imageInput.performer_ids = makeBulkUpdateIds( + performerIds || [], + performerMode + ); + } + } else { + // if performerIds non-empty, then we are setting them + imageInput.performer_ids = makeBulkUpdateIds( + performerIds || [], + performerMode + ); + } + + // if tagIds non-empty, then we are setting them + if ( + tagMode === GQL.BulkUpdateIdMode.Set && + (!tagIds || tagIds.length === 0) + ) { + // and all images have the same ids, + if (aggregateTagIds.length > 0) { + // then unset the tagIds, otherwise ignore + imageInput.tag_ids = makeBulkUpdateIds(tagIds || [], tagMode); + } + } else { + // if tagIds non-empty, then we are setting them + imageInput.tag_ids = makeBulkUpdateIds(tagIds || [], tagMode); + } + + return imageInput; + } + + async function onSave() { + setIsUpdating(true); + try { + await updateImages(); + Toast.success({ content: "Updated images" }); + props.onClose(true); + } catch (e) { + Toast.error(e); + } + setIsUpdating(false); + } + + function getRating(state: GQL.SlimImageDataFragment[]) { + let ret: number | undefined; + let first = true; + + state.forEach((image: GQL.SlimImageDataFragment) => { + if (first) { + ret = image.rating ?? undefined; + first = false; + } else if (ret !== image.rating) { + ret = undefined; + } + }); + + return ret; + } + + function getStudioId(state: GQL.SlimImageDataFragment[]) { + let ret: string | undefined; + let first = true; + + state.forEach((image: GQL.SlimImageDataFragment) => { + if (first) { + ret = image?.studio?.id; + first = false; + } else { + const studio = image?.studio?.id; + if (ret !== studio) { + ret = undefined; + } + } + }); + + return ret; + } + + function getPerformerIds(state: GQL.SlimImageDataFragment[]) { + let ret: string[] = []; + let first = true; + + state.forEach((image: GQL.SlimImageDataFragment) => { + if (first) { + ret = image.performers ? image.performers.map((p) => p.id).sort() : []; + first = false; + } else { + const perfIds = image.performers + ? image.performers.map((p) => p.id).sort() + : []; + + if (!_.isEqual(ret, perfIds)) { + ret = []; + } + } + }); + + return ret; + } + + function getTagIds(state: GQL.SlimImageDataFragment[]) { + let ret: string[] = []; + let first = true; + + state.forEach((image: GQL.SlimImageDataFragment) => { + if (first) { + ret = image.tags ? image.tags.map((t) => t.id).sort() : []; + first = false; + } else { + const tIds = image.tags ? image.tags.map((t) => t.id).sort() : []; + + if (!_.isEqual(ret, tIds)) { + ret = []; + } + } + }); + + return ret; + } + + useEffect(() => { + const state = props.selected; + let updateRating: number | undefined; + let updateStudioID: string | undefined; + let updatePerformerIds: string[] = []; + let updateTagIds: string[] = []; + let first = true; + + state.forEach((image: GQL.SlimImageDataFragment) => { + const imageRating = image.rating; + const imageStudioID = image?.studio?.id; + const imagePerformerIDs = (image.performers ?? []) + .map((p) => p.id) + .sort(); + const imageTagIDs = (image.tags ?? []).map((p) => p.id).sort(); + + if (first) { + updateRating = imageRating ?? undefined; + updateStudioID = imageStudioID; + updatePerformerIds = imagePerformerIDs; + updateTagIds = imageTagIDs; + first = false; + } else { + if (imageRating !== updateRating) { + updateRating = undefined; + } + if (imageStudioID !== updateStudioID) { + updateStudioID = undefined; + } + if (!_.isEqual(imagePerformerIDs, updatePerformerIds)) { + updatePerformerIds = []; + } + if (!_.isEqual(imageTagIDs, updateTagIds)) { + updateTagIds = []; + } + } + }); + + setRating(updateRating); + setStudioId(updateStudioID); + if (performerMode === GQL.BulkUpdateIdMode.Set) { + setPerformerIds(updatePerformerIds); + } + + if (tagMode === GQL.BulkUpdateIdMode.Set) { + setTagIds(updateTagIds); + } + }, [props.selected, performerMode, tagMode]); + + function renderMultiSelect( + type: "performers" | "tags", + ids: string[] | undefined + ) { + let mode = GQL.BulkUpdateIdMode.Add; + switch (type) { + case "performers": + mode = performerMode; + break; + case "tags": + mode = tagMode; + break; + } + + return ( + { + const itemIDs = items.map((i) => i.id); + switch (type) { + case "performers": + setPerformerIds(itemIDs); + break; + case "tags": + setTagIds(itemIDs); + break; + } + }} + onSetMode={(newMode) => { + switch (type) { + case "performers": + setPerformerMode(newMode); + break; + case "tags": + setTagMode(newMode); + break; + } + }} + ids={ids ?? []} + mode={mode} + /> + ); + } + + function render() { + return ( + props.onClose(false), + text: "Cancel", + variant: "secondary", + }} + isRunning={isUpdating} + > +
+ + {FormUtils.renderLabel({ + title: "Rating", + })} + + setRating(value)} + disabled={isUpdating} + /> + + + + + {FormUtils.renderLabel({ + title: "Studio", + })} + + + setStudioId(items.length > 0 ? items[0]?.id : undefined) + } + ids={studioId ? [studioId] : []} + isDisabled={isUpdating} + /> + + + + + Performers + {renderMultiSelect("performers", performerIds)} + + + + Tags + {renderMultiSelect("tags", tagIds)} + +
+
+ ); + } + + return render(); +}; diff --git a/ui/v2.5/src/components/Images/ImageCard.tsx b/ui/v2.5/src/components/Images/ImageCard.tsx new file mode 100644 index 000000000..71e38fcca --- /dev/null +++ b/ui/v2.5/src/components/Images/ImageCard.tsx @@ -0,0 +1,198 @@ +import React from "react"; +import { Button, ButtonGroup, Card, Form } from "react-bootstrap"; +import { Link } from "react-router-dom"; +import cx from "classnames"; +import * as GQL from "src/core/generated-graphql"; +import { Icon, TagLink, HoverPopover, SweatDrops } from "src/components/Shared"; +import { TextUtils } from "src/utils"; + +interface IImageCardProps { + image: GQL.SlimImageDataFragment; + selecting?: boolean; + selected: boolean | undefined; + zoomIndex: number; + onSelectedChanged: (selected: boolean, shiftKey: boolean) => void; +} + +export const ImageCard: React.FC = ( + props: IImageCardProps +) => { + function maybeRenderRatingBanner() { + if (!props.image.rating) { + return; + } + return ( +
+ RATING: {props.image.rating} +
+ ); + } + + function maybeRenderTagPopoverButton() { + if (props.image.tags.length <= 0) return; + + const popoverContent = props.image.tags.map((tag) => ( + + )); + + return ( + + + + ); + } + + function maybeRenderPerformerPopoverButton() { + if (props.image.performers.length <= 0) return; + + const popoverContent = props.image.performers.map((performer) => ( +
+ + {performer.name + + +
+ )); + + return ( + + + + ); + } + + function maybeRenderOCounter() { + if (props.image.o_counter) { + return ( +
+ +
+ ); + } + } + + function maybeRenderPopoverButtonGroup() { + if ( + props.image.tags.length > 0 || + props.image.performers.length > 0 || + props.image?.o_counter + ) { + return ( + <> +
+ + {maybeRenderTagPopoverButton()} + {maybeRenderPerformerPopoverButton()} + {maybeRenderOCounter()} + + + ); + } + } + + function handleImageClick( + event: React.MouseEvent + ) { + const { shiftKey } = event; + + if (props.selecting) { + props.onSelectedChanged(!props.selected, shiftKey); + event.preventDefault(); + } + } + + function handleDrag(event: React.DragEvent) { + if (props.selecting) { + event.dataTransfer.setData("text/plain", ""); + event.dataTransfer.setDragImage(new Image(), 0, 0); + } + } + + function handleDragOver(event: React.DragEvent) { + const ev = event; + const shiftKey = false; + + if (props.selecting && !props.selected) { + props.onSelectedChanged(true, shiftKey); + } + + ev.dataTransfer.dropEffect = "move"; + ev.preventDefault(); + } + + function isPortrait() { + const { file } = props.image; + const width = file.width ? file.width : 0; + const height = file.height ? file.height : 0; + return height > width; + } + + let shiftKey = false; + + return ( + + props.onSelectedChanged(!props.selected, shiftKey)} + onClick={(event: React.MouseEvent) => { + // eslint-disable-next-line prefer-destructuring + shiftKey = event.shiftKey; + event.stopPropagation(); + }} + /> + +
+ +
+ {props.image.title +
+ {maybeRenderRatingBanner()} + +
+
+
+ {props.image.title + ? props.image.title + : TextUtils.fileNameFromPath(props.image.path)} +
+
+ + {maybeRenderPopoverButtonGroup()} +
+ ); +}; diff --git a/ui/v2.5/src/components/Images/ImageDetails/Image.tsx b/ui/v2.5/src/components/Images/ImageDetails/Image.tsx new file mode 100644 index 000000000..0abceb958 --- /dev/null +++ b/ui/v2.5/src/components/Images/ImageDetails/Image.tsx @@ -0,0 +1,225 @@ +import { Tab, Nav, Dropdown } from "react-bootstrap"; +import React, { useEffect, useState } from "react"; +import { useParams, useHistory, Link } from "react-router-dom"; +import { + useFindImage, + useImageIncrementO, + useImageDecrementO, + useImageResetO, +} from "src/core/StashService"; +import { ErrorMessage, LoadingIndicator, Icon } from "src/components/Shared"; +import { useToast } from "src/hooks"; +import { TextUtils } from "src/utils"; +import * as Mousetrap from "mousetrap"; +import { OCounterButton } from "src/components/Scenes/SceneDetails/OCounterButton"; +import { ImageFileInfoPanel } from "./ImageFileInfoPanel"; +import { ImageEditPanel } from "./ImageEditPanel"; +import { ImageDetailPanel } from "./ImageDetailPanel"; +import { DeleteImagesDialog } from "../DeleteImagesDialog"; + +interface IImageParams { + id?: string; +} + +export const Image: React.FC = () => { + const { id = "new" } = useParams(); + const history = useHistory(); + const Toast = useToast(); + + const { data, error, loading } = useFindImage(id); + const image = data?.findImage; + const [oLoading, setOLoading] = useState(false); + const [incrementO] = useImageIncrementO(image?.id ?? "0"); + const [decrementO] = useImageDecrementO(image?.id ?? "0"); + const [resetO] = useImageResetO(image?.id ?? "0"); + + const [activeTabKey, setActiveTabKey] = useState("image-details-panel"); + + const [isDeleteAlertOpen, setIsDeleteAlertOpen] = useState(false); + + const onIncrementClick = async () => { + try { + setOLoading(true); + await incrementO(); + } catch (e) { + Toast.error(e); + } finally { + setOLoading(false); + } + }; + + const onDecrementClick = async () => { + try { + setOLoading(true); + await decrementO(); + } catch (e) { + Toast.error(e); + } finally { + setOLoading(false); + } + }; + + const onResetClick = async () => { + try { + setOLoading(true); + await resetO(); + } catch (e) { + Toast.error(e); + } finally { + setOLoading(false); + } + }; + + function onDeleteDialogClosed(deleted: boolean) { + setIsDeleteAlertOpen(false); + if (deleted) { + history.push("/images"); + } + } + + function maybeRenderDeleteDialog() { + if (isDeleteAlertOpen && image) { + return ( + + ); + } + } + + function renderOperations() { + return ( + + + + + + setIsDeleteAlertOpen(true)} + > + Delete Image + + + + ); + } + + function renderTabs() { + if (!image) { + return; + } + + return ( + k && setActiveTabKey(k)} + > +
+ +
+ + + + + + + + + + setIsDeleteAlertOpen(true)} + /> + + +
+ ); + } + + // set up hotkeys + useEffect(() => { + Mousetrap.bind("a", () => setActiveTabKey("image-details-panel")); + Mousetrap.bind("e", () => setActiveTabKey("image-edit-panel")); + Mousetrap.bind("f", () => setActiveTabKey("image-file-info-panel")); + Mousetrap.bind("o", () => onIncrementClick()); + + return () => { + Mousetrap.unbind("a"); + Mousetrap.unbind("e"); + Mousetrap.unbind("f"); + Mousetrap.unbind("o"); + }; + }); + + if (loading) { + return ; + } + + if (error) return ; + + if (!image) { + return ; + } + + return ( +
+ {maybeRenderDeleteDialog()} +
+
+ {image.studio && ( +

+ + {`${image.studio.name} + +

+ )} +

+ {image.title ?? TextUtils.fileNameFromPath(image.path)} +

+
+ {renderTabs()} +
+
+ {image.title +
+
+ ); +}; diff --git a/ui/v2.5/src/components/Images/ImageDetails/ImageDetailPanel.tsx b/ui/v2.5/src/components/Images/ImageDetails/ImageDetailPanel.tsx new file mode 100644 index 000000000..1e8d99174 --- /dev/null +++ b/ui/v2.5/src/components/Images/ImageDetails/ImageDetailPanel.tsx @@ -0,0 +1,103 @@ +import React from "react"; +import { Link } from "react-router-dom"; +import * as GQL from "src/core/generated-graphql"; +import { TextUtils } from "src/utils"; +import { TagLink } from "src/components/Shared"; +import { PerformerCard } from "src/components/Performers/PerformerCard"; +import { RatingStars } from "src/components/Scenes/SceneDetails/RatingStars"; + +interface IImageDetailProps { + image: GQL.ImageDataFragment; +} + +export const ImageDetailPanel: React.FC = (props) => { + function renderTags() { + if (props.image.tags.length === 0) return; + const tags = props.image.tags.map((tag) => ( + + )); + return ( + <> +
Tags
+ {tags} + + ); + } + + function renderPerformers() { + if (props.image.performers.length === 0) return; + const cards = props.image.performers.map((performer) => ( + + )); + + return ( + <> +
Performers
+
+ {cards} +
+ + ); + } + + function renderGalleries() { + if (props.image.galleries.length === 0) return; + const tags = props.image.galleries.map((gallery) => ( + + )); + return ( + <> +
Galleries
+ {tags} + + ); + } + + // filename should use entire row if there is no studio + const imageDetailsWidth = props.image.studio ? "col-9" : "col-12"; + + return ( + <> +
+
+
+

+ {props.image.title ?? + TextUtils.fileNameFromPath(props.image.path)} +

+
+ {props.image.rating ? ( +
+ Rating: +
+ ) : ( + "" + )} + {renderGalleries()} + {props.image.file.height ? ( +
Resolution: {TextUtils.resolution(props.image.file.height)}
+ ) : ( + "" + )} +
+ {props.image.studio && ( +
+ + {`${props.image.studio.name} + +
+ )} +
+
+
+ {renderTags()} + {renderPerformers()} +
+
+ + ); +}; diff --git a/ui/v2.5/src/components/Images/ImageDetails/ImageEditPanel.tsx b/ui/v2.5/src/components/Images/ImageDetails/ImageEditPanel.tsx new file mode 100644 index 000000000..f937e9b84 --- /dev/null +++ b/ui/v2.5/src/components/Images/ImageDetails/ImageEditPanel.tsx @@ -0,0 +1,210 @@ +import React, { useEffect, useState } from "react"; +import { Button, Form, Col, Row } from "react-bootstrap"; +import * as GQL from "src/core/generated-graphql"; +import { useImageUpdate } from "src/core/StashService"; +import { + PerformerSelect, + TagSelect, + StudioSelect, + LoadingIndicator, +} from "src/components/Shared"; +import { useToast } from "src/hooks"; +import { FormUtils } from "src/utils"; +import { RatingStars } from "src/components/Scenes/SceneDetails/RatingStars"; + +interface IProps { + image: GQL.ImageDataFragment; + isVisible: boolean; + onDelete: () => void; +} + +export const ImageEditPanel: React.FC = (props: IProps) => { + const Toast = useToast(); + const [title, setTitle] = useState(); + const [rating, setRating] = useState(); + const [studioId, setStudioId] = useState(); + const [performerIds, setPerformerIds] = useState(); + const [tagIds, setTagIds] = useState(); + + // Network state + const [isLoading, setIsLoading] = useState(true); + + const [updateImage] = useImageUpdate(getImageInput()); + + useEffect(() => { + if (props.isVisible) { + Mousetrap.bind("s s", () => { + onSave(); + }); + Mousetrap.bind("d d", () => { + props.onDelete(); + }); + + // numeric keypresses get caught by jwplayer, so blur the element + // if the rating sequence is started + Mousetrap.bind("r", () => { + if (document.activeElement instanceof HTMLElement) { + document.activeElement.blur(); + } + + Mousetrap.bind("0", () => setRating(NaN)); + Mousetrap.bind("1", () => setRating(1)); + Mousetrap.bind("2", () => setRating(2)); + Mousetrap.bind("3", () => setRating(3)); + Mousetrap.bind("4", () => setRating(4)); + Mousetrap.bind("5", () => setRating(5)); + + setTimeout(() => { + Mousetrap.unbind("0"); + Mousetrap.unbind("1"); + Mousetrap.unbind("2"); + Mousetrap.unbind("3"); + Mousetrap.unbind("4"); + Mousetrap.unbind("5"); + }, 1000); + }); + + return () => { + Mousetrap.unbind("s s"); + Mousetrap.unbind("d d"); + + Mousetrap.unbind("r"); + }; + } + }); + + function updateImageEditState(state: Partial) { + const perfIds = state.performers?.map((performer) => performer.id); + const tIds = state.tags ? state.tags.map((tag) => tag.id) : undefined; + + setTitle(state.title ?? undefined); + setRating(state.rating === null ? NaN : state.rating); + // setGalleryId(state?.gallery?.id ?? undefined); + setStudioId(state?.studio?.id ?? undefined); + setPerformerIds(perfIds); + setTagIds(tIds); + } + + useEffect(() => { + updateImageEditState(props.image); + setIsLoading(false); + }, [props.image]); + + function getImageInput(): GQL.ImageUpdateInput { + return { + id: props.image.id, + title, + rating, + studio_id: studioId, + performer_ids: performerIds, + tag_ids: tagIds, + }; + } + + async function onSave() { + setIsLoading(true); + try { + const result = await updateImage(); + if (result.data?.imageUpdate) { + Toast.success({ content: "Updated image" }); + } + } catch (e) { + Toast.error(e); + } + setIsLoading(false); + } + + if (isLoading) return ; + + return ( +
+
+
+ + +
+
+
+
+ {FormUtils.renderInputGroup({ + title: "Title", + value: title, + onChange: setTitle, + isEditing: true, + })} + + {FormUtils.renderLabel({ + title: "Rating", + })} + + setRating(value)} + /> + + + + + {FormUtils.renderLabel({ + title: "Studio", + })} + + + setStudioId(items.length > 0 ? items[0]?.id : undefined) + } + ids={studioId ? [studioId] : []} + /> + + + + + {FormUtils.renderLabel({ + title: "Performers", + labelProps: { + column: true, + sm: 3, + xl: 12, + }, + })} + + + setPerformerIds(items.map((item) => item.id)) + } + ids={performerIds} + /> + + + + + {FormUtils.renderLabel({ + title: "Tags", + labelProps: { + column: true, + sm: 3, + xl: 12, + }, + })} + + setTagIds(items.map((item) => item.id))} + ids={tagIds} + /> + + +
+
+
+ ); +}; diff --git a/ui/v2.5/src/components/Images/ImageDetails/ImageFileInfoPanel.tsx b/ui/v2.5/src/components/Images/ImageDetails/ImageFileInfoPanel.tsx new file mode 100644 index 000000000..05fb698db --- /dev/null +++ b/ui/v2.5/src/components/Images/ImageDetails/ImageFileInfoPanel.tsx @@ -0,0 +1,81 @@ +import React from "react"; +import { FormattedNumber } from "react-intl"; +import * as GQL from "src/core/generated-graphql"; +import { TextUtils } from "src/utils"; + +interface IImageFileInfoPanelProps { + image: GQL.ImageDataFragment; +} + +export const ImageFileInfoPanel: React.FC = ( + props: IImageFileInfoPanelProps +) => { + function renderChecksum() { + return ( +
+ Checksum + {props.image.checksum} +
+ ); + } + + function renderPath() { + const { + image: { path }, + } = props; + return ( +
+ Path + + {`file://${props.image.path}`}{" "} + +
+ ); + } + + function renderFileSize() { + if (props.image.file.size === undefined) { + return; + } + + const { size, unit } = TextUtils.fileSize(props.image.file.size ?? 0); + + return ( +
+ File Size + + + +
+ ); + } + + function renderDimensions() { + if (props.image.file.height && props.image.file.width) { + return ( +
+ Dimensions + + {props.image.file.width} x {props.image.file.height} + +
+ ); + } + } + + return ( +
+ {renderChecksum()} + {renderPath()} + {renderFileSize()} + {renderDimensions()} +
+ ); +}; diff --git a/ui/v2.5/src/components/Images/ImageList.tsx b/ui/v2.5/src/components/Images/ImageList.tsx new file mode 100644 index 000000000..b37794d2f --- /dev/null +++ b/ui/v2.5/src/components/Images/ImageList.tsx @@ -0,0 +1,277 @@ +import React, { useState } from "react"; +import _ from "lodash"; +import { useHistory } from "react-router-dom"; +import FsLightbox from "fslightbox-react"; +import { + FindImagesQueryResult, + SlimImageDataFragment, +} from "src/core/generated-graphql"; +import * as GQL from "src/core/generated-graphql"; +import { queryFindImages } from "src/core/StashService"; +import { useImagesList } from "src/hooks"; +import { TextUtils } from "src/utils"; +import { ListFilterModel } from "src/models/list-filter/filter"; +import { DisplayMode } from "src/models/list-filter/types"; +import { IListHookOperation, showWhenSelected } from "src/hooks/ListHook"; +import { ImageCard } from "./ImageCard"; +import { EditImagesDialog } from "./EditImagesDialog"; +import { DeleteImagesDialog } from "./DeleteImagesDialog"; +import "flexbin/flexbin.css"; +import { ExportDialog } from "../Shared/ExportDialog"; + +interface IImageWallProps { + images: GQL.SlimImageDataFragment[]; +} + +const ImageWall: React.FC = ({ images }) => { + const [lightboxToggle, setLightboxToggle] = useState(false); + const [currentIndex, setCurrentIndex] = useState(0); + + const openImage = (index: number) => { + setCurrentIndex(index); + setLightboxToggle(!lightboxToggle); + }; + + const photos = images.map((image) => image.paths.image ?? ""); + const thumbs = images.map((image, index) => ( +
openImage(index)} + onKeyPress={() => openImage(index)} + > + {image.title +
+ )); + + // FsLightbox doesn't update unless the key updates + const key = images.map((i) => i.id).join(","); + + function onLightboxOpen() { + // disable mousetrap + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (Mousetrap as any).pause(); + } + + function onLightboxClose() { + // re-enable mousetrap + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (Mousetrap as any).unpause(); + } + + return ( +
+
{thumbs}
+ +
+ ); +}; + +interface IImageList { + filterHook?: (filter: ListFilterModel) => ListFilterModel; + persistState?: boolean; + extraOperations?: IListHookOperation[]; +} + +export const ImageList: React.FC = ({ + filterHook, + persistState, + extraOperations, +}) => { + const history = useHistory(); + const [isExportDialogOpen, setIsExportDialogOpen] = useState(false); + const [isExportAll, setIsExportAll] = useState(false); + + const otherOperations = (extraOperations ?? []).concat([ + { + text: "View Random", + onClick: viewRandom, + }, + { + text: "Export...", + onClick: onExport, + isDisplayed: showWhenSelected, + }, + { + text: "Export all...", + onClick: onExportAll, + }, + ]); + + const addKeybinds = ( + result: FindImagesQueryResult, + filter: ListFilterModel + ) => { + Mousetrap.bind("p r", () => { + viewRandom(result, filter); + }); + + return () => { + Mousetrap.unbind("p r"); + }; + }; + + const listData = useImagesList({ + zoomable: true, + selectable: true, + otherOperations, + renderContent, + renderEditDialog: renderEditImagesDialog, + renderDeleteDialog: renderDeleteImagesDialog, + filterHook, + addKeybinds, + persistState, + }); + + async function viewRandom( + result: FindImagesQueryResult, + filter: ListFilterModel + ) { + // query for a random image + if (result.data && result.data.findImages) { + const { count } = result.data.findImages; + + const index = Math.floor(Math.random() * count); + const filterCopy = _.cloneDeep(filter); + filterCopy.itemsPerPage = 1; + filterCopy.currentPage = index + 1; + const singleResult = await queryFindImages(filterCopy); + if ( + singleResult && + singleResult.data && + singleResult.data.findImages && + singleResult.data.findImages.images.length === 1 + ) { + const { id } = singleResult!.data!.findImages!.images[0]; + // navigate to the image player page + history.push(`/images/${id}`); + } + } + } + + async function onExport() { + setIsExportAll(false); + setIsExportDialogOpen(true); + } + + async function onExportAll() { + setIsExportAll(true); + setIsExportDialogOpen(true); + } + + function maybeRenderImageExportDialog(selectedIds: Set) { + if (isExportDialogOpen) { + return ( + <> + { + setIsExportDialogOpen(false); + }} + /> + + ); + } + } + + function renderEditImagesDialog( + selectedImages: SlimImageDataFragment[], + onClose: (applied: boolean) => void + ) { + return ( + <> + + + ); + } + + function renderDeleteImagesDialog( + selectedImages: SlimImageDataFragment[], + onClose: (confirmed: boolean) => void + ) { + return ( + <> + + + ); + } + + function renderImageCard( + image: SlimImageDataFragment, + selectedIds: Set, + zoomIndex: number + ) { + return ( + 0} + selected={selectedIds.has(image.id)} + onSelectedChanged={(selected: boolean, shiftKey: boolean) => + listData.onSelectChange(image.id, selected, shiftKey) + } + /> + ); + } + + function renderImages( + result: FindImagesQueryResult, + filter: ListFilterModel, + selectedIds: Set, + zoomIndex: number + ) { + if (!result.data || !result.data.findImages) { + return; + } + if (filter.displayMode === DisplayMode.Grid) { + return ( +
+ {result.data.findImages.images.map((image) => + renderImageCard(image, selectedIds, zoomIndex) + )} +
+ ); + } + // if (filter.displayMode === DisplayMode.List) { + // return ; + // } + if (filter.displayMode === DisplayMode.Wall) { + return ; + } + } + + function renderContent( + result: FindImagesQueryResult, + filter: ListFilterModel, + selectedIds: Set, + zoomIndex: number + ) { + return ( + <> + {maybeRenderImageExportDialog(selectedIds)} + {renderImages(result, filter, selectedIds, zoomIndex)} + + ); + } + + return listData.template; +}; diff --git a/ui/v2.5/src/components/Images/Images.tsx b/ui/v2.5/src/components/Images/Images.tsx new file mode 100644 index 000000000..576b6f674 --- /dev/null +++ b/ui/v2.5/src/components/Images/Images.tsx @@ -0,0 +1,17 @@ +import React from "react"; +import { Route, Switch } from "react-router-dom"; +import { Image } from "./ImageDetails/Image"; +import { ImageList } from "./ImageList"; + +const Images = () => ( + + } + /> + + +); + +export default Images; diff --git a/ui/v2.5/src/components/Images/styles.scss b/ui/v2.5/src/components/Images/styles.scss new file mode 100644 index 000000000..9cdf480b6 --- /dev/null +++ b/ui/v2.5/src/components/Images/styles.scss @@ -0,0 +1,135 @@ +.image-header { + flex-basis: auto; + margin-top: 30px; +} + +#image-details-container { + .tab-content { + min-height: 15rem; + } + + .image-description { + width: 100%; + } +} + +.image-card { + &.card { + overflow: hidden; + padding: 0; + } + + &-check { + left: 0.5rem; + margin-top: -12px; + opacity: 0; + padding-left: 15px; + position: absolute; + top: 0.7rem; + width: 1.2rem; + z-index: 1; + + &:checked { + opacity: 0.75; + } + } + + .rating-banner { + transition: opacity 0.5s; + } + + &-preview { + display: flex; + justify-content: center; + margin-bottom: 5px; + position: relative; + + &-image { + height: 100%; + object-fit: contain; + width: 100%; + } + + &.portrait { + .image-card-preview-image { + object-fit: contain; + } + } + } + + &:hover { + .rating-banner { + opacity: 0; + transition: opacity 0.5s; + } + + .image-card-check { + opacity: 0.75; + transition: opacity 0.5s; + } + } +} + +.image-tabs { + max-height: calc(100vh - 4rem); + + overflow-wrap: break-word; + word-wrap: break-word; +} + +$imageTabWidth: 450px; + +@media (min-width: 1200px) { + .image-tabs { + flex: 0 0 $imageTabWidth; + max-width: $imageTabWidth; + overflow: auto; + } + + .image-container { + flex: 0 0 calc(100% - #{$imageTabWidth}); + max-width: calc(100% - #{$imageTabWidth}); + } +} + +.image-tabs, +.image-container { + padding-left: 15px; + padding-right: 15px; + position: relative; + width: 100%; +} + +.image-container { + display: flex; + + img { + max-height: calc(100vh - 4rem); + max-width: 100%; + object-fit: contain; + } +} + +@media (min-width: 1200px) { + .image-container { + height: calc(100vh - 4rem); + } +} +@media (min-width: 1200px), (max-width: 575px) { + .image-performers { + .performer-card { + width: 15rem; + + &-image { + height: 22.5rem; + } + } + } +} + +#image-edit-details { + .rating-stars { + font-size: 1.3em; + height: calc(1.5em + 0.75rem + 2px); + } +} diff --git a/ui/v2.5/src/components/List/ListFilter.tsx b/ui/v2.5/src/components/List/ListFilter.tsx index b6a0bf67c..28dc1e085 100644 --- a/ui/v2.5/src/components/List/ListFilter.tsx +++ b/ui/v2.5/src/components/List/ListFilter.tsx @@ -9,7 +9,6 @@ import { Form, OverlayTrigger, Tooltip, - SafeAnchorProps, InputGroup, FormControl, ButtonToolbar, @@ -41,7 +40,7 @@ interface IListFilterProps { itemsSelected?: boolean; } -const PAGE_SIZE_OPTIONS = ["20", "40", "60", "120"]; +const PAGE_SIZE_OPTIONS = ["20", "40", "60", "120", "250", "500", "1000"]; const minZoom = 0; const maxZoom = 3; @@ -160,11 +159,9 @@ export const ListFilter: React.FC = ( props.onFilterUpdate(newFilter); } - function onChangeSortBy(event: React.MouseEvent) { - const target = event.currentTarget as HTMLAnchorElement; - + function onChangeSortBy(event: React.MouseEvent) { const newFilter = _.cloneDeep(props.filter); - newFilter.sortBy = target.text; + newFilter.sortBy = event.currentTarget.text; newFilter.currentPage = 1; props.onFilterUpdate(newFilter); } @@ -257,6 +254,8 @@ export const ListFilter: React.FC = ( return "list"; case DisplayMode.Wall: return "square"; + case DisplayMode.Tagger: + return "tags"; } } function getLabel(option: DisplayMode) { @@ -267,6 +266,8 @@ export const ListFilter: React.FC = ( return "List"; case DisplayMode.Wall: return "Wall"; + case DisplayMode.Tagger: + return "Tagger"; } } @@ -405,7 +406,7 @@ export const ListFilter: React.FC = ( } function maybeRenderZoom() { - if (props.onChangeZoom) { + if (props.onChangeZoom && props.filter.displayMode === DisplayMode.Grid) { return (
= ( return ( <> - - +
+ @@ -513,33 +514,33 @@ export const ListFilter: React.FC = ( )} +
- - {PAGE_SIZE_OPTIONS.map((s) => ( - - ))} - -
+ + {PAGE_SIZE_OPTIONS.map((s) => ( + + ))} + - + {maybeRenderSelectedButtons()} {renderMore()} {renderDisplayModeOptions()} - {maybeRenderZoom()} + {maybeRenderZoom()}
-
+
{renderFilterTags()}
diff --git a/ui/v2.5/src/components/MainNavbar.tsx b/ui/v2.5/src/components/MainNavbar.tsx index ae74ba724..c10e799fc 100644 --- a/ui/v2.5/src/components/MainNavbar.tsx +++ b/ui/v2.5/src/components/MainNavbar.tsx @@ -25,6 +25,10 @@ const messages = defineMessages({ id: "scenes", defaultMessage: "Scenes", }, + images: { + id: "images", + defaultMessage: "Images", + }, movies: { id: "movies", defaultMessage: "Movies", @@ -49,6 +53,10 @@ const messages = defineMessages({ id: "galleries", defaultMessage: "Galleries", }, + sceneTagger: { + id: "sceneTagger", + defaultMessage: "Scene Tagger", + }, }); const menuItems: IMenuItem[] = [ @@ -57,6 +65,11 @@ const menuItems: IMenuItem[] = [ message: messages.scenes, href: "/scenes", }, + { + icon: "image", + message: messages.images, + href: "/images", + }, { href: "/movies", icon: "film", @@ -137,6 +150,8 @@ export const MainNavbar: React.FC = () => { ? "/movies/new" : location.pathname === "/tags" ? "/tags/new" + : location.pathname === "/galleries" + ? "/galleries/new" : null; const newButton = newPath === null ? ( @@ -153,6 +168,7 @@ export const MainNavbar: React.FC = () => { useEffect(() => { Mousetrap.bind("?", () => setShowManual(!showManual)); Mousetrap.bind("g s", () => goto("/scenes")); + Mousetrap.bind("g i", () => goto("/images")); Mousetrap.bind("g v", () => goto("/movies")); Mousetrap.bind("g k", () => goto("/scenes/markers")); Mousetrap.bind("g l", () => goto("/galleries")); diff --git a/ui/v2.5/src/components/Movies/MovieCard.tsx b/ui/v2.5/src/components/Movies/MovieCard.tsx index f90ab5088..1980f72f0 100644 --- a/ui/v2.5/src/components/Movies/MovieCard.tsx +++ b/ui/v2.5/src/components/Movies/MovieCard.tsx @@ -1,12 +1,14 @@ -import { Card } from "react-bootstrap"; import React, { FunctionComponent } from "react"; import { FormattedPlural } from "react-intl"; -import { Link } from "react-router-dom"; import * as GQL from "src/core/generated-graphql"; +import { BasicCard } from "../Shared/BasicCard"; interface IProps { movie: GQL.MovieDataFragment; sceneIndex?: number; + selecting?: boolean; + selected?: boolean; + onSelectedChanged?: (selected: boolean, shiftKey: boolean) => void; } export const MovieCard: FunctionComponent = (props: IProps) => { @@ -43,19 +45,29 @@ export const MovieCard: FunctionComponent = (props: IProps) => { } return ( - - - {props.movie.name - {maybeRenderRatingBanner()} - -
-
{props.movie.name}
- {maybeRenderSceneNumber()} -
-
+ + {props.movie.name + {maybeRenderRatingBanner()} + + } + details={ + <> +
{props.movie.name}
+ {maybeRenderSceneNumber()} + + } + selected={props.selected} + selecting={props.selecting} + onSelectedChanged={props.onSelectedChanged} + /> ); }; diff --git a/ui/v2.5/src/components/Movies/MovieDetails/Movie.tsx b/ui/v2.5/src/components/Movies/MovieDetails/Movie.tsx index 436456259..49bc6bc52 100644 --- a/ui/v2.5/src/components/Movies/MovieDetails/Movie.tsx +++ b/ui/v2.5/src/components/Movies/MovieDetails/Movie.tsx @@ -30,10 +30,14 @@ import { RatingStars } from "src/components/Scenes/SceneDetails/RatingStars"; import { MovieScenesPanel } from "./MovieScenesPanel"; import { MovieScrapeDialog } from "./MovieScrapeDialog"; +interface IMovieParams { + id?: string; +} + export const Movie: React.FC = () => { const history = useHistory(); const Toast = useToast(); - const { id = "new" } = useParams(); + const { id = "new" } = useParams(); const isNew = id === "new"; // Editing state diff --git a/ui/v2.5/src/components/Movies/MovieList.tsx b/ui/v2.5/src/components/Movies/MovieList.tsx index a8a5cdba3..e801bc056 100644 --- a/ui/v2.5/src/components/Movies/MovieList.tsx +++ b/ui/v2.5/src/components/Movies/MovieList.tsx @@ -1,30 +1,138 @@ -import React from "react"; +import React, { useState } from "react"; +import _ from "lodash"; import { FindMoviesQueryResult } from "src/core/generated-graphql"; import { ListFilterModel } from "src/models/list-filter/filter"; import { DisplayMode } from "src/models/list-filter/types"; -import { useMoviesList } from "src/hooks/ListHook"; +import { queryFindMovies } from "src/core/StashService"; +import { showWhenSelected, useMoviesList } from "src/hooks/ListHook"; +import { useHistory } from "react-router-dom"; import { MovieCard } from "./MovieCard"; +import { ExportDialog } from "../Shared/ExportDialog"; export const MovieList: React.FC = () => { + const history = useHistory(); + const [isExportDialogOpen, setIsExportDialogOpen] = useState(false); + const [isExportAll, setIsExportAll] = useState(false); + + const otherOperations = [ + { + text: "View Random", + onClick: viewRandom, + }, + { + text: "Export...", + onClick: onExport, + isDisplayed: showWhenSelected, + }, + { + text: "Export all...", + onClick: onExportAll, + }, + ]; + + const addKeybinds = ( + result: FindMoviesQueryResult, + filter: ListFilterModel + ) => { + Mousetrap.bind("p r", () => { + viewRandom(result, filter); + }); + + return () => { + Mousetrap.unbind("p r"); + }; + }; + const listData = useMoviesList({ renderContent, + addKeybinds, + otherOperations, + selectable: true, persistState: true, }); - function renderContent( + async function viewRandom( result: FindMoviesQueryResult, filter: ListFilterModel + ) { + // query for a random image + if (result.data && result.data.findMovies) { + const { count } = result.data.findMovies; + + const index = Math.floor(Math.random() * count); + const filterCopy = _.cloneDeep(filter); + filterCopy.itemsPerPage = 1; + filterCopy.currentPage = index + 1; + const singleResult = await queryFindMovies(filterCopy); + if ( + singleResult && + singleResult.data && + singleResult.data.findMovies && + singleResult.data.findMovies.movies.length === 1 + ) { + const { id } = singleResult!.data!.findMovies!.movies[0]; + // navigate to the movie page + history.push(`/movies/${id}`); + } + } + } + + async function onExport() { + setIsExportAll(false); + setIsExportDialogOpen(true); + } + + async function onExportAll() { + setIsExportAll(true); + setIsExportDialogOpen(true); + } + + function maybeRenderMovieExportDialog(selectedIds: Set) { + if (isExportDialogOpen) { + return ( + <> + { + setIsExportDialogOpen(false); + }} + /> + + ); + } + } + + function renderContent( + result: FindMoviesQueryResult, + filter: ListFilterModel, + selectedIds: Set ) { if (!result.data?.findMovies) { return; } if (filter.displayMode === DisplayMode.Grid) { return ( -
- {result.data.findMovies.movies.map((p) => ( - - ))} -
+ <> + {maybeRenderMovieExportDialog(selectedIds)} +
+ {result.data.findMovies.movies.map((p) => ( + 0} + selected={selectedIds.has(p.id)} + onSelectedChanged={(selected: boolean, shiftKey: boolean) => + listData.onSelectChange(p.id, selected, shiftKey) + } + /> + ))} +
+ ); } if (filter.displayMode === DisplayMode.List) { diff --git a/ui/v2.5/src/components/Performers/PerformerCard.tsx b/ui/v2.5/src/components/Performers/PerformerCard.tsx index 79d147f12..958f2803e 100644 --- a/ui/v2.5/src/components/Performers/PerformerCard.tsx +++ b/ui/v2.5/src/components/Performers/PerformerCard.tsx @@ -1,19 +1,25 @@ import React from "react"; -import { Card } from "react-bootstrap"; import { Link } from "react-router-dom"; -import { FormattedNumber, FormattedPlural } from "react-intl"; +import { FormattedNumber, FormattedPlural, FormattedMessage } from "react-intl"; import * as GQL from "src/core/generated-graphql"; import { NavUtils, TextUtils } from "src/utils"; import { CountryFlag } from "src/components/Shared"; +import { BasicCard } from "../Shared/BasicCard"; interface IPerformerCardProps { performer: GQL.PerformerDataFragment; ageFromDate?: string; + selecting?: boolean; + selected?: boolean; + onSelectedChanged?: (selected: boolean, shiftKey: boolean) => void; } export const PerformerCard: React.FC = ({ performer, ageFromDate, + selecting, + selected, + onSelectedChanged, }) => { const age = TextUtils.age(performer.birthdate, ageFromDate); const ageString = `${age} years old${ageFromDate ? " in this scene." : "."}`; @@ -22,37 +28,52 @@ export const PerformerCard: React.FC = ({ if (performer.favorite === false) { return; } - return
FAVORITE
; + return ( +
+ +
+ ); } return ( - - - {performer.name - {maybeRenderFavoriteBanner()} - -
-
{performer.name}
- {age !== 0 ?
{ageString}
: ""} - -
- Stars in  - -   - - + + {performer.name + {maybeRenderFavoriteBanner()} + + } + details={ + <> +
{performer.name}
+ {age !== 0 ?
{ageString}
: ""} + + - . -
-
-
+
+ Stars in  + +   + + + + . +
+ + } + selected={selected} + selecting={selecting} + onSelectedChanged={onSelectedChanged} + /> ); }; diff --git a/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx b/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx index 62856d8a3..e810895ce 100644 --- a/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx +++ b/ui/v2.5/src/components/Performers/PerformerDetails/Performer.tsx @@ -9,27 +9,37 @@ import { usePerformerCreate, usePerformerDestroy, } from "src/core/StashService"; -import { CountryFlag, Icon, LoadingIndicator } from "src/components/Shared"; +import { + CountryFlag, + ErrorMessage, + Icon, + LoadingIndicator, +} from "src/components/Shared"; import { useToast } from "src/hooks"; import { TextUtils } from "src/utils"; -import Lightbox from "react-images"; +import FsLightbox from "fslightbox-react"; import { PerformerDetailsPanel } from "./PerformerDetailsPanel"; import { PerformerOperationsPanel } from "./PerformerOperationsPanel"; import { PerformerScenesPanel } from "./PerformerScenesPanel"; +import { PerformerImagesPanel } from "./PerformerImagesPanel"; + +interface IPerformerParams { + id?: string; + tab?: string; +} export const Performer: React.FC = () => { const Toast = useToast(); const history = useHistory(); - const { tab = "details", id = "new" } = useParams(); + const { tab = "details", id = "new" } = useParams(); const isNew = id === "new"; // Performer state - const [performer, setPerformer] = useState< - Partial - >({}); const [imagePreview, setImagePreview] = useState(); const [imageEncoding, setImageEncoding] = useState(false); - const [lightboxIsOpen, setLightboxIsOpen] = useState(false); + const [lightboxToggle, setLightboxToggle] = useState(false); + const { data, loading: performerLoading, error } = useFindPerformer(id); + const performer = data?.findPerformer || ({} as Partial); // if undefined then get the existing image // if null then get the default (no) image @@ -40,29 +50,27 @@ export const Performer: React.FC = () => { : imagePreview ?? `${performer.image_path}?default=true`; // Network state - const [isLoading, setIsLoading] = useState(false); + const [loading, setIsLoading] = useState(false); + const isLoading = performerLoading || loading; - const { data, error } = useFindPerformer(id); const [updatePerformer] = usePerformerUpdate(); const [createPerformer] = usePerformerCreate(); const [deletePerformer] = usePerformerDestroy(); const activeTabKey = - tab === "scenes" || tab === "edit" || tab === "operations" + tab === "scenes" || + tab === "images" || + tab === "edit" || + tab === "operations" ? tab : "details"; - const setActiveTabKey = (newTab: string) => { + const setActiveTabKey = (newTab: string | null) => { if (tab !== newTab) { const tabParam = newTab === "details" ? "" : `/${newTab}`; history.replace(`/performers/${id}${tabParam}`); } }; - useEffect(() => { - setIsLoading(false); - if (data?.findPerformer) setPerformer(data.findPerformer); - }, [data]); - const onImageChange = (image?: string | null) => setImagePreview(image); const onImageEncoding = (isEncoding = false) => setImageEncoding(isEncoding); @@ -84,10 +92,10 @@ export const Performer: React.FC = () => { }; }); - if ((!isNew && (!data || !data.findPerformer)) || isLoading) - return ; - - if (error) return
{error.message}
; + if (isLoading) return ; + if (error) return ; + if (!performer.id && !isNew) + return ; async function onSave( performerInput: @@ -97,21 +105,24 @@ export const Performer: React.FC = () => { setIsLoading(true); try { if (!isNew) { - const result = await updatePerformer({ - variables: performerInput as GQL.PerformerUpdateInput, + await updatePerformer({ + variables: { + ...performerInput, + stash_ids: (performerInput?.stash_ids ?? []).map((s) => ({ + endpoint: s.endpoint, + stash_id: s.stash_id, + })), + } as GQL.PerformerUpdateInput, }); if (performerInput.image) { // Refetch image to bust browser cache - await fetch(`/performer/${performer.id}/image`, { cache: "reload" }); + await fetch(`/performer/${id}/image`, { cache: "reload" }); } - if (result.data?.performerUpdate) - setPerformer(result.data?.performerUpdate); } else { const result = await createPerformer({ variables: performerInput as GQL.PerformerCreateInput, }); if (result.data?.performerCreate) { - setPerformer(result.data.performerCreate); history.push(`/performers/${result.data.performerCreate.id}`); } } @@ -151,6 +162,9 @@ export const Performer: React.FC = () => { + + + { } function setFavorite(v: boolean) { - performer.favorite = v; - onSave(performer); + onSave({ ...performer, favorite: v }); } const renderIcons = () => ( @@ -283,19 +296,20 @@ export const Performer: React.FC = () => {
); - const photos = [{ src: activeImage, caption: "Image" }]; - if (!performer.id) { return ; } return (
-
+
{imageEncoding ? ( ) : ( - )} @@ -316,14 +330,7 @@ export const Performer: React.FC = () => {
{renderTabs()}
- setLightboxIsOpen(false)} - currentImage={0} - isOpen={lightboxIsOpen} - onClickImage={() => window.open(activeImage, "_blank")} - width={9999} - /> +
); }; diff --git a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerDetailsPanel.tsx b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerDetailsPanel.tsx index 900015bc3..04b8f2d0c 100644 --- a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerDetailsPanel.tsx +++ b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerDetailsPanel.tsx @@ -82,6 +82,7 @@ export const PerformerDetailsPanel: React.FC = ({ const [twitter, setTwitter] = useState(); const [instagram, setInstagram] = useState(); const [gender, setGender] = useState(undefined); + const [stashIDs, setStashIDs] = useState([]); // Network state const [isLoading, setIsLoading] = useState(false); @@ -121,6 +122,9 @@ export const PerformerDetailsPanel: React.FC = ({ setGender( genderToString((state as GQL.PerformerDataFragment).gender ?? undefined) ); + if ((state as GQL.PerformerDataFragment).stash_ids !== undefined) { + setStashIDs((state as GQL.PerformerDataFragment).stash_ids); + } } function translateScrapedGender(scrapedGender?: string) { @@ -239,8 +243,8 @@ export const PerformerDetailsPanel: React.FC = ({ }); useEffect(() => { - updatePerformerEditState(performer); - }, [performer]); + if (!isNew) updatePerformerEditState(performer); + }, [isNew, performer]); useEffect(() => { if (onImageChange) { @@ -288,6 +292,10 @@ export const PerformerDetailsPanel: React.FC = ({ instagram, image, gender: stringToGender(gender), + stash_ids: stashIDs.map((s) => ({ + stash_id: s.stash_id, + endpoint: s.endpoint, + })), }; if (!isNew) { @@ -562,7 +570,7 @@ export const PerformerDetailsPanel: React.FC = ({ /> {isEditing ? ( + )} + {link} + + ); + })} + + + + ); + } + const formatHeight = () => { if (isEditing) { return height; @@ -720,6 +782,7 @@ export const PerformerDetailsPanel: React.FC = ({ isEditing: !!isEditing, onChange: setInstagram, })} + {renderStashIDs()} diff --git a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerImagesPanel.tsx b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerImagesPanel.tsx new file mode 100644 index 000000000..cb242c580 --- /dev/null +++ b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerImagesPanel.tsx @@ -0,0 +1,14 @@ +import React from "react"; +import * as GQL from "src/core/generated-graphql"; +import { ImageList } from "src/components/Images/ImageList"; +import { performerFilterHook } from "src/core/performers"; + +interface IPerformerImagesPanel { + performer: Partial; +} + +export const PerformerImagesPanel: React.FC = ({ + performer, +}) => { + return ; +}; diff --git a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerScenesPanel.tsx b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerScenesPanel.tsx index dabe47153..64464cffa 100644 --- a/ui/v2.5/src/components/Performers/PerformerDetails/PerformerScenesPanel.tsx +++ b/ui/v2.5/src/components/Performers/PerformerDetails/PerformerScenesPanel.tsx @@ -1,8 +1,7 @@ import React from "react"; import * as GQL from "src/core/generated-graphql"; -import { PerformersCriterion } from "src/models/list-filter/criteria/performers"; -import { ListFilterModel } from "src/models/list-filter/filter"; import { SceneList } from "src/components/Scenes/SceneList"; +import { performerFilterHook } from "src/core/performers"; interface IPerformerDetailsProps { performer: Partial; @@ -11,37 +10,5 @@ interface IPerformerDetailsProps { export const PerformerScenesPanel: React.FC = ({ performer, }) => { - function filterHook(filter: ListFilterModel) { - const performerValue = { id: performer.id!, label: performer.name! }; - // if performers is already present, then we modify it, otherwise add - let performerCriterion = filter.criteria.find((c) => { - return c.type === "performers"; - }) as PerformersCriterion; - - if ( - performerCriterion && - (performerCriterion.modifier === GQL.CriterionModifier.IncludesAll || - performerCriterion.modifier === GQL.CriterionModifier.Includes) - ) { - // add the performer if not present - if ( - !performerCriterion.value.find((p) => { - return p.id === performer.id; - }) - ) { - performerCriterion.value.push(performerValue); - } - - performerCriterion.modifier = GQL.CriterionModifier.IncludesAll; - } else { - // overwrite - performerCriterion = new PerformersCriterion(); - performerCriterion.value = [performerValue]; - filter.criteria.push(performerCriterion); - } - - return filter; - } - - return ; + return ; }; diff --git a/ui/v2.5/src/components/Performers/PerformerList.tsx b/ui/v2.5/src/components/Performers/PerformerList.tsx index a0f45d2c4..de6f3cf09 100644 --- a/ui/v2.5/src/components/Performers/PerformerList.tsx +++ b/ui/v2.5/src/components/Performers/PerformerList.tsx @@ -1,21 +1,35 @@ import _ from "lodash"; -import React from "react"; +import React, { useState } from "react"; import { useHistory } from "react-router-dom"; import { FindPerformersQueryResult } from "src/core/generated-graphql"; import { queryFindPerformers } from "src/core/StashService"; import { usePerformersList } from "src/hooks"; +import { showWhenSelected } from "src/hooks/ListHook"; import { ListFilterModel } from "src/models/list-filter/filter"; import { DisplayMode } from "src/models/list-filter/types"; +import { ExportDialog } from "src/components/Shared/ExportDialog"; import { PerformerCard } from "./PerformerCard"; import { PerformerListTable } from "./PerformerListTable"; export const PerformerList: React.FC = () => { const history = useHistory(); + const [isExportDialogOpen, setIsExportDialogOpen] = useState(false); + const [isExportAll, setIsExportAll] = useState(false); + const otherOperations = [ { text: "Open Random", onClick: getRandom, }, + { + text: "Export...", + onClick: onExport, + isDisplayed: showWhenSelected, + }, + { + text: "Export all...", + onClick: onExportAll, + }, ]; const addKeybinds = ( @@ -31,10 +45,41 @@ export const PerformerList: React.FC = () => { }; }; + async function onExport() { + setIsExportAll(false); + setIsExportDialogOpen(true); + } + + async function onExportAll() { + setIsExportAll(true); + setIsExportDialogOpen(true); + } + + function maybeRenderPerformerExportDialog(selectedIds: Set) { + if (isExportDialogOpen) { + return ( + <> + { + setIsExportDialogOpen(false); + }} + /> + + ); + } + } + const listData = usePerformersList({ otherOperations, renderContent, addKeybinds, + selectable: true, persistState: true, }); @@ -63,18 +108,30 @@ export const PerformerList: React.FC = () => { function renderContent( result: FindPerformersQueryResult, - filter: ListFilterModel + filter: ListFilterModel, + selectedIds: Set ) { if (!result.data?.findPerformers) { return; } if (filter.displayMode === DisplayMode.Grid) { return ( -
- {result.data.findPerformers.performers.map((p) => ( - - ))} -
+ <> + {maybeRenderPerformerExportDialog(selectedIds)} +
+ {result.data.findPerformers.performers.map((p) => ( + 0} + selected={selectedIds.has(p.id)} + onSelectedChanged={(selected: boolean, shiftKey: boolean) => + listData.onSelectChange(p.id, selected, shiftKey) + } + /> + ))} +
+ ); } if (filter.displayMode === DisplayMode.List) { diff --git a/ui/v2.5/src/components/Performers/styles.scss b/ui/v2.5/src/components/Performers/styles.scss index 107a4b7a3..d5d1df7b4 100644 --- a/ui/v2.5/src/components/Performers/styles.scss +++ b/ui/v2.5/src/components/Performers/styles.scss @@ -11,7 +11,7 @@ margin: 10px auto; overflow: hidden; - .image-container .performer { + .performer-image-container .performer { max-height: calc(100vh - 6rem); max-width: 100%; } diff --git a/ui/v2.5/src/components/SceneFilenameParser/ParserInput.tsx b/ui/v2.5/src/components/SceneFilenameParser/ParserInput.tsx index f3016d952..a829d7a84 100644 --- a/ui/v2.5/src/components/SceneFilenameParser/ParserInput.tsx +++ b/ui/v2.5/src/components/SceneFilenameParser/ParserInput.tsx @@ -121,7 +121,7 @@ export const ParserInput: React.FC = ( setPattern(pattern + field.getFieldPattern()); } - const PAGE_SIZE_OPTIONS = ["20", "40", "60", "120"]; + const PAGE_SIZE_OPTIONS = ["20", "40", "60", "120", "250", "500", "1000"]; return ( diff --git a/ui/v2.5/src/components/SceneFilenameParser/SceneFilenameParser.tsx b/ui/v2.5/src/components/SceneFilenameParser/SceneFilenameParser.tsx index a5d28f717..c5133b3c9 100644 --- a/ui/v2.5/src/components/SceneFilenameParser/SceneFilenameParser.tsx +++ b/ui/v2.5/src/components/SceneFilenameParser/SceneFilenameParser.tsx @@ -129,7 +129,7 @@ export const SceneFilenameParser: React.FC = () => { queryParseSceneFilenames(parserFilter, parserInputData) .then((response) => { - const result = response.data.parseSceneFilenames; + const result = response?.data?.parseSceneFilenames; if (result) { parseResults(result.results); setTotalItems(result.count); diff --git a/ui/v2.5/src/components/SceneFilenameParser/SceneParserRow.tsx b/ui/v2.5/src/components/SceneFilenameParser/SceneParserRow.tsx index b95308361..c922f1e69 100644 --- a/ui/v2.5/src/components/SceneFilenameParser/SceneParserRow.tsx +++ b/ui/v2.5/src/components/SceneFilenameParser/SceneParserRow.tsx @@ -101,7 +101,6 @@ export class SceneParserResult { interface ISceneParserFieldProps { parserResult: ParserResult; className?: string; - fieldName: string; onSetChanged: (isSet: boolean) => void; onValueChanged: (value: T) => void; originalParserResult?: ParserResult; @@ -372,7 +371,6 @@ export const SceneParserRow = (props: ISceneParserRowProps) => { {props.showFields.get("Title") && ( @@ -386,7 +384,6 @@ export const SceneParserRow = (props: ISceneParserRowProps) => { {props.showFields.get("Date") && ( @@ -400,7 +397,6 @@ export const SceneParserRow = (props: ISceneParserRowProps) => { {props.showFields.get("Rating") && ( @@ -414,7 +410,6 @@ export const SceneParserRow = (props: ISceneParserRowProps) => { {props.showFields.get("Performers") && ( { {props.showFields.get("Tags") && ( { {props.showFields.get("Studio") && ( { if (err && err.code === 224003) { - this.handleError(); + // When jwplayer has been requested to play but the browser doesn't support the video format. + this.handleError(true); } }); + // this.player.on("meta", (metadata: any) => { if ( metadata.metadataType === "media" && !metadata.width && !metadata.height ) { - // treat this as a decoding error and try the next source - this.handleError(); + // Occurs during preload when videos with supported audio/unsupported video are preloaded. + // Treat this as a decoding error and try the next source without playing. + // However on Safari we get an media event when m3u8 is loaded which needs to be ignored. + const currentFile = this.player.getPlaylistItem().file; + if (currentFile != null && !currentFile.includes("m3u8")) { + const state = this.player.getState(); + const play = state === "buffering" || state === "playing"; + this.handleError(play); + } } }); @@ -143,7 +150,7 @@ export class ScenePlayerImpl extends React.Component< this.player.pause(); } - private handleError() { + private handleError(play: boolean) { const currentFile = this.player.getPlaylistItem(); if (currentFile) { // eslint-disable-next-line no-console @@ -152,8 +159,13 @@ export class ScenePlayerImpl extends React.Component< if (this.tryNextStream()) { // eslint-disable-next-line no-console - console.log("Trying next source in playlist"); + console.log( + `Trying next source in playlist: ${this.playlist.sources[0].file}` + ); this.player.load(this.playlist); + if (play) { + this.player.play(); + } } } @@ -211,28 +223,30 @@ export class ScenePlayerImpl extends React.Component< }; const seekHook = (seekToPosition: number, _videoTag: HTMLVideoElement) => { - if ( - !_videoTag.src || - ScenePlayerImpl.isDirectStream(_videoTag.src) || - _videoTag.src.endsWith(".m3u8") - ) { + if (!_videoTag.src || _videoTag.src.endsWith(".m3u8")) { + return false; + } + + if (ScenePlayerImpl.isDirectStream(_videoTag.src)) { + if (_videoTag.dataset.start) { + /* eslint-disable-next-line no-param-reassign */ + _videoTag.dataset.start = "0"; + } + // direct stream - fall back to default return false; } // remove the start parameter - let { src } = _videoTag; - - const startIndex = src.lastIndexOf("?start="); - if (startIndex !== -1) { - src = src.substring(0, startIndex); - } + const srcUrl = new URL(_videoTag.src); + srcUrl.searchParams.delete("start"); /* eslint-disable no-param-reassign */ _videoTag.dataset.start = seekToPosition.toString(); - - _videoTag.src = `${src}?start=${seekToPosition}`; + srcUrl.searchParams.append("start", seekToPosition.toString()); + _videoTag.src = srcUrl.toString(); /* eslint-enable no-param-reassign */ + _videoTag.play(); // return true to indicate not to fall through to default diff --git a/ui/v2.5/src/components/ScenePlayer/styles.scss b/ui/v2.5/src/components/ScenePlayer/styles.scss index 1614d4001..5dff4b042 100644 --- a/ui/v2.5/src/components/ScenePlayer/styles.scss +++ b/ui/v2.5/src/components/ScenePlayer/styles.scss @@ -41,6 +41,14 @@ $scrubberHeight: 120px; } } +.scene-tabs, +.scene-player-container { + padding-left: 15px; + padding-right: 15px; + position: relative; + width: 100%; +} + $sceneTabWidth: 450px; @media (min-width: 1200px) { @@ -48,20 +56,48 @@ $sceneTabWidth: 450px; flex: 0 0 $sceneTabWidth; max-width: $sceneTabWidth; overflow: auto; + + &.collapsed { + display: none; + } + } + + .scene-divider { + flex: 0 0 15px; + max-width: 15px; + + button { + background-color: transparent; + border: 0; + color: $link-color; + cursor: pointer; + font-size: 10px; + font-weight: 800; + height: 100%; + line-height: 100%; + padding: 0; + text-align: center; + width: 100%; + + &:active:not(:hover), + &:focus:not(:hover) { + background-color: transparent; + border: 0; + box-shadow: none; + } + } } .scene-player-container { - flex: 0 0 calc(100% - #{$sceneTabWidth}); - max-width: calc(100% - #{$sceneTabWidth}); - } -} + flex: 0 0 calc(100% - #{$sceneTabWidth} - 15px); + max-width: calc(100% - #{$sceneTabWidth} - 15px); + padding-left: 0; -.scene-tabs, -.scene-player-container { - padding-left: 15px; - padding-right: 15px; - position: relative; - width: 100%; + &.expanded { + flex: 0 0 calc(100% - 15px); + max-width: calc(100% - 15px); + } + } } .scrubber-wrapper { diff --git a/ui/v2.5/src/components/Scenes/SceneCard.tsx b/ui/v2.5/src/components/Scenes/SceneCard.tsx index b1151348d..d3d3eb04f 100644 --- a/ui/v2.5/src/components/Scenes/SceneCard.tsx +++ b/ui/v2.5/src/components/Scenes/SceneCard.tsx @@ -1,13 +1,62 @@ -import React, { useState } from "react"; +import React, { useEffect, useRef } from "react"; import { Button, ButtonGroup, Card, Form } from "react-bootstrap"; import { Link } from "react-router-dom"; import cx from "classnames"; import * as GQL from "src/core/generated-graphql"; import { useConfiguration } from "src/core/StashService"; -import { useVideoHover } from "src/hooks"; import { Icon, TagLink, HoverPopover, SweatDrops } from "src/components/Shared"; import { TextUtils } from "src/utils"; +interface IScenePreviewProps { + isPortrait: boolean; + image?: string; + video?: string; + soundActive: boolean; +} + +const ScenePreview: React.FC = ({ + image, + video, + isPortrait, + soundActive, +}) => { + const videoEl = useRef(null); + + useEffect(() => { + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.intersectionRatio > 0) + // Catch is necessary due to DOMException if user hovers before clicking on page + videoEl.current?.play().catch(() => {}); + else videoEl.current?.pause(); + }); + }, + { root: document.documentElement } + ); + + if (videoEl.current) observer.observe(videoEl.current); + }); + + useEffect(() => { + if (videoEl?.current?.volume) + videoEl.current.volume = soundActive ? 0.05 : 0; + }, [soundActive]); + + return ( +
+ +
+ ); +}; + interface ISceneCardProps { scene: GQL.SlimSceneDataFragment; selecting?: boolean; @@ -19,11 +68,6 @@ interface ISceneCardProps { export const SceneCard: React.FC = ( props: ISceneCardProps ) => { - const [previewPath, setPreviewPath] = useState(); - const hoverHandler = useVideoHover({ - resetOnMouseLeave: false, - }); - const config = useConfiguration(); const showStudioAsText = config?.data?.configuration.interface.showStudioAsText ?? false; @@ -197,13 +241,28 @@ export const SceneCard: React.FC = ( } } + function maybeRenderGallery() { + if (props.scene.gallery) { + return ( +
+ + + +
+ ); + } + } + function maybeRenderPopoverButtonGroup() { if ( props.scene.tags.length > 0 || props.scene.performers.length > 0 || props.scene.movies.length > 0 || props.scene.scene_markers.length > 0 || - props.scene?.o_counter + props.scene?.o_counter || + props.scene.gallery ) { return ( <> @@ -214,24 +273,13 @@ export const SceneCard: React.FC = ( {maybeRenderMoviePopoverButton()} {maybeRenderSceneMarkerPopoverButton()} {maybeRenderOCounter()} + {maybeRenderGallery()} ); } } - function onMouseEnter() { - if (!previewPath || previewPath === "") { - setPreviewPath(props.scene.paths.preview || ""); - } - hoverHandler.onMouseEnter(); - } - - function onMouseLeave() { - hoverHandler.onMouseLeave(); - setPreviewPath(""); - } - function handleSceneClick( event: React.MouseEvent ) { @@ -272,11 +320,7 @@ export const SceneCard: React.FC = ( let shiftKey = false; return ( - + = ( onDragOver={handleDragOver} draggable={props.selecting} > + {maybeRenderRatingBanner()} {maybeRenderSceneSpecsOverlay()} - {maybeRenderSceneStudioOverlay()}
diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx index 5220df0cb..6b8352ef3 100644 --- a/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx +++ b/ui/v2.5/src/components/Scenes/SceneDetails/Scene.tsx @@ -1,4 +1,4 @@ -import { Tab, Nav, Dropdown } from "react-bootstrap"; +import { Tab, Nav, Dropdown, Button, ButtonGroup } from "react-bootstrap"; import queryString from "query-string"; import React, { useEffect, useState } from "react"; import { useParams, useLocation, useHistory, Link } from "react-router-dom"; @@ -12,7 +12,7 @@ import { useSceneGenerateScreenshot, } from "src/core/StashService"; import { GalleryViewer } from "src/components/Galleries/GalleryViewer"; -import { LoadingIndicator, Icon } from "src/components/Shared"; +import { ErrorMessage, LoadingIndicator, Icon } from "src/components/Shared"; import { useToast } from "src/hooks"; import { ScenePlayer } from "src/components/ScenePlayer"; import { TextUtils, JWUtils } from "src/utils"; @@ -25,17 +25,23 @@ import { OCounterButton } from "./OCounterButton"; import { SceneMoviePanel } from "./SceneMoviePanel"; import { DeleteScenesDialog } from "../DeleteScenesDialog"; import { SceneGenerateDialog } from "../SceneGenerateDialog"; +import { SceneVideoFilterPanel } from "./SceneVideoFilterPanel"; + +interface ISceneParams { + id?: string; +} export const Scene: React.FC = () => { - const { id = "new" } = useParams(); + const { id = "new" } = useParams(); const location = useLocation(); const history = useHistory(); const Toast = useToast(); const [generateScreenshot] = useSceneGenerateScreenshot(); const [timestamp, setTimestamp] = useState(getInitialTimestamp()); + const [collapsed, setCollapsed] = useState(false); - const [scene, setScene] = useState(); const { data, error, loading } = useFindScene(id); + const scene = data?.findScene; const { data: sceneStreams, error: streamableError, @@ -54,10 +60,6 @@ export const Scene: React.FC = () => { const queryParams = queryString.parse(location.search); const autoplay = queryParams?.autoplay === "true"; - useEffect(() => { - if (data?.findScene) setScene(data.findScene); - }, [data]); - function getInitialTimestamp() { const params = queryString.parse(location.search); const initialTimestamp = params?.t ?? "0"; @@ -67,17 +69,10 @@ export const Scene: React.FC = () => { ); } - const updateOCounter = (newValue: number) => { - const modifiedScene = { ...scene } as GQL.SceneDataFragment; - modifiedScene.o_counter = newValue; - setScene(modifiedScene); - }; - const onIncrementClick = async () => { try { setOLoading(true); - const result = await incrementO(); - if (result.data) updateOCounter(result.data.sceneIncrementO); + await incrementO(); } catch (e) { Toast.error(e); } finally { @@ -88,8 +83,7 @@ export const Scene: React.FC = () => { const onDecrementClick = async () => { try { setOLoading(true); - const result = await decrementO(); - if (result.data) updateOCounter(result.data.sceneDecrementO); + await decrementO(); } catch (e) { Toast.error(e); } finally { @@ -100,8 +94,7 @@ export const Scene: React.FC = () => { const onResetClick = async () => { try { setOLoading(true); - const result = await resetO(); - if (result.data) updateOCounter(result.data.sceneResetO); + await resetO(); } catch (e) { Toast.error(e); } finally { @@ -210,7 +203,7 @@ export const Scene: React.FC = () => { return ( setActiveTabKey(k)} + onSelect={(k) => k && setActiveTabKey(k)} >
@@ -274,6 +272,9 @@ export const Scene: React.FC = () => { ) : ( "" )} + + + { setScene(newScene)} onDelete={() => setIsDeleteAlertOpen(true)} /> @@ -311,18 +311,24 @@ export const Scene: React.FC = () => { }; }); - if (loading || streamableLoading || !scene || !data?.findScene) { - return ; + function getCollapseButtonText() { + return collapsed ? ">" : "<"; } - if (error) return
{error.message}
; - if (streamableError) return
{streamableError.message}
; + if (loading || streamableLoading) return ; + if (error) return ; + if (streamableError) return ; + if (!scene) return ; return (
{maybeRenderSceneGenerateDialog()} {maybeRenderDeleteDialog()} -
+
{scene.studio && (

@@ -341,7 +347,16 @@ export const Scene: React.FC = () => {

{renderTabs()}
-
+
+ +
+
void; onDelete: () => void; } @@ -54,6 +55,7 @@ export const SceneEditPanel: React.FC = (props: IProps) => { >(new Map()); const [tagIds, setTagIds] = useState(); const [coverImage, setCoverImage] = useState(); + const [stashIDs, setStashIDs] = useState([]); const Scrapers = useListSceneScrapers(); const [queryableScrapers, setQueryableScrapers] = useState([]); @@ -62,6 +64,8 @@ export const SceneEditPanel: React.FC = (props: IProps) => { const [coverImagePreview, setCoverImagePreview] = useState(); + const stashConfig = useConfiguration(); + // Network state const [isLoading, setIsLoading] = useState(true); @@ -117,7 +121,7 @@ export const SceneEditPanel: React.FC = (props: IProps) => { ); setQueryableScrapers(newQueryableScrapers); - }, [Scrapers]); + }, [Scrapers, stashConfig]); useEffect(() => { let changed = false; @@ -171,6 +175,7 @@ export const SceneEditPanel: React.FC = (props: IProps) => { setMovieSceneIndexes(movieSceneIdx); setPerformerIds(perfIds); setTagIds(tIds); + setStashIDs(state?.stash_ids ?? []); } useEffect(() => { @@ -195,6 +200,10 @@ export const SceneEditPanel: React.FC = (props: IProps) => { movies: makeMovieInputs(), tag_ids: tagIds, cover_image: coverImage, + stash_ids: stashIDs.map((s) => ({ + stash_id: s.stash_id, + endpoint: s.endpoint, + })), }; } @@ -222,7 +231,6 @@ export const SceneEditPanel: React.FC = (props: IProps) => { try { const result = await updateScene(); if (result.data?.sceneUpdate) { - props.onUpdate(result.data.sceneUpdate); Toast.success({ content: "Updated scene" }); } } catch (e) { @@ -231,6 +239,15 @@ export const SceneEditPanel: React.FC = (props: IProps) => { setIsLoading(false); } + const removeStashID = (stashID: GQL.StashIdInput) => { + setStashIDs( + stashIDs.filter( + (s) => + !(s.endpoint === stashID.endpoint && s.stash_id === stashID.stash_id) + ) + ); + }; + function renderTableMovies() { return ( = (props: IProps) => { ImageUtils.onImageChange(event, onImageLoad); } + async function onScrapeStashBoxClicked(stashBoxIndex: number) { + setIsLoading(true); + try { + const result = await queryStashBoxScene(stashBoxIndex, props.scene.id); + if (!result.data || !result.data.queryStashBoxScene) { + return; + } + + if (result.data.queryStashBoxScene.length > 0) { + setScrapedScene(result.data.queryStashBoxScene[0]); + } else { + Toast.success({ + content: "No scenes found", + }); + } + } catch (e) { + Toast.error(e); + } finally { + setIsLoading(false); + } + } + + // function onStashBoxQueryClicked(/* stashBoxIndex: number */) { + // TODO + // } + async function onScrapeClicked(scraper: GQL.Scraper) { setIsLoading(true); try { const result = await queryScrapeScene(scraper.id, getSceneInput()); if (!result.data || !result.data.scrapeScene) { + Toast.success({ + content: "No scenes found", + }); return; } setScrapedScene(result.data.scrapeScene); @@ -309,8 +355,23 @@ export const SceneEditPanel: React.FC = (props: IProps) => { } function renderScraperMenu() { + const stashBoxes = stashConfig.data?.configuration.general.stashBoxes ?? []; + + // TODO - change name based on stashbox configuration return ( - + + {stashBoxes.map((s, index) => ( + onScrapeStashBoxClicked(index)} + > + stash-box + + ))} {queryableScrapers.map((s) => ( onScrapeClicked(s)}> {s.name} @@ -326,6 +387,44 @@ export const SceneEditPanel: React.FC = (props: IProps) => { ); } + function maybeRenderStashboxQueryButton() { + // const stashBoxes = stashConfig.data?.configuration.general.stashBoxes ?? []; + // if (stashBoxes.length === 0) { + // return; + // } + // TODO - hide this button for now, with the view to add it when we get + // the query dialog going + // if (stashBoxes.length === 1) { + // return ( + // + // ); + // } + // // TODO - change name based on stashbox configuration + // return ( + // + // + // + // + // + // {stashBoxes.map((s, index) => ( + // onStashBoxQueryClicked(index)} + // > + // stash-box + // + // ))} + // + // + // ); + } + function urlScrapable(scrapedUrl: string): boolean { return (Scrapers?.data?.listSceneScrapers ?? []).some((s) => (s?.scene?.urls ?? []).some((u) => scrapedUrl.includes(u)) @@ -432,7 +531,7 @@ export const SceneEditPanel: React.FC = (props: IProps) => {
{maybeRenderScrapeDialog()}
-
+
@@ -444,7 +543,10 @@ export const SceneEditPanel: React.FC = (props: IProps) => { Delete
- {renderScraperMenu()} + + {maybeRenderStashboxQueryButton()} + {renderScraperMenu()} +
@@ -572,6 +674,40 @@ export const SceneEditPanel: React.FC = (props: IProps) => {
+
+ + StashIDs +
    + {stashIDs.map((stashID) => { + const base = stashID.endpoint.match(/https?:\/\/.*?\//)?.[0]; + const link = base ? ( + + {stashID.stash_id} + + ) : ( + stashID.stash_id + ); + return ( +
  • + + {link} +
  • + ); + })} +
+
+
Details diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/SceneFileInfoPanel.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/SceneFileInfoPanel.tsx index 6f464e6de..4edff654b 100644 --- a/ui/v2.5/src/components/Scenes/SceneDetails/SceneFileInfoPanel.tsx +++ b/ui/v2.5/src/components/Scenes/SceneDetails/SceneFileInfoPanel.tsx @@ -189,6 +189,39 @@ export const SceneFileInfoPanel: React.FC = ( ); } + function renderStashIDs() { + if (!props.scene.stash_ids.length) { + return; + } + + return ( +
+ StashIDs +
    + {props.scene.stash_ids.map((stashID) => { + const base = stashID.endpoint.match(/https?:\/\/.*?\//)?.[0]; + const link = base ? ( + + {stashID.stash_id} + + ) : ( + stashID.stash_id + ); + return ( +
  • + {link} +
  • + ); + })} +
+
+ ); + } + return (
{renderOSHash()} @@ -203,6 +236,7 @@ export const SceneFileInfoPanel: React.FC = ( {renderVideoCodec()} {renderAudioCodec()} {renderUrl()} + {renderStashIDs()}
); }; diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/SceneScrapeDialog.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/SceneScrapeDialog.tsx index 699fd8689..b2ed6f860 100644 --- a/ui/v2.5/src/components/Scenes/SceneDetails/SceneScrapeDialog.tsx +++ b/ui/v2.5/src/components/Scenes/SceneDetails/SceneScrapeDialog.tsx @@ -18,6 +18,7 @@ import { useTagCreate, } from "src/core/StashService"; import { useToast } from "src/hooks"; +import { DurationUtils } from "src/utils"; function renderScrapedStudio( result: ScrapeResult, @@ -357,7 +358,7 @@ export const SceneScrapeDialog: React.FC = ( } async function createNewPerformer(toCreate: GQL.ScrapedScenePerformer) { - let performerInput: GQL.PerformerCreateInput = {}; + let performerInput: GQL.PerformerCreateInput = { name: "" }; try { performerInput = Object.assign(performerInput, toCreate); const result = await createPerformer({ @@ -395,6 +396,20 @@ export const SceneScrapeDialog: React.FC = ( let movieInput: GQL.MovieCreateInput = { name: "" }; try { movieInput = Object.assign(movieInput, toCreate); + + // #788 - convert duration and rating to the correct type + movieInput.duration = DurationUtils.stringToSeconds( + toCreate.duration ?? undefined + ); + if (!movieInput.duration) { + movieInput.duration = undefined; + } + + movieInput.rating = parseInt(toCreate.rating ?? "0", 10); + if (!movieInput.rating || Number.isNaN(movieInput.rating)) { + movieInput.rating = undefined; + } + const result = await createMovie({ variables: movieInput, }); diff --git a/ui/v2.5/src/components/Scenes/SceneDetails/SceneVideoFilterPanel.tsx b/ui/v2.5/src/components/Scenes/SceneDetails/SceneVideoFilterPanel.tsx new file mode 100644 index 000000000..ab7323f34 --- /dev/null +++ b/ui/v2.5/src/components/Scenes/SceneDetails/SceneVideoFilterPanel.tsx @@ -0,0 +1,665 @@ +import React, { useState } from "react"; +import { Button, Form } from "react-bootstrap"; +import { JWUtils } from "../../../utils"; +import * as GQL from "../../../core/generated-graphql"; + +interface ISceneVideoFilterPanelProps { + scene: GQL.SceneDataFragment; +} + +// References +// https://yoksel.github.io/svg-filters/#/ +// https://codepen.io/chriscoyier/pen/zbakI +// http://xahlee.info/js/js_scritping_svg_basics.html#:~:text=Just%20use%20JavaScript%20to%20script,%2C%20path%2C%20%E2%80%A6.). + +type SliderRange = { + min: number; + default: number; + max: number; + divider: number; +}; + +export const SceneVideoFilterPanel: React.FC = ( + props: ISceneVideoFilterPanelProps +) => { + const contrastRange: SliderRange = { + min: 0, + default: 100, + max: 200, + divider: 1, + }; + const brightnessRange: SliderRange = { + min: 0, + default: 100, + max: 200, + divider: 1, + }; + const gammaRange: SliderRange = { + min: 0, + default: 100, + max: 200, + divider: 200, + }; + const saturateRange: SliderRange = { + min: 0, + default: 100, + max: 200, + divider: 1, + }; + const hueRotateRange: SliderRange = { + min: 0, + default: 0, + max: 360, + divider: 1, + }; + const whiteBalanceRange: SliderRange = { + min: 0, + default: 100, + max: 200, + divider: 200, + }; + const colourRange: SliderRange = { + min: 0, + default: 100, + max: 200, + divider: 1, + }; + const blurRange: SliderRange = { min: 0, default: 0, max: 250, divider: 10 }; + const rotateRange: SliderRange = { + min: 0, + default: 2, + max: 4, + divider: 1 / 90, + }; + const scaleRange: SliderRange = { + min: 0, + default: 100, + max: 200, + divider: 1, + }; + const aspectRatioRange: SliderRange = { + min: 0, + default: 150, + max: 300, + divider: 100, + }; + + const [contrastValue, setContrastValue] = useState(contrastRange.default); + const [brightnessValue, setBrightnessValue] = useState( + brightnessRange.default + ); + const [gammaValue, setGammaValue] = useState(gammaRange.default); + const [saturateValue, setSaturateValue] = useState(saturateRange.default); + const [hueRotateValue, setHueRotateValue] = useState(hueRotateRange.default); + const [whiteBalanceValue, setWhiteBalanceValue] = useState( + whiteBalanceRange.default + ); + const [redValue, setRedValue] = useState(colourRange.default); + const [greenValue, setGreenValue] = useState(colourRange.default); + const [blueValue, setBlueValue] = useState(colourRange.default); + const [blurValue, setBlurValue] = useState(blurRange.default); + const [rotateValue, setRotateValue] = useState(rotateRange.default); + const [scaleValue, setScaleValue] = useState(scaleRange.default); + const [aspectRatioValue, setAspectRatioValue] = useState( + aspectRatioRange.default + ); + + function updateVideoStyle() { + const playerId = JWUtils.playerID; + const playerVideoElement = document + .getElementById(playerId) + ?.getElementsByClassName("jw-video")[0]; + + if (playerVideoElement != null) { + let styleString = "filter:"; + let style = playerVideoElement.attributes.getNamedItem("style"); + + if (style == null) { + style = document.createAttribute("style"); + playerVideoElement.attributes.setNamedItem(style); + } + + if ( + whiteBalanceValue !== whiteBalanceRange.default || + redValue !== colourRange.default || + greenValue !== colourRange.default || + blueValue !== colourRange.default || + gammaValue !== gammaRange.default + ) { + styleString += " url(#videoFilter)"; + } + + if (contrastValue !== contrastRange.default) { + styleString += ` contrast(${contrastValue}%)`; + } + + if (brightnessValue !== brightnessRange.default) { + styleString += ` brightness(${brightnessValue}%)`; + } + + if (saturateValue !== saturateRange.default) { + styleString += ` saturate(${saturateValue}%)`; + } + + if (hueRotateValue !== hueRotateRange.default) { + styleString += ` hue-rotate(${hueRotateValue}deg)`; + } + + if (blurValue > blurRange.default) { + styleString += ` blur(${blurValue / blurRange.divider}px)`; + } + + styleString += "; transform:"; + + if (rotateValue !== rotateRange.default) { + styleString += ` rotate(${ + (rotateValue - rotateRange.default) / rotateRange.divider + }deg)`; + } + + if ( + scaleValue !== scaleRange.default || + aspectRatioValue !== aspectRatioRange.default + ) { + let xScale = scaleValue / scaleRange.divider / 100.0; + let yScale = scaleValue / scaleRange.divider / 100.0; + + if (aspectRatioValue > aspectRatioRange.default) { + xScale *= + (aspectRatioRange.divider + + aspectRatioValue - + aspectRatioRange.default) / + aspectRatioRange.divider; + } else if (aspectRatioValue < aspectRatioRange.default) { + yScale *= + (aspectRatioRange.divider + + aspectRatioRange.default - + aspectRatioValue) / + aspectRatioRange.divider; + } + + styleString += ` scale(${xScale},${yScale})`; + } + + style.value = `${styleString};`; + } + } + + function updateVideoFilters() { + const filterContainer = document.getElementById("video-filter-container"); + + if (filterContainer == null) { + return; + } + + const svg1 = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + const videoFilter = document.createElementNS( + "http://www.w3.org/2000/svg", + "filter" + ); + videoFilter.setAttribute("id", "videoFilter"); + + if ( + whiteBalanceValue !== whiteBalanceRange.default || + redValue !== colourRange.default || + greenValue !== colourRange.default || + blueValue !== colourRange.default + ) { + const feColorMatrix = document.createElementNS( + "http://www.w3.org/2000/svg", + "feColorMatrix" + ); + feColorMatrix.setAttribute( + "values", + `${ + 1 + + (whiteBalanceValue - whiteBalanceRange.default) / + whiteBalanceRange.divider + + (redValue - colourRange.default) / colourRange.divider + } 0 0 0 0 0 ${ + 1.0 + (greenValue - colourRange.default) / colourRange.divider + } 0 0 0 0 0 ${ + 1 - + (whiteBalanceValue - whiteBalanceRange.default) / + whiteBalanceRange.divider + + (blueValue - colourRange.default) / colourRange.divider + } 0 0 0 0 0 1.0 0` + ); + videoFilter.appendChild(feColorMatrix); + } + + if (gammaValue !== gammaRange.default) { + const feComponentTransfer = document.createElementNS( + "http://www.w3.org/2000/svg", + "feComponentTransfer" + ); + + const feFuncR = document.createElementNS( + "http://www.w3.org/2000/svg", + "feFuncR" + ); + feFuncR.setAttribute("type", "gamma"); + feFuncR.setAttribute("amplitude", "1.0"); + feFuncR.setAttribute( + "exponent", + `${1 + (gammaRange.default - gammaValue) / gammaRange.divider}` + ); + feFuncR.setAttribute("offset", "0.0"); + feComponentTransfer.appendChild(feFuncR); + + const feFuncG = document.createElementNS( + "http://www.w3.org/2000/svg", + "feFuncG" + ); + feFuncG.setAttribute("type", "gamma"); + feFuncG.setAttribute("amplitude", "1.0"); + feFuncG.setAttribute( + "exponent", + `${1 + (gammaRange.default - gammaValue) / gammaRange.divider}` + ); + feFuncG.setAttribute("offset", "0.0"); + feComponentTransfer.appendChild(feFuncG); + + const feFuncB = document.createElementNS( + "http://www.w3.org/2000/svg", + "feFuncB" + ); + feFuncB.setAttribute("type", "gamma"); + feFuncB.setAttribute("amplitude", "1.0"); + feFuncB.setAttribute( + "exponent", + `${1 + (gammaRange.default - gammaValue) / gammaRange.divider}` + ); + feFuncB.setAttribute("offset", "0.0"); + feComponentTransfer.appendChild(feFuncB); + + const feFuncA = document.createElementNS( + "http://www.w3.org/2000/svg", + "feFuncA" + ); + feFuncA.setAttribute("type", "gamma"); + feFuncA.setAttribute("amplitude", "1.0"); + feFuncA.setAttribute("exponent", "1.0"); + feFuncA.setAttribute("offset", "0.0"); + feComponentTransfer.appendChild(feFuncA); + + videoFilter.appendChild(feComponentTransfer); + } + + svg1.appendChild(videoFilter); + + // Add or Replace existing svg + const filterContainerSvgs = filterContainer.getElementsByTagNameNS( + "http://www.w3.org/2000/svg", + "svg" + ); + if (filterContainerSvgs.length === 0) { + // attach container to document + filterContainer.appendChild(svg1); + } else { + // assume only one svg... maybe issue + filterContainer.replaceChild(svg1, filterContainerSvgs[0]); + } + } + + interface ISliderProps { + title: string; + className?: string; + range: SliderRange; + value: number; + setValue: (value: React.SetStateAction) => void; + displayValue: string; + } + + function renderSlider(sliderProps: ISliderProps) { + return ( +
+ {sliderProps.title} + + ) => + sliderProps.setValue(Number.parseInt(e.currentTarget.value, 10)) + } + /> + + sliderProps.setValue(sliderProps.range.default)} + onKeyPress={() => sliderProps.setValue(sliderProps.range.default)} + > + {sliderProps.displayValue} + +
+ ); + } + + function renderBlur() { + return renderSlider({ + title: "Blur", + range: blurRange, + value: blurValue, + setValue: setBlurValue, + displayValue: `${blurValue / blurRange.divider}px`, + }); + } + + function renderContrast() { + return renderSlider({ + title: "Contrast", + className: "contrast-slider", + range: contrastRange, + value: contrastValue, + setValue: setContrastValue, + displayValue: `${contrastValue / brightnessRange.divider}%`, + }); + } + + function renderBrightness() { + return renderSlider({ + title: "Brightness", + className: "brightness-slider", + range: brightnessRange, + value: brightnessValue, + setValue: setBrightnessValue, + displayValue: `${brightnessValue / brightnessRange.divider}%`, + }); + } + + function renderGammaSlider() { + return renderSlider({ + title: "Gamma", + className: "gamma-slider", + range: gammaRange, + value: gammaValue, + setValue: setGammaValue, + displayValue: `${(gammaValue - gammaRange.default) / gammaRange.divider}`, + }); + } + + function renderSaturate() { + return renderSlider({ + title: "Saturation", + className: "saturation-slider", + range: saturateRange, + value: saturateValue, + setValue: setSaturateValue, + displayValue: `${saturateValue / saturateRange.divider}%`, + }); + } + + function renderHueRotateSlider() { + return renderSlider({ + title: "Hue", + className: "hue-rotate-slider", + range: hueRotateRange, + value: hueRotateValue, + setValue: setHueRotateValue, + displayValue: `${hueRotateValue / hueRotateRange.divider}\xB0`, + }); + } + + function renderWhiteBalance() { + return renderSlider({ + title: "Warmth", + className: "white-balance-slider", + range: whiteBalanceRange, + value: whiteBalanceValue, + setValue: setWhiteBalanceValue, + displayValue: `${ + (whiteBalanceValue - whiteBalanceRange.default) / + whiteBalanceRange.divider + }`, + }); + } + + function renderRedSlider() { + return renderSlider({ + title: "Red", + className: "red-slider", + range: colourRange, + value: redValue, + setValue: setRedValue, + displayValue: `${ + (redValue - colourRange.default) / colourRange.divider + }%`, + }); + } + + function renderGreenSlider() { + return renderSlider({ + title: "Green", + className: "green-slider", + range: colourRange, + value: greenValue, + setValue: setGreenValue, + displayValue: `${ + (greenValue - colourRange.default) / colourRange.divider + }%`, + }); + } + + function renderBlueSlider() { + return renderSlider({ + title: "Blue", + className: "blue-slider", + range: colourRange, + value: blueValue, + setValue: setBlueValue, + displayValue: `${ + (blueValue - colourRange.default) / colourRange.divider + }%`, + }); + } + + function renderRotate() { + return renderSlider({ + title: "Rotate", + range: rotateRange, + value: rotateValue, + setValue: setRotateValue, + displayValue: `${ + (rotateValue - rotateRange.default) / rotateRange.divider + }\xB0`, + }); + } + + function renderScale() { + return renderSlider({ + title: "Scale", + range: scaleRange, + value: scaleValue, + setValue: setScaleValue, + displayValue: `${scaleValue / scaleRange.divider}%`, + }); + } + + function renderAspectRatio() { + return renderSlider({ + title: "Aspect", + range: aspectRatioRange, + value: aspectRatioValue, + setValue: setAspectRatioValue, + displayValue: `${ + (aspectRatioValue - aspectRatioRange.default) / aspectRatioRange.divider + }`, + }); + } + + function onRotateAndScale(direction: number) { + if (direction === 0) { + // Left -90 + setRotateValue(1); + } else { + // Right +90 + setRotateValue(3); + } + + // Calculate Required Scaling. + const sceneWidth = props.scene.file.width ?? 1; + const sceneHeight = props.scene.file.height ?? 1; + const sceneAspectRatio = sceneWidth / sceneHeight; + const sceneNewAspectRatio = sceneHeight / sceneWidth; + + const playerId = JWUtils.playerID; + const playerVideoElement = document + .getElementById(playerId) + ?.getElementsByClassName("jw-video")[0]; + + const playerWidth = playerVideoElement?.clientWidth ?? 1; + const playerHeight = playerVideoElement?.clientHeight ?? 1; + const playerAspectRation = playerWidth / playerHeight; + + // rs > ri ? (wi * hs/hi, hs) : (ws, hi * ws/wi) + // Determine if video is currently constrained by player height or width. + let scaledVideoHeight = 0; + let scaledVideoWidth = 0; + if (playerAspectRation > sceneAspectRatio) { + // Video has it's width scaled + // Video is constrained by it's height + scaledVideoHeight = playerHeight; + scaledVideoWidth = (playerHeight / sceneHeight) * sceneWidth; + } else { + // Video has it's height scaled + // Video is constrained by it's width + scaledVideoWidth = playerWidth; + scaledVideoHeight = (playerWidth / sceneWidth) * sceneHeight; + } + + // but now the video is rotated + let scaleFactor = 1; + if (playerAspectRation > sceneNewAspectRatio) { + // Rotated video will be constrained by it's height + // so we need to scaledVideoWidth to match the player height + scaleFactor = playerHeight / scaledVideoWidth; + } else { + // Rotated video will be constrained by it's width + // so we need to scaledVideoHeight to match the player width + scaleFactor = playerWidth / scaledVideoHeight; + } + + setScaleValue(scaleFactor * 100); + } + + function renderRotateAndScale() { + return ( +
+ + + + + + +
+ ); + } + + function onResetFilters() { + setContrastValue(contrastRange.default); + setBrightnessValue(brightnessRange.default); + setGammaValue(gammaRange.default); + setSaturateValue(saturateRange.default); + setHueRotateValue(hueRotateRange.default); + setWhiteBalanceValue(whiteBalanceRange.default); + setRedValue(colourRange.default); + setGreenValue(colourRange.default); + setBlueValue(colourRange.default); + setBlurValue(blurRange.default); + } + + function onResetTransforms() { + setScaleValue(scaleRange.default); + setRotateValue(rotateRange.default); + setAspectRatioValue(aspectRatioRange.default); + } + + function renderResetButton() { + return ( +
+ + + + + + +
+ ); + } + + function renderFilterContainer() { + return
; + } + + // On render update video style. + updateVideoFilters(); + updateVideoStyle(); + + return ( +
+
+ +
Filters
+
+
+ {renderBrightness()} + {renderContrast()} + {renderGammaSlider()} + {renderSaturate()} + {renderHueRotateSlider()} + {renderWhiteBalance()} + {renderRedSlider()} + {renderGreenSlider()} + {renderBlueSlider()} + {renderBlur()} +
+ +
Transforms
+
+
+ {renderRotate()} + {renderScale()} + {renderAspectRatio()} +
+ +
Actions
+
+
+ {renderRotateAndScale()} + {renderResetButton()} + {renderFilterContainer()} +
+ ); +}; diff --git a/ui/v2.5/src/components/Scenes/SceneGenerateDialog.tsx b/ui/v2.5/src/components/Scenes/SceneGenerateDialog.tsx index 5f3372742..abf950e9e 100644 --- a/ui/v2.5/src/components/Scenes/SceneGenerateDialog.tsx +++ b/ui/v2.5/src/components/Scenes/SceneGenerateDialog.tsx @@ -64,7 +64,6 @@ export const SceneGenerateDialog: React.FC = ( imagePreviews: previews && imagePreviews, markers, transcodes, - thumbnails: false, overwrite, sceneIDs: props.selectedIds, previewOptions: { diff --git a/ui/v2.5/src/components/Scenes/SceneList.tsx b/ui/v2.5/src/components/Scenes/SceneList.tsx index 572554905..e6045a902 100644 --- a/ui/v2.5/src/components/Scenes/SceneList.tsx +++ b/ui/v2.5/src/components/Scenes/SceneList.tsx @@ -10,12 +10,14 @@ import { useScenesList } from "src/hooks"; import { ListFilterModel } from "src/models/list-filter/filter"; import { DisplayMode } from "src/models/list-filter/types"; import { showWhenSelected } from "src/hooks/ListHook"; +import Tagger from "src/components/Tagger"; import { WallPanel } from "../Wall/WallPanel"; import { SceneCard } from "./SceneCard"; import { SceneListTable } from "./SceneListTable"; import { EditScenesDialog } from "./EditScenesDialog"; import { DeleteScenesDialog } from "./DeleteScenesDialog"; import { SceneGenerateDialog } from "./SceneGenerateDialog"; +import { ExportDialog } from "../Shared/ExportDialog"; interface ISceneList { filterHook?: (filter: ListFilterModel) => ListFilterModel; @@ -28,6 +30,8 @@ export const SceneList: React.FC = ({ }) => { const history = useHistory(); const [isGenerateDialogOpen, setIsGenerateDialogOpen] = useState(false); + const [isExportDialogOpen, setIsExportDialogOpen] = useState(false); + const [isExportAll, setIsExportAll] = useState(false); const otherOperations = [ { @@ -39,6 +43,15 @@ export const SceneList: React.FC = ({ onClick: generate, isDisplayed: showWhenSelected, }, + { + text: "Export...", + onClick: onExport, + isDisplayed: showWhenSelected, + }, + { + text: "Export all...", + onClick: onExportAll, + }, ]; const addKeybinds = ( @@ -96,6 +109,16 @@ export const SceneList: React.FC = ({ setIsGenerateDialogOpen(true); } + async function onExport() { + setIsExportAll(false); + setIsExportDialogOpen(true); + } + + async function onExportAll() { + setIsExportAll(true); + setIsExportDialogOpen(true); + } + function maybeRenderSceneGenerateDialog(selectedIds: Set) { if (isGenerateDialogOpen) { return ( @@ -111,6 +134,26 @@ export const SceneList: React.FC = ({ } } + function maybeRenderSceneExportDialog(selectedIds: Set) { + if (isExportDialogOpen) { + return ( + <> + { + setIsExportDialogOpen(false); + }} + /> + + ); + } + } + function renderEditScenesDialog( selectedScenes: SlimSceneDataFragment[], onClose: (applied: boolean) => void @@ -176,6 +219,9 @@ export const SceneList: React.FC = ({ if (filter.displayMode === DisplayMode.Wall) { return ; } + if (filter.displayMode === DisplayMode.Tagger) { + return ; + } } function renderContent( @@ -187,6 +233,7 @@ export const SceneList: React.FC = ({ return ( <> {maybeRenderSceneGenerateDialog(selectedIds)} + {maybeRenderSceneExportDialog(selectedIds)} {renderScenes(result, filter, selectedIds, zoomIndex)} ); diff --git a/ui/v2.5/src/components/Scenes/SceneListTable.tsx b/ui/v2.5/src/components/Scenes/SceneListTable.tsx index c33cb4e13..f85ddf5ba 100644 --- a/ui/v2.5/src/components/Scenes/SceneListTable.tsx +++ b/ui/v2.5/src/components/Scenes/SceneListTable.tsx @@ -1,9 +1,11 @@ +// @ts-nocheck /* eslint-disable jsx-a11y/control-has-associated-label */ import React from "react"; -import { Table } from "react-bootstrap"; +import { Table, Button } from "react-bootstrap"; import { Link } from "react-router-dom"; import * as GQL from "src/core/generated-graphql"; import { NavUtils, TextUtils } from "src/utils"; +import { Icon } from "src/components/Shared"; interface ISceneListTableProps { scenes: GQL.SlimSceneDataFragment[]; @@ -26,15 +28,14 @@ export const SceneListTable: React.FC = ( )); - const renderMovies = (movies: Partial[]) => { - return movies.map((sceneMovie) => + const renderMovies = (scene: GQL.SlimSceneDataFragment) => + scene.movies.map((sceneMovie) => !sceneMovie.movie ? undefined : (
{sceneMovie.movie.name}
) ); - }; const renderSceneRow = (scene: GQL.SlimSceneDataFragment) => ( @@ -68,7 +69,16 @@ export const SceneListTable: React.FC = ( )} - {renderMovies(scene.movies)} + {renderMovies(scene)} + + {scene.gallery && ( + + )} + ); @@ -85,6 +95,7 @@ export const SceneListTable: React.FC = ( Performers Studio Movies + Gallery {props.scenes.map(renderSceneRow)} diff --git a/ui/v2.5/src/components/Scenes/SceneMarkerList.tsx b/ui/v2.5/src/components/Scenes/SceneMarkerList.tsx index 10a9cb364..62d2e664d 100644 --- a/ui/v2.5/src/components/Scenes/SceneMarkerList.tsx +++ b/ui/v2.5/src/components/Scenes/SceneMarkerList.tsx @@ -40,6 +40,7 @@ export const SceneMarkerList: React.FC = ({ filterHook }) => { renderContent, filterHook, addKeybinds, + persistState: true, }); async function playRandom( diff --git a/ui/v2.5/src/components/Scenes/styles.scss b/ui/v2.5/src/components/Scenes/styles.scss index 5b16f4cfd..6368a68ab 100644 --- a/ui/v2.5/src/components/Scenes/styles.scss +++ b/ui/v2.5/src/components/Scenes/styles.scss @@ -163,13 +163,9 @@ textarea.scene-description { text-transform: uppercase; } -.scene-card { - &.card { - overflow: hidden; - padding: 0; - } - - .scene-card-check { +.scene-card, +.gallery-card { + &-check { left: 0.5rem; margin-top: -12px; opacity: 0; @@ -190,6 +186,34 @@ textarea.scene-description { transition: opacity 0.5s; } + &-preview { + display: flex; + justify-content: center; + margin-bottom: 5px; + position: relative; + + &-image, + &-video { + height: 100%; + object-fit: cover; + width: 100%; + } + + &-video { + position: absolute; + top: -9999px; + transition: top 0s; + transition-delay: 0s; + } + + &.portrait { + .scene-card-preview-image, + .scene-card-preview-video { + object-fit: contain; + } + } + } + &:hover { .scene-specs-overlay, .rating-banner, @@ -207,9 +231,19 @@ textarea.scene-description { opacity: 0.75; transition: opacity 0.5s; } + + .scene-card-preview-video { + top: 0; + transition-delay: 0.2s; + } } } +.scene-card.card { + overflow: hidden; + padding: 0; +} + .scene-cover { display: block; margin-bottom: 10px; @@ -232,6 +266,191 @@ textarea.scene-description { word-wrap: break-word; } +input[type="range"].filter-slider { + height: 100%; + margin: 0; + padding-left: 0; + padding-right: 0; +} + +@mixin contrast-slider() { + background: rgb(255, 255, 255); + background: linear-gradient( + -1deg, + rgba(255, 255, 255, 1) 0%, + rgba(255, 255, 255, 1) 40%, + rgba(0, 0, 0, 1) 60%, + rgba(0, 0, 0, 1) 100% + ), + linear-gradient(90deg, rgba(61, 61, 61, 1) 0%, rgba(255, 255, 255, 0) 100%); + background-blend-mode: color; +} + +input[type="range"].contrast-slider { + &::-webkit-slider-runnable-track { + @include contrast-slider; + } + + &::-moz-range-track { + @include contrast-slider; + } + + &::-ms-track { + @include contrast-slider; + } +} + +@mixin brightness-slider() { + background: rgb(41, 41, 41); + background: linear-gradient( + 90deg, + rgba(41, 41, 41, 1) 0%, + rgba(255, 255, 255, 1) 100% + ); +} + +input[type="range"].brightness-slider { + &::-webkit-slider-runnable-track { + @include brightness-slider; + } + + &::-moz-range-track { + @include brightness-slider; + } + + &::-ms-track { + @include brightness-slider; + } +} + +@mixin saturation-slider() { + background: rgb(198, 198, 199); + background: linear-gradient( + 90deg, + rgba(198, 198, 199, 1) 0%, + rgba(255, 71, 71, 1) 100% + ); +} + +input[type="range"].saturation-slider { + &::-webkit-slider-runnable-track { + @include saturation-slider; + } + + &::-moz-range-track { + @include saturation-slider; + } + + &::-ms-track { + @include saturation-slider; + } +} + +@mixin hue-rotate-slider() { + background: rgb(198, 198, 199); + background: linear-gradient( + to right, + orange, + yellow, + green, + cyan, + blue, + violet + ); +} + +input[type="range"].hue-rotate-slider { + &::-webkit-slider-runnable-track { + @include hue-rotate-slider; + } + + &::-moz-range-track { + @include hue-rotate-slider; + } + + &::-ms-track { + @include hue-rotate-slider; + } +} + +@mixin white-balance-slider() { + background: rgb(90, 138, 210); + background: linear-gradient( + 90deg, + rgba(90, 138, 210, 1) 0%, + rgba(83, 72, 72, 1) 50%, + rgba(252, 186, 8, 1) 100% + ); +} + +input[type="range"].white-balance-slider { + &::-webkit-slider-runnable-track { + @include white-balance-slider; + } + + &::-moz-range-track { + @include white-balance-slider; + } + + &::-ms-track { + @include white-balance-slider; + } +} + +@mixin red-slider() { + background: rgb(255, 0, 0); +} + +input[type="range"].red-slider { + &::-webkit-slider-runnable-track { + @include red-slider; + } + + &::-moz-range-track { + @include red-slider; + } + + &::-ms-track { + @include red-slider; + } +} + +@mixin green-slider() { + background: rgb(0, 255, 0); +} + +input[type="range"].green-slider { + &::-webkit-slider-runnable-track { + @include green-slider; + } + + &::-moz-range-track { + @include green-slider; + } + + &::-ms-track { + @include green-slider; + } +} + +@mixin blue-slider() { + background: rgb(0, 0, 255); +} + +input[type="range"].blue-slider { + &::-webkit-slider-runnable-track { + @include blue-slider; + } + + &::-moz-range-track { + @include blue-slider; + } + + &::-ms-track { + @include blue-slider; + } +} + @media (min-width: 1200px), (max-width: 575px) { .performer-card .flag-icon { height: 1.33rem; diff --git a/ui/v2.5/src/components/Settings/Settings.tsx b/ui/v2.5/src/components/Settings/Settings.tsx index e9036ca0d..7f8dac83e 100644 --- a/ui/v2.5/src/components/Settings/Settings.tsx +++ b/ui/v2.5/src/components/Settings/Settings.tsx @@ -8,6 +8,7 @@ import { SettingsInterfacePanel } from "./SettingsInterfacePanel"; import { SettingsLogsPanel } from "./SettingsLogsPanel"; import { SettingsTasksPanel } from "./SettingsTasksPanel/SettingsTasksPanel"; import { SettingsPluginsPanel } from "./SettingsPluginsPanel"; +import { SettingsScrapersPanel } from "./SettingsScrapersPanel"; export const Settings: React.FC = () => { const location = useLocation(); @@ -21,7 +22,7 @@ export const Settings: React.FC = () => { tab && onSelect(tab)} > @@ -35,6 +36,9 @@ export const Settings: React.FC = () => { Tasks + + Scrapers + Plugins @@ -58,6 +62,9 @@ export const Settings: React.FC = () => { + + + diff --git a/ui/v2.5/src/components/Settings/SettingsConfigurationPanel.tsx b/ui/v2.5/src/components/Settings/SettingsConfigurationPanel.tsx index 2ec4ae622..336d375fa 100644 --- a/ui/v2.5/src/components/Settings/SettingsConfigurationPanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsConfigurationPanel.tsx @@ -4,12 +4,70 @@ import * as GQL from "src/core/generated-graphql"; import { useConfiguration, useConfigureGeneral } from "src/core/StashService"; import { useToast } from "src/hooks"; import { Icon, LoadingIndicator } from "src/components/Shared"; -import { FolderSelect } from "src/components/Shared/FolderSelect/FolderSelect"; +import StashBoxConfiguration, { + IStashBoxInstance, +} from "./StashBoxConfiguration"; +import StashConfiguration from "./StashConfiguration"; + +interface IExclusionPatternsProps { + excludes: string[]; + setExcludes: (value: string[]) => void; +} + +const ExclusionPatterns: React.FC = (props) => { + function excludeRegexChanged(idx: number, value: string) { + const newExcludes = props.excludes.map((regex, i) => { + const ret = idx !== i ? regex : value; + return ret; + }); + props.setExcludes(newExcludes); + } + + function excludeRemoveRegex(idx: number) { + const newExcludes = props.excludes.filter((_regex, i) => i !== idx); + + props.setExcludes(newExcludes); + } + + function excludeAddRegex() { + const demo = "sample\\.mp4$"; + const newExcludes = props.excludes.concat(demo); + + props.setExcludes(newExcludes); + } + + return ( + <> + + {props.excludes && + props.excludes.map((regexp, i) => ( + + ) => + excludeRegexChanged(i, e.currentTarget.value) + } + /> + + + + + ))} + + + + ); +}; export const SettingsConfigurationPanel: React.FC = () => { const Toast = useToast(); // Editing config state - const [stashes, setStashes] = useState([]); + const [stashes, setStashes] = useState([]); const [databasePath, setDatabasePath] = useState( undefined ); @@ -47,18 +105,34 @@ export const SettingsConfigurationPanel: React.FC = () => { const [logOut, setLogOut] = useState(true); const [logLevel, setLogLevel] = useState("Info"); const [logAccess, setLogAccess] = useState(true); + + const [videoExtensions, setVideoExtensions] = useState(); + const [imageExtensions, setImageExtensions] = useState(); + const [galleryExtensions, setGalleryExtensions] = useState< + string | undefined + >(); + const [createGalleriesFromFolders, setCreateGalleriesFromFolders] = useState< + boolean + >(false); + const [excludes, setExcludes] = useState([]); + const [imageExcludes, setImageExcludes] = useState([]); const [scraperUserAgent, setScraperUserAgent] = useState( undefined ); const [scraperCDPPath, setScraperCDPPath] = useState( undefined ); + const [stashBoxes, setStashBoxes] = useState([]); const { data, error, loading } = useConfiguration(); const [updateGeneralConfig] = useConfigureGeneral({ - stashes, + stashes: stashes.map((s) => ({ + path: s.path, + excludeVideo: s.excludeVideo, + excludeImage: s.excludeImage, + })), databasePath, generatedPath, cachePath, @@ -79,9 +153,22 @@ export const SettingsConfigurationPanel: React.FC = () => { logOut, logLevel, logAccess, + createGalleriesFromFolders, + videoExtensions: commaDelimitedToList(videoExtensions), + imageExtensions: commaDelimitedToList(imageExtensions), + galleryExtensions: commaDelimitedToList(galleryExtensions), excludes, + imageExcludes, scraperUserAgent, scraperCDPPath, + stashBoxes: stashBoxes.map( + (b) => + ({ + name: b?.name ?? "", + api_key: b?.api_key ?? "", + endpoint: b?.endpoint ?? "", + } as GQL.StashBoxInput) + ), }); useEffect(() => { @@ -111,35 +198,37 @@ export const SettingsConfigurationPanel: React.FC = () => { setLogOut(conf.general.logOut); setLogLevel(conf.general.logLevel); setLogAccess(conf.general.logAccess); + setCreateGalleriesFromFolders(conf.general.createGalleriesFromFolders); + setVideoExtensions(listToCommaDelimited(conf.general.videoExtensions)); + setImageExtensions(listToCommaDelimited(conf.general.imageExtensions)); + setGalleryExtensions( + listToCommaDelimited(conf.general.galleryExtensions) + ); setExcludes(conf.general.excludes); + setImageExcludes(conf.general.imageExcludes); setScraperUserAgent(conf.general.scraperUserAgent ?? undefined); setScraperCDPPath(conf.general.scraperCDPPath ?? undefined); + setStashBoxes( + conf.general.stashBoxes.map((box, i) => ({ + name: box?.name ?? undefined, + endpoint: box.endpoint, + api_key: box.api_key, + index: i, + })) ?? [] + ); } }, [data, error]); - function onStashesChanged(directories: string[]) { - setStashes(directories); + function commaDelimitedToList(value: string | undefined) { + if (value) { + return value.split(",").map((s) => s.trim()); + } } - function excludeRegexChanged(idx: number, value: string) { - const newExcludes = excludes.map((regex, i) => { - const ret = idx !== i ? regex : value; - return ret; - }); - setExcludes(newExcludes); - } - - function excludeRemoveRegex(idx: number) { - const newExcludes = excludes.filter((_regex, i) => i !== idx); - - setExcludes(newExcludes); - } - - function excludeAddRegex() { - const demo = "sample\\.mp4$"; - const newExcludes = excludes.concat(demo); - - setExcludes(newExcludes); + function listToCommaDelimited(value: string[] | undefined) { + if (value) { + return value.join(", "); + } } async function onSave() { @@ -236,9 +325,9 @@ export const SettingsConfigurationPanel: React.FC = () => {
Stashes
- setStashes(s)} /> Directory locations to your content @@ -288,35 +377,56 @@ export const SettingsConfigurationPanel: React.FC = () => {
- -
Excluded Patterns
- - {excludes && - excludes.map((regexp, i) => ( - - ) => - excludeRegexChanged(i, e.currentTarget.value) - } - /> - - - - - ))} - - + +
Video Extensions
+ ) => + setVideoExtensions(e.currentTarget.value) + } + /> - Regexps of files/paths to exclude from Scan and add to Clean + Comma-delimited list of file extensions that will be identified as + videos. + +
+ + +
Image Extensions
+ ) => + setImageExtensions(e.currentTarget.value) + } + /> + + Comma-delimited list of file extensions that will be identified as + images. + +
+ + +
Gallery zip Extensions
+ ) => + setGalleryExtensions(e.currentTarget.value) + } + /> + + Comma-delimited list of file extensions that will be identified as + gallery zip files. + +
+ + +
Excluded Video Patterns
+ + + Regexps of video files/paths to exclude from Scan and add to Clean {
+ + +
Excluded Image/Gallery Patterns
+ + + Regexps of image and gallery files/paths to exclude from Scan and + add to Clean + + + + +
+ + + + setCreateGalleriesFromFolders(!createGalleriesFromFolders) + } + /> + + If true, creates galleries from folders containing images. + +

@@ -549,6 +692,13 @@ export const SettingsConfigurationPanel: React.FC = () => {
+ +

Stash-box integration

+ +
+ +
+

Authentication

diff --git a/ui/v2.5/src/components/Settings/SettingsPluginsPanel.tsx b/ui/v2.5/src/components/Settings/SettingsPluginsPanel.tsx index 4c93c6f00..8e92c18cd 100644 --- a/ui/v2.5/src/components/Settings/SettingsPluginsPanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsPluginsPanel.tsx @@ -1,45 +1,24 @@ -import React, { useState, useEffect } from "react"; +import React from "react"; import { Button } from "react-bootstrap"; import { mutateReloadPlugins, usePlugins } from "src/core/StashService"; import { useToast } from "src/hooks"; -import * as GQL from "src/core/generated-graphql"; import { TextUtils } from "src/utils"; -import { Icon, LoadingIndicator } from "../Shared"; +import { Icon, LoadingIndicator } from "src/components/Shared"; export const SettingsPluginsPanel: React.FC = () => { const Toast = useToast(); - - const plugins = usePlugins(); - - // Network state - const [isLoading, setIsLoading] = useState(true); - - useEffect(() => { - if (plugins) { - setIsLoading(false); - } - }, [plugins]); + const { data, loading } = usePlugins(); async function onReloadPlugins() { - setIsLoading(true); - try { - await mutateReloadPlugins(); - - // reload the performer scrapers - await plugins.refetch(); - } catch (e) { - Toast.error(e); - } finally { - setIsLoading(false); - } + await mutateReloadPlugins().catch((e) => Toast.error(e)); } - function renderLink(plugin: GQL.Plugin) { - if (plugin.url) { + function renderLink(url?: string) { + if (url) { return ( + + ); + } + + return items; + } + + return
    {getListItems()}
; +}; + +export const SettingsScrapersPanel: React.FC = () => { + const Toast = useToast(); + const { + data: performerScrapers, + loading: loadingPerformers, + } = useListPerformerScrapers(); + const { + data: sceneScrapers, + loading: loadingScenes, + } = useListSceneScrapers(); + const { + data: movieScrapers, + loading: loadingMovies, + } = useListMovieScrapers(); + + async function onReloadScrapers() { + await mutateReloadScrapers().catch((e) => Toast.error(e)); + } + + function renderPerformerScrapeTypes(types: ScrapeType[]) { + const typeStrings = types + .filter((t) => t !== ScrapeType.Fragment) + .map((t) => { + switch (t) { + case ScrapeType.Name: + return "Search by name"; + default: + return t; + } + }); + + return ( +
    + {typeStrings.map((t) => ( +
  • {t}
  • + ))} +
+ ); + } + + function renderSceneScrapeTypes(types: ScrapeType[]) { + const typeStrings = types.map((t) => { + switch (t) { + case ScrapeType.Fragment: + return "Scene Metadata"; + default: + return t; + } + }); + + return ( +
    + {typeStrings.map((t) => ( +
  • {t}
  • + ))} +
+ ); + } + + function renderMovieScrapeTypes(types: ScrapeType[]) { + const typeStrings = types.map((t) => { + switch (t) { + case ScrapeType.Fragment: + return "Movie Metadata"; + default: + return t; + } + }); + + return ( +
    + {typeStrings.map((t) => ( +
  • {t}
  • + ))} +
+ ); + } + + function renderURLs(urls: string[]) { + return ; + } + + function renderSceneScrapers() { + const elements = (sceneScrapers?.listSceneScrapers ?? []).map((scraper) => ( + + {scraper.name} + + {renderSceneScrapeTypes(scraper.scene?.supported_scrapes ?? [])} + + {renderURLs(scraper.scene?.urls ?? [])} + + )); + + return renderTable("Scene scrapers", elements); + } + + function renderPerformerScrapers() { + const elements = (performerScrapers?.listPerformerScrapers ?? []).map( + (scraper) => ( + + {scraper.name} + + {renderPerformerScrapeTypes( + scraper.performer?.supported_scrapes ?? [] + )} + + {renderURLs(scraper.performer?.urls ?? [])} + + ) + ); + + return renderTable("Performer scrapers", elements); + } + + function renderMovieScrapers() { + const elements = (movieScrapers?.listMovieScrapers ?? []).map((scraper) => ( + + {scraper.name} + + {renderMovieScrapeTypes(scraper.movie?.supported_scrapes ?? [])} + + {renderURLs(scraper.movie?.urls ?? [])} + + )); + + return renderTable("Movie scrapers", elements); + } + + function renderTable(title: string, elements: JSX.Element[]) { + if (elements.length > 0) { + return ( +
+
{title}
+ + + + + + + + + {elements} +
NameSupported typesURLs
+
+ ); + } + } + + if (loadingScenes || loadingPerformers || loadingMovies) + return ; + + return ( + <> +

Scrapers

+
+ +
+ +
+ {renderSceneScrapers()} + {renderPerformerScrapers()} + {renderMovieScrapers()} +
+ + ); +}; diff --git a/ui/v2.5/src/components/Settings/SettingsTasksPanel/GenerateButton.tsx b/ui/v2.5/src/components/Settings/SettingsTasksPanel/GenerateButton.tsx index 65ceec0c4..77ced445b 100644 --- a/ui/v2.5/src/components/Settings/SettingsTasksPanel/GenerateButton.tsx +++ b/ui/v2.5/src/components/Settings/SettingsTasksPanel/GenerateButton.tsx @@ -9,7 +9,6 @@ export const GenerateButton: React.FC = () => { const [previews, setPreviews] = useState(true); const [markers, setMarkers] = useState(true); const [transcodes, setTranscodes] = useState(false); - const [thumbnails, setThumbnails] = useState(false); const [imagePreviews, setImagePreviews] = useState(false); async function onGenerate() { @@ -20,7 +19,6 @@ export const GenerateButton: React.FC = () => { imagePreviews: previews && imagePreviews, markers, transcodes, - thumbnails, }); Toast.success({ content: "Started generating" }); } catch (e) { @@ -66,12 +64,6 @@ export const GenerateButton: React.FC = () => { label="Transcodes (MP4 conversions of unsupported video formats)" onChange={() => setTranscodes(!transcodes)} /> - setThumbnails(!thumbnails)} - />
+ +
+ ))} + + setCurrentDirectory(v)} + defaultDirectories={libraryPaths} + appendButton={ + + } + /> +
+ + ); +}; diff --git a/ui/v2.5/src/components/Settings/SettingsTasksPanel/SettingsTasksPanel.tsx b/ui/v2.5/src/components/Settings/SettingsTasksPanel/SettingsTasksPanel.tsx index 5e999aada..533309ec5 100644 --- a/ui/v2.5/src/components/Settings/SettingsTasksPanel/SettingsTasksPanel.tsx +++ b/ui/v2.5/src/components/Settings/SettingsTasksPanel/SettingsTasksPanel.tsx @@ -15,14 +15,21 @@ import { mutateRunPluginTask, } from "src/core/StashService"; import { useToast } from "src/hooks"; +import * as GQL from "src/core/generated-graphql"; import { Modal } from "src/components/Shared"; -import { Plugin, PluginTask } from "src/core/generated-graphql"; import { GenerateButton } from "./GenerateButton"; +import { ImportDialog } from "./ImportDialog"; +import { ScanDialog } from "./ScanDialog"; + +type Plugin = Pick; +type PluginTask = Pick; export const SettingsTasksPanel: React.FC = () => { const Toast = useToast(); const [isImportAlertOpen, setIsImportAlertOpen] = useState(false); const [isCleanAlertOpen, setIsCleanAlertOpen] = useState(false); + const [isImportDialogOpen, setIsImportDialogOpen] = useState(false); + const [isScanDialogOpen, setIsScanDialogOpen] = useState(false); const [useFileMetadata, setUseFileMetadata] = useState(false); const [status, setStatus] = useState(""); const [progress, setProgress] = useState(0); @@ -132,9 +139,36 @@ export const SettingsTasksPanel: React.FC = () => { ); } - async function onScan() { + function renderImportDialog() { + if (!isImportDialogOpen) { + return; + } + + return setIsImportDialogOpen(false)} />; + } + + function renderScanDialog() { + if (!isScanDialogOpen) { + return; + } + + return ; + } + + function onScanDialogClosed(paths?: string[]) { + if (paths) { + onScan(paths); + } + + setIsScanDialogOpen(false); + } + + async function onScan(paths?: string[]) { try { - await mutateMetadataScan({ useFileMetadata }); + await mutateMetadataScan({ + useFileMetadata, + paths, + }); Toast.success({ content: "Started scan" }); jobStatus.refetch(); } catch (e) { @@ -199,17 +233,11 @@ export const SettingsTasksPanel: React.FC = () => { ); } - async function onPluginTaskClicked( - plugin: Partial, - operation: Partial - ) { - await mutateRunPluginTask(plugin.id!, operation.name!); + async function onPluginTaskClicked(plugin: Plugin, operation: PluginTask) { + await mutateRunPluginTask(plugin.id, operation.name); } - function renderPluginTasks( - plugin: Partial, - pluginTasks: Partial[] | undefined - ) { + function renderPluginTasks(plugin: Plugin, pluginTasks: PluginTask[]) { if (!pluginTasks) { return; } @@ -259,6 +287,8 @@ export const SettingsTasksPanel: React.FC = () => { <> {renderImportAlert()} {renderCleanAlert()} + {renderImportDialog()} + {renderScanDialog()}

Running Jobs

@@ -276,9 +306,21 @@ export const SettingsTasksPanel: React.FC = () => { />
- + Scan for new content and add it to the database. @@ -355,10 +397,11 @@ export const SettingsTasksPanel: React.FC = () => { }) } > - Export + Full Export - Export the database content into JSON format. + Exports the database content into JSON format in the metadata + directory. @@ -368,10 +411,24 @@ export const SettingsTasksPanel: React.FC = () => { variant="danger" onClick={() => setIsImportAlertOpen(true)} > - Import + Full Import - Import from exported JSON. This is a destructive action. + Import from exported JSON in the metadata directory. Wipes the + existing database. + + + + + + + Incremental import from a supplied export zip file. diff --git a/ui/v2.5/src/components/Settings/StashBoxConfiguration.tsx b/ui/v2.5/src/components/Settings/StashBoxConfiguration.tsx new file mode 100644 index 000000000..32ee4c3f2 --- /dev/null +++ b/ui/v2.5/src/components/Settings/StashBoxConfiguration.tsx @@ -0,0 +1,137 @@ +import React, { useState } from "react"; +import { Button, Form, InputGroup } from "react-bootstrap"; +import { Icon } from "src/components/Shared"; + +interface IInstanceProps { + instance: IStashBoxInstance; + onSave: (instance: IStashBoxInstance) => void; + onDelete: (id: number) => void; + isMulti: boolean; +} + +const Instance: React.FC = ({ + instance, + onSave, + onDelete, + isMulti, +}) => { + const handleInput = (key: string, value: string) => { + const newObj = { + ...instance, + [key]: value, + }; + onSave(newObj); + }; + + return ( + + + 0} + onInput={(e: React.ChangeEvent) => + handleInput("name", e.currentTarget.value) + } + /> + 0} + onInput={(e: React.ChangeEvent) => + handleInput("endpoint", e.currentTarget.value) + } + /> + 0} + onInput={(e: React.ChangeEvent) => + handleInput("api_key", e.currentTarget.value) + } + /> + + + + + + ); +}; + +interface IStashBoxConfigurationProps { + boxes: IStashBoxInstance[]; + saveBoxes: (boxes: IStashBoxInstance[]) => void; +} + +export interface IStashBoxInstance { + name?: string; + endpoint?: string; + api_key?: string; + index: number; +} + +export const StashBoxConfiguration: React.FC = ({ + boxes, + saveBoxes, +}) => { + const [index, setIndex] = useState(1000); + + const handleSave = (instance: IStashBoxInstance) => + saveBoxes( + boxes.map((box) => (box.index === instance.index ? instance : box)) + ); + const handleDelete = (id: number) => + saveBoxes(boxes.filter((box) => box.index !== id)); + const handleAdd = () => { + saveBoxes([...boxes, { index }]); + setIndex(index + 1); + }; + + return ( + +
Stash-box Endpoints
+ {boxes.length > 0 && ( +
+
Name
+
Endpoint
+
API Key
+
+ )} + {boxes.map((instance) => ( + 1} + /> + ))} + + + Stash-box facilitates automated tagging of scenes and performers based + on fingerprints and filenames. +
+ Endpoint and API key can be found on your account page on the stash-box + instance. Names are required when more than one instance is added. +
+
+ ); +}; + +export default StashBoxConfiguration; diff --git a/ui/v2.5/src/components/Settings/StashConfiguration.tsx b/ui/v2.5/src/components/Settings/StashConfiguration.tsx new file mode 100644 index 000000000..555ba4751 --- /dev/null +++ b/ui/v2.5/src/components/Settings/StashConfiguration.tsx @@ -0,0 +1,132 @@ +import React, { useState } from "react"; +import { Button, Form, Row, Col } from "react-bootstrap"; +import { Icon } from "src/components/Shared"; +import * as GQL from "src/core/generated-graphql"; +import { FolderSelectDialog } from "../Shared/FolderSelect/FolderSelectDialog"; + +interface IStashProps { + index: number; + stash: GQL.StashConfig; + onSave: (instance: GQL.StashConfig) => void; + onDelete: () => void; +} + +const Stash: React.FC = ({ index, stash, onSave, onDelete }) => { + // eslint-disable-next-line + const handleInput = (key: string, value: any) => { + const newObj = { + ...stash, + [key]: value, + }; + onSave(newObj); + }; + + const classAdd = index % 2 === 1 ? "bg-dark" : ""; + + return ( + + + {stash.path} + + + handleInput("excludeVideo", !stash.excludeVideo)} + /> + + + + handleInput("excludeImage", !stash.excludeImage)} + /> + + + + + + ); +}; + +interface IStashConfigurationProps { + stashes: GQL.StashConfig[]; + setStashes: (v: GQL.StashConfig[]) => void; +} + +export const StashConfiguration: React.FC = ({ + stashes, + setStashes, +}) => { + const [isDisplayingDialog, setIsDisplayingDialog] = useState(false); + + const handleSave = (index: number, stash: GQL.StashConfig) => + setStashes(stashes.map((s, i) => (i === index ? stash : s))); + const handleDelete = (index: number) => + setStashes(stashes.filter((s, i) => i !== index)); + const handleAdd = (folder?: string) => { + setIsDisplayingDialog(false); + + if (!folder) { + return; + } + + setStashes([ + ...stashes, + { + path: folder, + excludeImage: false, + excludeVideo: false, + }, + ]); + }; + + function maybeRenderDialog() { + if (!isDisplayingDialog) { + return; + } + + return ; + } + + return ( + <> + {maybeRenderDialog()} + + {stashes.length > 0 && ( + +
Path
+
Exclude Video
+
Exclude Image
+
+ )} + {stashes.map((stash, index) => ( + handleSave(index, s)} + onDelete={() => handleDelete(index)} + key={stash.path} + /> + ))} + +
+ + ); +}; + +export default StashConfiguration; diff --git a/ui/v2.5/src/components/Settings/styles.scss b/ui/v2.5/src/components/Settings/styles.scss index ecef8049a..89844ce14 100644 --- a/ui/v2.5/src/components/Settings/styles.scss +++ b/ui/v2.5/src/components/Settings/styles.scss @@ -40,3 +40,33 @@ #configuration-tabs-tabpane-tasks h5 { margin-bottom: 1em; } + +.scraper-table { + display: block; + margin-bottom: 16px; + overflow: auto; + width: 100%; + + tr { + border-top: 1px solid #181513; + + &:nth-child(2n) { + background-color: #2c3b47; + } + } + + th, + td { + border: 1px solid #181513; + padding: 6px 13px; + } + + ul { + margin-bottom: 0; + padding-left: 0; + } + + li { + list-style: none; + } +} diff --git a/ui/v2.5/src/components/Shared/BasicCard.tsx b/ui/v2.5/src/components/Shared/BasicCard.tsx new file mode 100644 index 000000000..a4662281b --- /dev/null +++ b/ui/v2.5/src/components/Shared/BasicCard.tsx @@ -0,0 +1,101 @@ +import React from "react"; +import { Card, Form } from "react-bootstrap"; +import { Link } from "react-router-dom"; + +interface IBasicCardProps { + className?: string; + linkClassName?: string; + url: string; + image: JSX.Element; + details: JSX.Element; + overlays?: JSX.Element; + popovers?: JSX.Element; + selecting?: boolean; + selected?: boolean; + onSelectedChanged?: (selected: boolean, shiftKey: boolean) => void; +} + +export const BasicCard: React.FC = ( + props: IBasicCardProps +) => { + function handleImageClick( + event: React.MouseEvent + ) { + const { shiftKey } = event; + + if (!props.onSelectedChanged) { + return; + } + + if (props.selecting) { + props.onSelectedChanged(!props.selected, shiftKey); + event.preventDefault(); + } + } + + function handleDrag(event: React.DragEvent) { + if (props.selecting) { + event.dataTransfer.setData("text/plain", ""); + event.dataTransfer.setDragImage(new Image(), 0, 0); + } + } + + function handleDragOver(event: React.DragEvent) { + const ev = event; + const shiftKey = false; + + if (!props.onSelectedChanged) { + return; + } + + if (props.selecting && !props.selected) { + props.onSelectedChanged(true, shiftKey); + } + + ev.dataTransfer.dropEffect = "move"; + ev.preventDefault(); + } + + let shiftKey = false; + + function maybeRenderCheckbox() { + if (props.onSelectedChanged) { + return ( + props.onSelectedChanged!(!props.selected, shiftKey)} + onClick={(event: React.MouseEvent) => { + // eslint-disable-next-line prefer-destructuring + shiftKey = event.shiftKey; + event.stopPropagation(); + }} + /> + ); + } + } + + return ( + + {maybeRenderCheckbox()} + +
+ + {props.image} + + {props.overlays} +
+
{props.details}
+ + {props.popovers} +
+ ); +}; diff --git a/ui/v2.5/src/components/Shared/ErrorMessage.tsx b/ui/v2.5/src/components/Shared/ErrorMessage.tsx new file mode 100644 index 000000000..2e40a35df --- /dev/null +++ b/ui/v2.5/src/components/Shared/ErrorMessage.tsx @@ -0,0 +1,13 @@ +import React, { ReactNode } from "react"; + +interface IProps { + error: string | ReactNode; +} + +const ErrorMessage: React.FC = ({ error }) => ( +
+

Error: {error}

+
+); + +export default ErrorMessage; diff --git a/ui/v2.5/src/components/Shared/ExportDialog.tsx b/ui/v2.5/src/components/Shared/ExportDialog.tsx new file mode 100644 index 000000000..22fe31c67 --- /dev/null +++ b/ui/v2.5/src/components/Shared/ExportDialog.tsx @@ -0,0 +1,70 @@ +import React, { useState } from "react"; +import { Form } from "react-bootstrap"; +import { mutateExportObjects } from "src/core/StashService"; +import { Modal } from "src/components/Shared"; +import { useToast } from "src/hooks"; +import { downloadFile } from "src/utils"; +import { ExportObjectsInput } from "src/core/generated-graphql"; + +interface IExportDialogProps { + exportInput: ExportObjectsInput; + onClose: () => void; +} + +export const ExportDialog: React.FC = ( + props: IExportDialogProps +) => { + const [includeDependencies, setIncludeDependencies] = useState(true); + + // Network state + const [isRunning, setIsRunning] = useState(false); + + const Toast = useToast(); + + async function onExport() { + try { + setIsRunning(true); + const ret = await mutateExportObjects({ + ...props.exportInput, + includeDependencies, + }); + + // download the result + if (ret.data && ret.data.exportObjects) { + const link = ret.data.exportObjects; + downloadFile(link); + } + } catch (e) { + Toast.error(e); + } finally { + setIsRunning(false); + props.onClose(); + } + } + + return ( + props.onClose(), + text: "Cancel", + variant: "secondary", + }} + isRunning={isRunning} + > +
+ + setIncludeDependencies(!includeDependencies)} + /> + +
+
+ ); +}; diff --git a/ui/v2.5/src/components/Shared/FolderSelect/FolderSelect.tsx b/ui/v2.5/src/components/Shared/FolderSelect/FolderSelect.tsx index 9f5c4286b..b0e43541a 100644 --- a/ui/v2.5/src/components/Shared/FolderSelect/FolderSelect.tsx +++ b/ui/v2.5/src/components/Shared/FolderSelect/FolderSelect.tsx @@ -1,133 +1,83 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect } from "react"; import { FormattedMessage } from "react-intl"; -import { Button, InputGroup, Form, Modal } from "react-bootstrap"; +import { Button, InputGroup, Form } from "react-bootstrap"; import { LoadingIndicator } from "src/components/Shared"; import { useDirectory } from "src/core/StashService"; interface IProps { - directories: string[]; - onDirectoriesChanged: (directories: string[]) => void; + currentDirectory: string; + setCurrentDirectory: (value: string) => void; + defaultDirectories?: string[]; + appendButton?: JSX.Element; } -export const FolderSelect: React.FC = (props: IProps) => { - const [currentDirectory, setCurrentDirectory] = useState(""); - const [isDisplayingDialog, setIsDisplayingDialog] = useState(false); - const [selectedDirectories, setSelectedDirectories] = useState([]); +export const FolderSelect: React.FC = ({ + currentDirectory, + setCurrentDirectory, + defaultDirectories, + appendButton, +}) => { const { data, error, loading } = useDirectory(currentDirectory); - useEffect(() => { - setSelectedDirectories(props.directories); - }, [props.directories]); + const selectableDirectories: string[] = currentDirectory + ? data?.directory.directories ?? defaultDirectories ?? [] + : defaultDirectories ?? []; useEffect(() => { - if (currentDirectory === "" && data?.directory.path) + if (currentDirectory === "" && !defaultDirectories && data?.directory.path) setCurrentDirectory(data.directory.path); - }, [currentDirectory, data]); + }, [currentDirectory, setCurrentDirectory, data, defaultDirectories]); - const selectableDirectories: string[] = data?.directory.directories ?? []; - - function onSelectDirectory() { - selectedDirectories.push(currentDirectory); - setSelectedDirectories(selectedDirectories); - setCurrentDirectory(""); - setIsDisplayingDialog(false); - props.onDirectoriesChanged(selectedDirectories); + function goUp() { + if (defaultDirectories?.includes(currentDirectory)) { + setCurrentDirectory(""); + } else if (data?.directory.parent) { + setCurrentDirectory(data.directory.parent); + } } - function onRemoveDirectory(directory: string) { - const newSelectedDirectories = selectedDirectories.filter( - (dir) => dir !== directory - ); - setSelectedDirectories(newSelectedDirectories); - props.onDirectoriesChanged(newSelectedDirectories); - } - - const topDirectory = data?.directory?.parent ? ( -
  • - -
  • - ) : null; - - function renderDialog() { - return ( - setIsDisplayingDialog(false)} - title="" - > - Select Directory - -
    - - ) => - setCurrentDirectory(e.currentTarget.value) - } - value={currentDirectory} - spellCheck={false} - /> - - {!data || !data.directory || loading ? ( - - ) : ( - "" - )} - - -
      - {topDirectory} - {selectableDirectories.map((path) => { - return ( -
    • - -
    • - ); - })} -
    -
    -
    - - - -
    - ); - } + const topDirectory = + currentDirectory && data?.directory?.parent ? ( +
  • + +
  • + ) : null; return ( <> {error ?

    {error.message}

    : ""} - {renderDialog()} - - {selectedDirectories.map((path) => { + + ) => + setCurrentDirectory(e.currentTarget.value) + } + value={currentDirectory} + spellCheck={false} + /> + {appendButton ? ( + {appendButton} + ) : undefined} + {!data || !data.directory || loading ? ( + + + + ) : undefined} + +
      + {topDirectory} + {selectableDirectories.map((path) => { return ( -
      - {path}{" "} - -
      + ); })} - - - +
    ); }; diff --git a/ui/v2.5/src/components/Shared/FolderSelect/FolderSelectDialog.tsx b/ui/v2.5/src/components/Shared/FolderSelect/FolderSelectDialog.tsx new file mode 100644 index 000000000..155665c22 --- /dev/null +++ b/ui/v2.5/src/components/Shared/FolderSelect/FolderSelectDialog.tsx @@ -0,0 +1,33 @@ +import React, { useState } from "react"; +import { Button, Modal } from "react-bootstrap"; +import { FolderSelect } from "./FolderSelect"; + +interface IProps { + onClose: (directory?: string) => void; +} + +export const FolderSelectDialog: React.FC = (props: IProps) => { + const [currentDirectory, setCurrentDirectory] = useState(""); + + return ( + props.onClose()} title=""> + Select Directory + +
    + setCurrentDirectory(v)} + /> +
    +
    + + + +
    + ); +}; diff --git a/ui/v2.5/src/components/Shared/LoadingIndicator.tsx b/ui/v2.5/src/components/Shared/LoadingIndicator.tsx index 5fb99232e..d87defbcf 100644 --- a/ui/v2.5/src/components/Shared/LoadingIndicator.tsx +++ b/ui/v2.5/src/components/Shared/LoadingIndicator.tsx @@ -5,6 +5,7 @@ import cx from "classnames"; interface ILoadingProps { message?: string; inline?: boolean; + small?: boolean; } const CLASSNAME = "LoadingIndicator"; @@ -13,12 +14,15 @@ const CLASSNAME_MESSAGE = `${CLASSNAME}-message`; const LoadingIndicator: React.FC = ({ message, inline = false, + small = false, }) => ( -
    - +
    + Loading... -

    {message ?? "Loading..."}

    + {message !== "" && ( +

    {message ?? "Loading..."}

    + )}
    ); diff --git a/ui/v2.5/src/components/Help/Page.tsx b/ui/v2.5/src/components/Shared/MarkdownPage.tsx similarity index 88% rename from ui/v2.5/src/components/Help/Page.tsx rename to ui/v2.5/src/components/Shared/MarkdownPage.tsx index 1ab94109a..175a1b1c1 100644 --- a/ui/v2.5/src/components/Help/Page.tsx +++ b/ui/v2.5/src/components/Shared/MarkdownPage.tsx @@ -7,7 +7,7 @@ interface IPageProps { page: any; } -export const Page: React.FC = ({ page }) => { +export const MarkdownPage: React.FC = ({ page }) => { const [markdown, setMarkdown] = useState(""); useEffect(() => { diff --git a/ui/v2.5/src/components/Shared/Modal.tsx b/ui/v2.5/src/components/Shared/Modal.tsx index 06a83f2ea..94d83c9f1 100644 --- a/ui/v2.5/src/components/Shared/Modal.tsx +++ b/ui/v2.5/src/components/Shared/Modal.tsx @@ -17,7 +17,9 @@ interface IModal { cancel?: IButton; accept?: IButton; isRunning?: boolean; + disabled?: boolean; modalProps?: ModalProps; + dialogClassName?: string; } const ModalComponent: React.FC = ({ @@ -29,9 +31,17 @@ const ModalComponent: React.FC = ({ accept, onHide, isRunning, + disabled, modalProps, + dialogClassName, }) => ( - + {icon ? : ""} {header ?? ""} @@ -44,6 +54,7 @@ const ModalComponent: React.FC = ({ disabled={isRunning} variant={cancel.variant ?? "primary"} onClick={cancel.onClick} + className="mr-2" > {cancel.text ?? "Cancel"} @@ -51,9 +62,10 @@ const ModalComponent: React.FC = ({ "" )} + + +
    + Blacklist items are excluded from queries. Note that they are + regular expressions and also case-insensitive. Certain characters + must be escaped with a backslash: [\^$.|?*+() +
    + {config.blacklist.map((item, index) => ( + + {item.toString()} + + + ))} + + + + Active stash-box instance: + + + {!stashBoxes.length && } + {stashConfig.data?.configuration.general.stashBoxes.map((i) => ( + + ))} + + +
    +
    + + + ); +}; + +export default Config; diff --git a/ui/v2.5/src/components/Tagger/PerformerModal.tsx b/ui/v2.5/src/components/Tagger/PerformerModal.tsx new file mode 100755 index 000000000..cfcbe5850 --- /dev/null +++ b/ui/v2.5/src/components/Tagger/PerformerModal.tsx @@ -0,0 +1,177 @@ +import React, { useState } from "react"; +import { Button } from "react-bootstrap"; +import cx from "classnames"; + +import { LoadingIndicator, Icon, Modal } from "src/components/Shared"; +import * as GQL from "src/core/generated-graphql"; +import { genderToString } from "src/core/StashService"; +import { IStashBoxPerformer } from "./utils"; + +interface IPerformerModalProps { + performer: IStashBoxPerformer; + modalVisible: boolean; + showModal: (show: boolean) => void; + handlePerformerCreate: (imageIndex: number) => void; +} + +const PerformerModal: React.FC = ({ + modalVisible, + performer, + handlePerformerCreate, + showModal, +}) => { + const [imageIndex, setImageIndex] = useState(0); + const [imageState, setImageState] = useState< + "loading" | "error" | "loaded" | "empty" + >("empty"); + const [loadDict, setLoadDict] = useState>({}); + + const { images } = performer; + + const changeImage = (index: number) => { + setImageIndex(index); + if (!loadDict[index]) setImageState("loading"); + }; + const setPrev = () => + changeImage(imageIndex === 0 ? images.length - 1 : imageIndex - 1); + const setNext = () => + changeImage(imageIndex === images.length - 1 ? 0 : imageIndex + 1); + + const handleLoad = (index: number) => { + setLoadDict({ + ...loadDict, + [index]: true, + }); + setImageState("loaded"); + }; + const handleError = () => setImageState("error"); + + return ( + handlePerformerCreate(imageIndex), + }} + cancel={{ onClick: () => showModal(false), variant: "secondary" }} + onHide={() => showModal(false)} + dialogClassName="performer-create-modal" + > +
    +
    +
    + Performer information +
    +
    + Name: + {performer.name} +
    +
    + Gender: + + {performer.gender && genderToString(performer.gender)} + +
    +
    + Birthdate: + + {performer.birthdate ?? "Unknown"} + +
    +
    + Ethnicity: + + {performer.ethnicity} + +
    +
    + Country: + + {performer.country ?? ""} + +
    +
    + Eye Color: + + {performer.eye_color} + +
    +
    + Height: + {performer.height} +
    +
    + Measurements: + + {performer.measurements} + +
    + {performer?.gender !== GQL.GenderEnum.Male && ( +
    + Fake Tits: + {performer.fake_tits} +
    + )} +
    + Career Length: + + {performer.career_length} + +
    +
    + Tattoos: + {performer.tattoos} +
    +
    + Piercings: + {performer.piercings} +
    +
    + {images.length > 0 && ( +
    +
    + handleLoad(imageIndex)} + onError={handleError} + /> + {imageState === "loading" && ( + + )} + {imageState === "error" && ( +
    + Error loading image. +
    + )} +
    +
    + +
    + Select performer image +
    + {imageIndex + 1} of {images.length} +
    + +
    +
    + )} +
    +
    + ); +}; + +export default PerformerModal; diff --git a/ui/v2.5/src/components/Tagger/PerformerResult.tsx b/ui/v2.5/src/components/Tagger/PerformerResult.tsx new file mode 100755 index 000000000..74aa9ddff --- /dev/null +++ b/ui/v2.5/src/components/Tagger/PerformerResult.tsx @@ -0,0 +1,155 @@ +import React, { useEffect, useState } from "react"; +import { Button, ButtonGroup } from "react-bootstrap"; +import cx from "classnames"; + +import { SuccessIcon, PerformerSelect } from "src/components/Shared"; +import * as GQL from "src/core/generated-graphql"; +import { ValidTypes } from "src/components/Shared/Select"; +import { IStashBoxPerformer } from "./utils"; + +import PerformerModal from "./PerformerModal"; + +export type PerformerOperation = + | { type: "create"; data: IStashBoxPerformer } + | { type: "update"; data: GQL.SlimPerformerDataFragment } + | { type: "existing"; data: GQL.PerformerDataFragment } + | { type: "skip" }; + +interface IPerformerResultProps { + performer: IStashBoxPerformer; + setPerformer: (data: PerformerOperation) => void; +} + +const PerformerResult: React.FC = ({ + performer, + setPerformer, +}) => { + const [selectedPerformer, setSelectedPerformer] = useState(); + const [selectedSource, setSelectedSource] = useState< + "create" | "existing" | "skip" | undefined + >(); + const [modalVisible, showModal] = useState(false); + const { data: performerData } = GQL.useFindPerformerQuery({ + variables: { id: performer.id ?? "" }, + skip: !performer.id, + }); + const { data: stashData, loading: stashLoading } = GQL.useFindPerformersQuery( + { + variables: { + performer_filter: { + stash_id: performer.stash_id, + }, + }, + } + ); + + useEffect(() => { + if (stashData?.findPerformers.performers.length) + setPerformer({ + type: "existing", + data: stashData.findPerformers.performers[0], + }); + else if (performerData?.findPerformer) { + setSelectedPerformer(performerData.findPerformer.id); + setSelectedSource("existing"); + setPerformer({ + type: "update", + data: performerData.findPerformer, + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [stashData, performerData]); + + const handlePerformerSelect = (performers: ValidTypes[]) => { + if (performers.length) { + setSelectedSource("existing"); + setSelectedPerformer(performers[0].id); + setPerformer({ + type: "update", + data: performers[0] as GQL.SlimPerformerDataFragment, + }); + } else { + setSelectedSource(undefined); + setSelectedPerformer(null); + } + }; + + const handlePerformerCreate = (imageIndex: number) => { + const selectedImage = performer.images[imageIndex]; + const images = selectedImage ? [selectedImage] : []; + setSelectedSource("create"); + setPerformer({ + type: "create", + data: { + ...performer, + images, + }, + }); + showModal(false); + }; + + const handlePerformerSkip = () => { + setSelectedSource("skip"); + setPerformer({ + type: "skip", + }); + }; + + if (stashLoading) return
    Loading performer
    ; + + if (stashData?.findPerformers.performers?.[0]?.id) { + return ( +
    +
    + Performer: + {performer.name} +
    + + + Matched: + + + {stashData.findPerformers.performers[0].name} + +
    + ); + } + return ( +
    + +
    + Performer: + {performer.name} +
    + + + + + +
    + ); +}; + +export default PerformerResult; diff --git a/ui/v2.5/src/components/Tagger/StashSearchResult.tsx b/ui/v2.5/src/components/Tagger/StashSearchResult.tsx new file mode 100755 index 000000000..7b1cb3185 --- /dev/null +++ b/ui/v2.5/src/components/Tagger/StashSearchResult.tsx @@ -0,0 +1,413 @@ +import React, { useState, useReducer } from "react"; +import cx from "classnames"; +import { Button } from "react-bootstrap"; +import { uniq } from "lodash"; +import { blobToBase64 } from "base64-blob"; + +import * as GQL from "src/core/generated-graphql"; +import { LoadingIndicator, SuccessIcon } from "src/components/Shared"; +import PerformerResult, { PerformerOperation } from "./PerformerResult"; +import StudioResult, { StudioOperation } from "./StudioResult"; +import { IStashBoxScene } from "./utils"; +import { + useCreateTag, + useCreatePerformer, + useCreateStudio, + useUpdatePerformerStashID, + useUpdateStudioStashID, +} from "./queries"; + +const getDurationStatus = ( + scene: IStashBoxScene, + stashDuration: number | undefined | null +) => { + const fingerprintDuration = + scene.fingerprints.map((f) => f.duration)?.[0] ?? null; + const sceneDuration = scene.duration || fingerprintDuration; + if (!sceneDuration || !stashDuration) return ""; + const diff = Math.abs(sceneDuration - stashDuration); + if (diff < 5) { + return ( +
    + + Duration is a match +
    + ); + } + return
    Duration off by {Math.floor(diff)}s
    ; +}; + +const getFingerprintStatus = ( + scene: IStashBoxScene, + stashChecksum?: string +) => { + if (scene.fingerprints.some((f) => f.hash === stashChecksum)) + return ( +
    + + Checksum is a match +
    + ); +}; + +interface IStashSearchResultProps { + scene: IStashBoxScene; + stashScene: GQL.SlimSceneDataFragment; + isActive: boolean; + setActive: () => void; + showMales: boolean; + setScene: (scene: GQL.SlimSceneDataFragment) => void; + setCoverImage: boolean; + tagOperation: string; + setTags: boolean; + endpoint: string; + queueFingerprintSubmission: (sceneId: string, endpoint: string) => void; +} + +interface IPerformerReducerAction { + id: string; + data: PerformerOperation; +} + +const performerReducer = ( + state: Record, + action: IPerformerReducerAction +) => ({ ...state, [action.id]: action.data }); + +const StashSearchResult: React.FC = ({ + scene, + stashScene, + isActive, + setActive, + showMales, + setScene, + setCoverImage, + tagOperation, + setTags, + endpoint, + queueFingerprintSubmission, +}) => { + const [studio, setStudio] = useState(); + const [performers, dispatch] = useReducer(performerReducer, {}); + const [saveState, setSaveState] = useState(""); + const [error, setError] = useState<{ message?: string; details?: string }>( + {} + ); + + const createStudio = useCreateStudio(); + const createPerformer = useCreatePerformer(); + const createTag = useCreateTag(); + const updatePerformerStashID = useUpdatePerformerStashID(); + const updateStudioStashID = useUpdateStudioStashID(); + const [updateScene] = GQL.useSceneUpdateMutation({ + onError: (errors) => errors, + }); + const { data: allTags } = GQL.useAllTagsForFilterQuery(); + + const setPerformer = ( + performerData: PerformerOperation, + performerID: string + ) => dispatch({ id: performerID, data: performerData }); + + const handleSave = async () => { + setError({}); + let performerIDs = []; + let studioID = null; + + if (!studio) return; + + if (studio.type === "create") { + setSaveState("Creating studio"); + const newStudio = { + name: studio.data.name, + stash_ids: [ + { + endpoint, + stash_id: scene.studio.stash_id, + }, + ], + url: studio.data.url, + }; + const studioCreateResult = await createStudio( + newStudio, + scene.studio.stash_id + ); + + if (!studioCreateResult?.data?.studioCreate) { + setError({ + message: `Failed to save studio "${newStudio.name}"`, + details: studioCreateResult?.errors?.[0].message, + }); + return setSaveState(""); + } + studioID = studioCreateResult.data.studioCreate.id; + } else if (studio.type === "update") { + setSaveState("Saving studio stashID"); + const res = await updateStudioStashID(studio.data, [ + ...studio.data.stash_ids, + { stash_id: scene.studio.stash_id, endpoint }, + ]); + if (!res?.data?.studioUpdate) { + setError({ + message: `Failed to save stashID to studio "${studio.data.name}"`, + details: res?.errors?.[0].message, + }); + return setSaveState(""); + } + studioID = res.data.studioUpdate.id; + } else if (studio.type === "existing") { + studioID = studio.data.id; + } + + setSaveState("Saving performers"); + performerIDs = await Promise.all( + Object.keys(performers).map(async (stashID) => { + const performer = performers[stashID]; + if (performer.type === "skip") return "Skip"; + + let performerID = performer.data.id; + + if (performer.type === "create") { + const imgurl = performer.data.images[0]; + let imgData = null; + if (imgurl) { + const img = await fetch(imgurl, { + mode: "cors", + cache: "no-store", + }); + if (img.status === 200) { + const blob = await img.blob(); + imgData = await blobToBase64(blob); + } + } + + const performerInput = { + name: performer.data.name, + gender: performer.data.gender, + country: performer.data.country, + height: performer.data.height, + ethnicity: performer.data.ethnicity, + birthdate: performer.data.birthdate, + eye_color: performer.data.eye_color, + fake_tits: performer.data.fake_tits, + measurements: performer.data.measurements, + career_length: performer.data.career_length, + tattoos: performer.data.tattoos, + piercings: performer.data.piercings, + twitter: performer.data.twitter, + instagram: performer.data.instagram, + image: imgData, + stash_ids: [ + { + endpoint, + stash_id: stashID, + }, + ], + }; + + const res = await createPerformer(performerInput, stashID); + if (!res?.data?.performerCreate) { + setError({ + message: `Failed to save performer "${performerInput.name}"`, + details: res?.errors?.[0].message, + }); + return null; + } + performerID = res.data?.performerCreate.id; + } + + if (performer.type === "update") { + const stashIDs = performer.data.stash_ids; + await updatePerformerStashID(performer.data.id, [ + ...stashIDs, + { stash_id: stashID, endpoint }, + ]); + } + + return performerID; + }) + ); + + if (!performerIDs.some((id) => !id)) { + setSaveState("Updating scene"); + const imgurl = scene.images[0]; + let imgData = null; + if (imgurl && setCoverImage) { + const img = await fetch(imgurl, { + mode: "cors", + cache: "no-store", + }); + if (img.status === 200) { + const blob = await img.blob(); + // Sanity check on image size since bad images will fail + if (blob.size > 10000) imgData = await blobToBase64(blob); + } + } + + let updatedTags = stashScene?.tags?.map((t) => t.id) ?? []; + if (setTags) { + const newTagIDs = tagOperation === "merge" ? updatedTags : []; + const tags = scene.tags ?? []; + if (tags.length > 0) { + const tagDict: Record = (allTags?.allTagsSlim ?? []) + .filter((t) => t.name) + .reduce( + (dict, t) => ({ ...dict, [t.name.toLowerCase()]: t.id }), + {} + ); + const newTags: string[] = []; + tags.forEach((tag) => { + if (tagDict[tag.name.toLowerCase()]) + newTagIDs.push(tagDict[tag.name.toLowerCase()]); + else newTags.push(tag.name); + }); + + const createdTags = await Promise.all( + newTags.map((tag) => createTag(tag)) + ); + createdTags.forEach((createdTag) => { + if (createdTag?.data?.tagCreate?.id) + newTagIDs.push(createdTag.data.tagCreate.id); + }); + } + updatedTags = uniq(newTagIDs); + } + + const sceneUpdateResult = await updateScene({ + variables: { + id: stashScene.id ?? "", + title: scene.title, + details: scene.details, + date: scene.date, + performer_ids: performerIDs.filter((id) => id !== "Skip") as string[], + studio_id: studioID, + cover_image: imgData, + url: scene.url, + tag_ids: updatedTags, + rating: stashScene.rating, + movies: stashScene.movies.map((m) => ({ + movie_id: m.movie.id, + scene_index: m.scene_index, + })), + stash_ids: [ + ...(stashScene?.stash_ids ?? []), + { + endpoint, + stash_id: scene.stash_id, + }, + ], + }, + }); + + if (!sceneUpdateResult?.data?.sceneUpdate) { + setError({ + message: "Failed to save scene", + details: sceneUpdateResult?.errors?.[0].message, + }); + } else if (sceneUpdateResult.data?.sceneUpdate) + setScene(sceneUpdateResult.data.sceneUpdate); + + queueFingerprintSubmission(stashScene.id, endpoint); + } + + setSaveState(""); + }; + + const classname = cx("row no-gutters mt-2 search-result", { + "selected-result": isActive, + }); + + const sceneTitle = scene.url ? ( + + {scene?.title} + + ) : ( + {scene?.title} + ); + + const saveEnabled = + Object.keys(performers ?? []).length === + scene.performers.filter((p) => p.gender !== "MALE" || showMales).length && + Object.keys(performers ?? []).every((id) => performers?.[id].type) && + saveState === ""; + + return ( + // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions +
  • !isActive && setActive()} + > +
    +
    + +
    +

    + {sceneTitle} +

    +
    + {scene?.studio?.name} • {scene?.date} +
    +
    + Performers: {scene?.performers?.map((p) => p.name).join(", ")} +
    + {getDurationStatus(scene, stashScene.file?.duration)} + {getFingerprintStatus( + scene, + stashScene.checksum ?? stashScene.oshash ?? undefined + )} +
    +
    +
    + {isActive && ( +
    + + {scene.performers + .filter((p) => p.gender !== "MALE" || showMales) + .map((performer) => ( + + setPerformer(data, performer.stash_id) + } + key={`${scene.stash_id}${performer.stash_id}`} + /> + ))} +
    + {error.message && ( + + + Error: + + {error.message} + + )} + {saveState && ( + + {saveState} + + )} + +
    +
    + )} +
  • + ); +}; + +export default StashSearchResult; diff --git a/ui/v2.5/src/components/Tagger/StudioResult.tsx b/ui/v2.5/src/components/Tagger/StudioResult.tsx new file mode 100755 index 000000000..f082ad87f --- /dev/null +++ b/ui/v2.5/src/components/Tagger/StudioResult.tsx @@ -0,0 +1,161 @@ +import React, { useEffect, useState, Dispatch, SetStateAction } from "react"; +import { Button, ButtonGroup } from "react-bootstrap"; +import cx from "classnames"; + +import { SuccessIcon, Modal, StudioSelect } from "src/components/Shared"; +import * as GQL from "src/core/generated-graphql"; +import { ValidTypes } from "src/components/Shared/Select"; +import { IStashBoxStudio } from "./utils"; + +export type StudioOperation = + | { type: "create"; data: IStashBoxStudio } + | { type: "update"; data: GQL.SlimStudioDataFragment } + | { type: "existing"; data: GQL.StudioDataFragment } + | { type: "skip" }; + +interface IStudioResultProps { + studio: IStashBoxStudio | null; + setStudio: Dispatch>; +} + +const StudioResult: React.FC = ({ studio, setStudio }) => { + const [selectedStudio, setSelectedStudio] = useState(); + const [modalVisible, showModal] = useState(false); + const [selectedSource, setSelectedSource] = useState< + "create" | "existing" | "skip" | undefined + >(); + const { data: studioData } = GQL.useFindStudioQuery({ + variables: { id: studio?.id ?? "" }, + skip: !studio?.id, + }); + const { + data: stashIDData, + loading: loadingStashID, + } = GQL.useFindStudiosQuery({ + variables: { + studio_filter: { + stash_id: studio?.stash_id, + }, + }, + }); + + useEffect(() => { + if (stashIDData?.findStudios.studios?.[0]) + setStudio({ + type: "existing", + data: stashIDData.findStudios.studios[0], + }); + else if (studioData?.findStudio) { + setSelectedSource("existing"); + setSelectedStudio(studioData.findStudio.id); + setStudio({ + type: "update", + data: studioData.findStudio, + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [stashIDData, studioData]); + + const handleStudioSelect = (newStudio: ValidTypes[]) => { + if (newStudio.length) { + setSelectedSource("existing"); + setSelectedStudio(newStudio[0].id); + setStudio({ + type: "update", + data: newStudio[0] as GQL.SlimStudioDataFragment, + }); + } else { + setSelectedSource(undefined); + setSelectedStudio(null); + } + }; + + const handleStudioCreate = () => { + if (!studio) return; + setSelectedSource("create"); + setStudio({ + type: "create", + data: studio, + }); + showModal(false); + }; + + const handleStudioSkip = () => { + setSelectedSource("skip"); + setStudio({ type: "skip" }); + }; + + if (loadingStashID) return
    Loading studio
    ; + + if (stashIDData?.findStudios.studios.length) { + return ( +
    +
    + Studio: + {studio?.name} +
    + + + Matched: + + + {stashIDData.findStudios.studios[0].name} + +
    + ); + } + + return ( +
    + showModal(false), variant: "secondary" }} + > +
    + Name: + {studio?.name} +
    +
    + URL: + {studio?.url ?? ""} +
    +
    + Logo: + + + +
    +
    + +
    + Studio: + {studio?.name} +
    + + + + + +
    + ); +}; + +export default StudioResult; diff --git a/ui/v2.5/src/components/Tagger/Tagger.tsx b/ui/v2.5/src/components/Tagger/Tagger.tsx new file mode 100755 index 000000000..01246e26d --- /dev/null +++ b/ui/v2.5/src/components/Tagger/Tagger.tsx @@ -0,0 +1,521 @@ +import React, { useState } from "react"; +import { Button, Card, Form, InputGroup } from "react-bootstrap"; +import { Link } from "react-router-dom"; +import { HashLink } from "react-router-hash-link"; + +import * as GQL from "src/core/generated-graphql"; +import { LoadingIndicator } from "src/components/Shared"; +import { + stashBoxQuery, + stashBoxBatchQuery, + useConfiguration, +} from "src/core/StashService"; +import { Manual } from "src/components/Help/Manual"; + +import StashSearchResult from "./StashSearchResult"; +import Config, { ITaggerConfig, initialConfig, ParseMode } from "./Config"; +import { + parsePath, + selectScenes, + IStashBoxScene, + sortScenesByDuration, +} from "./utils"; + +const dateRegex = /\.(\d\d)\.(\d\d)\.(\d\d)\./; +function prepareQueryString( + scene: Partial, + paths: string[], + filename: string, + mode: ParseMode, + blacklist: string[] +) { + if ((mode === "auto" && scene.date && scene.studio) || mode === "metadata") { + let str = [ + scene.date, + scene.studio?.name ?? "", + (scene?.performers ?? []).map((p) => p.name).join(" "), + scene?.title ? scene.title.replace(/[^a-zA-Z0-9 ]+/g, "") : "", + ] + .filter((s) => s !== "") + .join(" "); + blacklist.forEach((b) => { + str = str.replace(new RegExp(b, "gi"), " "); + }); + return str; + } + let s = ""; + if (mode === "auto" || mode === "filename") { + s = filename; + } else if (mode === "path") { + s = [...paths, filename].join(" "); + } else { + s = paths[paths.length - 1]; + } + blacklist.forEach((b) => { + s = s.replace(new RegExp(b, "i"), ""); + }); + const date = s.match(dateRegex); + s = s.replace(/-/g, " "); + if (date) { + s = s.replace(date[0], ` 20${date[1]}-${date[2]}-${date[3]} `); + } + return s.replace(/\./g, " "); +} + +interface ITaggerListProps { + scenes: GQL.SlimSceneDataFragment[]; + selectedEndpoint: { endpoint: string; index: number }; + config: ITaggerConfig; + queueFingerprintSubmission: (sceneId: string, endpoint: string) => void; + clearSubmissionQueue: (endpoint: string) => void; +} + +const TaggerList: React.FC = ({ + scenes, + selectedEndpoint, + config, + queueFingerprintSubmission, + clearSubmissionQueue, +}) => { + const [fingerprintError, setFingerprintError] = useState(""); + const [loading, setLoading] = useState(false); + const [queryString, setQueryString] = useState>({}); + + const [searchResults, setSearchResults] = useState< + Record + >({}); + const [searchErrors, setSearchErrors] = useState< + Record + >({}); + const [selectedResult, setSelectedResult] = useState< + Record + >(); + const [taggedScenes, setTaggedScenes] = useState< + Record> + >({}); + const [loadingFingerprints, setLoadingFingerprints] = useState(false); + const [fingerprints, setFingerprints] = useState< + Record + >({}); + const fingerprintQueue = + config.fingerprintQueue[selectedEndpoint.endpoint] ?? []; + + const doBoxSearch = (sceneID: string, searchVal: string) => { + stashBoxQuery(searchVal, selectedEndpoint.index) + .then((queryData) => { + const s = selectScenes(queryData.data?.queryStashBoxScene); + setSearchResults({ + ...searchResults, + [sceneID]: s, + }); + setSearchErrors({ + ...searchErrors, + [sceneID]: undefined, + }); + setLoading(false); + }) + .catch(() => { + setLoading(false); + // Destructure to remove existing result + const { [sceneID]: unassign, ...results } = searchResults; + setSearchResults(results); + setSearchErrors({ + ...searchErrors, + [sceneID]: "Network Error", + }); + }); + + setLoading(true); + }; + + const [ + submitFingerPrints, + { loading: submittingFingerprints }, + ] = GQL.useSubmitStashBoxFingerprintsMutation({ + onCompleted: (result) => { + setFingerprintError(""); + if (result.submitStashBoxFingerprints) + clearSubmissionQueue(selectedEndpoint.endpoint); + }, + onError: () => { + setFingerprintError("Network Error"); + }, + }); + + const handleFingerprintSubmission = () => { + submitFingerPrints({ + variables: { + input: { + stash_box_index: selectedEndpoint.index, + scene_ids: fingerprintQueue, + }, + }, + }); + }; + + const handleTaggedScene = (scene: Partial) => { + setTaggedScenes({ + ...taggedScenes, + [scene.id as string]: scene, + }); + }; + + const handleFingerprintSearch = async () => { + setLoadingFingerprints(true); + const newFingerprints = { ...fingerprints }; + + const sceneIDs = scenes + .filter((s) => s.stash_ids.length === 0) + .map((s) => s.id); + + const results = await stashBoxBatchQuery( + sceneIDs, + selectedEndpoint.index + ).catch(() => { + setLoadingFingerprints(false); + setFingerprintError("Network Error"); + }); + + if (!results) return; + + // clear search errors + setSearchErrors({}); + + selectScenes(results.data?.queryStashBoxScene).forEach((scene) => { + scene.fingerprints?.forEach((f) => { + newFingerprints[f.hash] = scene; + }); + }); + + // Null any ids that are still undefined since it means they weren't found + sceneIDs.forEach((id) => { + newFingerprints[id] = newFingerprints[id] ?? null; + }); + + setFingerprints(newFingerprints); + setLoadingFingerprints(false); + setFingerprintError(""); + }; + + const canFingerprintSearch = () => + scenes.some( + (s) => s.stash_ids.length === 0 && fingerprints[s.id] === undefined + ); + + const getFingerprintCount = () => { + const count = scenes.filter( + (s) => + s.stash_ids.length === 0 && + ((s.checksum && fingerprints[s.checksum]) || + (s.oshash && fingerprints[s.oshash])) + ).length; + return `${count > 0 ? count : "No"} new fingerprint matches found`; + }; + + const renderScenes = () => + scenes.map((scene) => { + const { paths, file, ext } = parsePath(scene.path); + const originalDir = scene.path.slice( + 0, + scene.path.length - file.length - ext.length + ); + const defaultQueryString = prepareQueryString( + scene, + paths, + file, + config.mode, + config.blacklist + ); + const modifiedQuery = queryString[scene.id]; + const fingerprintMatch = + fingerprints[scene.checksum ?? ""] ?? + fingerprints[scene.oshash ?? ""] ?? + null; + const isTagged = taggedScenes[scene.id]; + const hasStashIDs = scene.stash_ids.length > 0; + + let maincontent; + if (!isTagged && hasStashIDs) { + maincontent = ( +
    Scene already tagged
    + ); + } else if (!isTagged && !hasStashIDs) { + maincontent = ( + + ) => + setQueryString({ + ...queryString, + [scene.id]: e.currentTarget.value, + }) + } + onKeyPress={(e: React.KeyboardEvent) => + e.key === "Enter" && + doBoxSearch( + scene.id, + queryString[scene.id] || defaultQueryString + ) + } + /> + + + + + ); + } else if (isTagged) { + maincontent = ( +
    + Scene successfully tagged: + + {taggedScenes[scene.id].title} + +
    + ); + } + + let searchResult; + if (searchErrors[scene.id]) { + searchResult = ( +
    + {searchErrors[scene.id]} +
    + ); + } else if (fingerprintMatch && !isTagged && !hasStashIDs) { + searchResult = ( + {}} + setScene={handleTaggedScene} + scene={fingerprintMatch} + setCoverImage={config.setCoverImage} + setTags={config.setTags} + tagOperation={config.tagOperation} + endpoint={selectedEndpoint.endpoint} + queueFingerprintSubmission={queueFingerprintSubmission} + /> + ); + } else if ( + searchResults[scene.id]?.length > 0 && + !isTagged && + !fingerprintMatch + ) { + searchResult = ( +
      + {sortScenesByDuration( + searchResults[scene.id], + scene.file.duration ?? undefined + ).map( + (sceneResult, i) => + sceneResult && ( + + setSelectedResult({ + ...selectedResult, + [scene.id]: i, + }) + } + setCoverImage={config.setCoverImage} + tagOperation={config.tagOperation} + setTags={config.setTags} + setScene={handleTaggedScene} + endpoint={selectedEndpoint.endpoint} + queueFingerprintSubmission={queueFingerprintSubmission} + /> + ) + )} +
    + ); + } else if (searchResults[scene.id]?.length === 0) { + searchResult = ( +
    No results found.
    + ); + } + + return ( +
    +
    +
    + + {originalDir} + + {`${file}.${ext}`} + +
    +
    {maincontent}
    +
    + {searchResult} +
    + ); + }); + + return ( + +
    +
    + Path +
    +
    + Query +
    + {fingerprintError} +
    + {fingerprintQueue.length > 0 && ( + + )} +
    +
    + +
    +
    + {renderScenes()} +
    + ); +}; + +interface ITaggerProps { + scenes: GQL.SlimSceneDataFragment[]; +} + +export const Tagger: React.FC = ({ scenes }) => { + const stashConfig = useConfiguration(); + const [config, setConfig] = useState(initialConfig); + const [showConfig, setShowConfig] = useState(false); + const [showManual, setShowManual] = useState(false); + + const savedEndpointIndex = + stashConfig.data?.configuration.general.stashBoxes.findIndex( + (s) => s.endpoint === config.selectedEndpoint + ) ?? -1; + const selectedEndpointIndex = + savedEndpointIndex === -1 && + stashConfig.data?.configuration.general.stashBoxes.length + ? 0 + : savedEndpointIndex; + const selectedEndpoint = + stashConfig.data?.configuration.general.stashBoxes[selectedEndpointIndex]; + + const queueFingerprintSubmission = (sceneId: string, endpoint: string) => { + setConfig({ + ...config, + fingerprintQueue: { + ...config.fingerprintQueue, + [endpoint]: [...(config.fingerprintQueue[endpoint] ?? []), sceneId], + }, + }); + }; + + const clearSubmissionQueue = (endpoint: string) => { + setConfig({ + ...config, + fingerprintQueue: { + ...config.fingerprintQueue, + [endpoint]: [], + }, + }); + }; + + return ( + <> + setShowManual(false)} + defaultActiveTab="Tagger.md" + /> +
    + {selectedEndpointIndex !== -1 && selectedEndpoint ? ( + <> +
    + + +
    + + + + + ) : ( +
    +

    + To use the scene tagger a stash-box instance needs to be + configured. +

    +
    + Please see{" "} + + el.scrollIntoView({ behavior: "smooth", block: "center" }) + } + > + Settings. + +
    +
    + )} +
    + + ); +}; diff --git a/ui/v2.5/src/components/Tagger/index.ts b/ui/v2.5/src/components/Tagger/index.ts new file mode 100644 index 000000000..05d179c57 --- /dev/null +++ b/ui/v2.5/src/components/Tagger/index.ts @@ -0,0 +1 @@ +export { Tagger as default } from "./Tagger"; diff --git a/ui/v2.5/src/components/Tagger/queries.ts b/ui/v2.5/src/components/Tagger/queries.ts new file mode 100644 index 000000000..98a685896 --- /dev/null +++ b/ui/v2.5/src/components/Tagger/queries.ts @@ -0,0 +1,243 @@ +import * as GQL from "src/core/generated-graphql"; +import { sortBy } from "lodash"; + +export const useUpdatePerformerStashID = () => { + const [updatePerformer] = GQL.usePerformerUpdateMutation({ + onError: (errors) => errors, + }); + + const updatePerformerHandler = ( + performerID: string, + stashIDs: GQL.StashIdInput[] + ) => + updatePerformer({ + variables: { + id: performerID, + stash_ids: stashIDs.map((s) => ({ + stash_id: s.stash_id, + endpoint: s.endpoint, + })), + }, + update: (store, updatedPerformer) => { + if (!updatedPerformer.data?.performerUpdate) return; + const newStashID = stashIDs[stashIDs.length - 1].stash_id; + + store.writeQuery< + GQL.FindPerformersQuery, + GQL.FindPerformersQueryVariables + >({ + query: GQL.FindPerformersDocument, + variables: { + performer_filter: { + stash_id: newStashID, + }, + }, + data: { + findPerformers: { + count: 1, + performers: [updatedPerformer.data.performerUpdate], + __typename: "FindPerformersResultType", + }, + }, + }); + }, + }); + + return updatePerformerHandler; +}; + +export const useCreatePerformer = () => { + const [createPerformer] = GQL.usePerformerCreateMutation({ + onError: (errors) => errors, + }); + + const handleCreate = (performer: GQL.PerformerCreateInput, stashID: string) => + createPerformer({ + variables: performer, + update: (store, newPerformer) => { + if (!newPerformer?.data?.performerCreate) return; + + const currentQuery = store.readQuery< + GQL.AllPerformersForFilterQuery, + GQL.AllPerformersForFilterQueryVariables + >({ + query: GQL.AllPerformersForFilterDocument, + }); + const allPerformersSlim = sortBy( + [ + ...(currentQuery?.allPerformersSlim ?? []), + newPerformer.data.performerCreate, + ], + ["name"] + ); + if (allPerformersSlim.length > 1) { + store.writeQuery< + GQL.AllPerformersForFilterQuery, + GQL.AllPerformersForFilterQueryVariables + >({ + query: GQL.AllPerformersForFilterDocument, + data: { + allPerformersSlim, + }, + }); + } + + store.writeQuery< + GQL.FindPerformersQuery, + GQL.FindPerformersQueryVariables + >({ + query: GQL.FindPerformersDocument, + variables: { + performer_filter: { + stash_id: stashID, + }, + }, + data: { + findPerformers: { + count: 1, + performers: [newPerformer.data.performerCreate], + __typename: "FindPerformersResultType", + }, + }, + }); + }, + }); + + return handleCreate; +}; + +export const useUpdateStudioStashID = () => { + const [updateStudio] = GQL.useStudioUpdateMutation({ + onError: (errors) => errors, + }); + + const handleUpdate = ( + studio: GQL.SlimStudioDataFragment, + stashIDs: GQL.StashIdInput[] + ) => + updateStudio({ + variables: { + id: studio.id, + parent_id: studio.parent_studio?.id, + stash_ids: stashIDs.map((s) => ({ + stash_id: s.stash_id, + endpoint: s.endpoint, + })), + }, + update: (store, result) => { + if (!result.data?.studioUpdate) return; + const newStashID = stashIDs[stashIDs.length - 1].stash_id; + + store.writeQuery({ + query: GQL.FindStudiosDocument, + variables: { + studio_filter: { + stash_id: newStashID, + }, + }, + data: { + findStudios: { + count: 1, + studios: [result.data.studioUpdate], + __typename: "FindStudiosResultType", + }, + }, + }); + }, + }); + + return handleUpdate; +}; + +export const useCreateStudio = () => { + const [createStudio] = GQL.useStudioCreateMutation({ + onError: (errors) => errors, + }); + + const handleCreate = (studio: GQL.StudioCreateInput, stashID: string) => + createStudio({ + variables: studio, + update: (store, result) => { + if (!result?.data?.studioCreate) return; + + const currentQuery = store.readQuery< + GQL.AllStudiosForFilterQuery, + GQL.AllStudiosForFilterQueryVariables + >({ + query: GQL.AllStudiosForFilterDocument, + }); + const allStudiosSlim = sortBy( + [...(currentQuery?.allStudiosSlim ?? []), result.data.studioCreate], + ["name"] + ); + if (allStudiosSlim.length > 1) { + store.writeQuery< + GQL.AllStudiosForFilterQuery, + GQL.AllStudiosForFilterQueryVariables + >({ + query: GQL.AllStudiosForFilterDocument, + data: { + allStudiosSlim, + }, + }); + } + + store.writeQuery({ + query: GQL.FindStudiosDocument, + variables: { + studio_filter: { + stash_id: stashID, + }, + }, + data: { + findStudios: { + count: 1, + studios: [result.data.studioCreate], + __typename: "FindStudiosResultType", + }, + }, + }); + }, + }); + + return handleCreate; +}; + +export const useCreateTag = () => { + const [createTag] = GQL.useTagCreateMutation({ + onError: (errors) => errors, + }); + + const handleCreate = (tag: string) => + createTag({ + variables: { + name: tag, + }, + update: (store, result) => { + if (!result.data?.tagCreate) return; + + const currentQuery = store.readQuery< + GQL.AllTagsForFilterQuery, + GQL.AllTagsForFilterQueryVariables + >({ + query: GQL.AllTagsForFilterDocument, + }); + const allTagsSlim = sortBy( + [...(currentQuery?.allTagsSlim ?? []), result.data.tagCreate], + ["name"] + ); + + store.writeQuery< + GQL.AllTagsForFilterQuery, + GQL.AllTagsForFilterQueryVariables + >({ + query: GQL.AllTagsForFilterDocument, + data: { + allTagsSlim, + }, + }); + }, + }); + + return handleCreate; +}; diff --git a/ui/v2.5/src/components/Tagger/styles.scss b/ui/v2.5/src/components/Tagger/styles.scss new file mode 100644 index 000000000..13f321cb9 --- /dev/null +++ b/ui/v2.5/src/components/Tagger/styles.scss @@ -0,0 +1,99 @@ +.tagger-container { + max-width: 1400px; + // min-width: 1200px; +} + +.tagger-table { + overflow: visible; +} + +.search-item { + background-color: #495b68; + margin-left: -20px; + margin-right: -20px; + padding: 1rem; +} + +.search-result { + background-color: rgba(61, 80, 92, 0.3); + padding: 0.5rem 1rem; + + &:hover { + background-color: hsl(204, 20, 30); + cursor: pointer; + } +} + +.selected-result { + background-color: hsl(204, 20, 30); + border-radius: 3px; + + &:hover { + cursor: default; + } +} + +.scene-select { + &:hover { + cursor: pointer; + } +} + +.scene-image { + max-height: 10rem; + max-width: 14rem; + min-width: 168px; + padding: 0 1rem; +} + +.scene-metadata { + margin-right: 1rem; + width: calc(100% - 17rem); +} + +.select-existing { + width: 2rem; +} + +.performer-select, +.studio-select { + width: 14rem; + + // stylelint-disable-next-line selector-class-pattern + &-active .react-select__control { + background-color: #137cbd; + } +} + +.entity-name { + flex: 1; + margin-right: auto; +} + +.scene-link { + color: $text-color; + font-weight: 500; +} + +.performer-create-modal { + font-size: 1.2rem; + max-width: 768px; + + .image-selection { + height: 450px; + text-align: center; + + .performer-image { + height: 85%; + } + + img { + max-height: 100%; + max-width: 100%; + } + } + + .LoadingIndicator { + height: 100%; + } +} diff --git a/ui/v2.5/src/components/Tagger/utils.ts b/ui/v2.5/src/components/Tagger/utils.ts new file mode 100644 index 000000000..39b34bd94 --- /dev/null +++ b/ui/v2.5/src/components/Tagger/utils.ts @@ -0,0 +1,177 @@ +import * as GQL from "src/core/generated-graphql"; +import { getCountryByISO } from "src/utils/country"; + +const toTitleCase = (phrase: string) => { + return phrase + .toLowerCase() + .split(" ") + .map((word) => word.charAt(0).toUpperCase() + word.slice(1)) + .join(" "); +}; + +export const parsePath = (filePath: string) => { + const path = filePath.toLowerCase(); + const isWin = /^([a-z]:|\\\\)/.test(path); + const normalizedPath = isWin + ? path.replace(/^[a-z]:/, "").replace(/\\/g, "/") + : path; + const pathComponents = normalizedPath + .split("/") + .filter((component) => component.trim().length > 0); + const fileName = pathComponents[pathComponents.length - 1]; + + const ext = fileName.match(/\.[a-z0-9]*$/)?.[0] ?? ""; + const file = fileName.slice(0, ext.length * -1); + const paths = + pathComponents.length > 2 + ? pathComponents.slice(0, pathComponents.length - 2) + : []; + + return { paths, file, ext }; +}; + +export interface IStashBoxFingerprint { + hash: string; + algorithm: string; + duration: number; +} + +export interface IStashBoxPerformer { + id?: string; + stash_id: string; + name: string; + gender?: GQL.GenderEnum; + url?: string; + twitter?: string; + instagram?: string; + birthdate?: string; + ethnicity?: string; + country?: string; + eye_color?: string; + height?: string; + measurements?: string; + fake_tits?: string; + career_length?: string; + tattoos?: string; + piercings?: string; + aliases?: string; + images: string[]; +} + +export interface IStashBoxTag { + id?: string; + name: string; +} + +export interface IStashBoxStudio { + id?: string; + stash_id: string; + name: string; + url?: string; + image?: string; +} + +export interface IStashBoxScene { + stash_id: string; + title: string; + date: string; + duration: number; + details?: string; + url?: string; + + studio: IStashBoxStudio; + images: string[]; + tags: IStashBoxTag[]; + performers: IStashBoxPerformer[]; + fingerprints: IStashBoxFingerprint[]; +} + +const selectStudio = (studio: GQL.ScrapedSceneStudio): IStashBoxStudio => ({ + id: studio?.stored_id ?? undefined, + stash_id: studio.remote_site_id!, + name: studio.name, + url: studio.url ?? undefined, +}); + +const selectFingerprints = ( + scene: GQL.ScrapedScene | null +): IStashBoxFingerprint[] => scene?.fingerprints ?? []; + +const selectTags = (tags: GQL.ScrapedSceneTag[]): IStashBoxTag[] => + tags.map((t) => ({ + id: t.stored_id ?? undefined, + name: t.name ?? "", + })); + +const selectPerformers = ( + performers: GQL.ScrapedScenePerformer[] +): IStashBoxPerformer[] => + performers.map((p) => ({ + id: p.stored_id ?? undefined, + stash_id: p.remote_site_id!, + name: p.name ?? "", + gender: (p.gender ?? GQL.GenderEnum.Female) as GQL.GenderEnum, + url: p.url ?? undefined, + twitter: p.twitter ?? undefined, + instagram: p.instagram ?? undefined, + birthdate: p.birthdate ?? undefined, + ethnicity: p.ethnicity ? toTitleCase(p.ethnicity) : undefined, + country: getCountryByISO(p.country) ?? undefined, + eye_color: p.eye_color ? toTitleCase(p.eye_color) : undefined, + height: p.height ?? undefined, + measurements: p.measurements ?? undefined, + fake_tits: p.fake_tits ? toTitleCase(p.fake_tits) : undefined, + career_length: p.career_length ?? undefined, + tattoos: p.tattoos ? toTitleCase(p.tattoos) : undefined, + piercings: p.piercings ? toTitleCase(p.piercings) : undefined, + aliases: p.aliases ?? undefined, + images: p.images ?? [], + })); + +export const selectScenes = ( + scenes?: (GQL.ScrapedScene | null)[] +): IStashBoxScene[] => { + const result = (scenes ?? []) + .filter((s) => s !== null) + .map( + (s) => + ({ + stash_id: s?.remote_site_id!, + title: s?.title ?? "", + date: s?.date ?? "", + duration: s?.duration ?? 0, + details: s?.details, + url: s?.url, + images: s?.image ? [s.image] : [], + studio: selectStudio(s?.studio!), + fingerprints: selectFingerprints(s), + performers: selectPerformers(s?.performers ?? []), + tags: selectTags(s?.tags ?? []), + } as IStashBoxScene) + ); + + return result; +}; + +export const sortScenesByDuration = ( + scenes: IStashBoxScene[], + targetDuration?: number +) => + scenes.sort((a, b) => { + const adur = + a?.duration || (a?.fingerprints.map((f) => f.duration)?.[0] ?? null); + const bdur = + b?.duration || (b?.fingerprints.map((f) => f.duration)?.[0] ?? null); + if (!adur && !bdur) return 0; + if (adur && !bdur) return -1; + if (!adur && bdur) return 1; + + if (!targetDuration) return 0; + + const aDiff = Math.abs((adur ?? 0) - targetDuration); + const bDiff = Math.abs((bdur ?? 0) - targetDuration); + + if (aDiff < bDiff) return -1; + if (aDiff > bDiff) return 1; + return 0; + }); diff --git a/ui/v2.5/src/components/Tags/TagCard.tsx b/ui/v2.5/src/components/Tags/TagCard.tsx index 3da8f4b68..d157703df 100644 --- a/ui/v2.5/src/components/Tags/TagCard.tsx +++ b/ui/v2.5/src/components/Tags/TagCard.tsx @@ -1,16 +1,26 @@ -import { Card, Button, ButtonGroup } from "react-bootstrap"; +import { Button, ButtonGroup } from "react-bootstrap"; import React from "react"; import { Link } from "react-router-dom"; import * as GQL from "src/core/generated-graphql"; import { NavUtils } from "src/utils"; import { Icon } from "../Shared"; +import { BasicCard } from "../Shared/BasicCard"; interface IProps { tag: GQL.TagDataFragment; zoomIndex: number; + selecting?: boolean; + selected?: boolean; + onSelectedChanged?: (selected: boolean, shiftKey: boolean) => void; } -export const TagCard: React.FC = ({ tag, zoomIndex }) => { +export const TagCard: React.FC = ({ + tag, + zoomIndex, + selecting, + selected, + onSelectedChanged, +}) => { function maybeRenderScenesPopoverButton() { if (!tag.scene_count) return; @@ -52,18 +62,22 @@ export const TagCard: React.FC = ({ tag, zoomIndex }) => { } return ( - - + - -
    -
    {tag.name}
    -
    - {maybeRenderPopoverButtonGroup()} -
    + } + details={
    {tag.name}
    } + popovers={maybeRenderPopoverButtonGroup()} + selected={selected} + selecting={selecting} + onSelectedChanged={onSelectedChanged} + /> ); }; diff --git a/ui/v2.5/src/components/Tags/TagDetails/Tag.tsx b/ui/v2.5/src/components/Tags/TagDetails/Tag.tsx index b577e3d7f..a01f147a9 100644 --- a/ui/v2.5/src/components/Tags/TagDetails/Tag.tsx +++ b/ui/v2.5/src/components/Tags/TagDetails/Tag.tsx @@ -20,11 +20,17 @@ import { import { useToast } from "src/hooks"; import { TagScenesPanel } from "./TagScenesPanel"; import { TagMarkersPanel } from "./TagMarkersPanel"; +import { TagImagesPanel } from "./TagImagesPanel"; + +interface ITabParams { + id?: string; + tab?: string; +} export const Tag: React.FC = () => { const history = useHistory(); const Toast = useToast(); - const { tab = "scenes", id = "new" } = useParams(); + const { tab = "scenes", id = "new" } = useParams(); const isNew = id === "new"; // Editing state @@ -44,8 +50,8 @@ export const Tag: React.FC = () => { const [createTag] = useTagCreate(getTagInput() as GQL.TagUpdateInput); const [deleteTag] = useTagDestroy(getTagInput() as GQL.TagUpdateInput); - const activeTabKey = tab === "markers" ? tab : "scenes"; - const setActiveTabKey = (newTab: string) => { + const activeTabKey = tab === "markers" || tab === "images" ? tab : "scenes"; + const setActiveTabKey = (newTab: string | null) => { if (tab !== newTab) { const tabParam = newTab === "scenes" ? "" : `/${newTab}`; history.replace(`/tags/${id}${tabParam}`); @@ -241,6 +247,9 @@ export const Tag: React.FC = () => { + + + diff --git a/ui/v2.5/src/components/Tags/TagDetails/TagImagesPanel.tsx b/ui/v2.5/src/components/Tags/TagDetails/TagImagesPanel.tsx new file mode 100644 index 000000000..5df08f505 --- /dev/null +++ b/ui/v2.5/src/components/Tags/TagDetails/TagImagesPanel.tsx @@ -0,0 +1,12 @@ +import React from "react"; +import * as GQL from "src/core/generated-graphql"; +import { tagFilterHook } from "src/core/tags"; +import { ImageList } from "src/components/Images/ImageList"; + +interface ITagImagesPanel { + tag: GQL.TagDataFragment; +} + +export const TagImagesPanel: React.FC = ({ tag }) => { + return ; +}; diff --git a/ui/v2.5/src/components/Tags/TagDetails/TagScenesPanel.tsx b/ui/v2.5/src/components/Tags/TagDetails/TagScenesPanel.tsx index 1c402dae7..1cfd0dc68 100644 --- a/ui/v2.5/src/components/Tags/TagDetails/TagScenesPanel.tsx +++ b/ui/v2.5/src/components/Tags/TagDetails/TagScenesPanel.tsx @@ -1,45 +1,12 @@ import React from "react"; import * as GQL from "src/core/generated-graphql"; -import { ListFilterModel } from "src/models/list-filter/filter"; import { SceneList } from "src/components/Scenes/SceneList"; -import { TagsCriterion } from "src/models/list-filter/criteria/tags"; +import { tagFilterHook } from "src/core/tags"; interface ITagScenesPanel { tag: GQL.TagDataFragment; } export const TagScenesPanel: React.FC = ({ tag }) => { - function filterHook(filter: ListFilterModel) { - const tagValue = { id: tag.id, label: tag.name }; - // if tag is already present, then we modify it, otherwise add - let tagCriterion = filter.criteria.find((c) => { - return c.type === "tags"; - }) as TagsCriterion; - - if ( - tagCriterion && - (tagCriterion.modifier === GQL.CriterionModifier.IncludesAll || - tagCriterion.modifier === GQL.CriterionModifier.Includes) - ) { - // add the tag if not present - if ( - !tagCriterion.value.find((p) => { - return p.id === tag.id; - }) - ) { - tagCriterion.value.push(tagValue); - } - - tagCriterion.modifier = GQL.CriterionModifier.IncludesAll; - } else { - // overwrite - tagCriterion = new TagsCriterion("tags"); - tagCriterion.value = [tagValue]; - filter.criteria.push(tagCriterion); - } - - return filter; - } - - return ; + return ; }; diff --git a/ui/v2.5/src/components/Tags/TagList.tsx b/ui/v2.5/src/components/Tags/TagList.tsx index 6044506a7..ab58468d8 100644 --- a/ui/v2.5/src/components/Tags/TagList.tsx +++ b/ui/v2.5/src/components/Tags/TagList.tsx @@ -1,17 +1,23 @@ import React, { useState } from "react"; +import _ from "lodash"; import { FindTagsQueryResult } from "src/core/generated-graphql"; import { ListFilterModel } from "src/models/list-filter/filter"; import { DisplayMode } from "src/models/list-filter/types"; -import { useTagsList } from "src/hooks/ListHook"; +import { showWhenSelected, useTagsList } from "src/hooks/ListHook"; import { Button } from "react-bootstrap"; -import { Link } from "react-router-dom"; +import { Link, useHistory } from "react-router-dom"; import * as GQL from "src/core/generated-graphql"; -import { mutateMetadataAutoTag, useTagDestroy } from "src/core/StashService"; +import { + queryFindTags, + mutateMetadataAutoTag, + useTagDestroy, +} from "src/core/StashService"; import { useToast } from "src/hooks"; import { FormattedNumber } from "react-intl"; import { NavUtils } from "src/utils"; import { Icon, Modal } from "src/components/Shared"; import { TagCard } from "./TagCard"; +import { ExportDialog } from "../Shared/ExportDialog"; interface ITagList { filterHook?: (filter: ListFilterModel) => ListFilterModel; @@ -25,9 +31,101 @@ export const TagList: React.FC = ({ filterHook }) => { const [deleteTag] = useTagDestroy(getDeleteTagInput() as GQL.TagDestroyInput); + const history = useHistory(); + const [isExportDialogOpen, setIsExportDialogOpen] = useState(false); + const [isExportAll, setIsExportAll] = useState(false); + + const otherOperations = [ + { + text: "View Random", + onClick: viewRandom, + }, + { + text: "Export...", + onClick: onExport, + isDisplayed: showWhenSelected, + }, + { + text: "Export all...", + onClick: onExportAll, + }, + ]; + + const addKeybinds = ( + result: FindTagsQueryResult, + filter: ListFilterModel + ) => { + Mousetrap.bind("p r", () => { + viewRandom(result, filter); + }); + + return () => { + Mousetrap.unbind("p r"); + }; + }; + + async function viewRandom( + result: FindTagsQueryResult, + filter: ListFilterModel + ) { + // query for a random tag + if (result.data && result.data.findTags) { + const { count } = result.data.findTags; + + const index = Math.floor(Math.random() * count); + const filterCopy = _.cloneDeep(filter); + filterCopy.itemsPerPage = 1; + filterCopy.currentPage = index + 1; + const singleResult = await queryFindTags(filterCopy); + if ( + singleResult && + singleResult.data && + singleResult.data.findTags && + singleResult.data.findTags.tags.length === 1 + ) { + const { id } = singleResult!.data!.findTags!.tags[0]; + // navigate to the tag page + history.push(`/tags/${id}`); + } + } + } + + async function onExport() { + setIsExportAll(false); + setIsExportDialogOpen(true); + } + + async function onExportAll() { + setIsExportAll(true); + setIsExportDialogOpen(true); + } + + function maybeRenderExportDialog(selectedIds: Set) { + if (isExportDialogOpen) { + return ( + <> + { + setIsExportDialogOpen(false); + }} + /> + + ); + } + } + const listData = useTagsList({ renderContent, filterHook, + addKeybinds, + otherOperations, + selectable: true, zoomable: true, defaultZoomIndex: 0, persistState: true, @@ -61,7 +159,7 @@ export const TagList: React.FC = ({ filterHook }) => { } } - function renderContent( + function renderTags( result: FindTagsQueryResult, filter: ListFilterModel, selectedIds: Set, @@ -73,7 +171,16 @@ export const TagList: React.FC = ({ filterHook }) => { return (
    {result.data.findTags.tags.map((tag) => ( - + 0} + selected={selectedIds.has(tag.id)} + onSelectedChanged={(selected: boolean, shiftKey: boolean) => + listData.onSelectChange(tag.id, selected, shiftKey) + } + /> ))}
    ); @@ -149,5 +256,19 @@ export const TagList: React.FC = ({ filterHook }) => { } } + function renderContent( + result: FindTagsQueryResult, + filter: ListFilterModel, + selectedIds: Set, + zoomIndex: number + ) { + return ( + <> + {maybeRenderExportDialog(selectedIds)} + {renderTags(result, filter, selectedIds, zoomIndex)} + + ); + } + return listData.template; }; diff --git a/ui/v2.5/src/components/Wall/WallItem.tsx b/ui/v2.5/src/components/Wall/WallItem.tsx index 0abdf64cb..4d2b36c3d 100644 --- a/ui/v2.5/src/components/Wall/WallItem.tsx +++ b/ui/v2.5/src/components/Wall/WallItem.tsx @@ -8,8 +8,12 @@ import cx from "classnames"; interface IWallItemProps { scene?: GQL.SlimSceneDataFragment; sceneMarker?: GQL.SceneMarkerDataFragment; + image?: GQL.SlimImageDataFragment; clickHandler?: ( - item: GQL.SlimSceneDataFragment | GQL.SceneMarkerDataFragment + item: + | GQL.SlimSceneDataFragment + | GQL.SceneMarkerDataFragment + | GQL.SlimImageDataFragment ) => void; className: string; } @@ -42,14 +46,6 @@ const Preview: React.FC<{ if (!previews) return
    ; - if (isMissing) { - return ( -
    - Pending preview generation -
    - ); - } - const image = ( setIsMissing(true)} + onError={(error: React.SyntheticEvent) => { + // Error code 4 indicates media not found or unsupported + setIsMissing(error.currentTarget.error?.code === 4); + }} ref={videoElement} /> ); + if (isMissing) { + // show the image if the video preview is unavailable + if (previews.image) { + return image; + } + + return ( +
    + Pending preview generation +
    + ); + } + if (previewType === "video") { return video; } @@ -98,11 +110,17 @@ export const WallItem: React.FC = (props: IWallItemProps) => { video: props.sceneMarker.stream, animation: props.sceneMarker.preview, } - : { + : props.scene + ? { video: props.scene?.paths.preview ?? undefined, animation: props.scene?.paths.webp ?? undefined, image: props.scene?.paths.screenshot ?? undefined, - }; + } + : props.image + ? { + image: props.image?.paths.thumbnail ?? undefined, + } + : undefined; const setInactive = () => setActive(false); const toggleActive = (e: TransitionEvent) => { @@ -133,6 +151,9 @@ export const WallItem: React.FC = (props: IWallItemProps) => { if (props.sceneMarker) { props?.clickHandler?.(props.sceneMarker); } + if (props.image) { + props?.clickHandler?.(props.image); + } }; let linkSrc: string = "#"; @@ -141,6 +162,8 @@ export const WallItem: React.FC = (props: IWallItemProps) => { linkSrc = `/scenes/${props.scene.id}`; } else if (props.sceneMarker) { linkSrc = NavUtils.makeSceneMarkerUrl(props.sceneMarker); + } else if (props.image) { + linkSrc = `/images/${props.image.id}`; } } diff --git a/ui/v2.5/src/components/Wall/WallPanel.tsx b/ui/v2.5/src/components/Wall/WallPanel.tsx index e00b02854..b3f2a3f2d 100644 --- a/ui/v2.5/src/components/Wall/WallPanel.tsx +++ b/ui/v2.5/src/components/Wall/WallPanel.tsx @@ -5,8 +5,12 @@ import { WallItem } from "./WallItem"; interface IWallPanelProps { scenes?: GQL.SlimSceneDataFragment[]; sceneMarkers?: GQL.SceneMarkerDataFragment[]; + images?: GQL.SlimImageDataFragment[]; clickHandler?: ( - item: GQL.SlimSceneDataFragment | GQL.SceneMarkerDataFragment + item: + | GQL.SlimSceneDataFragment + | GQL.SceneMarkerDataFragment + | GQL.SlimImageDataFragment ) => void; } @@ -56,11 +60,21 @@ export const WallPanel: React.FC = ( /> )); + const images = (props.images ?? []).map((image, index, imageArray) => ( + + )); + return (
    {scenes} {sceneMarkers} + {images}
    ); diff --git a/ui/v2.5/src/core/StashService.ts b/ui/v2.5/src/core/StashService.ts index 535f49b77..8bbc9e3ce 100644 --- a/ui/v2.5/src/core/StashService.ts +++ b/ui/v2.5/src/core/StashService.ts @@ -1,32 +1,45 @@ +import { ApolloCache, DocumentNode } from "@apollo/client"; +import { + isField, + resultKeyNameFromField, + getQueryDefinition, + getOperationName, +} from "@apollo/client/utilities"; import { ListFilterModel } from "../models/list-filter/filter"; import * as GQL from "./generated-graphql"; import { createClient } from "./createClient"; -const { client, cache } = createClient(); +const { client } = createClient(); export const getClient = () => client; -// TODO: Invalidation should happen through apollo client, rather than rewriting cache directly -const invalidateQueries = (queries: string[]) => { - if (cache) { - const keyMatchers = queries.map((query) => { - return new RegExp(`^${query}`, "i"); - }); +const getQueryNames = (queries: DocumentNode[]): string[] => + queries.map((q) => getOperationName(q)).filter((n) => n !== null) as string[]; - // TODO: Hack to invalidate, manipulating private data - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const rootQuery = (cache as any).data.data.ROOT_QUERY; - Object.keys(rootQuery).forEach((key) => { - if ( - keyMatchers.some((matcher) => { - return !!key.match(matcher); - }) - ) { - delete rootQuery[key]; - } +// Will delete the entire cache for any queries passed in +const deleteCache = (queries: DocumentNode[]) => { + const fields = queries + .map((q) => { + const field = getQueryDefinition(q).selectionSet.selections[0]; + return isField(field) ? resultKeyNameFromField(field) : ""; + }) + .filter((name) => name !== "") + .reduce( + (prevFields, name) => ({ + ...prevFields, + // eslint-disable-next-line @typescript-eslint/no-explicit-any + [name]: (_items: any, { DELETE }: any) => DELETE, + }), + {} + ); + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + return (cache: ApolloCache) => + cache.modify({ + id: "ROOT_QUERY", + fields, }); - } }; export const useFindGalleries = (filter: ListFilterModel) => @@ -37,6 +50,15 @@ export const useFindGalleries = (filter: ListFilterModel) => }, }); +export const queryFindGalleries = (filter: ListFilterModel) => + client.query({ + query: GQL.FindGalleriesDocument, + variables: { + filter: filter.makeFindFilter(), + gallery_filter: filter.makeImageFilter(), + }, + }); + export const useFindScenes = (filter: ListFilterModel) => GQL.useFindScenesQuery({ variables: { @@ -71,6 +93,23 @@ export const queryFindSceneMarkers = (filter: ListFilterModel) => }, }); +export const useFindImages = (filter: ListFilterModel) => + GQL.useFindImagesQuery({ + variables: { + filter: filter.makeFindFilter(), + image_filter: filter.makeImageFilter(), + }, + }); + +export const queryFindImages = (filter: ListFilterModel) => + client.query({ + query: GQL.FindImagesDocument, + variables: { + filter: filter.makeFindFilter(), + image_filter: filter.makeImageFilter(), + }, + }); + export const useFindStudios = (filter: ListFilterModel) => GQL.useFindStudiosQuery({ variables: { @@ -79,6 +118,15 @@ export const useFindStudios = (filter: ListFilterModel) => }, }); +export const queryFindStudios = (filter: ListFilterModel) => + client.query({ + query: GQL.FindStudiosDocument, + variables: { + filter: filter.makeFindFilter(), + studio_filter: filter.makeStudioFilter(), + }, + }); + export const useFindMovies = (filter: ListFilterModel) => GQL.useFindMoviesQuery({ variables: { @@ -87,6 +135,15 @@ export const useFindMovies = (filter: ListFilterModel) => }, }); +export const queryFindMovies = (filter: ListFilterModel) => + client.query({ + query: GQL.FindMoviesDocument, + variables: { + filter: filter.makeFindFilter(), + movie_filter: filter.makeMovieFilter(), + }, + }); + export const useFindPerformers = (filter: ListFilterModel) => GQL.useFindPerformersQuery({ variables: { @@ -103,6 +160,15 @@ export const useFindTags = (filter: ListFilterModel) => }, }); +export const queryFindTags = (filter: ListFilterModel) => + client.query({ + query: GQL.FindTagsDocument, + variables: { + filter: filter.makeFindFilter(), + tag_filter: filter.makeTagFilter(), + }, + }); + export const queryFindPerformers = (filter: ListFilterModel) => client.query({ query: GQL.FindPerformersDocument, @@ -119,6 +185,9 @@ export const useFindScene = (id: string) => export const useSceneStreams = (id: string) => GQL.useSceneStreamsQuery({ variables: { id } }); +export const useFindImage = (id: string) => + GQL.useFindImageQuery({ variables: { id } }); + export const useFindPerformer = (id: string) => { const skip = id === "new"; return GQL.useFindPerformerQuery({ variables: { id }, skip }); @@ -136,20 +205,28 @@ export const useFindTag = (id: string) => { return GQL.useFindTagQuery({ variables: { id }, skip }); }; -// TODO - scene marker manipulation functions are handled differently -export const sceneMarkerMutationImpactedQueries = [ - "findSceneMarkers", - "findScenes", - "markerStrings", - "sceneMarkerTags", +const sceneMarkerMutationImpactedQueries = [ + GQL.FindSceneDocument, + GQL.FindScenesDocument, + GQL.FindSceneMarkersDocument, + GQL.MarkerStringsDocument, ]; export const useSceneMarkerCreate = () => - GQL.useSceneMarkerCreateMutation({ refetchQueries: ["FindScene"] }); + GQL.useSceneMarkerCreateMutation({ + refetchQueries: getQueryNames([GQL.FindSceneDocument]), + update: deleteCache(sceneMarkerMutationImpactedQueries), + }); export const useSceneMarkerUpdate = () => - GQL.useSceneMarkerUpdateMutation({ refetchQueries: ["FindScene"] }); + GQL.useSceneMarkerUpdateMutation({ + refetchQueries: getQueryNames([GQL.FindSceneDocument]), + update: deleteCache(sceneMarkerMutationImpactedQueries), + }); export const useSceneMarkerDestroy = () => - GQL.useSceneMarkerDestroyMutation({ refetchQueries: ["FindScene"] }); + GQL.useSceneMarkerDestroyMutation({ + refetchQueries: getQueryNames([GQL.FindSceneDocument]), + update: deleteCache(sceneMarkerMutationImpactedQueries), + }); export const useListPerformerScrapers = () => GQL.useListPerformerScrapersQuery(); @@ -168,6 +245,8 @@ export const useScrapePerformer = ( export const useListSceneScrapers = () => GQL.useListSceneScrapersQuery(); +export const useListGalleryScrapers = () => GQL.useListGalleryScrapersQuery(); + export const useListMovieScrapers = () => GQL.useListMovieScrapersQuery(); export const useScrapeFreeonesPerformers = (q: string) => @@ -199,185 +278,369 @@ export const useConfiguration = () => GQL.useConfigurationQuery(); export const useDirectory = (path?: string) => GQL.useDirectoryQuery({ variables: { path } }); -export const performerMutationImpactedQueries = [ - "FindPerformers", - "FindScenes", - "FindSceneMarkers", - "AllPerformers", - "AllPerformersForFilter", +const performerMutationImpactedQueries = [ + GQL.FindPerformersDocument, + GQL.FindSceneDocument, + GQL.FindScenesDocument, + GQL.AllPerformersForFilterDocument, ]; export const usePerformerCreate = () => GQL.usePerformerCreateMutation({ - refetchQueries: performerMutationImpactedQueries, - update: () => invalidateQueries(performerMutationImpactedQueries), + refetchQueries: getQueryNames([ + GQL.FindPerformersDocument, + GQL.AllPerformersForFilterDocument, + ]), + update: deleteCache([ + GQL.FindPerformersDocument, + GQL.AllPerformersForFilterDocument, + ]), }); export const usePerformerUpdate = () => GQL.usePerformerUpdateMutation({ - refetchQueries: performerMutationImpactedQueries, - update: () => invalidateQueries(performerMutationImpactedQueries), + update: deleteCache(performerMutationImpactedQueries), }); export const usePerformerDestroy = () => GQL.usePerformerDestroyMutation({ - refetchQueries: performerMutationImpactedQueries, - update: () => invalidateQueries(performerMutationImpactedQueries), + refetchQueries: getQueryNames([ + GQL.FindPerformersDocument, + GQL.AllPerformersForFilterDocument, + ]), + update: deleteCache(performerMutationImpactedQueries), }); -export const sceneMutationImpactedQueries = [ - "findPerformers", - "findScenes", - "findSceneMarkers", - "findStudios", - "findMovies", - "allTags", - // TODO - add "findTags" when it is implemented +const sceneMutationImpactedQueries = [ + GQL.FindPerformerDocument, + GQL.FindPerformersDocument, + GQL.FindScenesDocument, + GQL.FindSceneMarkersDocument, + GQL.FindStudioDocument, + GQL.FindStudiosDocument, + GQL.FindMovieDocument, + GQL.FindMoviesDocument, + GQL.FindTagDocument, + GQL.FindTagsDocument, + GQL.AllTagsDocument, ]; export const useSceneUpdate = (input: GQL.SceneUpdateInput) => GQL.useSceneUpdateMutation({ variables: input, - update: () => invalidateQueries(sceneMutationImpactedQueries), - refetchQueries: ["AllTagsForFilter"], + update: deleteCache(sceneMutationImpactedQueries), }); -// remove findScenes for bulk scene update so that we don't lose -// existing results -export const sceneBulkMutationImpactedQueries = [ - "findPerformers", - "findSceneMarkers", - "findStudios", - "findMovies", - "allTags", -]; - export const useBulkSceneUpdate = (input: GQL.BulkSceneUpdateInput) => GQL.useBulkSceneUpdateMutation({ variables: input, - update: () => invalidateQueries(sceneBulkMutationImpactedQueries), + update: deleteCache(sceneMutationImpactedQueries), }); export const useScenesUpdate = (input: GQL.SceneUpdateInput[]) => GQL.useScenesUpdateMutation({ variables: { input } }); +type SceneOMutation = + | GQL.SceneIncrementOMutation + | GQL.SceneDecrementOMutation + | GQL.SceneResetOMutation; +const updateSceneO = ( + id: string, + cache: ApolloCache, + updatedOCount?: number +) => { + const scene = cache.readQuery< + GQL.FindSceneQuery, + GQL.FindSceneQueryVariables + >({ + query: GQL.FindSceneDocument, + variables: { id }, + }); + if (updatedOCount === undefined || !scene?.findScene) return; + + cache.writeQuery({ + query: GQL.FindSceneDocument, + variables: { id }, + data: { + ...scene, + findScene: { + ...scene.findScene, + o_counter: updatedOCount, + }, + }, + }); +}; + export const useSceneIncrementO = (id: string) => GQL.useSceneIncrementOMutation({ variables: { id }, + update: (cache, data) => + updateSceneO(id, cache, data.data?.sceneIncrementO), }); export const useSceneDecrementO = (id: string) => GQL.useSceneDecrementOMutation({ variables: { id }, + update: (cache, data) => + updateSceneO(id, cache, data.data?.sceneDecrementO), }); export const useSceneResetO = (id: string) => GQL.useSceneResetOMutation({ variables: { id }, + update: (cache, data) => updateSceneO(id, cache, data.data?.sceneResetO), }); export const useSceneDestroy = (input: GQL.SceneDestroyInput) => GQL.useSceneDestroyMutation({ variables: input, - update: () => invalidateQueries(sceneMutationImpactedQueries), + update: deleteCache(sceneMutationImpactedQueries), }); export const useScenesDestroy = (input: GQL.ScenesDestroyInput) => GQL.useScenesDestroyMutation({ variables: input, - update: () => invalidateQueries(sceneMutationImpactedQueries), + update: deleteCache(sceneMutationImpactedQueries), }); export const useSceneGenerateScreenshot = () => GQL.useSceneGenerateScreenshotMutation({ - update: () => invalidateQueries(["findScenes"]), + update: deleteCache([GQL.FindScenesDocument]), + }); + +const imageMutationImpactedQueries = [ + GQL.FindPerformerDocument, + GQL.FindPerformersDocument, + GQL.FindImagesDocument, + GQL.FindStudioDocument, + GQL.FindStudiosDocument, + GQL.FindTagDocument, + GQL.FindTagsDocument, + GQL.AllTagsDocument, + GQL.FindGalleryDocument, + GQL.FindGalleriesDocument, +]; + +export const useImageUpdate = (input: GQL.ImageUpdateInput) => + GQL.useImageUpdateMutation({ + variables: input, + update: deleteCache(imageMutationImpactedQueries), + }); + +export const useBulkImageUpdate = (input: GQL.BulkImageUpdateInput) => + GQL.useBulkImageUpdateMutation({ + variables: input, + update: deleteCache(imageMutationImpactedQueries), + }); + +export const useImagesDestroy = (input: GQL.ImagesDestroyInput) => + GQL.useImagesDestroyMutation({ + variables: input, + update: deleteCache(imageMutationImpactedQueries), + }); + +type ImageOMutation = + | GQL.ImageIncrementOMutation + | GQL.ImageDecrementOMutation + | GQL.ImageResetOMutation; +const updateImageO = ( + id: string, + cache: ApolloCache, + updatedOCount?: number +) => { + const image = cache.readQuery< + GQL.FindImageQuery, + GQL.FindImageQueryVariables + >({ + query: GQL.FindImageDocument, + variables: { id }, + }); + if (updatedOCount === undefined || !image?.findImage) return; + + cache.writeQuery({ + query: GQL.FindImageDocument, + variables: { id }, + data: { + findImage: { + ...image.findImage, + o_counter: updatedOCount, + }, + }, + }); +}; + +export const useImageIncrementO = (id: string) => + GQL.useImageIncrementOMutation({ + variables: { id }, + update: (cache, data) => + updateImageO(id, cache, data.data?.imageIncrementO), + }); + +export const useImageDecrementO = (id: string) => + GQL.useImageDecrementOMutation({ + variables: { id }, + update: (cache, data) => + updateImageO(id, cache, data.data?.imageDecrementO), + }); + +export const useImageResetO = (id: string) => + GQL.useImageResetOMutation({ + variables: { id }, + update: (cache, data) => updateImageO(id, cache, data.data?.imageResetO), + }); + +const galleryMutationImpactedQueries = [ + GQL.FindPerformerDocument, + GQL.FindPerformersDocument, + GQL.FindImagesDocument, + GQL.FindStudioDocument, + GQL.FindStudiosDocument, + GQL.FindTagDocument, + GQL.FindTagsDocument, + GQL.AllTagsDocument, + GQL.FindGalleryDocument, + GQL.FindGalleriesDocument, +]; + +export const useGalleryCreate = (input: GQL.GalleryCreateInput) => + GQL.useGalleryCreateMutation({ + variables: input, + update: deleteCache(galleryMutationImpactedQueries), + }); + +export const useGalleryUpdate = (input: GQL.GalleryUpdateInput) => + GQL.useGalleryUpdateMutation({ + variables: input, + update: deleteCache(galleryMutationImpactedQueries), + }); + +export const useBulkGalleryUpdate = (input: GQL.BulkGalleryUpdateInput) => + GQL.useBulkGalleryUpdateMutation({ + variables: input, + update: deleteCache(galleryMutationImpactedQueries), + }); + +export const useGalleryDestroy = (input: GQL.GalleryDestroyInput) => + GQL.useGalleryDestroyMutation({ + variables: input, + update: deleteCache(galleryMutationImpactedQueries), + }); + +export const mutateAddGalleryImages = (input: GQL.GalleryAddInput) => + client.mutate({ + mutation: GQL.AddGalleryImagesDocument, + variables: input, + update: deleteCache(galleryMutationImpactedQueries), + }); + +export const mutateRemoveGalleryImages = (input: GQL.GalleryRemoveInput) => + client.mutate({ + mutation: GQL.RemoveGalleryImagesDocument, + variables: input, + update: deleteCache(galleryMutationImpactedQueries), }); export const studioMutationImpactedQueries = [ - "FindStudios", - "FindScenes", - "AllStudios", - "AllStudiosForFilter", + GQL.FindStudiosDocument, + GQL.FindSceneDocument, + GQL.FindScenesDocument, + GQL.AllStudiosForFilterDocument, ]; export const useStudioCreate = (input: GQL.StudioCreateInput) => GQL.useStudioCreateMutation({ variables: input, - refetchQueries: studioMutationImpactedQueries, - update: () => invalidateQueries(studioMutationImpactedQueries), + refetchQueries: getQueryNames([GQL.AllStudiosForFilterDocument]), + update: deleteCache([ + GQL.FindStudiosDocument, + GQL.AllStudiosForFilterDocument, + ]), }); export const useStudioUpdate = (input: GQL.StudioUpdateInput) => GQL.useStudioUpdateMutation({ variables: input, - update: () => invalidateQueries(studioMutationImpactedQueries), + update: deleteCache(studioMutationImpactedQueries), }); export const useStudioDestroy = (input: GQL.StudioDestroyInput) => GQL.useStudioDestroyMutation({ variables: input, - update: () => invalidateQueries(studioMutationImpactedQueries), + update: deleteCache(studioMutationImpactedQueries), }); export const movieMutationImpactedQueries = [ - "findMovies", - "findScenes", - "allMovies", + GQL.FindSceneDocument, + GQL.FindScenesDocument, + GQL.FindMoviesDocument, + GQL.AllMoviesForFilterDocument, ]; export const useMovieCreate = (input: GQL.MovieCreateInput) => GQL.useMovieCreateMutation({ variables: input, - update: () => invalidateQueries(movieMutationImpactedQueries), + update: deleteCache([ + GQL.FindMoviesDocument, + GQL.AllMoviesForFilterDocument, + ]), }); export const useMovieUpdate = (input: GQL.MovieUpdateInput) => GQL.useMovieUpdateMutation({ variables: input, - update: () => invalidateQueries(movieMutationImpactedQueries), + update: deleteCache(movieMutationImpactedQueries), }); export const useMovieDestroy = (input: GQL.MovieDestroyInput) => GQL.useMovieDestroyMutation({ variables: input, - update: () => invalidateQueries(movieMutationImpactedQueries), + update: deleteCache(movieMutationImpactedQueries), }); export const tagMutationImpactedQueries = [ - "findScenes", - "findSceneMarkers", - "sceneMarkerTags", - "allTags", - "findTags", + GQL.FindSceneDocument, + GQL.FindScenesDocument, + GQL.FindSceneMarkersDocument, + GQL.AllTagsDocument, + GQL.AllTagsForFilterDocument, + GQL.FindTagsDocument, ]; export const useTagCreate = (input: GQL.TagCreateInput) => GQL.useTagCreateMutation({ variables: input, - refetchQueries: ["AllTags", "AllTagsForFilter", "FindTags"], - update: () => invalidateQueries(tagMutationImpactedQueries), + refetchQueries: getQueryNames([ + GQL.AllTagsDocument, + GQL.AllTagsForFilterDocument, + GQL.FindTagsDocument, + ]), + update: deleteCache([ + GQL.FindTagsDocument, + GQL.AllTagsDocument, + GQL.AllTagsForFilterDocument, + ]), }); export const useTagUpdate = (input: GQL.TagUpdateInput) => GQL.useTagUpdateMutation({ variables: input, - refetchQueries: ["AllTags", "AllTagsForFilter", "FindTags"], - update: () => invalidateQueries(tagMutationImpactedQueries), + update: deleteCache(tagMutationImpactedQueries), }); export const useTagDestroy = (input: GQL.TagDestroyInput) => GQL.useTagDestroyMutation({ variables: input, - refetchQueries: ["AllTags", "AllTagsForFilter", "FindTags"], - update: () => invalidateQueries(tagMutationImpactedQueries), + update: deleteCache(tagMutationImpactedQueries), }); export const useConfigureGeneral = (input: GQL.ConfigGeneralInput) => GQL.useConfigureGeneralMutation({ variables: { input }, - refetchQueries: ["Configuration"], + refetchQueries: getQueryNames([GQL.ConfigurationDocument]), + update: deleteCache([GQL.ConfigurationDocument]), }); export const useConfigureInterface = (input: GQL.ConfigInterfaceInput) => GQL.useConfigureInterfaceMutation({ variables: { input }, - refetchQueries: ["Configuration"], + refetchQueries: getQueryNames([GQL.ConfigurationDocument]), + update: deleteCache([GQL.ConfigurationDocument]), }); export const useMetadataUpdate = () => GQL.useMetadataUpdateSubscription(); @@ -439,6 +702,15 @@ export const queryScrapeSceneURL = (url: string) => fetchPolicy: "network-only", }); +export const queryScrapeGalleryURL = (url: string) => + client.query({ + query: GQL.ScrapeGalleryUrlDocument, + variables: { + url, + }, + fetchPolicy: "network-only", + }); + export const queryScrapeMovieURL = (url: string) => client.query({ query: GQL.ScrapeMovieUrlDocument, @@ -461,17 +733,44 @@ export const queryScrapeScene = ( fetchPolicy: "network-only", }); +export const queryStashBoxScene = (stashBoxIndex: number, sceneID: string) => + client.query({ + query: GQL.QueryStashBoxSceneDocument, + variables: { + input: { + stash_box_index: stashBoxIndex, + scene_ids: [sceneID], + }, + }, + }); + +export const queryScrapeGallery = ( + scraperId: string, + scene: GQL.GalleryUpdateInput +) => + client.query({ + query: GQL.ScrapeGalleryDocument, + variables: { + scraper_id: scraperId, + scene, + }, + fetchPolicy: "network-only", + }); + export const mutateReloadScrapers = () => client.mutate({ mutation: GQL.ReloadScrapersDocument, + refetchQueries: [ + GQL.refetchListMovieScrapersQuery(), + GQL.refetchListPerformerScrapersQuery(), + GQL.refetchListSceneScrapersQuery(), + ], }); -const reloadPluginsMutationImpactedQueries = ["plugins", "pluginTasks"]; - export const mutateReloadPlugins = () => client.mutate({ mutation: GQL.ReloadPluginsDocument, - update: () => invalidateQueries(reloadPluginsMutationImpactedQueries), + refetchQueries: [GQL.refetchPluginsQuery(), GQL.refetchPluginTasksQuery()], }); export const mutateRunPluginTask = ( @@ -517,11 +816,23 @@ export const mutateMetadataExport = () => mutation: GQL.MetadataExportDocument, }); +export const mutateExportObjects = (input: GQL.ExportObjectsInput) => + client.mutate({ + mutation: GQL.ExportObjectsDocument, + variables: { input }, + }); + export const mutateMetadataImport = () => client.mutate({ mutation: GQL.MetadataImportDocument, }); +export const mutateImportObjects = (input: GQL.ImportObjectsInput) => + client.mutate({ + mutation: GQL.ImportObjectsDocument, + variables: { input }, + }); + export const querySceneByPathRegex = (filter: GQL.FindFilterType) => client.query({ query: GQL.FindScenesByPathRegexDocument, @@ -582,3 +893,23 @@ export const stringToGender = (value?: string, caseInsensitive?: boolean) => { }; export const getGenderStrings = () => Array.from(stringGenderMap.keys()); + +export const stashBoxQuery = (searchVal: string, stashBoxIndex: number) => + client?.query< + GQL.QueryStashBoxSceneQuery, + GQL.QueryStashBoxSceneQueryVariables + >({ + query: GQL.QueryStashBoxSceneDocument, + variables: { input: { q: searchVal, stash_box_index: stashBoxIndex } }, + }); + +export const stashBoxBatchQuery = (sceneIds: string[], stashBoxIndex: number) => + client?.query< + GQL.QueryStashBoxSceneQuery, + GQL.QueryStashBoxSceneQueryVariables + >({ + query: GQL.QueryStashBoxSceneDocument, + variables: { + input: { scene_ids: sceneIds, stash_box_index: stashBoxIndex }, + }, + }); diff --git a/ui/v2.5/src/core/createClient.ts b/ui/v2.5/src/core/createClient.ts index 8acb64695..658b6d51d 100644 --- a/ui/v2.5/src/core/createClient.ts +++ b/ui/v2.5/src/core/createClient.ts @@ -1,11 +1,14 @@ -import ApolloClient from "apollo-client"; -import { InMemoryCache } from "apollo-cache-inmemory"; -import { WebSocketLink } from "apollo-link-ws"; -import { HttpLink } from "apollo-link-http"; -import { onError } from "apollo-link-error"; -import { ServerError } from "apollo-link-http-common"; -import { split, from } from "apollo-link"; -import { getMainDefinition } from "apollo-utilities"; +import { + ApolloClient, + InMemoryCache, + split, + from, + ServerError, +} from "@apollo/client"; +import { WebSocketLink } from "@apollo/client/link/ws"; +import { onError } from "@apollo/client/link/error"; +import { getMainDefinition } from "@apollo/client/utilities"; +import { createUploadLink } from "apollo-upload-client"; export const getPlatformURL = (ws?: boolean) => { const platformUrl = new URL(window.location.origin); @@ -36,7 +39,7 @@ export const createClient = () => { const url = `${platformUrl.toString().slice(0, -1)}/graphql`; const wsUrl = `${wsPlatformUrl.toString().slice(0, -1)}/graphql`; - const httpLink = new HttpLink({ + const httpLink = createUploadLink({ uri: url, }); @@ -64,6 +67,7 @@ export const createClient = () => { ); }, wsLink, + // @ts-ignore httpLink ); diff --git a/ui/v2.5/src/core/performers.ts b/ui/v2.5/src/core/performers.ts new file mode 100644 index 000000000..e7552b447 --- /dev/null +++ b/ui/v2.5/src/core/performers.ts @@ -0,0 +1,39 @@ +import { PerformersCriterion } from "src/models/list-filter/criteria/performers"; +import * as GQL from "src/core/generated-graphql"; +import { ListFilterModel } from "src/models/list-filter/filter"; + +export const performerFilterHook = ( + performer: Partial +) => { + return (filter: ListFilterModel) => { + const performerValue = { id: performer.id!, label: performer.name! }; + // if performers is already present, then we modify it, otherwise add + let performerCriterion = filter.criteria.find((c) => { + return c.type === "performers"; + }) as PerformersCriterion; + + if ( + performerCriterion && + (performerCriterion.modifier === GQL.CriterionModifier.IncludesAll || + performerCriterion.modifier === GQL.CriterionModifier.Includes) + ) { + // add the performer if not present + if ( + !performerCriterion.value.find((p) => { + return p.id === performer.id; + }) + ) { + performerCriterion.value.push(performerValue); + } + + performerCriterion.modifier = GQL.CriterionModifier.IncludesAll; + } else { + // overwrite + performerCriterion = new PerformersCriterion(); + performerCriterion.value = [performerValue]; + filter.criteria.push(performerCriterion); + } + + return filter; + }; +}; diff --git a/ui/v2.5/src/core/studios.ts b/ui/v2.5/src/core/studios.ts new file mode 100644 index 000000000..00724b6c5 --- /dev/null +++ b/ui/v2.5/src/core/studios.ts @@ -0,0 +1,37 @@ +import * as GQL from "src/core/generated-graphql"; +import { StudiosCriterion } from "src/models/list-filter/criteria/studios"; +import { ListFilterModel } from "src/models/list-filter/filter"; + +export const studioFilterHook = (studio: Partial) => { + return (filter: ListFilterModel) => { + const studioValue = { id: studio.id!, label: studio.name! }; + // if studio is already present, then we modify it, otherwise add + let studioCriterion = filter.criteria.find((c) => { + return c.type === "studios"; + }) as StudiosCriterion; + + if ( + studioCriterion && + (studioCriterion.modifier === GQL.CriterionModifier.IncludesAll || + studioCriterion.modifier === GQL.CriterionModifier.Includes) + ) { + // add the studio if not present + if ( + !studioCriterion.value.find((p) => { + return p.id === studio.id; + }) + ) { + studioCriterion.value.push(studioValue); + } + + studioCriterion.modifier = GQL.CriterionModifier.IncludesAll; + } else { + // overwrite + studioCriterion = new StudiosCriterion(); + studioCriterion.value = [studioValue]; + filter.criteria.push(studioCriterion); + } + + return filter; + }; +}; diff --git a/ui/v2.5/src/core/tags.ts b/ui/v2.5/src/core/tags.ts new file mode 100644 index 000000000..d8269f229 --- /dev/null +++ b/ui/v2.5/src/core/tags.ts @@ -0,0 +1,37 @@ +import * as GQL from "src/core/generated-graphql"; +import { TagsCriterion } from "src/models/list-filter/criteria/tags"; +import { ListFilterModel } from "src/models/list-filter/filter"; + +export const tagFilterHook = (tag: GQL.TagDataFragment) => { + return (filter: ListFilterModel) => { + const tagValue = { id: tag.id, label: tag.name }; + // if tag is already present, then we modify it, otherwise add + let tagCriterion = filter.criteria.find((c) => { + return c.type === "tags"; + }) as TagsCriterion; + + if ( + tagCriterion && + (tagCriterion.modifier === GQL.CriterionModifier.IncludesAll || + tagCriterion.modifier === GQL.CriterionModifier.Includes) + ) { + // add the tag if not present + if ( + !tagCriterion.value.find((p) => { + return p.id === tag.id; + }) + ) { + tagCriterion.value.push(tagValue); + } + + tagCriterion.modifier = GQL.CriterionModifier.IncludesAll; + } else { + // overwrite + tagCriterion = new TagsCriterion("tags"); + tagCriterion.value = [tagValue]; + filter.criteria.push(tagCriterion); + } + + return filter; + }; +}; diff --git a/ui/v2.5/src/docs/en/Galleries.md b/ui/v2.5/src/docs/en/Galleries.md index bf1612a75..e7d9120c6 100644 --- a/ui/v2.5/src/docs/en/Galleries.md +++ b/ui/v2.5/src/docs/en/Galleries.md @@ -1,13 +1,12 @@ # Galleries -Stash offers support for image galleries. -Here are some remarks on using them: +**Note:** images are now included during the scan process and are loaded independently of galleries. It is _no longer necessary_ to have images in zip files to be scanned into your library. + +Galleries are automatically created from zip files found during scanning that contain images. It is also possible to automatically create galleries from folders containing images, by selecting the "Create galleries from folders containing images" checkbox in the Configuration page. It is also possible to manually create galleries. + +For best results, images in zip file should be stored without compression (copy, store or no compression options depending on the software you use. Eg on linux: `zip -0 -r gallery.zip foldertozip/`). This impacts **heavily** on the zip read performance. + +If an filename of an image in the gallery zip file ends with `cover.jpg`, it will be treated like a cover and presented first in the gallery view page and as a gallery cover in the gallery list view. If more than one images match the name the first one found in natural sort order is selected. + +Images can be added to a gallery by navigating to the gallery's page, selecting the "Add" tab, querying for and selecting the images to add, then selecting "Add to Gallery" from the `...` menu button. Likewise, images may be removed from a gallery by selecting the "Images" tab, selecting the images to remove and selecting "Remove from Gallery" from the `...` menu button. -- **Galleries are zip-folders with images (e.g. jpeg or png) in them.** -- Stash searches for zip galleries in the same paths it searches for videos. -- In order for a gallery to be associated with a scene, the zip file and the video file must be in the same folder. -- For best results, images in zip file should be stored without compression (copy, store or no compression options depending on the software you use. Eg on linux: `zip -0 -r gallery.zip foldertozip/`). This impacts **heavily** on the zip read performance. -- Stash uses the golang native (pure go) image decoders (more suitable for cross compilation). With huge images, decoding and converting to thumbnails can be slow and in some cases cause visual errors or delays when loading the gallery page. -- Stash adds a gallery to its related scene during the scanning process if they have matching names. For example, gallery `/my/stash/collection/media_filename.zip` will be auto assigned to `/my/stash/collection/media_filename.mp4` (where **mp4** can any supported video extension). -- If an filename of an image in the gallery zip file ends with `cover.jpg`, it will be treated like a cover and presented first in the gallery view page and as a gallery cover in the gallery list view. If more than one images match the name the first one found in natural sort order is selected. -- Gallery thumbnails are cached. The first time you go to a gallery page the thumbnails of the images are created and stored to the disk cache for later use. If you want to populate the cache beforehand, this can be done using the Generate task. \ No newline at end of file diff --git a/ui/v2.5/src/docs/en/KeyboardShortcuts.md b/ui/v2.5/src/docs/en/KeyboardShortcuts.md index 0a5b65fa7..b1bf8e1ca 100644 --- a/ui/v2.5/src/docs/en/KeyboardShortcuts.md +++ b/ui/v2.5/src/docs/en/KeyboardShortcuts.md @@ -11,6 +11,7 @@ | Keyboard sequence | Target page | |-------------------|--------| | `g s` | Scenes | +| `g i` | Images | | `g v` | Movies | | `g k` | Markers | | `g l` | Galleries | diff --git a/ui/v2.5/src/docs/en/Scraping.md b/ui/v2.5/src/docs/en/Scraping.md index 230cb66c3..10207f8a6 100644 --- a/ui/v2.5/src/docs/en/Scraping.md +++ b/ui/v2.5/src/docs/en/Scraping.md @@ -32,7 +32,7 @@ The stash community maintains a number of custom scraper configuration files tha ## Basic scraper configuration file structure -``` +```yaml name: performerByName: @@ -46,6 +46,10 @@ sceneByURL: movieByURL: +galleryByFragment: + +galleryByURL: + ``` @@ -62,6 +66,8 @@ The scraping types and their required fields are outlined in the following table | Scraper in `Scrape...` dropdown button in Scene Edit page | Valid `sceneByFragment` configuration. | | Scrape scene from URL | Valid `sceneByURL` configuration with matching URL. | | Scrape movie from URL | Valid `movieByURL` configuration with matching URL. | +| Scraper in `Scrape...` dropdown button in Gallery Edit page | Valid `galleryByFragment` configuration. | +| Scrape gallery from URL | Valid `galleryByURL` configuration with matching URL. | URL-based scraping accepts multiple scrape configurations, and each configuration requires a `url` field. stash iterates through these configurations, attempting to match the entered URL against the `url` fields in the configuration. It executes the first scraping configuration where the entered URL contains the value of the `url` field. @@ -71,7 +77,7 @@ URL-based scraping accepts multiple scrape configurations, and each configuratio Executes a script to perform the scrape. The `script` field is required for this action and accepts a list of string arguments. For example: -``` +```yaml action: script script: - python @@ -93,12 +99,14 @@ The script is sent input and expects output based on the scraping type, as detai | `sceneByFragment` | JSON-encoded scene fragment | JSON-encoded scene fragment | | `sceneByURL` | `{"url": ""}` | JSON-encoded scene fragment | | `movieByURL` | `{"url": ""}` | JSON-encoded movie fragment | +| `galleryByFragment` | JSON-encoded gallery fragment | JSON-encoded gallery fragment | +| `galleryByURL` | `{"url": ""}` | JSON-encoded gallery fragment | For `performerByName`, only `name` is required in the returned performer fragments. One entire object is sent back to `performerByFragment` to scrape a specific performer, so the other fields may be included to assist in scraping a performer. For example, the `url` field may be filled in for the specific performer page, then `performerByFragment` can extract by using its value. As an example, the following python code snippet can be used to scrape a performer: -``` +```python import json import sys import string @@ -162,11 +170,11 @@ elif sys.argv[1] == "scrapeURL": ### scrapeXPath -This action scrapes a web page using an xpath configuration to parse. This action is valid for `performerByName`, `performerByURL` and `sceneByURL` only. +This action scrapes a web page using an xpath configuration to parse. This action is **not valid** for `performerByFragment`. This action requires that the top-level `xPathScrapers` configuration is populated. The `scraper` field is required and must match the name of a scraper name configured in `xPathScrapers`. For example: -``` +```yaml sceneByURL: - action: scrapeXPath url: @@ -180,7 +188,7 @@ XPath scraping configurations specify the mapping between object fields and an x ### scrapeJson -This action works in the same way as `scrapeXPath`, but uses a mapped json configuration to parse. It uses the top-level `jsonScrapers` configuration. This action is valid for `performerByName`, `performerByURL`, `sceneByFragment` and `sceneByURL`. +This action works in the same way as `scrapeXPath`, but uses a mapped json configuration to parse. It uses the top-level `jsonScrapers` configuration. This action is **not valid** for `performerByFragment`. JSON scraping configurations specify the mapping between object fields and a GJSON selector. The JSON scraper scrapes the applicable URL and uses [GJSON](https://github.com/tidwall/gjson/blob/master/SYNTAX.md) to parse the returned JSON object and populate the object fields. @@ -188,7 +196,7 @@ JSON scraping configurations specify the mapping between object fields and a GJS For `performerByName`, the `queryURL` field must be present also. This field is used to perform a search query URL for performer names. The placeholder string sequence `{}` is replaced with the performer name search string. For the subsequent performer scrape to work, the `URL` field must be filled in with the URL of the performer page that matches a URL given in a `performerByURL` scraping configuration. For example: -``` +```yaml name: Boobpedia performerByName: action: scrapeXPath @@ -216,16 +224,44 @@ For `sceneByFragment`, the `queryURL` field must also be present. This field is * `{filename}` - the base filename of the scene * `{title}` - the title of the scene +These placeholder field values may be manipulated with regex replacements by adding a `queryURLReplace` section, containing a map of placeholder field to regex configuration which uses the same format as the `replace` post-process action covered below. + For example: -``` +```yaml sceneByFragment: action: scrapeJson - queryURL: https://metadataapi.net/api/scenes?parse={filename}&limit=1 scraper: sceneQueryScraper + queryURL: https://metadataapi.net/api/scenes?parse={filename}&limit=1 + queryURLReplace: + filename: + - regex: + with: ``` -### Xpath and JSON scrapers configuration +The above configuration would scrape from the value of `queryURL`, replacing `{filename}` with the base filename of the scene, after it has been manipulated by the regex replacements. + +### Stash + +A different stash server can be configured as a scraping source. This action applies only to `performerByName`, `performerByFragment`, and `sceneByFragment` types. This action requires that the top-level `stashServer` field is configured. + +`stashServer` contains a single `url` field for the remote stash server. The username and password can be embedded in this string using `username:password@host`. + +An example stash scrape configuration is below: + +```yaml +name: stash +performerByName: + action: stash +performerByFragment: + action: stash +sceneByFragment: + action: stash +stashServer: + url: http://stashserver.com:9999 +``` + +## Xpath and JSON scrapers configuration The top-level `xPathScrapers` field contains xpath scraping configurations, freely named. These are referenced in the `scraper` field for `scrapeXPath` scrapers. @@ -233,13 +269,13 @@ Likewise, the top-level `jsonScrapers` field contains json scraping configuratio Collectively, these configurations are known as mapped scraping configurations. -A mapped scraping configuration may contain a `common` field, and must contain `performer` or `scene` depending on the scraping type it is configured for. +A mapped scraping configuration may contain a `common` field, and must contain `performer`, `scene`, `movie` or `gallery` depending on the scraping type it is configured for. -Within the `performer`/`scene` field are key/value pairs corresponding to the golang fields (see below) on the performer/scene object. These fields are case-sensitive. +Within the `performer`/`scene`/`movie`/`gallery` field are key/value pairs corresponding to the golang fields (see below) on the performer/scene object. These fields are case-sensitive. The values of these may be either a simple selector value, which tells the system where to get the value of the field from, or a more advanced configuration (see below). For example, for an xpath configuration: -``` +```yaml performer: Name: //h1[@itemprop="name"] ``` @@ -248,14 +284,14 @@ This will set the `Name` attribute of the returned performer to the text content For a json configuration: -``` +```yaml performer: Name: data.name ``` The value may also be a sub-object. If it is a sub-object, then the selector must be set to the `selector` key of the sub-object. For example, using the same xpath as above: -``` +```yaml performer: Name: selector: //h1[@itemprop="name"] @@ -263,21 +299,21 @@ performer: # post-processing config values ``` -#### Fixed attribute values +### Fixed attribute values Alternatively, an attribute value may be set to a fixed value, rather than scraping it from the webpage. This can be done by replacing `selector` with `fixed`. For example: -``` +```yaml performer: Gender: fixed: Female ``` -##### Common fragments +### Common fragments The `common` field is used to configure selector fragments that can be referenced in the selector strings. These are key-value pairs where the key is the string to reference the fragment, and the value is the string that the fragment will be replaced with. For example: -``` +```yaml common: $infoPiece: //div[@class="infoPiece"]/span performer: @@ -286,14 +322,14 @@ performer: The `Measurements` xpath string will replace `$infoPiece` with `//div[@class="infoPiece"]/span`, resulting in: `//div[@class="infoPiece"]/span[text() = 'Measurements:']/../span[@class="smallInfo"]`. -##### Post-processing options +### Post-processing options Post-processing operations are contained in the `postProcess` key. Post-processing operations are performed in the order they are specified. The following post-processing operations are available: * `feetToCm`: converts a string containing feet and inches numbers into centimetres. Looks for up to two separate integers and interprets the first as the number of feet, and the second as the number of inches. The numbers can be separated by any non-numeric character including the `.` character. It does not handle decimal numbers. For example `6.3` and `6ft3.3` would both be interpreted as 6 feet, 3 inches before converting into centimetres. * `map`: contains a map of input values to output values. Where a value matches one of the input values, it is replaced with the matching output value. If no value is matched, then value is unmodified. Example: -``` +```yaml performer: Gender: selector: //div[class="example element"] @@ -309,7 +345,7 @@ Gets the contents of the selected div element, and sets the returned value to `F * `replace`: contains an array of sub-objects. Each sub-object must have a `regex` and `with` field. The `regex` field is the regex pattern to replace, and `with` is the string to replace it with. `$` is used to reference capture groups - `$1` is the first capture group, `$2` the second and so on. Replacements are performed in order of the array. Example: -``` +```yaml CareerLength: selector: $infoPiece[text() = 'Career Start and End:']/../span[@class="smallInfo"] postProcess: @@ -329,12 +365,28 @@ For backwards compatibility, `replace`, `subscraper` and `parseDate` are also al Post-processing on attribute post-process is done in the following order: `concat`, `replace`, `subscraper`, `parseDate` and then `split`. -##### CDP support +### XPath resources: + +- Test XPaths in Firefox: https://addons.mozilla.org/en-US/firefox/addon/try-xpath/ +- XPath cheatsheet: https://devhints.io/xpath + +### GJSON resources: + +- GJSON Path Syntax: https://github.com/tidwall/gjson/blob/master/SYNTAX.md + +### Debugging support +To print the received html/json from a scraper request to the log file, add the following to your scraper yml file: +```yaml +debug: + printHTML: true +``` + +### CDP support Some websites deliver content that cannot be scraped using the raw html file alone. These websites use javascript to dynamically load the content. As such, direct xpath scraping will not work on these websites. There is an option to use Chrome DevTools Protocol to load the webpage using an instance of Chrome, then scrape the result. Chrome CDP support can be enabled for a specific scraping configuration by adding the following to the root of the yml configuration: -``` +```yaml driver: useCDP: true ``` @@ -345,11 +397,11 @@ When `useCDP` is set to true, stash will execute or connect to an instance of Ch `Chrome CDP path` can be set to a path to the chrome executable, or an http(s) address to remote chrome instance (for example: `http://localhost:9222/json/version`). -##### XPath scraper example +### XPath scraper example A performer and scene xpath scraper is shown as an example below: -``` +```yaml name: Pornhub performerByURL: - action: scrapeXPath @@ -407,11 +459,11 @@ xPathScrapers: See also [#333](https://github.com/stashapp/stash/pull/333) for more examples. -##### JSON scraper example +### JSON scraper example A performer and scene scraper for ThePornDB is shown below: -``` +```yaml name: ThePornDB performerByName: action: scrapeJson @@ -490,17 +542,8 @@ jsonScrapers: Name: $data.tags.#.tag ``` -#### XPath resources: - -- Test XPaths in Firefox: https://addons.mozilla.org/en-US/firefox/addon/try-xpath/ -- XPath cheatsheet: https://devhints.io/xpath - -#### GJSON resources: - -- GJSON Path Syntax: https://github.com/tidwall/gjson/blob/master/SYNTAX.md - -#### Object fields -##### Performer +## Object fields +### Performer ``` Name @@ -522,9 +565,9 @@ Aliases Image ``` -*Note:* - `Gender` must be one of `male`, `female`, `transgender_male`, `transgender_female` (case insensitive). +*Note:* - `Gender` must be one of `male`, `female`, `transgender_male`, `transgender_female`, `intersex`, `non_binary` (case insensitive). -##### Scene +### Scene ``` Title Details @@ -536,18 +579,18 @@ Movies (see Movie Fields) Tags (see Tag fields) Performers (list of Performer fields) ``` -##### Studio +### Studio ``` Name URL ``` -##### Tag +### Tag ``` Name ``` -##### Movie +### Movie ``` Name Aliases @@ -562,29 +605,14 @@ FrontImage BackImage ``` -### Stash - -A different stash server can be configured as a scraping source. This action applies only to `performerByName`, `performerByFragment`, and `sceneByFragment` types. This action requires that the top-level `stashServer` field is configured. - -`stashServer` contains a single `url` field for the remote stash server. The username and password can be embedded in this string using `username:password@host`. - -An example stash scrape configuration is below: - +### Gallery ``` -name: stash -performerByName: - action: stash -performerByFragment: - action: stash -sceneByFragment: - - action: stash -stashServer: - url: http://stashserver.com:9999 -``` - -### Debugging support -To print the received html/json from a scraper request to the log file, add the following to your scraper yml file: -``` -debug: - printHTML: true +Title +Details +URL +Date +Rating +Studio (see Studio Fields) +Tags (see Tag fields) +Performers (list of Performer fields) ``` diff --git a/ui/v2.5/src/docs/en/Tagger.md b/ui/v2.5/src/docs/en/Tagger.md new file mode 100644 index 000000000..9469997fa --- /dev/null +++ b/ui/v2.5/src/docs/en/Tagger.md @@ -0,0 +1,21 @@ +# Scene Tagger + +Stash can be integrated with stash-box which acts as a centralized metadata database. This is in the early stages of development but can be used for fingerprint/keyword lookups and automated tagging of performers and scenes. The batch tagging interface can be accessed from the [scene view](/scenes?disp=3). For more information join our [Discord](https://discord.gg/2TsNFKt). + +#### Searching + +The fingerprint search matches your current selection of files against the remote stash-box instance. Any scenes with a matching fingerprint will be returned, although there is currently no validation of fingerprints so it’s recommended to double-check the validity before saving. + +If no fingerprint match is found it’s possible to search by keywords. The search works by matching the query against a scene’s title_, release date_, _studio name_, and _performer names_. By default the tagger uses metadata set on the file, or parses the filename, this can be changed in the config. + +An important thing to note is that it only returns a match *if all query terms are a match*. As an example, if a scene is titled `"A Trip to the Mall"` with the performer `"Jane Doe"`, a search for `"Trip to the Mall 1080p"` will *not* match, however `"trip mall doe"` would. Usually a few pieces of info is enough, for instance performer name + release date or studio name. To avoid common non-related keywords you can add them to the blacklist in the tagger config. Any items in the blacklist are stripped out of the query. + +#### Saving +When a scene is matched stash will try to match the studio and performers against your local studios and performers. If you have previously matched them, they will automatically be selected. If not you either have to select the correct performer/studio from the dropdown, choose create to create a new entity, or skip to ignore it. + +Once a scene is saved the scene and the matched studio/performers will have the stash_id saved which will then be used for future tagging. + +By default male performers are not shown, this can be enabled in the tagger config. Likewise scene tags are by default not saved. They can be set to either merge with existing tags on the scene, or overwrite them. It is not recommended to set tags currently since they are hard to deduplicate and can litter your data. + +#### Submitting fingerprints +After a scene is saved you will prompted to submit the fingerprint back to the stash-box instance. This is optional, but can be helpful for other users who have an identical copy who will then be able to match via the fingerprint search. No other information than the stash_id and file fingerprint is submitted. diff --git a/ui/v2.5/src/globals.d.ts b/ui/v2.5/src/globals.d.ts index 18f423bf3..dc0144b95 100644 --- a/ui/v2.5/src/globals.d.ts +++ b/ui/v2.5/src/globals.d.ts @@ -1 +1,3 @@ declare module "*.md"; +declare module "string.prototype.replaceall"; +declare module "mousetrap-pause"; diff --git a/ui/v2.5/src/hooks/ListHook.tsx b/ui/v2.5/src/hooks/ListHook.tsx index b6bfaf037..7b9351964 100644 --- a/ui/v2.5/src/hooks/ListHook.tsx +++ b/ui/v2.5/src/hooks/ListHook.tsx @@ -1,12 +1,12 @@ import _ from "lodash"; import queryString from "query-string"; import React, { useCallback, useRef, useState, useEffect } from "react"; -import { ApolloError } from "apollo-client"; +import { ApolloError } from "@apollo/client"; import { useHistory, useLocation } from "react-router-dom"; import { SlimSceneDataFragment, SceneMarkerDataFragment, - GalleryDataFragment, + GallerySlimDataFragment, StudioDataFragment, PerformerDataFragment, FindScenesQueryResult, @@ -18,6 +18,8 @@ import { MovieDataFragment, FindTagsQueryResult, TagDataFragment, + FindImagesQueryResult, + SlimImageDataFragment, } from "src/core/generated-graphql"; import { useInterfaceLocalForage, @@ -29,6 +31,7 @@ import { Pagination, PaginationIndex } from "src/components/List/Pagination"; import { useFindScenes, useFindSceneMarkers, + useFindImages, useFindMovies, useFindStudios, useFindGalleries, @@ -58,11 +61,11 @@ const getSelectedData = ( interface IListHookData { filter: ListFilterModel; - template: JSX.Element; + template: React.ReactElement; onSelectChange: (id: string, selected: boolean, shiftKey: boolean) => void; } -interface IListHookOperation { +export interface IListHookOperation { text: string; onClick: ( result: T, @@ -74,6 +77,7 @@ interface IListHookOperation { filter: ListFilterModel, selectedIds: Set ) => boolean; + postRefetch?: boolean; } interface IListHookOptions { @@ -88,15 +92,15 @@ interface IListHookOptions { filter: ListFilterModel, selectedIds: Set, zoomIndex: number - ) => JSX.Element | undefined; + ) => React.ReactNode; renderEditDialog?: ( selected: E[], onClose: (applied: boolean) => void - ) => JSX.Element | undefined; + ) => React.ReactNode; renderDeleteDialog?: ( selected: E[], onClose: (confirmed: boolean) => void - ) => JSX.Element | undefined; + ) => React.ReactNode; addKeybinds?: ( result: T, filter: ListFilterModel, @@ -280,12 +284,19 @@ const RenderList = < setZoomIndex(newZoomIndex); } + async function onOperationClicked(o: IListHookOperation) { + await o.onClick(result, filter, selectedIds); + if (o.postRefetch) { + result.refetch(); + } + } + const operations = otherOperations && otherOperations.map((o) => ({ text: o.text, onClick: () => { - o.onClick(result, filter, selectedIds); + onOperationClicked(o); }, isDisplayed: () => { if (o.isDisplayed) { @@ -542,10 +553,23 @@ export const useSceneMarkersList = ( result?.data?.findSceneMarkers?.count ?? 0, }); -export const useGalleriesList = ( - props: IListHookOptions +export const useImagesList = ( + props: IListHookOptions ) => - useList({ + useList({ + ...props, + filterMode: FilterMode.Images, + useData: useFindImages, + getData: (result: FindImagesQueryResult) => + result?.data?.findImages?.images ?? [], + getCount: (result: FindImagesQueryResult) => + result?.data?.findImages?.count ?? 0, + }); + +export const useGalleriesList = ( + props: IListHookOptions +) => + useList({ ...props, filterMode: FilterMode.Galleries, useData: useFindGalleries, @@ -607,8 +631,8 @@ export const useTagsList = ( result?.data?.findTags?.count ?? 0, }); -export const showWhenSelected = ( - _result: FindScenesQueryResult, +export const showWhenSelected = ( + _result: T, _filter: ListFilterModel, selectedIds: Set ) => { diff --git a/ui/v2.5/src/hooks/LocalForage.ts b/ui/v2.5/src/hooks/LocalForage.ts index e31702a65..43763e2ad 100644 --- a/ui/v2.5/src/hooks/LocalForage.ts +++ b/ui/v2.5/src/hooks/LocalForage.ts @@ -38,7 +38,7 @@ function useLocalForage( async function runAsync() { try { const serialized = await localForage.getItem(key); - const parsed = JSON.parse(serialized); + const parsed = JSON.parse(serialized ?? "null"); if (!Object.is(parsed, null)) { setData(parsed); Cache[key] = parsed; diff --git a/ui/v2.5/src/hooks/Toast.tsx b/ui/v2.5/src/hooks/Toast.tsx index 62b1703a0..b8e94da71 100644 --- a/ui/v2.5/src/hooks/Toast.tsx +++ b/ui/v2.5/src/hooks/Toast.tsx @@ -3,7 +3,7 @@ import { Toast } from "react-bootstrap"; interface IToast { header?: string; - content: JSX.Element | string; + content: React.ReactNode | string; delay?: number; variant?: "success" | "danger" | "warning"; } diff --git a/ui/v2.5/src/hooks/VideoHover.ts b/ui/v2.5/src/hooks/VideoHover.ts deleted file mode 100644 index ea2c10e66..000000000 --- a/ui/v2.5/src/hooks/VideoHover.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { useEffect, useRef } from "react"; -import { useConfiguration } from "../core/StashService"; - -export interface IVideoHoverHookData { - videoEl: React.RefObject; - isPlaying: React.MutableRefObject; - isHovering: React.MutableRefObject; - options: IVideoHoverHookOptions; -} - -export interface IVideoHoverHookOptions { - resetOnMouseLeave: boolean; -} - -export const useVideoHover = (options: IVideoHoverHookOptions) => { - const videoEl = useRef(null); - const isPlaying = useRef(false); - const isHovering = useRef(false); - const config = useConfiguration(); - - const onMouseEnter = () => { - isHovering.current = true; - - const videoTag = videoEl.current; - if (!videoTag) { - return; - } - if (videoTag.paused && !isPlaying.current) { - videoTag.play().catch(() => {}); - } - }; - - const onMouseLeave = () => { - isHovering.current = false; - - const videoTag = videoEl.current; - if (!videoTag) { - return; - } - if (!videoTag.paused && isPlaying) { - videoTag.pause(); - if (options.resetOnMouseLeave) { - videoTag.removeAttribute("src"); - videoTag.load(); - isPlaying.current = false; - } - } - }; - - const soundEnabled = - config?.data?.configuration?.interface?.soundOnPreview ?? true; - - useEffect(() => { - const videoTag = videoEl.current; - if (!videoTag) { - return; - } - videoTag.onplaying = () => { - if (isHovering.current === true) { - isPlaying.current = true; - } else { - videoTag.pause(); - } - }; - videoTag.onpause = () => { - isPlaying.current = false; - }; - }, [videoEl]); - - useEffect(() => { - const videoTag = videoEl.current; - if (!videoTag) { - return; - } - videoTag.volume = soundEnabled ? 0.05 : 0; - }, [soundEnabled]); - - return { - videoEl, - isPlaying, - isHovering, - options, - onMouseEnter, - onMouseLeave, - }; -}; diff --git a/ui/v2.5/src/hooks/index.ts b/ui/v2.5/src/hooks/index.ts index 1fc486dd2..2afa5efc4 100644 --- a/ui/v2.5/src/hooks/index.ts +++ b/ui/v2.5/src/hooks/index.ts @@ -1,9 +1,9 @@ export { default as useToast } from "./Toast"; export { useInterfaceLocalForage, useChangelogStorage } from "./LocalForage"; -export { useVideoHover } from "./VideoHover"; export { useScenesList, useSceneMarkersList, + useImagesList, useGalleriesList, useStudiosList, usePerformersList, diff --git a/ui/v2.5/src/index.scss b/ui/v2.5/src/index.scss index e63933c91..17ee99fde 100755 --- a/ui/v2.5/src/index.scss +++ b/ui/v2.5/src/index.scss @@ -4,6 +4,7 @@ @import "src/components/Changelog/styles.scss"; @import "src/components/Galleries/styles.scss"; @import "src/components/Help/styles.scss"; +@import "src/components/Images/styles.scss"; @import "src/components/List/styles.scss"; @import "src/components/Movies/styles.scss"; @import "src/components/Performers/styles.scss"; @@ -16,6 +17,13 @@ @import "src/components/Tags/styles.scss"; @import "src/components/Wall/styles.scss"; @import "../node_modules/flag-icon-css/css/flag-icon.min.css"; +@import "src/components/Tagger/styles.scss"; + +/* stylelint-disable */ +#root { + position: relative !important; +} +/* stylelint-enable */ html { font-size: 14px; @@ -60,10 +68,15 @@ code, } .input-control, -.input-control:focus { +.input-control:focus, +.input-control:disabled { background-color: $secondary; } +.input-control:disabled { + opacity: 0.8; +} + textarea.text-input { line-height: 2.5ex; min-height: 12ex; @@ -99,7 +112,7 @@ textarea.text-input { .zoom-0 { width: 240px; - .scene-card-video { + .scene-card-preview { height: 135px; } @@ -116,7 +129,7 @@ textarea.text-input { .zoom-1 { width: 320px; - .scene-card-video { + .scene-card-preview { height: 180px; } @@ -128,12 +141,16 @@ textarea.text-input { .tag-card-image { max-height: 240px; } + + .image-card-preview { + height: 240px; + } } .zoom-2 { width: 480px; - .scene-card-video { + .scene-card-preview { height: 270px; } @@ -145,12 +162,16 @@ textarea.text-input { .tag-card-image { max-height: 360px; } + + .image-card-preview { + height: 360px; + } } .zoom-3 { width: 640px; - .scene-card-video { + .scene-card-preview { height: 360px; } @@ -162,20 +183,17 @@ textarea.text-input { .gallery-card-image { max-height: 480px; } + + .image-card-preview { + height: 480px; + } } } -.scene-card-video { - object-fit: cover; - - &.portrait { - object-fit: contain; - } -} - -.scene-card-video, +.scene-card-preview, .gallery-card-image, -.tag-card-image { +.tag-card-image, +.image-card-preview { height: auto; width: 100%; } @@ -225,14 +243,6 @@ div.dropdown-menu { .dropdown-item { display: flex; - - & > :not(:last-child) { - margin-right: 7px; - } - - & > :last-child { - margin-right: 0; - } } } diff --git a/ui/v2.5/src/index.tsx b/ui/v2.5/src/index.tsx index 0b2dc7bca..9ebba95f9 100755 --- a/ui/v2.5/src/index.tsx +++ b/ui/v2.5/src/index.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { ApolloProvider } from "react-apollo"; +import { ApolloProvider } from "@apollo/client"; import ReactDOM from "react-dom"; import { BrowserRouter } from "react-router-dom"; import { App } from "./App"; diff --git a/ui/v2.5/src/locale/en-GB.json b/ui/v2.5/src/locale/en-GB.json new file mode 100644 index 000000000..2e2c7e7ae --- /dev/null +++ b/ui/v2.5/src/locale/en-GB.json @@ -0,0 +1,16 @@ +{ + "developmentVersion": "Development Version", + "images": "Images", + "galleries": "Galleries", + "library-size": "Library size", + "markers": "Markers", + "movies": "Movies", + "new": "New", + "performers": "Performers", + "scenes": "Scenes", + "studios": "Studios", + "tags": "Tags", + "up-dir": "Up a directory", + "favourite": "FAVOURITE", + "sceneTagger": "Scene Tagger" +} diff --git a/ui/v2.5/src/locale/en.json b/ui/v2.5/src/locale/en-US.json similarity index 70% rename from ui/v2.5/src/locale/en.json rename to ui/v2.5/src/locale/en-US.json index 129f13b9f..a1a9e2742 100644 --- a/ui/v2.5/src/locale/en.json +++ b/ui/v2.5/src/locale/en-US.json @@ -1,5 +1,6 @@ { "developmentVersion": "Development Version", + "images": "Images", "galleries": "Galleries", "library-size": "Library size", "markers": "Markers", @@ -9,5 +10,7 @@ "scenes": "Scenes", "studios": "Studios", "tags": "Tags", - "up-dir": "Up a directory" + "up-dir": "Up a directory", + "favourite": "FAVORITE", + "sceneTagger": "Scene Tagger" } diff --git a/ui/v2.5/src/locale/index.ts b/ui/v2.5/src/locale/index.ts index 96dee0bbd..401bfffb0 100644 --- a/ui/v2.5/src/locale/index.ts +++ b/ui/v2.5/src/locale/index.ts @@ -1,5 +1,7 @@ -import en from "./en.json"; +import enGB from "./en-GB.json"; +import enUS from "./en-US.json"; export default { - en, + enGB, + enUS, }; diff --git a/ui/v2.5/src/models/list-filter/criteria/country.ts b/ui/v2.5/src/models/list-filter/criteria/country.ts new file mode 100644 index 000000000..b5e7cf871 --- /dev/null +++ b/ui/v2.5/src/models/list-filter/criteria/country.ts @@ -0,0 +1,16 @@ +import { CriterionModifier } from "src/core/generated-graphql"; +import { Criterion, CriterionType, ICriterionOption } from "./criterion"; + +export class CountryCriterion extends Criterion { + public type: CriterionType = "country"; + public parameterName: string = "performers"; + public modifier = CriterionModifier.Equals; + public modifierOptions = []; + public options: string[] = [true.toString(), false.toString()]; + public value: string = ""; +} + +export class CountryCriterionOption implements ICriterionOption { + public label: string = Criterion.getLabel("performers"); + public value: CriterionType = "country"; +} diff --git a/ui/v2.5/src/models/list-filter/criteria/criterion.ts b/ui/v2.5/src/models/list-filter/criteria/criterion.ts index cfc6af532..07e77e308 100644 --- a/ui/v2.5/src/models/list-filter/criteria/criterion.ts +++ b/ui/v2.5/src/models/list-filter/criteria/criterion.ts @@ -6,13 +6,16 @@ import { ILabeledId, ILabeledValue, IOptionType } from "../types"; export type CriterionType = | "none" + | "path" | "rating" | "o_counter" | "resolution" + | "average_resolution" | "duration" | "favorite" | "hasMarkers" | "sceneIsMissing" + | "imageIsMissing" | "performerIsMissing" | "galleryIsMissing" | "tagIsMissing" @@ -23,6 +26,7 @@ export type CriterionType = | "performers" | "studios" | "movies" + | "galleries" | "birth_year" | "age" | "ethnicity" @@ -48,12 +52,16 @@ export abstract class Criterion { switch (type) { case "none": return "None"; + case "path": + return "Path"; case "rating": return "Rating"; case "o_counter": return "O-Counter"; case "resolution": return "Resolution"; + case "average_resolution": + return "Average Resolution"; case "duration": return "Duration"; case "favorite": @@ -61,6 +69,7 @@ export abstract class Criterion { case "hasMarkers": return "Has Markers"; case "sceneIsMissing": + case "imageIsMissing": case "performerIsMissing": case "galleryIsMissing": case "tagIsMissing": @@ -77,6 +86,8 @@ export abstract class Criterion { return "Studios"; case "movies": return "Movies"; + case "galleries": + return "Galleries"; case "birth_year": return "Birth Year"; case "age": @@ -237,10 +248,12 @@ export class StringCriterion extends Criterion { public parameterName: string; public modifier = CriterionModifier.Equals; public modifierOptions = [ - Criterion.getModifierOption(CriterionModifier.Equals), - Criterion.getModifierOption(CriterionModifier.NotEquals), - Criterion.getModifierOption(CriterionModifier.IsNull), - Criterion.getModifierOption(CriterionModifier.NotNull), + StringCriterion.getModifierOption(CriterionModifier.Equals), + StringCriterion.getModifierOption(CriterionModifier.NotEquals), + StringCriterion.getModifierOption(CriterionModifier.Includes), + StringCriterion.getModifierOption(CriterionModifier.Excludes), + StringCriterion.getModifierOption(CriterionModifier.IsNull), + StringCriterion.getModifierOption(CriterionModifier.NotNull), ]; public options: string[] | undefined; public value: string = ""; @@ -264,6 +277,13 @@ export class StringCriterion extends Criterion { } } +export class MandatoryStringCriterion extends StringCriterion { + public modifierOptions = [ + StringCriterion.getModifierOption(CriterionModifier.Equals), + StringCriterion.getModifierOption(CriterionModifier.NotEquals), + ]; +} + export class NumberCriterion extends Criterion { public type: CriterionType; public parameterName: string; diff --git a/ui/v2.5/src/models/list-filter/criteria/galleries.ts b/ui/v2.5/src/models/list-filter/criteria/galleries.ts new file mode 100644 index 000000000..99101e886 --- /dev/null +++ b/ui/v2.5/src/models/list-filter/criteria/galleries.ts @@ -0,0 +1,27 @@ +import * as GQL from "src/core/generated-graphql"; +import { ILabeledId, IOptionType, encodeILabeledId } from "../types"; +import { Criterion, CriterionType, ICriterionOption } from "./criterion"; + +export class GalleriesCriterion extends Criterion { + public type: CriterionType = "galleries"; + public parameterName: string = "galleries"; + public modifier = GQL.CriterionModifier.IncludesAll; + public modifierOptions = [ + Criterion.getModifierOption(GQL.CriterionModifier.IncludesAll), + Criterion.getModifierOption(GQL.CriterionModifier.Includes), + Criterion.getModifierOption(GQL.CriterionModifier.Excludes), + ]; + public options: IOptionType[] = []; + public value: ILabeledId[] = []; + + public encodeValue() { + return this.value.map((o) => { + return encodeILabeledId(o); + }); + } +} + +export class GalleriesCriterionOption implements ICriterionOption { + public label: string = Criterion.getLabel("galleries"); + public value: CriterionType = "galleries"; +} diff --git a/ui/v2.5/src/models/list-filter/criteria/is-missing.ts b/ui/v2.5/src/models/list-filter/criteria/is-missing.ts index ac3686773..24c132a16 100644 --- a/ui/v2.5/src/models/list-filter/criteria/is-missing.ts +++ b/ui/v2.5/src/models/list-filter/criteria/is-missing.ts @@ -20,6 +20,7 @@ export class SceneIsMissingCriterion extends IsMissingCriterion { "movie", "performers", "tags", + "stash_id", ]; } @@ -28,6 +29,22 @@ export class SceneIsMissingCriterionOption implements ICriterionOption { public value: CriterionType = "sceneIsMissing"; } +export class ImageIsMissingCriterion extends IsMissingCriterion { + public type: CriterionType = "imageIsMissing"; + public options: string[] = [ + "title", + "galleries", + "studio", + "performers", + "tags", + ]; +} + +export class ImageIsMissingCriterionOption implements ICriterionOption { + public label: string = Criterion.getLabel("imageIsMissing"); + public value: CriterionType = "imageIsMissing"; +} + export class PerformerIsMissingCriterion extends IsMissingCriterion { public type: CriterionType = "performerIsMissing"; public options: string[] = [ @@ -47,6 +64,7 @@ export class PerformerIsMissingCriterion extends IsMissingCriterion { "gender", "scenes", "image", + "stash_id", ]; } @@ -57,7 +75,16 @@ export class PerformerIsMissingCriterionOption implements ICriterionOption { export class GalleryIsMissingCriterion extends IsMissingCriterion { public type: CriterionType = "galleryIsMissing"; - public options: string[] = ["scene"]; + public options: string[] = [ + "title", + "details", + "url", + "date", + "studio", + "performers", + "tags", + "scene", + ]; } export class GalleryIsMissingCriterionOption implements ICriterionOption { @@ -77,7 +104,7 @@ export class TagIsMissingCriterionOption implements ICriterionOption { export class StudioIsMissingCriterion extends IsMissingCriterion { public type: CriterionType = "studioIsMissing"; - public options: string[] = ["image"]; + public options: string[] = ["image", "stash_id"]; } export class StudioIsMissingCriterionOption implements ICriterionOption { @@ -87,7 +114,7 @@ export class StudioIsMissingCriterionOption implements ICriterionOption { export class MovieIsMissingCriterion extends IsMissingCriterion { public type: CriterionType = "movieIsMissing"; - public options: string[] = ["front_image", "back_image"]; + public options: string[] = ["front_image", "back_image", "scenes"]; } export class MovieIsMissingCriterionOption implements ICriterionOption { diff --git a/ui/v2.5/src/models/list-filter/criteria/resolution.ts b/ui/v2.5/src/models/list-filter/criteria/resolution.ts index 475fbdc6a..edcd03128 100644 --- a/ui/v2.5/src/models/list-filter/criteria/resolution.ts +++ b/ui/v2.5/src/models/list-filter/criteria/resolution.ts @@ -14,3 +14,13 @@ export class ResolutionCriterionOption implements ICriterionOption { public label: string = Criterion.getLabel("resolution"); public value: CriterionType = "resolution"; } + +export class AverageResolutionCriterion extends ResolutionCriterion { + public type: CriterionType = "average_resolution"; + public parameterName: string = "average_resolution"; +} + +export class AverageResolutionCriterionOption extends ResolutionCriterionOption { + public label: string = Criterion.getLabel("average_resolution"); + public value: CriterionType = "average_resolution"; +} diff --git a/ui/v2.5/src/models/list-filter/criteria/utils.ts b/ui/v2.5/src/models/list-filter/criteria/utils.ts index 13ef6d774..0e3d95a47 100644 --- a/ui/v2.5/src/models/list-filter/criteria/utils.ts +++ b/ui/v2.5/src/models/list-filter/criteria/utils.ts @@ -6,6 +6,7 @@ import { StringCriterion, NumberCriterion, DurationCriterion, + MandatoryStringCriterion, } from "./criterion"; import { FavoriteCriterion } from "./favorite"; import { HasMarkersCriterion } from "./has-markers"; @@ -16,20 +17,24 @@ import { TagIsMissingCriterion, StudioIsMissingCriterion, MovieIsMissingCriterion, + ImageIsMissingCriterion, } from "./is-missing"; import { NoneCriterion } from "./none"; import { PerformersCriterion } from "./performers"; import { RatingCriterion } from "./rating"; -import { ResolutionCriterion } from "./resolution"; +import { AverageResolutionCriterion, ResolutionCriterion } from "./resolution"; import { StudiosCriterion, ParentStudiosCriterion } from "./studios"; import { TagsCriterion } from "./tags"; import { GenderCriterion } from "./gender"; import { MoviesCriterion } from "./movies"; +import { GalleriesCriterion } from "./galleries"; export function makeCriteria(type: CriterionType = "none") { switch (type) { case "none": return new NoneCriterion(); + case "path": + return new MandatoryStringCriterion(type, type); case "rating": return new RatingCriterion(); case "o_counter": @@ -38,6 +43,8 @@ export function makeCriteria(type: CriterionType = "none") { return new NumberCriterion(type, type); case "resolution": return new ResolutionCriterion(); + case "average_resolution": + return new AverageResolutionCriterion(); case "duration": return new DurationCriterion(type, type); case "favorite": @@ -46,6 +53,8 @@ export function makeCriteria(type: CriterionType = "none") { return new HasMarkersCriterion(); case "sceneIsMissing": return new SceneIsMissingCriterion(); + case "imageIsMissing": + return new ImageIsMissingCriterion(); case "performerIsMissing": return new PerformerIsMissingCriterion(); case "galleryIsMissing": @@ -68,6 +77,8 @@ export function makeCriteria(type: CriterionType = "none") { return new ParentStudiosCriterion(); case "movies": return new MoviesCriterion(); + case "galleries": + return new GalleriesCriterion(); case "birth_year": return new NumberCriterion(type, type); case "age": { diff --git a/ui/v2.5/src/models/list-filter/filter.ts b/ui/v2.5/src/models/list-filter/filter.ts index 237c2802e..b58e0429e 100644 --- a/ui/v2.5/src/models/list-filter/filter.ts +++ b/ui/v2.5/src/models/list-filter/filter.ts @@ -10,6 +10,7 @@ import { StudioFilterType, GalleryFilterType, TagFilterType, + ImageFilterType, } from "src/core/generated-graphql"; import { stringToGender } from "src/core/StashService"; import { @@ -20,6 +21,7 @@ import { NumberCriterion, StringCriterion, DurationCriterion, + MandatoryStringCriterion, } from "./criteria/criterion"; import { FavoriteCriterion, @@ -37,6 +39,7 @@ import { TagIsMissingCriterionOption, StudioIsMissingCriterionOption, MovieIsMissingCriterionOption, + ImageIsMissingCriterionOption, } from "./criteria/is-missing"; import { NoneCriterionOption } from "./criteria/none"; import { @@ -45,6 +48,8 @@ import { } from "./criteria/performers"; import { RatingCriterion, RatingCriterionOption } from "./criteria/rating"; import { + AverageResolutionCriterion, + AverageResolutionCriterionOption, ResolutionCriterion, ResolutionCriterionOption, } from "./criteria/resolution"; @@ -63,6 +68,7 @@ import { makeCriteria } from "./criteria/utils"; import { DisplayMode, FilterMode } from "./types"; import { GenderCriterionOption, GenderCriterion } from "./criteria/gender"; import { MoviesCriterionOption, MoviesCriterion } from "./criteria/movies"; +import { GalleriesCriterion } from "./criteria/galleries"; interface IQueryParameters { perPage?: string; @@ -112,6 +118,7 @@ export class ListFilterModel { "o_counter", "date", "filesize", + "file_mod_time", "duration", "framerate", "bitrate", @@ -121,9 +128,11 @@ export class ListFilterModel { DisplayMode.Grid, DisplayMode.List, DisplayMode.Wall, + DisplayMode.Tagger, ]; this.criterionOptions = [ new NoneCriterionOption(), + ListFilterModel.createCriterionOption("path"), new RatingCriterionOption(), ListFilterModel.createCriterionOption("o_counter"), new ResolutionCriterionOption(), @@ -136,6 +145,30 @@ export class ListFilterModel { new MoviesCriterionOption(), ]; break; + case FilterMode.Images: + this.sortBy = "path"; + this.sortByOptions = [ + "title", + "path", + "rating", + "o_counter", + "filesize", + "file_mod_time", + "random", + ]; + this.displayModeOptions = [DisplayMode.Grid, DisplayMode.Wall]; + this.criterionOptions = [ + new NoneCriterionOption(), + ListFilterModel.createCriterionOption("path"), + new RatingCriterionOption(), + ListFilterModel.createCriterionOption("o_counter"), + new ResolutionCriterionOption(), + new ImageIsMissingCriterionOption(), + new TagsCriterionOption(), + new PerformersCriterionOption(), + new StudiosCriterionOption(), + ]; + break; case FilterMode.Performers: { this.sortBy = "name"; this.sortByOptions = [ @@ -195,11 +228,17 @@ export class ListFilterModel { break; case FilterMode.Galleries: this.sortBy = "path"; - this.sortByOptions = ["path"]; + this.sortByOptions = ["path", "file_mod_time", "images_count"]; this.displayModeOptions = [DisplayMode.Grid, DisplayMode.List]; this.criterionOptions = [ new NoneCriterionOption(), + ListFilterModel.createCriterionOption("path"), + new RatingCriterionOption(), + new AverageResolutionCriterionOption(), new GalleryIsMissingCriterionOption(), + new TagsCriterionOption(), + new PerformersCriterionOption(), + new StudiosCriterionOption(), ]; break; case FilterMode.SceneMarkers: @@ -380,6 +419,14 @@ export class ListFilterModel { const result: SceneFilterType = {}; this.criteria.forEach((criterion) => { switch (criterion.type) { + case "path": { + const pathCrit = criterion as MandatoryStringCriterion; + result.path = { + value: pathCrit.value, + modifier: pathCrit.modifier, + }; + break; + } case "rating": { const ratingCrit = criterion as RatingCriterion; result.rating = { @@ -602,6 +649,96 @@ export class ListFilterModel { return result; } + public makeImageFilter(): ImageFilterType { + const result: ImageFilterType = {}; + this.criteria.forEach((criterion) => { + switch (criterion.type) { + case "path": { + const pathCrit = criterion as MandatoryStringCriterion; + result.path = { + value: pathCrit.value, + modifier: pathCrit.modifier, + }; + break; + } + case "rating": { + const ratingCrit = criterion as RatingCriterion; + result.rating = { + value: ratingCrit.value, + modifier: ratingCrit.modifier, + }; + break; + } + case "o_counter": { + const oCounterCrit = criterion as NumberCriterion; + result.o_counter = { + value: oCounterCrit.value, + modifier: oCounterCrit.modifier, + }; + break; + } + case "resolution": { + switch ((criterion as ResolutionCriterion).value) { + case "240p": + result.resolution = ResolutionEnum.Low; + break; + case "480p": + result.resolution = ResolutionEnum.Standard; + break; + case "720p": + result.resolution = ResolutionEnum.StandardHd; + break; + case "1080p": + result.resolution = ResolutionEnum.FullHd; + break; + case "4k": + result.resolution = ResolutionEnum.FourK; + break; + // no default + } + break; + } + case "imageIsMissing": + result.is_missing = (criterion as IsMissingCriterion).value; + break; + case "tags": { + const tagsCrit = criterion as TagsCriterion; + result.tags = { + value: tagsCrit.value.map((tag) => tag.id), + modifier: tagsCrit.modifier, + }; + break; + } + case "performers": { + const perfCrit = criterion as PerformersCriterion; + result.performers = { + value: perfCrit.value.map((perf) => perf.id), + modifier: perfCrit.modifier, + }; + break; + } + case "studios": { + const studCrit = criterion as StudiosCriterion; + result.studios = { + value: studCrit.value.map((studio) => studio.id), + modifier: studCrit.modifier, + }; + break; + } + case "galleries": { + const perfCrit = criterion as GalleriesCriterion; + result.galleries = { + value: perfCrit.value.map((gallery) => gallery.id), + modifier: perfCrit.modifier, + }; + break; + } + // no default + } + }); + return result; + } + public makeMovieFilter(): MovieFilterType { const result: MovieFilterType = {}; this.criteria.forEach((criterion) => { @@ -647,9 +784,70 @@ export class ListFilterModel { const result: GalleryFilterType = {}; this.criteria.forEach((criterion) => { switch (criterion.type) { + case "path": { + const pathCrit = criterion as MandatoryStringCriterion; + result.path = { + value: pathCrit.value, + modifier: pathCrit.modifier, + }; + break; + } + case "rating": { + const ratingCrit = criterion as RatingCriterion; + result.rating = { + value: ratingCrit.value, + modifier: ratingCrit.modifier, + }; + break; + } + case "average_resolution": { + switch ((criterion as AverageResolutionCriterion).value) { + case "240p": + result.average_resolution = ResolutionEnum.Low; + break; + case "480p": + result.average_resolution = ResolutionEnum.Standard; + break; + case "720p": + result.average_resolution = ResolutionEnum.StandardHd; + break; + case "1080p": + result.average_resolution = ResolutionEnum.FullHd; + break; + case "4k": + result.average_resolution = ResolutionEnum.FourK; + break; + // no default + } + break; + } case "galleryIsMissing": result.is_missing = (criterion as IsMissingCriterion).value; break; + case "tags": { + const tagsCrit = criterion as TagsCriterion; + result.tags = { + value: tagsCrit.value.map((tag) => tag.id), + modifier: tagsCrit.modifier, + }; + break; + } + case "performers": { + const perfCrit = criterion as PerformersCriterion; + result.performers = { + value: perfCrit.value.map((perf) => perf.id), + modifier: perfCrit.modifier, + }; + break; + } + case "studios": { + const studCrit = criterion as StudiosCriterion; + result.studios = { + value: studCrit.value.map((studio) => studio.id), + modifier: studCrit.modifier, + }; + break; + } // no default } }); diff --git a/ui/v2.5/src/models/list-filter/types.ts b/ui/v2.5/src/models/list-filter/types.ts index 11e66596f..4507b63eb 100644 --- a/ui/v2.5/src/models/list-filter/types.ts +++ b/ui/v2.5/src/models/list-filter/types.ts @@ -4,6 +4,7 @@ export enum DisplayMode { Grid, List, Wall, + Tagger, } export enum FilterMode { @@ -14,6 +15,7 @@ export enum FilterMode { SceneMarkers, Movies, Tags, + Images, } export interface ILabeledId { @@ -27,7 +29,9 @@ export interface ILabeledValue { } export function encodeILabeledId(o: ILabeledId) { - return { ...o, label: encodeURIComponent(o.label) }; + // escape \ to \\ so that it encodes to JSON correctly + const adjustedLabel = o.label.replaceAll("\\", "\\\\"); + return { ...o, label: encodeURIComponent(adjustedLabel) }; } export interface IOptionType { diff --git a/ui/v2.5/src/utils/country.ts b/ui/v2.5/src/utils/country.ts index 915ea68dd..f94d23ca2 100644 --- a/ui/v2.5/src/utils/country.ts +++ b/ui/v2.5/src/utils/country.ts @@ -27,4 +27,10 @@ const getISOCountry = (country: string | null | undefined) => { }; }; +export const getCountryByISO = (iso: string | null | undefined) => { + if (!iso) return null; + + return Countries.getName(iso, "en") ?? null; +}; + export default getISOCountry; diff --git a/ui/v2.5/src/utils/download.ts b/ui/v2.5/src/utils/download.ts new file mode 100644 index 000000000..d7ac375ac --- /dev/null +++ b/ui/v2.5/src/utils/download.ts @@ -0,0 +1,7 @@ +const downloadFile = (url: string) => { + const a = document.createElement("a"); + a.href = url; + a.click(); +}; + +export default downloadFile; diff --git a/ui/v2.5/src/utils/focus.ts b/ui/v2.5/src/utils/focus.ts index b1e48201a..0ab1e3b68 100644 --- a/ui/v2.5/src/utils/focus.ts +++ b/ui/v2.5/src/utils/focus.ts @@ -10,6 +10,7 @@ const useFocus = () => { } }; + // eslint-disable-next-line no-undef return [htmlElRef, setFocus] as const; }; diff --git a/ui/v2.5/src/utils/index.ts b/ui/v2.5/src/utils/index.ts index d12c2943e..d9561cbfd 100644 --- a/ui/v2.5/src/utils/index.ts +++ b/ui/v2.5/src/utils/index.ts @@ -10,3 +10,4 @@ export { default as SessionUtils } from "./session"; export { default as flattenMessages } from "./flattenMessages"; export { default as getISOCountry } from "./country"; export { default as useFocus } from "./focus"; +export { default as downloadFile } from "./download"; diff --git a/ui/v2.5/src/utils/navigation.ts b/ui/v2.5/src/utils/navigation.ts index a4867bf6c..3a3cfadf8 100644 --- a/ui/v2.5/src/utils/navigation.ts +++ b/ui/v2.5/src/utils/navigation.ts @@ -1,5 +1,6 @@ import * as GQL from "src/core/generated-graphql"; import { PerformersCriterion } from "src/models/list-filter/criteria/performers"; +import { CountryCriterion } from "src/models/list-filter/criteria/country"; import { StudiosCriterion, ParentStudiosCriterion, @@ -22,6 +23,17 @@ const makePerformerScenesUrl = ( return `/scenes?${filter.makeQueryParameters()}`; }; +const makePerformersCountryUrl = ( + performer: Partial +) => { + if (!performer.id) return "#"; + const filter = new ListFilterModel(FilterMode.Performers); + const criterion = new CountryCriterion(); + criterion.value = `${performer.country}`; + filter.criteria.push(criterion); + return `/performers?${filter.makeQueryParameters()}`; +}; + const makeStudioScenesUrl = (studio: Partial) => { if (!studio.id) return "#"; const filter = new ListFilterModel(FilterMode.Scenes); @@ -82,6 +94,7 @@ const makeSceneMarkerUrl = ( export default { makePerformerScenesUrl, + makePerformersCountryUrl, makeStudioScenesUrl, makeTagSceneMarkersUrl, makeTagScenesUrl, diff --git a/ui/v2.5/src/utils/text.ts b/ui/v2.5/src/utils/text.ts index 3b5ec83a4..1ea04b60e 100644 --- a/ui/v2.5/src/utils/text.ts +++ b/ui/v2.5/src/utils/text.ts @@ -16,6 +16,7 @@ const Units: Unit[] = [ "terabyte", "petabyte", ]; +const shortUnits = ["B", "KB", "MB", "GB", "TB", "PB"]; const truncate = ( value?: string, @@ -32,7 +33,7 @@ const fileSize = (bytes: number = 0) => { let unit = 0; let count = bytes; - while (count >= 1024) { + while (count >= 1024 && unit + 1 < Units.length) { count /= 1024; unit++; } @@ -43,6 +44,11 @@ const fileSize = (bytes: number = 0) => { }; }; +const formatFileSizeUnit = (u: Unit) => { + const i = Units.indexOf(u); + return shortUnits[i]; +}; + const secondsToTimestamp = (seconds: number) => { let ret = new Date(seconds * 1000).toISOString().substr(11, 8); @@ -135,12 +141,13 @@ const formatDate = (intl: IntlShape, date?: string) => { return ""; } - return intl.formatDate(date, { format: "long" }); + return intl.formatDate(date, { format: "long", timeZone: "utc" }); }; const TextUtils = { truncate, fileSize, + formatFileSizeUnit, secondsToTimestamp, fileNameFromPath, age: getAge, diff --git a/ui/v2.5/yarn.lock b/ui/v2.5/yarn.lock index 50a11c14b..4b2599311 100644 --- a/ui/v2.5/yarn.lock +++ b/ui/v2.5/yarn.lock @@ -2,54 +2,51 @@ # yarn lockfile v1 -"@apollo/react-common@^3.1.4": - version "3.1.4" - resolved "https://registry.yarnpkg.com/@apollo/react-common/-/react-common-3.1.4.tgz#ec13c985be23ea8e799c9ea18e696eccc97be345" - integrity sha512-X5Kyro73bthWSCBJUC5XYQqMnG0dLWuDZmVkzog9dynovhfiVCV4kPSdgSIkqnb++cwCzOVuQ4rDKVwo2XRzQA== +"@apollo/client@^3.1.3", "@apollo/client@^3.1.5": + version "3.2.0" + resolved "https://registry.yarnpkg.com/@apollo/client/-/client-3.2.0.tgz#d16ea4384a2126bf60e7d87b0a6c6df00382220b" + integrity sha512-6ISMYW9QpEykJAkN6ZZteTkXXwtYSPGbh+4iBZ478p/Eox1JOMGYlqosGgMGv2oduug9SnsR65y0iCAxKOFGiQ== dependencies: - ts-invariant "^0.4.4" - tslib "^1.10.0" - -"@apollo/react-components@^3.1.5": - version "3.1.5" - resolved "https://registry.yarnpkg.com/@apollo/react-components/-/react-components-3.1.5.tgz#040d2f35ce4947747efe16f76d59dcbd797ffdaf" - integrity sha512-c82VyUuE9VBnJB7bnX+3dmwpIPMhyjMwyoSLyQWPHxz8jK4ak30XszJtqFf4eC4hwvvLYa+Ou6X73Q8V8e2/jg== - dependencies: - "@apollo/react-common" "^3.1.4" - "@apollo/react-hooks" "^3.1.5" + "@graphql-typed-document-node/core" "^3.0.0" + "@types/zen-observable" "^0.8.0" + "@wry/context" "^0.5.2" + "@wry/equality" "^0.2.0" + fast-json-stable-stringify "^2.0.0" + graphql-tag "^2.11.0" + hoist-non-react-statics "^3.3.2" + optimism "^0.12.1" prop-types "^15.7.2" + symbol-observable "^2.0.0" + terser "^5.2.0" ts-invariant "^0.4.4" tslib "^1.10.0" + zen-observable "^0.8.14" -"@apollo/react-hoc@^3.1.5": - version "3.1.5" - resolved "https://registry.yarnpkg.com/@apollo/react-hoc/-/react-hoc-3.1.5.tgz#6552d2fb4aafc59fdc8f4e353358b98b89cfab6f" - integrity sha512-jlZ2pvEnRevLa54H563BU0/xrYSgWQ72GksarxUzCHQW85nmn9wQln0kLBX7Ua7SBt9WgiuYQXQVechaaCulfQ== +"@apollo/client@^3.1.4": + version "3.1.4" + resolved "https://registry.yarnpkg.com/@apollo/client/-/client-3.1.4.tgz#2848a9f29619275df9af55966c4f5984e31cea6e" + integrity sha512-XPZ2eL+0GN25FazEOAVAvEyAzWPVsHeo+DgG/45d4Rb+srzPN+vmRgpQL5TdX2BYJoPd04J/g8OZwYUEKL8laA== dependencies: - "@apollo/react-common" "^3.1.4" - "@apollo/react-components" "^3.1.5" - hoist-non-react-statics "^3.3.0" + "@types/zen-observable" "^0.8.0" + "@wry/context" "^0.5.2" + "@wry/equality" "^0.2.0" + fast-json-stable-stringify "^2.0.0" + graphql-tag "^2.11.0" + hoist-non-react-statics "^3.3.2" + optimism "^0.12.1" + prop-types "^15.7.2" + symbol-observable "^1.2.0" + terser "^5.2.0" ts-invariant "^0.4.4" tslib "^1.10.0" + zen-observable "^0.8.14" -"@apollo/react-hooks@^3.1.5": - version "3.1.5" - resolved "https://registry.yarnpkg.com/@apollo/react-hooks/-/react-hooks-3.1.5.tgz#7e710be52461255ae7fc0b3b9c2ece64299c10e6" - integrity sha512-y0CJ393DLxIIkksRup4nt+vSjxalbZBXnnXxYbviq/woj+zKa431zy0yT4LqyRKpFy9ahMIwxBnBwfwIoupqLQ== +"@ardatan/aggregate-error@0.0.6": + version "0.0.6" + resolved "https://registry.yarnpkg.com/@ardatan/aggregate-error/-/aggregate-error-0.0.6.tgz#fe6924771ea40fc98dc7a7045c2e872dc8527609" + integrity sha512-vyrkEHG1jrukmzTPtyWB4NLPauUw5bQeg4uhn8f+1SSynmrOcyvlb1GKQjjgoBzElLdfXCRYX8UnBlhklOHYRQ== dependencies: - "@apollo/react-common" "^3.1.4" - "@wry/equality" "^0.1.9" - ts-invariant "^0.4.4" - tslib "^1.10.0" - -"@apollo/react-ssr@^3.1.5": - version "3.1.5" - resolved "https://registry.yarnpkg.com/@apollo/react-ssr/-/react-ssr-3.1.5.tgz#53703cd493afcde567acc6d5512cab03dafce6de" - integrity sha512-wuLPkKlctNn3u8EU8rlECyktpOUCeekFfb0KhIKknpGY6Lza2Qu0bThx7D9MIbVEzhKadNNrzLcpk0Y8/5UuWg== - dependencies: - "@apollo/react-common" "^3.1.4" - "@apollo/react-hooks" "^3.1.5" - tslib "^1.10.0" + tslib "~2.0.1" "@babel/code-frame@7.8.3", "@babel/code-frame@^7.0.0", "@babel/code-frame@^7.8.3": version "7.8.3" @@ -58,6 +55,13 @@ dependencies: "@babel/highlight" "^7.8.3" +"@babel/code-frame@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.10.4.tgz#168da1a36e90da68ae8d49c0f1b48c7c6249213a" + integrity sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg== + dependencies: + "@babel/highlight" "^7.10.4" + "@babel/code-frame@^7.5.5": version "7.5.5" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.5.5.tgz#bc0782f6d69f7b7d49531219699b988f669a8f9d" @@ -169,6 +173,15 @@ semver "^5.4.1" source-map "^0.5.0" +"@babel/generator@^7.11.5": + version "7.11.6" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.11.6.tgz#b868900f81b163b4d464ea24545c61cbac4dc620" + integrity sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA== + dependencies: + "@babel/types" "^7.11.5" + jsesc "^2.5.1" + source-map "^0.5.0" + "@babel/generator@^7.4.0", "@babel/generator@^7.8.4": version "7.8.4" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.8.4.tgz#35bbc74486956fe4251829f9f6c48330e8d0985e" @@ -199,7 +212,7 @@ lodash "^4.17.13" source-map "^0.5.0" -"@babel/generator@^7.9.0", "@babel/generator@^7.9.5", "@babel/generator@^7.9.6": +"@babel/generator@^7.9.0", "@babel/generator@^7.9.6": version "7.9.6" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.9.6.tgz#5408c82ac5de98cda0d77d8124e99fa1f2170a43" integrity sha512-+htwWKJbH2bL72HRluF8zumBxzuX0ZZUFl3JLNyoUjM/Ho8wnVpPXM6aUz8cfKDqQ/h7zHqKt4xzJteUosckqQ== @@ -338,6 +351,15 @@ "@babel/traverse" "^7.8.3" "@babel/types" "^7.8.3" +"@babel/helper-function-name@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz#d2d3b20c59ad8c47112fa7d2a94bc09d5ef82f1a" + integrity sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ== + dependencies: + "@babel/helper-get-function-arity" "^7.10.4" + "@babel/template" "^7.10.4" + "@babel/types" "^7.10.4" + "@babel/helper-function-name@^7.7.4": version "7.7.4" resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.7.4.tgz#ab6e041e7135d436d8f0a3eca15de5b67a341a2e" @@ -365,6 +387,13 @@ "@babel/template" "^7.8.3" "@babel/types" "^7.9.5" +"@babel/helper-get-function-arity@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz#98c1cbea0e2332f33f9a4661b8ce1505b2c19ba2" + integrity sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A== + dependencies: + "@babel/types" "^7.10.4" + "@babel/helper-get-function-arity@^7.7.4": version "7.7.4" resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.4.tgz#cb46348d2f8808e632f0ab048172130e636005f0" @@ -495,6 +524,13 @@ "@babel/template" "^7.8.3" "@babel/types" "^7.8.3" +"@babel/helper-split-export-declaration@^7.11.0": + version "7.11.0" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz#f8a491244acf6a676158ac42072911ba83ad099f" + integrity sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg== + dependencies: + "@babel/types" "^7.11.0" + "@babel/helper-split-export-declaration@^7.7.4": version "7.7.4" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.4.tgz#57292af60443c4a3622cf74040ddc28e68336fd8" @@ -509,6 +545,11 @@ dependencies: "@babel/types" "^7.8.3" +"@babel/helper-validator-identifier@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz#a78c7a7251e01f616512d31b10adcf52ada5e0d2" + integrity sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw== + "@babel/helper-validator-identifier@^7.9.5": version "7.9.5" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz#90977a8e6fbf6b431a7dc31752eee233bf052d80" @@ -560,10 +601,19 @@ esutils "^2.0.2" js-tokens "^4.0.0" -"@babel/parser@7.9.4": - version "7.9.4" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.9.4.tgz#68a35e6b0319bbc014465be43828300113f2f2e8" - integrity sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA== +"@babel/highlight@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.10.4.tgz#7d1bdfd65753538fabe6c38596cdb76d9ac60143" + integrity sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA== + dependencies: + "@babel/helper-validator-identifier" "^7.10.4" + chalk "^2.0.0" + js-tokens "^4.0.0" + +"@babel/parser@7.11.5", "@babel/parser@^7.10.4", "@babel/parser@^7.11.5": + version "7.11.5" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.11.5.tgz#c7ff6303df71080ec7a4f5b8c003c58f1cf51037" + integrity sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q== "@babel/parser@^7.0.0", "@babel/parser@^7.7.4": version "7.7.7" @@ -1420,6 +1470,14 @@ "@babel/helper-plugin-utils" "^7.8.3" "@babel/plugin-transform-typescript" "^7.9.0" +"@babel/runtime-corejs3@^7.10.2": + version "7.11.2" + resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.11.2.tgz#02c3029743150188edeb66541195f54600278419" + integrity sha512-qh5IR+8VgFz83VBa6OkaET6uN/mJOhHONuy3m1sgF0CV6mXdPSEBdA7e1eUbVvyNtANjMbg22JUv71BaDXLY6A== + dependencies: + core-js-pure "^3.0.0" + regenerator-runtime "^0.13.4" + "@babel/runtime-corejs3@^7.7.4": version "7.7.7" resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.7.7.tgz#78fcbd472daec13abc42678bfc319e58a62235a3" @@ -1443,13 +1501,20 @@ dependencies: regenerator-runtime "^0.13.4" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.4.0", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.4.2", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.4": version "7.7.7" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.7.7.tgz#194769ca8d6d7790ec23605af9ee3e42a0aa79cf" integrity sha512-uCnC2JEVAu8AKB5do1WRIsvrdJ0flYx/A/9f/6chdacnEZ7LmavjdsDXr5ksYBegxtuTPR5Va9/+13QF/kFkCA== dependencies: regenerator-runtime "^0.13.2" +"@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2": + version "7.11.2" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.11.2.tgz#f549c13c754cc40b87644b9fa9f09a6a95fe0736" + integrity sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/runtime@^7.3.4": version "7.8.4" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.8.4.tgz#d79f5a2040f7caa24d53e563aad49cbc05581308" @@ -1464,13 +1529,22 @@ dependencies: regenerator-runtime "^0.13.2" -"@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.8.4": version "7.9.6" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.9.6.tgz#a9102eb5cadedf3f31d08a9ecf294af7827ea29f" integrity sha512-64AF1xY3OAkFHqOb9s4jpgk1Mm5vDZ4L3acHvAml+53nO1XbXLuDodsVpO4OIUsmemlUHMxNdYMNJmsvOwLrvQ== dependencies: regenerator-runtime "^0.13.4" +"@babel/template@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278" + integrity sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA== + dependencies: + "@babel/code-frame" "^7.10.4" + "@babel/parser" "^7.10.4" + "@babel/types" "^7.10.4" + "@babel/template@^7.4.0", "@babel/template@^7.8.3": version "7.8.3" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.3.tgz#e02ad04fe262a657809327f578056ca15fd4d1b8" @@ -1498,20 +1572,20 @@ "@babel/parser" "^7.8.6" "@babel/types" "^7.8.6" -"@babel/traverse@7.9.5": - version "7.9.5" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.9.5.tgz#6e7c56b44e2ac7011a948c21e283ddd9d9db97a2" - integrity sha512-c4gH3jsvSuGUezlP6rzSJ6jf8fYjLj3hsMZRx/nX0h+fmHN0w+ekubRrHPqnMec0meycA2nwCsJ7dC8IPem2FQ== +"@babel/traverse@7.11.5": + version "7.11.5" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.11.5.tgz#be777b93b518eb6d76ee2e1ea1d143daa11e61c3" + integrity sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ== dependencies: - "@babel/code-frame" "^7.8.3" - "@babel/generator" "^7.9.5" - "@babel/helper-function-name" "^7.9.5" - "@babel/helper-split-export-declaration" "^7.8.3" - "@babel/parser" "^7.9.0" - "@babel/types" "^7.9.5" + "@babel/code-frame" "^7.10.4" + "@babel/generator" "^7.11.5" + "@babel/helper-function-name" "^7.10.4" + "@babel/helper-split-export-declaration" "^7.11.0" + "@babel/parser" "^7.11.5" + "@babel/types" "^7.11.5" debug "^4.1.0" globals "^11.1.0" - lodash "^4.17.13" + lodash "^4.17.19" "@babel/traverse@^7.0.0": version "7.7.4" @@ -1573,13 +1647,13 @@ globals "^11.1.0" lodash "^4.17.13" -"@babel/types@7.9.5": - version "7.9.5" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.9.5.tgz#89231f82915a8a566a703b3b20133f73da6b9444" - integrity sha512-XjnvNqenk818r5zMaba+sLQjnbda31UfUURv3ei0qPQw4u+j2jMyJ5b11y8ZHYTRSI3NnInQkkkRT4fLqqPdHg== +"@babel/types@7.11.5", "@babel/types@^7.10.4", "@babel/types@^7.11.0", "@babel/types@^7.11.5": + version "7.11.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.11.5.tgz#d9de577d01252d77c6800cee039ee64faf75662d" + integrity sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q== dependencies: - "@babel/helper-validator-identifier" "^7.9.5" - lodash "^4.17.13" + "@babel/helper-validator-identifier" "^7.10.4" + lodash "^4.17.19" to-fast-properties "^2.0.0" "@babel/types@^7.0.0", "@babel/types@^7.4.4", "@babel/types@^7.7.4": @@ -1704,33 +1778,38 @@ resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46" integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA== -"@formatjs/intl-displaynames@^1.2.8": - version "1.2.8" - resolved "https://registry.yarnpkg.com/@formatjs/intl-displaynames/-/intl-displaynames-1.2.8.tgz#39dae2fd0bb59fb4b085bf8afe041dff903a269d" - integrity sha512-x/MwUZLgXeDEk0/RXEfGn5c4iZK2UeEHz8gS9yjpoUJ+ybFGr8Jucz45b1UNaABQc57/9vzL91DIPh+5e+OO0w== - dependencies: - "@formatjs/intl-utils" "^2.2.4" +"@formatjs/ecma402-abstract@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@formatjs/ecma402-abstract/-/ecma402-abstract-1.2.0.tgz#5b03ba4931436070ad926d1b2e89bf07edc5ea5b" + integrity sha512-jc1bZHhIE1YI0HnZIZcdlKpF4wle2pkgQpzXHDoyy4bUqzBSvDqktnF26hOkyA04KD4wqd61gkuTvRrHMmroAg== -"@formatjs/intl-listformat@^1.4.6": - version "1.4.6" - resolved "https://registry.yarnpkg.com/@formatjs/intl-listformat/-/intl-listformat-1.4.6.tgz#466931d2498b9650470e07dc482c837d755aa144" - integrity sha512-yOyeTETGxGTjCJPhKVQ+xC7MlyFepNDdobMz+I7hywpCv8kQAw4rdswh1mgA2xeEPF/+NDrBDoXhQaXflKM1hw== +"@formatjs/intl-displaynames@^3.3.6": + version "3.3.6" + resolved "https://registry.yarnpkg.com/@formatjs/intl-displaynames/-/intl-displaynames-3.3.6.tgz#2b5c938ea1cd38e859f2d716ea317feccbbd8896" + integrity sha512-yrTDL3U0MR10vp17noLI2JuNiHq/Fp1P8/mW/t1gCMOpw38FY4bFTOV68FWxSZwzsy/yETqXHjPUTUbpLtEO/Q== dependencies: - "@formatjs/intl-utils" "^2.2.4" + "@formatjs/ecma402-abstract" "^1.2.0" -"@formatjs/intl-numberformat@^4.2.1": - version "4.2.1" - resolved "https://registry.yarnpkg.com/@formatjs/intl-numberformat/-/intl-numberformat-4.2.1.tgz#76abb5e949142331310aa059e305e9ec65e07243" - integrity sha512-UFFCeTno+kCdzvgkjqj7kmupm3KLhWT3DLdsFeiubvkthN00o3CAkSidJCngzwMVuXv40lmcr/TXqgpgxgwKmg== +"@formatjs/intl-listformat@^4.2.5": + version "4.2.5" + resolved "https://registry.yarnpkg.com/@formatjs/intl-listformat/-/intl-listformat-4.2.5.tgz#2a39223c5fda3f865d56cea80d7459f5bd9828a8" + integrity sha512-mcH/CdRH58ao3caZzIdAA32vZM5woxTszIieRjhY2qHxCorVzBPXFYCGTVCO9rtKVFlkMR/pyzaqH3Y1gNiRmw== dependencies: - "@formatjs/intl-utils" "^3.1.0" + "@formatjs/ecma402-abstract" "^1.2.0" -"@formatjs/intl-relativetimeformat@^4.5.14": - version "4.5.14" - resolved "https://registry.yarnpkg.com/@formatjs/intl-relativetimeformat/-/intl-relativetimeformat-4.5.14.tgz#8ec90d536031b0b38446a261e627cca3f4978ae2" - integrity sha512-/lGg37Xqjh3h3yjOhfk67B/4d9MUFK2v56uxC8SlWptpFjsdp9iXX12T9sU9wsGTBCFA0k6gDKUJAqmr6P1YVQ== +"@formatjs/intl-numberformat@^5.6.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@formatjs/intl-numberformat/-/intl-numberformat-5.6.0.tgz#87bd1e56246fba2c7af58f73930cbe379dd0aef8" + integrity sha512-MfYcqX1LE2N4P9eVtQXI/L6APlXgjexCj0b7GxJfK+icrwbA0XINSPGTt96kUxO5hf/tDu0MxJXnt9gwMKm/EA== dependencies: - "@formatjs/intl-utils" "^2.2.4" + "@formatjs/ecma402-abstract" "^1.2.0" + +"@formatjs/intl-relativetimeformat@^7.2.5": + version "7.2.5" + resolved "https://registry.yarnpkg.com/@formatjs/intl-relativetimeformat/-/intl-relativetimeformat-7.2.5.tgz#3101a8262bd7fb329d7bd555135f67a36c5e58df" + integrity sha512-KTf0zTP7YbrVAPPJMnZNYRrNvEwuNwqOVNcfz0cQwewjE2ImxPW+03zdRHkwDt92WbRv6T0EDRBpgC2Dxaip6Q== + dependencies: + "@formatjs/ecma402-abstract" "^1.2.0" "@formatjs/intl-unified-numberformat@^3.3.5": version "3.3.5" @@ -1744,322 +1823,393 @@ resolved "https://registry.yarnpkg.com/@formatjs/intl-utils/-/intl-utils-2.2.4.tgz#fe62a96799d1f7dbe621fd38a4bd2e5a6a16cb0e" integrity sha512-83fsJywew0o9wQsW3VuEp33HRiFd0qbQDyFFnwZCwk59eLZ33CtKyJ5ofKMrU2KK6hk1zaIdzisrZeoNfmI3Tw== -"@formatjs/intl-utils@^3.1.0": - version "3.1.0" - resolved "https://registry.yarnpkg.com/@formatjs/intl-utils/-/intl-utils-3.1.0.tgz#759012e2e444f7fbeb61f5148a8d95202713d82e" - integrity sha512-Q7+ksGpN6Dzp+W4qPai6OcUJ6pka2jPICJ0WlStFdy1RKGm90basrjSmox4xUR9DnMsWE5AfaiSu95nohCTLXw== - -"@fortawesome/fontawesome-common-types@^0.2.28": - version "0.2.28" - resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.28.tgz#1091bdfe63b3f139441e9cba27aa022bff97d8b2" - integrity sha512-gtis2/5yLdfI6n0ia0jH7NJs5i/Z/8M/ZbQL6jXQhCthEOe5Cr5NcQPhgTvFxNOtURE03/ZqUcEskdn2M+QaBg== - -"@fortawesome/fontawesome-svg-core@^1.2.28": - version "1.2.28" - resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.28.tgz#e5b8c8814ef375f01f5d7c132d3c3a2f83a3abf9" - integrity sha512-4LeaNHWvrneoU0i8b5RTOJHKx7E+y7jYejplR7uSVB34+mp3Veg7cbKk7NBCLiI4TyoWS1wh9ZdoyLJR8wSAdg== +"@formatjs/intl@^1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@formatjs/intl/-/intl-1.3.0.tgz#843d79ced6908c2ca25abf65ccee52bc72da6b85" + integrity sha512-wjzzA7CALsYDjDOdpmGGsMYUblp9LcPtxdjjdZyd8s4xQ5lZZUWrJxqzInkax89TWeGTprHGYh31qPpYbjsRRQ== dependencies: - "@fortawesome/fontawesome-common-types" "^0.2.28" + "@formatjs/ecma402-abstract" "^1.2.0" + "@formatjs/intl-displaynames" "^3.3.6" + "@formatjs/intl-listformat" "^4.2.5" + "@formatjs/intl-relativetimeformat" "^7.2.5" + fast-memoize "^2.5.2" + intl-messageformat "^9.3.6" + intl-messageformat-parser "^6.0.5" -"@fortawesome/free-regular-svg-icons@^5.13.0": - version "5.13.0" - resolved "https://registry.yarnpkg.com/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.13.0.tgz#925a13d8bdda0678f71551828cac80ab47b8150c" - integrity sha512-70FAyiS5j+ANYD4dh9NGowTorNDnyvQHHpCM7FpnF7GxtDjBUCKdrFqCPzesEIpNDFNd+La3vex+jDk4nnUfpA== +"@fortawesome/fontawesome-common-types@^0.2.30": + version "0.2.30" + resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.30.tgz#2f1cc5b46bd76723be41d0013a8450c9ba92b777" + integrity sha512-TsRwpTuKwFNiPhk1UfKgw7zNPeV5RhNp2Uw3pws+9gDAkPGKrtjR1y2lI3SYn7+YzyfuNknflpBA1LRKjt7hMg== + +"@fortawesome/fontawesome-svg-core@^1.2.30": + version "1.2.30" + resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.30.tgz#f56dc6791861fe5d1af04fb8abddb94658c576db" + integrity sha512-E3sAXATKCSVnT17HYmZjjbcmwihrNOCkoU7dVMlasrcwiJAHxSKeZ+4WN5O+ElgO/FaYgJmASl8p9N7/B/RttA== dependencies: - "@fortawesome/fontawesome-common-types" "^0.2.28" + "@fortawesome/fontawesome-common-types" "^0.2.30" -"@fortawesome/free-solid-svg-icons@^5.13.0": - version "5.13.0" - resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.13.0.tgz#44d9118668ad96b4fd5c9434a43efc5903525739" - integrity sha512-IHUgDJdomv6YtG4p3zl1B5wWf9ffinHIvebqQOmV3U+3SLw4fC+LUCCgwfETkbTtjy5/Qws2VoVf6z/ETQpFpg== +"@fortawesome/free-regular-svg-icons@^5.14.0": + version "5.14.0" + resolved "https://registry.yarnpkg.com/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.14.0.tgz#ca513ac7699625af42938744297ac483361da043" + integrity sha512-6LCFvjGSMPoUQbn3NVlgiG4CY5iIY8fOm+to/D6QS/GvdqhDt+xZklQeERdCvVRbnFa1ITc1rJHPRXqkX5wztQ== dependencies: - "@fortawesome/fontawesome-common-types" "^0.2.28" + "@fortawesome/fontawesome-common-types" "^0.2.30" -"@fortawesome/react-fontawesome@^0.1.9": - version "0.1.9" - resolved "https://registry.yarnpkg.com/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.9.tgz#c865b9286c707407effcec99958043711367cd02" - integrity sha512-49V3WNysLZU5fZ3sqSuys4nGRytsrxJktbv3vuaXkEoxv22C6T7TEG0TW6+nqVjMnkfCQd5xOnmJoZHMF78tOw== +"@fortawesome/free-solid-svg-icons@^5.14.0": + version "5.14.0" + resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.14.0.tgz#970453f5e8c4915ad57856c3a0252ac63f6fec18" + integrity sha512-M933RDM8cecaKMWDSk3FRYdnzWGW7kBBlGNGfvqLVwcwhUPNj9gcw+xZMrqBdRqxnSXdl3zWzTCNNGEtFUq67Q== + dependencies: + "@fortawesome/fontawesome-common-types" "^0.2.30" + +"@fortawesome/react-fontawesome@^0.1.11": + version "0.1.11" + resolved "https://registry.yarnpkg.com/@fortawesome/react-fontawesome/-/react-fontawesome-0.1.11.tgz#c1a95a2bdb6a18fa97b355a563832e248bf6ef4a" + integrity sha512-sClfojasRifQKI0OPqTy8Ln8iIhnxR/Pv/hukBhWnBz9kQRmqi6JSH3nghlhAY7SUeIIM7B5/D2G8WjX0iepVg== dependencies: prop-types "^15.7.2" -"@graphql-codegen/add@^1.13.5": - version "1.13.5" - resolved "https://registry.yarnpkg.com/@graphql-codegen/add/-/add-1.13.5.tgz#14041a5a7a8765dc885b249d6fadfd23ec97353b" - integrity sha512-zxkqrqYR3cQyrzbX1SMdQQhlyESvvfwZgpSzzui8s8rH90ylzxB9oM3uY6agcYSBUQqigKNaO+1SFtGYMWzYvQ== +"@graphql-codegen/add@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@graphql-codegen/add/-/add-2.0.1.tgz#b2cf2ef0e2c83f49dfa1f6a52fee94dfb47e296f" + integrity sha512-P8+PTHMrNdR/FJn+AQmLtba76H57E5R82Qw8QsntYUYAPUPXOIy4bn3v0rzcrkOnFHanTFjckNcLCF7khEHwJQ== dependencies: - "@graphql-codegen/plugin-helpers" "1.13.5" - tslib "~1.11.1" + "@graphql-codegen/plugin-helpers" "^1.17.8" + tslib "~2.0.0" -"@graphql-codegen/cli@^1.13.5": - version "1.13.5" - resolved "https://registry.yarnpkg.com/@graphql-codegen/cli/-/cli-1.13.5.tgz#578c96dcbee68190b0365634214a25dfe067f4c8" - integrity sha512-/A19GkD9NpFhRNSt6/sVepZ09hwwpQUhjgKthBea0cteH3jFXa/3h1Sgtk/0z90MLxRNRVaY9V85O7SUU21xBA== +"@graphql-codegen/cli@^1.17.8": + version "1.17.8" + resolved "https://registry.yarnpkg.com/@graphql-codegen/cli/-/cli-1.17.8.tgz#06a68894f01796046bb7fa092814948e592e8a75" + integrity sha512-OIOqzdL9kcgO67fBazwpr3Pb+T2l2erYGsO37qJmCrvyqeNO9we+oYrGgVqswkRnfqaqvVWRJ2OmwzvFrXNy/A== dependencies: - "@graphql-codegen/core" "1.13.5" - "@graphql-codegen/plugin-helpers" "1.13.5" - "@graphql-toolkit/apollo-engine-loader" "~0.10.4" - "@graphql-toolkit/code-file-loader" "~0.10.4" - "@graphql-toolkit/common" "~0.10.4" - "@graphql-toolkit/core" "~0.10.4" - "@graphql-toolkit/git-loader" "~0.10.4" - "@graphql-toolkit/github-loader" "~0.10.4" - "@graphql-toolkit/graphql-file-loader" "~0.10.4" - "@graphql-toolkit/json-file-loader" "~0.10.4" - "@graphql-toolkit/prisma-loader" "~0.10.4" - "@graphql-toolkit/url-loader" "~0.10.4" - ansi-escapes "4.3.1" - camel-case "4.1.1" - chalk "4.0.0" - chokidar "3.4.0" - commander "5.1.0" - common-tags "1.8.0" - constant-case "3.0.3" - cosmiconfig "6.0.0" - debounce "1.2.0" - dependency-graph "0.9.0" - detect-indent "6.0.0" - glob "7.1.6" - graphql-config "3.0.1" - indent-string "4.0.0" - inquirer "7.1.0" - is-glob "4.0.1" - json-to-pretty-yaml "1.2.2" - listr "0.14.3" - listr-update-renderer "0.5.0" - log-symbols "4.0.0" - lower-case "2.0.1" - minimatch "3.0.4" - mkdirp "1.0.4" - pascal-case "3.1.1" - request "2.88.2" - ts-log "2.1.4" - tslib "^1.11.1" - upper-case "2.0.1" - valid-url "1.0.9" - wrap-ansi "7.0.0" + "@graphql-codegen/core" "1.17.8" + "@graphql-codegen/plugin-helpers" "^1.17.8" + "@graphql-tools/apollo-engine-loader" "^6.0.18" + "@graphql-tools/code-file-loader" "^6.0.18" + "@graphql-tools/git-loader" "^6.0.18" + "@graphql-tools/github-loader" "^6.0.18" + "@graphql-tools/graphql-file-loader" "^6.0.18" + "@graphql-tools/json-file-loader" "^6.0.18" + "@graphql-tools/load" "^6.0.18" + "@graphql-tools/prisma-loader" "^6.0.18" + "@graphql-tools/url-loader" "^6.0.18" + "@graphql-tools/utils" "^6.0.18" + ansi-escapes "^4.3.1" + camel-case "^4.1.1" + chalk "^4.1.0" + chokidar "^3.4.2" + common-tags "^1.8.0" + constant-case "^3.0.3" + cosmiconfig "^7.0.0" + debounce "^1.2.0" + dependency-graph "^0.9.0" + detect-indent "^6.0.0" + glob "^7.1.6" + graphql-config "^3.0.2" + indent-string "^4.0.0" + inquirer "^7.3.3" + is-glob "^4.0.1" + json-to-pretty-yaml "^1.2.2" + listr "^0.14.3" + listr-update-renderer "^0.5.0" + log-symbols "^4.0.0" + lower-case "^2.0.1" + minimatch "^3.0.4" + mkdirp "^1.0.4" + pascal-case "^3.1.1" + request "^2.88.2" + string-env-interpolation "^1.0.1" + ts-log "^2.1.4" + tslib "~2.0.0" + upper-case "^2.0.1" + valid-url "^1.0.9" + wrap-ansi "^7.0.0" + yargs "^15.4.1" -"@graphql-codegen/core@1.13.5": - version "1.13.5" - resolved "https://registry.yarnpkg.com/@graphql-codegen/core/-/core-1.13.5.tgz#82cdc80757281bd84573ea52ddbf94cb4e893dcc" - integrity sha512-RKkzvCXWfXaNeleHRpp5qWmiwnNCxsc6cVlLmSiZMQad363yOjU2m95oqy4a7WwL6pE/x0NqxtxCFLP7fD+LMQ== +"@graphql-codegen/core@1.17.8": + version "1.17.8" + resolved "https://registry.yarnpkg.com/@graphql-codegen/core/-/core-1.17.8.tgz#d70281bcf9f4b7b560ab5b16f7fc7a2853c49a8a" + integrity sha512-HUntoeLhLZf6wroD1HYLsniz51N3zW7cjgwojGKgbUsI6Oa8pGsh+kKaN9xtvlb/hIpsRJ00q9LbPVIM/kXQtQ== dependencies: - "@graphql-codegen/plugin-helpers" "1.13.5" - "@graphql-toolkit/common" "~0.10.4" - "@graphql-toolkit/schema-merging" "~0.10.4" - tslib "~1.11.1" + "@graphql-codegen/plugin-helpers" "^1.17.8" + "@graphql-tools/merge" "^6.0.18" + "@graphql-tools/utils" "^6.0.18" + tslib "~2.0.0" -"@graphql-codegen/plugin-helpers@1.13.5": - version "1.13.5" - resolved "https://registry.yarnpkg.com/@graphql-codegen/plugin-helpers/-/plugin-helpers-1.13.5.tgz#a4ef9b432b547c82a9c6d55068bdc10c2d39bd74" - integrity sha512-Vq+Qh1K74YHge4uxzekWr4Mf63dFem+RpVsw2w/EmssQLK8WasgIEJ1wQp5nWGwvc5Bj7gImO3+VZRxgMEcrcw== +"@graphql-codegen/plugin-helpers@^1.17.8": + version "1.17.8" + resolved "https://registry.yarnpkg.com/@graphql-codegen/plugin-helpers/-/plugin-helpers-1.17.8.tgz#d47e8bd6b425b38cfc4e2d670b31183b8e9a91e8" + integrity sha512-Ck1E4QC4yrhX5zUtMlc/uloly6TWSpJDA7+aJrdMOY+MEUgrrM7wJsLV9azNae2OmrhZAQopSuGezjHNbsqSdA== dependencies: - "@graphql-toolkit/common" "~0.10.4" + "@graphql-tools/utils" "^6.0.18" camel-case "4.1.1" common-tags "1.8.0" constant-case "3.0.3" import-from "3.0.0" + lodash "~4.17.15" lower-case "2.0.1" param-case "3.0.3" pascal-case "3.1.1" - tslib "~1.11.1" + tslib "~2.0.0" upper-case "2.0.1" -"@graphql-codegen/time@^1.13.5": - version "1.13.5" - resolved "https://registry.yarnpkg.com/@graphql-codegen/time/-/time-1.13.5.tgz#eda8ca54c96e624f477da40e58efcfb65ec958a5" - integrity sha512-ItHfkye5By0EnEyNj7fbqD502dWLrbkwNUTC7dnGIKnz54ey52NslQpO5iEiimSQWhkmFkyWJuggeHG5RSsiaQ== +"@graphql-codegen/time@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@graphql-codegen/time/-/time-2.0.1.tgz#00e22a7e0b507a0854ca153dbd981f4fa96969d9" + integrity sha512-cUABPIm+6xHahQmGbh2vWM3cbMG5QGX4c3waiskpP2Hvc9ANO7OkzT2dZwyz7UKkqPf8p9rtqH2d5HTWr+omQA== dependencies: - "@graphql-codegen/plugin-helpers" "1.13.5" - moment "~2.25.0" + "@graphql-codegen/plugin-helpers" "^1.17.8" + moment "~2.27.0" -"@graphql-codegen/typescript-compatibility@^1.13.5": - version "1.13.5" - resolved "https://registry.yarnpkg.com/@graphql-codegen/typescript-compatibility/-/typescript-compatibility-1.13.5.tgz#fff58c33cf0fdf609f6b82940a88889962b2596d" - integrity sha512-44dlartSVzo9YFcG8VQNJjQ0sl+hk+UDNXkZQ3AAMZnlWOGIzL5JOfNOkDAcOp0zqUAN9c9ptvLUyZUn6vrZnQ== +"@graphql-codegen/typescript-operations@^1.17.8": + version "1.17.8" + resolved "https://registry.yarnpkg.com/@graphql-codegen/typescript-operations/-/typescript-operations-1.17.8.tgz#17c811c7a719df56fec0878f00b2b7449fb45165" + integrity sha512-nU1ZldRB4vGcg4FQhOp3GOOyjfIwO+cI110zZhxQw8SV7pbNDJnCckbvkdEOkW+1/jVJcUul8jQVvuym5olipw== dependencies: - "@graphql-codegen/plugin-helpers" "1.13.5" - "@graphql-codegen/visitor-plugin-common" "1.13.5" - pascal-case "3.1.1" - tslib "~1.11.1" - -"@graphql-codegen/typescript-operations@^1.13.5": - version "1.13.5" - resolved "https://registry.yarnpkg.com/@graphql-codegen/typescript-operations/-/typescript-operations-1.13.5.tgz#842c779cd4d1facface08d937aa2fc37a4529a6f" - integrity sha512-OusFqBo2zUij0w4uLIw8+lWPXdRTWaW1sm56TjJZFwSHMQjZywYir+I0/x/wxZRX2aalE4sMJTy0AQVQx1f+Mg== - dependencies: - "@graphql-codegen/plugin-helpers" "1.13.5" - "@graphql-codegen/typescript" "1.13.5" - "@graphql-codegen/visitor-plugin-common" "1.13.5" + "@graphql-codegen/plugin-helpers" "^1.17.8" + "@graphql-codegen/typescript" "^1.17.8" + "@graphql-codegen/visitor-plugin-common" "^1.17.13" auto-bind "~4.0.0" - tslib "~1.11.1" + tslib "~2.0.0" -"@graphql-codegen/typescript-react-apollo@^1.13.5": - version "1.13.5" - resolved "https://registry.yarnpkg.com/@graphql-codegen/typescript-react-apollo/-/typescript-react-apollo-1.13.5.tgz#04ac91510e9681530388dd5697d4e8f127324ca4" - integrity sha512-omo2wNGd2p/upKd9IsWdU8hOBga3k3rOAVfA/cJK7pUbDW052ZTNcjpyTGoevsv8egUOX7jHl5xrwDTfo67RJQ== +"@graphql-codegen/typescript-react-apollo@^2.0.6": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@graphql-codegen/typescript-react-apollo/-/typescript-react-apollo-2.0.6.tgz#da5a515f3cf9b1438a58ae64d0d96f9c62a0c3c7" + integrity sha512-DAIUp7K2HWNn8h6w2D+R5ZKntZbHVumrT8ZIYNR6kzjdBrjDR2qPf71pSIljv+y47eR6sbQlj5i7ABZcQXPo7Q== dependencies: - "@graphql-codegen/plugin-helpers" "1.13.5" - "@graphql-codegen/visitor-plugin-common" "1.13.5" + "@graphql-codegen/plugin-helpers" "^1.17.8" + "@graphql-codegen/visitor-plugin-common" "^1.17.13" auto-bind "~4.0.0" - camel-case "4.1.1" - pascal-case "3.1.1" - tslib "~1.11.1" + camel-case "^4.1.1" + pascal-case "^3.1.1" + tslib "~2.0.0" -"@graphql-codegen/typescript@1.13.5", "@graphql-codegen/typescript@^1.13.5": - version "1.13.5" - resolved "https://registry.yarnpkg.com/@graphql-codegen/typescript/-/typescript-1.13.5.tgz#c843ad5985410393946c18be1a2b79e80d30ce0e" - integrity sha512-wa9rF7xFaobxzw79Ok40pkjAS2as7nsgm+bRIwyP07YZSOYoa2TOepvAcT4NdZg5+ocmYWIZDpWwtchRHZoT5A== +"@graphql-codegen/typescript@^1.17.8", "@graphql-codegen/typescript@^1.17.9": + version "1.17.9" + resolved "https://registry.yarnpkg.com/@graphql-codegen/typescript/-/typescript-1.17.9.tgz#9f96f39c3b59c13f52db8955fff3aa5889bb7827" + integrity sha512-r6bPSJIeQoMicMEru2pxwAtw5DaY4KCnCd8R2xoQ8aZpkpxqhzd5fmdhh8oXLE4CAZhUCuV0Uad7Ps17Lr5jaw== dependencies: - "@graphql-codegen/plugin-helpers" "1.13.5" - "@graphql-codegen/visitor-plugin-common" "1.13.5" + "@graphql-codegen/plugin-helpers" "^1.17.8" + "@graphql-codegen/visitor-plugin-common" "^1.17.14" auto-bind "~4.0.0" - tslib "~1.11.1" + tslib "~2.0.1" -"@graphql-codegen/visitor-plugin-common@1.13.5": - version "1.13.5" - resolved "https://registry.yarnpkg.com/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-1.13.5.tgz#a39a089843b1050d8193068cd27d89524ef92657" - integrity sha512-NBQBYCGasRKXQK9JW91hjz3CdHK9QjXw7MjfXxkEGcyt7YKtvEKsfCapoUP5BUl7pQFmkqBUacoNg6iVMhtEKg== +"@graphql-codegen/visitor-plugin-common@^1.17.13", "@graphql-codegen/visitor-plugin-common@^1.17.14": + version "1.17.14" + resolved "https://registry.yarnpkg.com/@graphql-codegen/visitor-plugin-common/-/visitor-plugin-common-1.17.14.tgz#dfd15932a2d9f2a0be3ce945206a173609908769" + integrity sha512-tHVkMqQcWuq5xuXm7q8JKcKUVdEYaT61Hnz1OZNgYCBQIOCKUljZcmPuy3F8aJU29XHyZ2vwF2hNG6q4CE3fcQ== dependencies: - "@graphql-codegen/plugin-helpers" "1.13.5" - "@graphql-toolkit/relay-operation-optimizer" "~0.10.4" - array.prototype.flatmap "1.2.3" + "@graphql-codegen/plugin-helpers" "^1.17.8" + "@graphql-tools/relay-operation-optimizer" "^6.0.18" + array.prototype.flatmap "^1.2.3" auto-bind "~4.0.0" - dependency-graph "0.9.0" - graphql-tag "2.10.3" - parse-filepath "1.0.2" - pascal-case "3.1.1" - tslib "~1.11.1" + dependency-graph "^0.9.0" + graphql-tag "^2.11.0" + parse-filepath "^1.0.2" + pascal-case "^3.1.1" + tslib "~2.0.1" -"@graphql-toolkit/apollo-engine-loader@~0.10.4": - version "0.10.6" - resolved "https://registry.yarnpkg.com/@graphql-toolkit/apollo-engine-loader/-/apollo-engine-loader-0.10.6.tgz#febdef73fdff168eeeeac9c902d403ec81055b81" - integrity sha512-/IsLQiUwECkWtFkVpIeGt4OCXXBOzSbhZ7LYFoAph8eqJzjrnOb/L5XIFTj/pC/WPUHx4oC4KJz0/1ybjkEVRg== +"@graphql-tools/apollo-engine-loader@^6.0.18": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@graphql-tools/apollo-engine-loader/-/apollo-engine-loader-6.2.1.tgz#0ac2de546b259f38040ad0703fdbadc09fc413a0" + integrity sha512-OjrwByHVfQ3Im8P9ASifVY6z9ah8Vad+Mei5uW/ABWw/jyJ0jBj0clzxZ//+7p+d7lizJKjJoOukCMzpPTuiEQ== dependencies: - "@graphql-toolkit/common" "0.10.6" - cross-fetch "3.0.4" - tslib "1.11.1" + "@graphql-tools/utils" "6.2.1" + cross-fetch "3.0.5" + tslib "~2.0.1" -"@graphql-toolkit/code-file-loader@~0.10.4": - version "0.10.6" - resolved "https://registry.yarnpkg.com/@graphql-toolkit/code-file-loader/-/code-file-loader-0.10.6.tgz#eadb9dece4850701a7bbdacb7824c19f11e80f29" - integrity sha512-ITkPCURTtJGh0WmMshVQ5bh54AWwOgur8iuAil3Vh14gjuOYxhdOSVQ+L2AVQ22CXclhJLbcm6mhPKjtG7UmBw== +"@graphql-tools/code-file-loader@^6.0.18": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@graphql-tools/code-file-loader/-/code-file-loader-6.2.1.tgz#9873aff6d7367fe17c01d0cd6a66181066a652fb" + integrity sha512-Dvxq3ayVKQfGWWAAZXb+KOBX6WPbcn5NEdIiurwXPyNndxJ3FIei5nIvENqSUTnQvLSz1NXLDw1lQyQEsfabZw== dependencies: - "@graphql-toolkit/common" "0.10.6" - "@graphql-toolkit/graphql-tag-pluck" "0.10.6" - tslib "1.11.1" + "@graphql-tools/graphql-tag-pluck" "6.2.1" + "@graphql-tools/utils" "6.2.1" + fs-extra "9.0.1" + tslib "~2.0.1" -"@graphql-toolkit/common@0.10.6", "@graphql-toolkit/common@~0.10.4", "@graphql-toolkit/common@~0.10.6": - version "0.10.6" - resolved "https://registry.yarnpkg.com/@graphql-toolkit/common/-/common-0.10.6.tgz#43591fd3478ab27ec95bf39d5a8afdd17f0ac2fd" - integrity sha512-rrH/KPheh/wCZzqUmNayBHd+aNWl/751C4iTL/327TzONdAVrV7ZQOyEkpGLW6YEFWPIlWxNkaBoEALIjCxTGg== +"@graphql-tools/delegate@6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@graphql-tools/delegate/-/delegate-6.2.1.tgz#97847e6d236e55a70de8fb7ece9eb615c57fbc6b" + integrity sha512-IsmrZ+aiT00V567PfawxvdjwMf8RrfjSuyOy7LvqJ6ABUlpcjZejYMFIl82ZjHf8m3OU1xF5Wier8G6XFYzPYw== dependencies: - aggregate-error "3.0.1" - camel-case "4.1.1" - graphql-tools "5.0.0" - lodash "4.17.15" + "@ardatan/aggregate-error" "0.0.6" + "@graphql-tools/schema" "6.2.1" + "@graphql-tools/utils" "6.2.1" + dataloader "2.0.0" + is-promise "4.0.0" + tslib "~2.0.1" -"@graphql-toolkit/core@~0.10.4", "@graphql-toolkit/core@~0.10.6": - version "0.10.6" - resolved "https://registry.yarnpkg.com/@graphql-toolkit/core/-/core-0.10.6.tgz#bb2d6bb2954f47bac7ed7709f317921ecd61b459" - integrity sha512-dUgYTmyIZH+rBVacjPgqO+7qCG5b6pD8niHVghX2h4UAMEApx2o/2TAsSsAMFlqrMA/haW1UIMsmKPw8Yj19ZA== +"@graphql-tools/git-loader@^6.0.18": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@graphql-tools/git-loader/-/git-loader-6.2.1.tgz#0dc1843ea1fa249f95f3e87e8a3074fa0c6af9c5" + integrity sha512-yPMvnvn5mahfMMFWjfDGiuOYtqhIhchnOE7RaxoI6pbYix2KWbGrBZufUW6lhr+JeuMG3bjz1oiO4XJUEu6oZw== dependencies: - "@graphql-toolkit/common" "0.10.6" - "@graphql-toolkit/schema-merging" "0.10.6" - aggregate-error "3.0.1" - globby "11.0.0" - import-from "^3.0.0" - is-glob "4.0.1" - lodash "4.17.15" - p-limit "2.3.0" + "@graphql-tools/graphql-tag-pluck" "6.2.1" + "@graphql-tools/utils" "6.2.1" + tslib "~2.0.1" + +"@graphql-tools/github-loader@^6.0.18": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@graphql-tools/github-loader/-/github-loader-6.2.1.tgz#c839d8588f0f769f8f4073f9ffe32b933374f11e" + integrity sha512-/IEBiHiHPFcBYAlTueT2dvyS3xOYlaVt7NLJrSQYUHt9Gitg/ItarEGywB2jKXlYX4ebyRWi8SRn/72BuR6XhA== + dependencies: + "@graphql-tools/graphql-tag-pluck" "6.2.1" + "@graphql-tools/utils" "6.2.1" + cross-fetch "3.0.5" + tslib "~2.0.1" + +"@graphql-tools/graphql-file-loader@^6.0.0", "@graphql-tools/graphql-file-loader@^6.0.18": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@graphql-tools/graphql-file-loader/-/graphql-file-loader-6.2.1.tgz#a5025a50fad233922bd36afde4bce36f57e1ca67" + integrity sha512-HfM6kC48KTDhVizeuJVOmh+nJD8s1SefK8aZgFBvnXVWW+HdPx2+T5MH6sSVvqgMrlCxLxuyuwcPIcXqpvYvxA== + dependencies: + "@graphql-tools/import" "6.2.1" + "@graphql-tools/utils" "6.2.1" + fs-extra "9.0.1" + tslib "~2.0.1" + +"@graphql-tools/graphql-tag-pluck@6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@graphql-tools/graphql-tag-pluck/-/graphql-tag-pluck-6.2.1.tgz#95c0e9afd854575cb2169c30f081ab2190011aa8" + integrity sha512-rgdoJx8jfHiMFVR6O6Xb0xeoMal+p7c+TXli5YIjUdgY/jJnQi589qOs394YY3JeB0xDPm/s8hU+leXm25Mxcg== + dependencies: + "@babel/parser" "7.11.5" + "@babel/traverse" "7.11.5" + "@babel/types" "7.11.5" + "@graphql-tools/utils" "6.2.1" + tslib "~2.0.1" + optionalDependencies: + vue-template-compiler "^2.6.12" + +"@graphql-tools/import@6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@graphql-tools/import/-/import-6.2.1.tgz#e5c197f9726d0cdf98ad3b4d1c9af36e4198a11e" + integrity sha512-10mMCB8x4s0P010n8HHiUExg5m2NMr3Hm30WRSoP3P0m8Q0l8AmwXVwHUtykEaWhu+4xvFZ5Hl3fS+Wm0f7jdg== + dependencies: + fs-extra "9.0.1" resolve-from "5.0.0" - tslib "1.11.1" + tslib "~2.0.1" + +"@graphql-tools/json-file-loader@^6.0.0", "@graphql-tools/json-file-loader@^6.0.18": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@graphql-tools/json-file-loader/-/json-file-loader-6.2.1.tgz#1afe79b9549d31dd29f784490a702d5d5ff2fc12" + integrity sha512-WQ4qIl8dBOg6fLh7GZZ2YzSTelBiTPZlohVCBJ8iuqzS1Tryty6c/FAbMBfscNlQ4nb4y3zb4LR0F0pbq2urcQ== + dependencies: + "@graphql-tools/utils" "6.2.1" + fs-extra "9.0.1" + tslib "~2.0.1" + +"@graphql-tools/load@^6.0.0", "@graphql-tools/load@^6.0.18": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@graphql-tools/load/-/load-6.2.1.tgz#c5f3e1dd21b9f80b42184f1c89dfdcf41fa40d14" + integrity sha512-PkM2xv/bBFQIkmgCX0LsZnHBuJY5YhDrpsljdD+0mXw2b9w0zUG2qIqBoMBLfG3ncjduE9Hwl1oKSBhFUPJmyA== + dependencies: + "@graphql-tools/merge" "6.2.1" + "@graphql-tools/utils" "6.2.1" + globby "11.0.1" + import-from "3.0.0" + is-glob "4.0.1" + p-limit "3.0.2" + tslib "~2.0.1" unixify "1.0.0" valid-url "1.0.9" -"@graphql-toolkit/git-loader@~0.10.4": - version "0.10.6" - resolved "https://registry.yarnpkg.com/@graphql-toolkit/git-loader/-/git-loader-0.10.6.tgz#eb9cf669a4bb58f8a28a177237d5b99a1c749046" - integrity sha512-n8ZeFTI9PFakLg4++okI9wdPbBUqnnvNsRPqTozdwgO+97kls0fvhe6Cp45dKLG4xVPPTDdqPazQbmjOQtW32g== +"@graphql-tools/merge@6.2.1", "@graphql-tools/merge@^6.0.0", "@graphql-tools/merge@^6.0.18": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@graphql-tools/merge/-/merge-6.2.1.tgz#edce28a343776ea580e7feceb3b6557c99ce9cfe" + integrity sha512-GJyMOYM2JyZhvMlVwpUZUv5ocvWCh+/KdPCCzXqo3Va/kOO1ntm8lKGNf3JZpQDgpC5FvaQfsu/b5pE7TBFxyw== dependencies: - "@graphql-toolkit/common" "0.10.6" - "@graphql-toolkit/graphql-tag-pluck" "0.10.6" - simple-git "1.132.0" + "@graphql-tools/schema" "6.2.1" + "@graphql-tools/utils" "6.2.1" + tslib "~2.0.1" -"@graphql-toolkit/github-loader@~0.10.4": - version "0.10.6" - resolved "https://registry.yarnpkg.com/@graphql-toolkit/github-loader/-/github-loader-0.10.6.tgz#a1f4b4ffd6574d19f85d01f11f60b314e83e56c2" - integrity sha512-fwwclgMmpcvUQqWCX4kh99N0R3Qjs5kXN6Fcz5IJQSI4ExNlAJk0xG1+8ipYp35oF5+1dc+dMezgLT/kMpRUOA== +"@graphql-tools/prisma-loader@^6.0.18": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@graphql-tools/prisma-loader/-/prisma-loader-6.2.1.tgz#320984025f374026565cae02aee3c2814b6a820c" + integrity sha512-dXfT33cusZNqq45gnYSBxCY50ZUXkO3KJji/oQhOuAYN2Qu5EwRQbCV8FhN4vhwD4+E6Z0yNCqk0YjDifFoZEg== dependencies: - "@graphql-toolkit/common" "0.10.6" - "@graphql-toolkit/graphql-tag-pluck" "0.10.6" - cross-fetch "3.0.4" + "@graphql-tools/url-loader" "6.2.1" + "@graphql-tools/utils" "6.2.1" + "@types/http-proxy-agent" "^2.0.2" + "@types/js-yaml" "^3.12.5" + "@types/json-stable-stringify" "^1.0.32" + "@types/jsonwebtoken" "^8.5.0" + ajv "^6.12.4" + bluebird "^3.7.2" + chalk "^4.1.0" + debug "^4.1.1" + dotenv "^8.2.0" + fs-extra "9.0.1" + graphql-request "^3.0.0" + http-proxy-agent "^4.0.1" + https-proxy-agent "^5.0.0" + isomorphic-fetch "^2.2.1" + js-yaml "^3.14.0" + json-stable-stringify "^1.0.1" + jsonwebtoken "^8.5.1" + lodash "^4.17.20" + replaceall "^0.1.6" + scuid "^1.1.0" + tslib "~2.0.1" + yaml-ast-parser "^0.0.43" -"@graphql-toolkit/graphql-file-loader@~0.10.4", "@graphql-toolkit/graphql-file-loader@~0.10.6": - version "0.10.6" - resolved "https://registry.yarnpkg.com/@graphql-toolkit/graphql-file-loader/-/graphql-file-loader-0.10.6.tgz#2b90b846ef2ca9d8dcb233fff86d714e4e9fefbc" - integrity sha512-D5GutvfUccIFX5Cx/blvrHnt8fXxQ9gM51cgTyGV+6dL2VdCrmOJucEWG7+ki5baCAB78/OyhtP+/tmKNQVPKQ== +"@graphql-tools/relay-operation-optimizer@^6.0.18": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@graphql-tools/relay-operation-optimizer/-/relay-operation-optimizer-6.2.1.tgz#0942273b1ed1e0c09f09fea2108a5a1c57518758" + integrity sha512-AkL2KsGHYQYi4QIVoAOflYR4twhZBz1xWBVo099jdlfqQxO8nxqr4IV1yIx0iT6VOu4DgawVqBXnBa7yH6fc2A== dependencies: - "@graphql-toolkit/common" "0.10.6" - tslib "1.11.1" + "@graphql-tools/utils" "6.2.1" + relay-compiler "10.0.1" + tslib "~2.0.1" -"@graphql-toolkit/graphql-tag-pluck@0.10.6": - version "0.10.6" - resolved "https://registry.yarnpkg.com/@graphql-toolkit/graphql-tag-pluck/-/graphql-tag-pluck-0.10.6.tgz#8285637a1c1dfa42a71ed168683317c7e265e703" - integrity sha512-LZpDGZpsRHlK6fyDVWAC7Bn82RBKrjwrSfi1UTL5uIXyZd1t7YbF3MwvTYMJ+bbJQv21D/vHhXeCDwiWTDaYZw== +"@graphql-tools/schema@6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@graphql-tools/schema/-/schema-6.2.1.tgz#6b584defa37d6649180491ae32e1bb737b1d33e5" + integrity sha512-SKoZmxlLwOq08/NZ8Y5aShgEHneTJ8Ksw4gU1WBSWVJGf64ROoxIxN4Uc47F0cZxwOBqVqUy/EdXWZ8Jt97uQQ== dependencies: - "@babel/parser" "7.9.4" - "@babel/traverse" "7.9.5" - "@babel/types" "7.9.5" - "@graphql-toolkit/common" "0.10.6" - optionalDependencies: - vue-template-compiler "^2.6.11" + "@graphql-tools/utils" "6.2.1" + tslib "~2.0.1" -"@graphql-toolkit/json-file-loader@~0.10.4", "@graphql-toolkit/json-file-loader@~0.10.6": - version "0.10.6" - resolved "https://registry.yarnpkg.com/@graphql-toolkit/json-file-loader/-/json-file-loader-0.10.6.tgz#1395f25daadf6ce40de5bb0eeb27eca493eaac1e" - integrity sha512-gTf3gWtc4ZH1OFLl79BRHX0DsjecV0xDxKLKFpGYx22ay72iJqddKFKXxRHeGWFjIrNIi90RUyKlKY8uGyYw2w== +"@graphql-tools/url-loader@6.2.1", "@graphql-tools/url-loader@^6.0.0", "@graphql-tools/url-loader@^6.0.18": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@graphql-tools/url-loader/-/url-loader-6.2.1.tgz#9265dc6578263a79d386ea307bbb733740c9a0d2" + integrity sha512-6NTibFENUn02oI53VhW2Dcas0QdHYDyqRD5yxk0D+rQUP558t4eoYLJ8/hhdqbJ5t+2/lBRkzVU/5oJkZQosMQ== dependencies: - "@graphql-toolkit/common" "0.10.6" - tslib "1.11.1" - -"@graphql-toolkit/prisma-loader@~0.10.4": - version "0.10.6" - resolved "https://registry.yarnpkg.com/@graphql-toolkit/prisma-loader/-/prisma-loader-0.10.6.tgz#b14dc5bfc3149dad33fded0bacf0518bd92cc871" - integrity sha512-vjY5fHn0048Kedhj9DAe/z4+u8VKX6OSLtVQglWYPaxh3Plytv+EPQCsN7AMyjuaNkfQJYBZvTZOL4V/lyK0YQ== - dependencies: - "@graphql-toolkit/common" "0.10.6" - "@graphql-toolkit/url-loader" "0.10.6" - prisma-yml "1.34.10" - tslib "1.11.1" - -"@graphql-toolkit/relay-operation-optimizer@~0.10.4": - version "0.10.6" - resolved "https://registry.yarnpkg.com/@graphql-toolkit/relay-operation-optimizer/-/relay-operation-optimizer-0.10.6.tgz#f40e422afdf84d992f2280620ee023579132b0ca" - integrity sha512-cbaVFJQc6xPRKBwz139RxwQgzrqEw0ggL+0Y7HkyjyiBScq+CzfqYjgwhaLYPGJiACwwjq6X6D6ESDtTS34HXQ== - dependencies: - "@graphql-toolkit/common" "0.10.6" - relay-compiler "9.1.0" - -"@graphql-toolkit/schema-merging@0.10.6", "@graphql-toolkit/schema-merging@~0.10.4", "@graphql-toolkit/schema-merging@~0.10.6": - version "0.10.6" - resolved "https://registry.yarnpkg.com/@graphql-toolkit/schema-merging/-/schema-merging-0.10.6.tgz#9f57a349621a4377a3431a0320221d9aa6a9d982" - integrity sha512-BNABgYaNCw4Li3EiH/x7oDpkN+ml3M0SWqjnsW1Pf2NcyfGlv033Bda+O/q4XYtseZ0OOOh52GLXtUgwyPFb8A== - dependencies: - "@graphql-toolkit/common" "0.10.6" - deepmerge "4.2.2" - graphql-tools "5.0.0" - tslib "1.11.1" - -"@graphql-toolkit/url-loader@0.10.6", "@graphql-toolkit/url-loader@~0.10.4", "@graphql-toolkit/url-loader@~0.10.6": - version "0.10.6" - resolved "https://registry.yarnpkg.com/@graphql-toolkit/url-loader/-/url-loader-0.10.6.tgz#6ea14e2cb821697e133e402a1a5063d318bc237a" - integrity sha512-Ts8h4zfcOKSp9TNk6uOvmkwDofXUr5cspxO2cpmd0zLnxceHYhe0D2Q/Bq1SPw3NGN2AqvAr7zD8T2z5NDVUKQ== - dependencies: - "@graphql-toolkit/common" "0.10.6" - cross-fetch "3.0.4" - graphql-tools "5.0.0" - tslib "1.11.1" + "@graphql-tools/delegate" "6.2.1" + "@graphql-tools/utils" "6.2.1" + "@graphql-tools/wrap" "6.2.1" + "@types/websocket" "1.0.1" + cross-fetch "3.0.5" + subscriptions-transport-ws "0.9.18" + tslib "~2.0.1" valid-url "1.0.9" + websocket "1.0.32" + +"@graphql-tools/utils@6.2.1", "@graphql-tools/utils@^6.0.0", "@graphql-tools/utils@^6.0.18": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@graphql-tools/utils/-/utils-6.2.1.tgz#07774dbcc7657d9f9b898721f10fc613ce116b56" + integrity sha512-DZ6a2bjOH4sWKhNUachvYy+3ocXDvDcTtComOD/z7ncszdlZPU6RXNOgBTxh/bMVHBPqlEh/VjCVMwBysZRbJw== + dependencies: + "@ardatan/aggregate-error" "0.0.6" + camel-case "4.1.1" + tslib "~2.0.1" + +"@graphql-tools/wrap@6.2.1": + version "6.2.1" + resolved "https://registry.yarnpkg.com/@graphql-tools/wrap/-/wrap-6.2.1.tgz#4df858924a95fa90861a712a7578d94d8dcdd07e" + integrity sha512-bjmN2Xh3haRp5tMDNPpUDV/9IlvFfmG9umsAOb3WFRJBoVi/dX0YwWIyucM3WBVix4ory8Op5eT4KkoKeaFSMw== + dependencies: + "@graphql-tools/delegate" "6.2.1" + "@graphql-tools/schema" "6.2.1" + "@graphql-tools/utils" "6.2.1" + is-promise "4.0.0" + tslib "~2.0.1" + +"@graphql-typed-document-node/core@^3.0.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.1.0.tgz#0eee6373e11418bfe0b5638f654df7a4ca6a3950" + integrity sha512-wYn6r8zVZyQJ6rQaALBEln5B1pzxb9shV5Ef97kTvn6yVGrqyXVnDqnU24MXnFubR+rZjBY9NWuxX3FB2sTsjg== "@hapi/address@2.x.x": version "2.1.4" @@ -2241,22 +2391,22 @@ "@types/istanbul-reports" "^1.1.1" "@types/yargs" "^13.0.0" -"@jimp/bmp@^0.12.1": - version "0.12.1" - resolved "https://registry.yarnpkg.com/@jimp/bmp/-/bmp-0.12.1.tgz#43cf1f711797c029aa7570a492769b4778638da2" - integrity sha512-t16IamuBMv4GiGa1VAMzsgrVKVANxXG81wXECzbikOUkUv7pKJ2vHZDgkLBEsZQ9sAvFCneM1+yoSRpuENrfVQ== +"@jimp/bmp@^0.16.1": + version "0.16.1" + resolved "https://registry.yarnpkg.com/@jimp/bmp/-/bmp-0.16.1.tgz#6e2da655b2ba22e721df0795423f34e92ef13768" + integrity sha512-iwyNYQeBawrdg/f24x3pQ5rEx+/GwjZcCXd3Kgc+ZUd+Ivia7sIqBsOnDaMZdKCBPlfW364ekexnlOqyVa0NWg== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.12.1" + "@jimp/utils" "^0.16.1" bmp-js "^0.1.0" -"@jimp/core@^0.12.1": - version "0.12.1" - resolved "https://registry.yarnpkg.com/@jimp/core/-/core-0.12.1.tgz#a46341e5476e00115b1fab399627d65f9ab2d442" - integrity sha512-mWfjExYEjHxBal+1gPesGChOQBSpxO7WUQkrO9KM7orboitOdQ15G5UA75ce7XVZ+5t+FQPOLmVkVZzzTQSEJA== +"@jimp/core@^0.16.1": + version "0.16.1" + resolved "https://registry.yarnpkg.com/@jimp/core/-/core-0.16.1.tgz#68c4288f6ef7f31a0f6b859ba3fb28dae930d39d" + integrity sha512-la7kQia31V6kQ4q1kI/uLimu8FXx7imWVajDGtwUG8fzePLWDFJyZl0fdIXVCL1JW2nBcRHidUot6jvlRDi2+g== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.12.1" + "@jimp/utils" "^0.16.1" any-base "^1.1.0" buffer "^5.2.0" exif-parser "^0.1.12" @@ -2267,265 +2417,266 @@ pixelmatch "^4.0.2" tinycolor2 "^1.4.1" -"@jimp/custom@^0.12.1": - version "0.12.1" - resolved "https://registry.yarnpkg.com/@jimp/custom/-/custom-0.12.1.tgz#e54d0fb2c29f4eb3b5b0bd00dc4cd25a78f48af4" - integrity sha512-bVClp8FEJ/11GFTKeRTrfH7NgUWvVO5/tQzO/68aOwMIhbz9BOYQGh533K9+mSy29VjZJo8jxZ0C9ZwYHuFwfA== +"@jimp/custom@^0.16.1": + version "0.16.1" + resolved "https://registry.yarnpkg.com/@jimp/custom/-/custom-0.16.1.tgz#28b659c59e20a1d75a0c46067bd3f4bd302cf9c5" + integrity sha512-DNUAHNSiUI/j9hmbatD6WN/EBIyeq4AO0frl5ETtt51VN1SvE4t4v83ZA/V6ikxEf3hxLju4tQ5Pc3zmZkN/3A== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/core" "^0.12.1" + "@jimp/core" "^0.16.1" -"@jimp/gif@^0.12.1": - version "0.12.1" - resolved "https://registry.yarnpkg.com/@jimp/gif/-/gif-0.12.1.tgz#e5fe9e25796ef6390044b9f1a595e2ef2ebe9fe8" - integrity sha512-cGn/AcvMGUGcqR6ByClGSnrja4AYmTwsGVXTQ1+EmfAdTiy6ztGgZCTDpZ/tq4SpdHXwm9wDHez7damKhTrH0g== +"@jimp/gif@^0.16.1": + version "0.16.1" + resolved "https://registry.yarnpkg.com/@jimp/gif/-/gif-0.16.1.tgz#d1f7c3a58f4666482750933af8b8f4666414f3ca" + integrity sha512-r/1+GzIW1D5zrP4tNrfW+3y4vqD935WBXSc8X/wm23QTY9aJO9Lw6PEdzpYCEY+SOklIFKaJYUAq/Nvgm/9ryw== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.12.1" + "@jimp/utils" "^0.16.1" + gifwrap "^0.9.2" omggif "^1.0.9" -"@jimp/jpeg@^0.12.1": - version "0.12.1" - resolved "https://registry.yarnpkg.com/@jimp/jpeg/-/jpeg-0.12.1.tgz#adaacd30d819241cdddc978dc4facc882a0846ab" - integrity sha512-UoCUHbKLj2CDCETd7LrJnmK/ExDsSfJXmc1pKkfgomvepjXogdl2KTHf141wL6D+9CfSD2VBWQLC5TvjMvcr9A== +"@jimp/jpeg@^0.16.1": + version "0.16.1" + resolved "https://registry.yarnpkg.com/@jimp/jpeg/-/jpeg-0.16.1.tgz#3b7bb08a4173f2f6d81f3049b251df3ee2ac8175" + integrity sha512-8352zrdlCCLFdZ/J+JjBslDvml+fS3Z8gttdml0We759PnnZGqrnPRhkOEOJbNUlE+dD4ckLeIe6NPxlS/7U+w== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.12.1" - jpeg-js "^0.4.0" + "@jimp/utils" "^0.16.1" + jpeg-js "0.4.2" -"@jimp/plugin-blit@^0.12.1": - version "0.12.1" - resolved "https://registry.yarnpkg.com/@jimp/plugin-blit/-/plugin-blit-0.12.1.tgz#555a492fd71370820b7a1b85cc04ba3c58b0c4c7" - integrity sha512-VRBB6bx6EpQuaH0WX8ytlGNqUQcmuxXBbzL3e+cD0W6MluYibzQy089okvXcyUS72Q+qpSMmUDCVr3pDqLAsSA== +"@jimp/plugin-blit@^0.16.1": + version "0.16.1" + resolved "https://registry.yarnpkg.com/@jimp/plugin-blit/-/plugin-blit-0.16.1.tgz#09ea919f9d326de3b9c2826fe4155da37dde8edb" + integrity sha512-fKFNARm32RoLSokJ8WZXHHH2CGzz6ire2n1Jh6u+XQLhk9TweT1DcLHIXwQMh8oR12KgjbgsMGvrMVlVknmOAg== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.12.1" + "@jimp/utils" "^0.16.1" -"@jimp/plugin-blur@^0.12.1": - version "0.12.1" - resolved "https://registry.yarnpkg.com/@jimp/plugin-blur/-/plugin-blur-0.12.1.tgz#93cf1b6c44e4c7bbb80914ef953c8bb3dac31295" - integrity sha512-rTFY0yrwVJFNgNsAlYGn2GYCRLVEcPQ6cqAuhNylXuR/7oH3Acul+ZWafeKtvN8D8uMlth/6VP74gruXvwffZw== +"@jimp/plugin-blur@^0.16.1": + version "0.16.1" + resolved "https://registry.yarnpkg.com/@jimp/plugin-blur/-/plugin-blur-0.16.1.tgz#e614fa002797dcd662e705d4cea376e7db968bf5" + integrity sha512-1WhuLGGj9MypFKRcPvmW45ht7nXkOKu+lg3n2VBzIB7r4kKNVchuI59bXaCYQumOLEqVK7JdB4glaDAbCQCLyw== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.12.1" + "@jimp/utils" "^0.16.1" -"@jimp/plugin-circle@^0.12.1": - version "0.12.1" - resolved "https://registry.yarnpkg.com/@jimp/plugin-circle/-/plugin-circle-0.12.1.tgz#56100e5b04c98b711e2c2188d0825c0e1766be69" - integrity sha512-+/OiBDjby7RBbQoDX8ZsqJRr1PaGPdTaaKUVGAsrE7KCNO9ODYNFAizB9lpidXkGgJ4Wx5R4mJy21i22oY/a4Q== +"@jimp/plugin-circle@^0.16.1": + version "0.16.1" + resolved "https://registry.yarnpkg.com/@jimp/plugin-circle/-/plugin-circle-0.16.1.tgz#20e3194a67ca29740aba2630fd4d0a89afa27491" + integrity sha512-JK7yi1CIU7/XL8hdahjcbGA3V7c+F+Iw+mhMQhLEi7Q0tCnZ69YJBTamMiNg3fWPVfMuvWJJKOBRVpwNTuaZRg== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.12.1" + "@jimp/utils" "^0.16.1" -"@jimp/plugin-color@^0.12.1": - version "0.12.1" - resolved "https://registry.yarnpkg.com/@jimp/plugin-color/-/plugin-color-0.12.1.tgz#193f4d851c29b5d393843b68385eee3d13b7ea7e" - integrity sha512-xlnK/msWN4uZ+Bu7+UrCs9oMzTSA9QE0jWFnF3h0aBsD8t1LGxozkckHe8nHtC/y/sxIa8BGKSfkiaW+r6FbnA== +"@jimp/plugin-color@^0.16.1": + version "0.16.1" + resolved "https://registry.yarnpkg.com/@jimp/plugin-color/-/plugin-color-0.16.1.tgz#0f298ba74dee818b663834cd80d53e56f3755233" + integrity sha512-9yQttBAO5SEFj7S6nJK54f+1BnuBG4c28q+iyzm1JjtnehjqMg6Ljw4gCSDCvoCQ3jBSYHN66pmwTV74SU1B7A== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.12.1" + "@jimp/utils" "^0.16.1" tinycolor2 "^1.4.1" -"@jimp/plugin-contain@^0.12.1": - version "0.12.1" - resolved "https://registry.yarnpkg.com/@jimp/plugin-contain/-/plugin-contain-0.12.1.tgz#6dffe0632e5acbc5d5d17671910f6671a4849c5a" - integrity sha512-WZ/D6G0jhnBh2bkBh610PEh/caGhAUIAxYLsQsfSSlOxPsDhbj3S6hMbFKRgnDvf0hsd5zTIA0j1B0UG4kh18A== +"@jimp/plugin-contain@^0.16.1": + version "0.16.1" + resolved "https://registry.yarnpkg.com/@jimp/plugin-contain/-/plugin-contain-0.16.1.tgz#3c5f5c495fd9bb08a970739d83694934f58123f2" + integrity sha512-44F3dUIjBDHN+Ym/vEfg+jtjMjAqd2uw9nssN67/n4FdpuZUVs7E7wadKY1RRNuJO+WgcD5aDQcsvurXMETQTg== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.12.1" + "@jimp/utils" "^0.16.1" -"@jimp/plugin-cover@^0.12.1": - version "0.12.1" - resolved "https://registry.yarnpkg.com/@jimp/plugin-cover/-/plugin-cover-0.12.1.tgz#c0e9005d891efbaa6533ca4d6874d3e14cc51179" - integrity sha512-ddWwTQO40GcabJ2UwUYCeuNxnjV4rBTiLprnjGMqAJCzdz3q3Sp20FkRf+H+E22k2v2LHss8dIOFOF4i6ycr9Q== +"@jimp/plugin-cover@^0.16.1": + version "0.16.1" + resolved "https://registry.yarnpkg.com/@jimp/plugin-cover/-/plugin-cover-0.16.1.tgz#0e8caec16a40abe15b1b32e5383a603a3306dc41" + integrity sha512-YztWCIldBAVo0zxcQXR+a/uk3/TtYnpKU2CanOPJ7baIuDlWPsG+YE4xTsswZZc12H9Kl7CiziEbDtvF9kwA/Q== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.12.1" + "@jimp/utils" "^0.16.1" -"@jimp/plugin-crop@^0.12.1": - version "0.12.1" - resolved "https://registry.yarnpkg.com/@jimp/plugin-crop/-/plugin-crop-0.12.1.tgz#44a5adb5f5222c3d3c6c94410b1995fe88041ada" - integrity sha512-CKjVkrNO8FDZKYVpMireQW4SgKBSOdF+Ip/1sWssHHe77+jGEKqOjhYju+VhT3dZJ3+75rJNI9II7Kethp+rTw== +"@jimp/plugin-crop@^0.16.1": + version "0.16.1" + resolved "https://registry.yarnpkg.com/@jimp/plugin-crop/-/plugin-crop-0.16.1.tgz#b362497c873043fe47ba881ab08604bf7226f50f" + integrity sha512-UQdva9oQzCVadkyo3T5Tv2CUZbf0klm2cD4cWMlASuTOYgaGaFHhT9st+kmfvXjKL8q3STkBu/zUPV6PbuV3ew== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.12.1" + "@jimp/utils" "^0.16.1" -"@jimp/plugin-displace@^0.12.1": - version "0.12.1" - resolved "https://registry.yarnpkg.com/@jimp/plugin-displace/-/plugin-displace-0.12.1.tgz#d83b5d4d45a35b5d7b7722ec8657d46a3ccc6da1" - integrity sha512-MQAw2iuf1/bVJ6P95WWTLA+WBjvIZ7TeGBerkvBaTK8oWdj+NSLNRIYOIoyPbZ7DTL8f1SN4Vd6KD6BZaoWrwg== +"@jimp/plugin-displace@^0.16.1": + version "0.16.1" + resolved "https://registry.yarnpkg.com/@jimp/plugin-displace/-/plugin-displace-0.16.1.tgz#4dd9db518c3e78de9d723f86a234bf98922afe8d" + integrity sha512-iVAWuz2+G6Heu8gVZksUz+4hQYpR4R0R/RtBzpWEl8ItBe7O6QjORAkhxzg+WdYLL2A/Yd4ekTpvK0/qW8hTVw== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.12.1" + "@jimp/utils" "^0.16.1" -"@jimp/plugin-dither@^0.12.1": - version "0.12.1" - resolved "https://registry.yarnpkg.com/@jimp/plugin-dither/-/plugin-dither-0.12.1.tgz#1265a063423a20b9425f5055fe3ddafaa0eea9fe" - integrity sha512-mCrBHdx2ViTLJDLcrobqGLlGhZF/Mq41bURWlElQ2ArvrQ3/xR52We9DNDfC08oQ2JVb6q3v1GnCCdn0KNojGQ== +"@jimp/plugin-dither@^0.16.1": + version "0.16.1" + resolved "https://registry.yarnpkg.com/@jimp/plugin-dither/-/plugin-dither-0.16.1.tgz#b47de2c0bb09608bed228b41c3cd01a85ec2d45b" + integrity sha512-tADKVd+HDC9EhJRUDwMvzBXPz4GLoU6s5P7xkVq46tskExYSptgj5713J5Thj3NMgH9Rsqu22jNg1H/7tr3V9Q== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.12.1" + "@jimp/utils" "^0.16.1" -"@jimp/plugin-fisheye@^0.12.1": - version "0.12.1" - resolved "https://registry.yarnpkg.com/@jimp/plugin-fisheye/-/plugin-fisheye-0.12.1.tgz#0afa268abbfcc88212f49b2b84b04da89f35cae2" - integrity sha512-CHvYSXtHNplzkkYzB44tENPDmvfUHiYCnAETTY+Hx58kZ0w8ERZ+OiLhUmiBcvH/QHm/US1iiNjgGUAfeQX6dg== +"@jimp/plugin-fisheye@^0.16.1": + version "0.16.1" + resolved "https://registry.yarnpkg.com/@jimp/plugin-fisheye/-/plugin-fisheye-0.16.1.tgz#f625047b6cdbe1b83b89e9030fd025ab19cdb1a4" + integrity sha512-BWHnc5hVobviTyIRHhIy9VxI1ACf4CeSuCfURB6JZm87YuyvgQh5aX5UDKtOz/3haMHXBLP61ZBxlNpMD8CG4A== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.12.1" + "@jimp/utils" "^0.16.1" -"@jimp/plugin-flip@^0.12.1": - version "0.12.1" - resolved "https://registry.yarnpkg.com/@jimp/plugin-flip/-/plugin-flip-0.12.1.tgz#b80415e69cf477d40f1960bc6081441ba0ce54dc" - integrity sha512-xi+Yayrnln8A/C9E3yQBExjxwBSeCkt/ZQg1CxLgszVyX/3Zo8+nkV8MJYpkTpj8LCZGTOKlsE05mxu/a3lbJQ== +"@jimp/plugin-flip@^0.16.1": + version "0.16.1" + resolved "https://registry.yarnpkg.com/@jimp/plugin-flip/-/plugin-flip-0.16.1.tgz#7a99ea22bde802641017ed0f2615870c144329bb" + integrity sha512-KdxTf0zErfZ8DyHkImDTnQBuHby+a5YFdoKI/G3GpBl3qxLBvC+PWkS2F/iN3H7wszP7/TKxTEvWL927pypT0w== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.12.1" + "@jimp/utils" "^0.16.1" -"@jimp/plugin-gaussian@^0.12.1": - version "0.12.1" - resolved "https://registry.yarnpkg.com/@jimp/plugin-gaussian/-/plugin-gaussian-0.12.1.tgz#7cd1fa2c7b6f6d91776af043d202aa595430c34a" - integrity sha512-7O6eKlhL37hsLfV6WAX1Cvce7vOqSwL1oWbBveC1agutDlrtvcTh1s2mQ4Pde654hCJu55mq1Ur10+ote5j3qw== +"@jimp/plugin-gaussian@^0.16.1": + version "0.16.1" + resolved "https://registry.yarnpkg.com/@jimp/plugin-gaussian/-/plugin-gaussian-0.16.1.tgz#0845e314085ccd52e34fad9a83949bc0d81a68e8" + integrity sha512-u9n4wjskh3N1mSqketbL6tVcLU2S5TEaFPR40K6TDv4phPLZALi1Of7reUmYpVm8mBDHt1I6kGhuCJiWvzfGyg== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.12.1" + "@jimp/utils" "^0.16.1" -"@jimp/plugin-invert@^0.12.1": - version "0.12.1" - resolved "https://registry.yarnpkg.com/@jimp/plugin-invert/-/plugin-invert-0.12.1.tgz#9403089d9f740d54be72270faa28f392bf3dff9c" - integrity sha512-JTAs7A1Erbxwl+7ph7tgcb2PZ4WzB+3nb2WbfiWU8iCrKj17mMDSc5soaCCycn8wfwqvgB1vhRfGpseOLWxsuQ== +"@jimp/plugin-invert@^0.16.1": + version "0.16.1" + resolved "https://registry.yarnpkg.com/@jimp/plugin-invert/-/plugin-invert-0.16.1.tgz#7e6f5a15707256f3778d06921675bbcf18545c97" + integrity sha512-2DKuyVXANH8WDpW9NG+PYFbehzJfweZszFYyxcaewaPLN0GxvxVLOGOPP1NuUTcHkOdMFbE0nHDuB7f+sYF/2w== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.12.1" + "@jimp/utils" "^0.16.1" -"@jimp/plugin-mask@^0.12.1": - version "0.12.1" - resolved "https://registry.yarnpkg.com/@jimp/plugin-mask/-/plugin-mask-0.12.1.tgz#3cbf2c990c9ecb76b34e8c13c028bc469acfd593" - integrity sha512-bnDdY0RO/x5Mhqoy+056SN1wEj++sD4muAKqLD2CIT8Zq5M/0TA4hkdf/+lwFy3H2C0YTK39PSE9xyb4jPX3kA== +"@jimp/plugin-mask@^0.16.1": + version "0.16.1" + resolved "https://registry.yarnpkg.com/@jimp/plugin-mask/-/plugin-mask-0.16.1.tgz#e7f2460e05c3cda7af5e76f33ccb0579f66f90df" + integrity sha512-snfiqHlVuj4bSFS0v96vo2PpqCDMe4JB+O++sMo5jF5mvGcGL6AIeLo8cYqPNpdO6BZpBJ8MY5El0Veckhr39Q== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.12.1" + "@jimp/utils" "^0.16.1" -"@jimp/plugin-normalize@^0.12.1": - version "0.12.1" - resolved "https://registry.yarnpkg.com/@jimp/plugin-normalize/-/plugin-normalize-0.12.1.tgz#e1cc7724792f7ace9573ed550bd9cda57e06e560" - integrity sha512-4kSaI4JLM/PNjHwbnAHgyh51V5IlPfPxYvsZyZ1US32pebWtocxSMaSuOaJUg7OGSkwSDBv81UR2h5D+Dz1b5A== +"@jimp/plugin-normalize@^0.16.1": + version "0.16.1" + resolved "https://registry.yarnpkg.com/@jimp/plugin-normalize/-/plugin-normalize-0.16.1.tgz#032dfd88eefbc4dedc8b1b2d243832e4f3af30c8" + integrity sha512-dOQfIOvGLKDKXPU8xXWzaUeB0nvkosHw6Xg1WhS1Z5Q0PazByhaxOQkSKgUryNN/H+X7UdbDvlyh/yHf3ITRaw== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.12.1" + "@jimp/utils" "^0.16.1" -"@jimp/plugin-print@^0.12.1": - version "0.12.1" - resolved "https://registry.yarnpkg.com/@jimp/plugin-print/-/plugin-print-0.12.1.tgz#1e604cd796fcffd7a9188ce3e94a1f5f1bc56a9f" - integrity sha512-T0lNS3qU9SwCHOEz7AGrdp50+gqiWGZibOL3350/X/dqoFs1EvGDjKVeWncsGCyLlpfd7M/AibHZgu8Fx2bWng== +"@jimp/plugin-print@^0.16.1": + version "0.16.1" + resolved "https://registry.yarnpkg.com/@jimp/plugin-print/-/plugin-print-0.16.1.tgz#66b803563f9d109825970714466e6ab9ae639ff6" + integrity sha512-ceWgYN40jbN4cWRxixym+csyVymvrryuKBQ+zoIvN5iE6OyS+2d7Mn4zlNgumSczb9GGyZZESIgVcBDA1ezq0Q== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.12.1" + "@jimp/utils" "^0.16.1" load-bmfont "^1.4.0" -"@jimp/plugin-resize@^0.12.1": - version "0.12.1" - resolved "https://registry.yarnpkg.com/@jimp/plugin-resize/-/plugin-resize-0.12.1.tgz#cb0347320eb392136a16e179c396f636891038af" - integrity sha512-sbNn4tdBGcgGlPt9XFxCuDl4ZOoxa8/Re8nAikyxYhRss2Dqz91ARbBQxOf1vlUGeicQMsjEuWbPQAogTSJRug== +"@jimp/plugin-resize@^0.16.1": + version "0.16.1" + resolved "https://registry.yarnpkg.com/@jimp/plugin-resize/-/plugin-resize-0.16.1.tgz#65e39d848ed13ba2d6c6faf81d5d590396571d10" + integrity sha512-u4JBLdRI7dargC04p2Ha24kofQBk3vhaf0q8FwSYgnCRwxfvh2RxvhJZk9H7Q91JZp6wgjz/SjvEAYjGCEgAwQ== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.12.1" + "@jimp/utils" "^0.16.1" -"@jimp/plugin-rotate@^0.12.1": - version "0.12.1" - resolved "https://registry.yarnpkg.com/@jimp/plugin-rotate/-/plugin-rotate-0.12.1.tgz#29101e949f96047bcee2afaba5008be8f92ed8e8" - integrity sha512-RYkLzwG2ervG6hHy8iepbIVeWdT1kz4Qz044eloqo6c66MK0KAqp228YI8+CAKm0joQnVDC/A0FgRIj/K8uyAw== +"@jimp/plugin-rotate@^0.16.1": + version "0.16.1" + resolved "https://registry.yarnpkg.com/@jimp/plugin-rotate/-/plugin-rotate-0.16.1.tgz#53fb5d51a4b3d05af9c91c2a8fffe5d7a1a47c8c" + integrity sha512-ZUU415gDQ0VjYutmVgAYYxC9Og9ixu2jAGMCU54mSMfuIlmohYfwARQmI7h4QB84M76c9hVLdONWjuo+rip/zg== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.12.1" + "@jimp/utils" "^0.16.1" -"@jimp/plugin-scale@^0.12.1": - version "0.12.1" - resolved "https://registry.yarnpkg.com/@jimp/plugin-scale/-/plugin-scale-0.12.1.tgz#bf9c2e5af47dc07d48d8ab16fecba96b40af3734" - integrity sha512-zjNVI1fUj+ywfG78T1ZU33g9a5sk4rhEQkkhtny8koAscnVsDN2YaZEKoFli54kqaWh5kSS5DDL7a/9pEfXnFQ== +"@jimp/plugin-scale@^0.16.1": + version "0.16.1" + resolved "https://registry.yarnpkg.com/@jimp/plugin-scale/-/plugin-scale-0.16.1.tgz#89f6ba59feed3429847ed226aebda33a240cc647" + integrity sha512-jM2QlgThIDIc4rcyughD5O7sOYezxdafg/2Xtd1csfK3z6fba3asxDwthqPZAgitrLgiKBDp6XfzC07Y/CefUw== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.12.1" + "@jimp/utils" "^0.16.1" -"@jimp/plugin-shadow@^0.12.1": - version "0.12.1" - resolved "https://registry.yarnpkg.com/@jimp/plugin-shadow/-/plugin-shadow-0.12.1.tgz#63508e3d321dbd057acc1e93b90c326257e0f1c3" - integrity sha512-Z82IwvunXWQ2jXegd3W3TYUXpfJcEvNbHodr7Z+oVnwhM1OoQ5QC6RSRQwsj2qXIhbGffQjH8eguHgEgAV+u5w== +"@jimp/plugin-shadow@^0.16.1": + version "0.16.1" + resolved "https://registry.yarnpkg.com/@jimp/plugin-shadow/-/plugin-shadow-0.16.1.tgz#a7af892a740febf41211e10a5467c3c5c521a04c" + integrity sha512-MeD2Is17oKzXLnsphAa1sDstTu6nxscugxAEk3ji0GV1FohCvpHBcec0nAq6/czg4WzqfDts+fcPfC79qWmqrA== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.12.1" + "@jimp/utils" "^0.16.1" -"@jimp/plugin-threshold@^0.12.1": - version "0.12.1" - resolved "https://registry.yarnpkg.com/@jimp/plugin-threshold/-/plugin-threshold-0.12.1.tgz#deaa1ac912522b9b7353820e84c8706ff433aa04" - integrity sha512-PFezt5fSk0q+xKvdpuv0eLggy2I7EgYotrK8TRZOT0jimuYFXPF0Z514c6szumoW5kEsRz04L1HkPT1FqI97Yg== +"@jimp/plugin-threshold@^0.16.1": + version "0.16.1" + resolved "https://registry.yarnpkg.com/@jimp/plugin-threshold/-/plugin-threshold-0.16.1.tgz#34f3078f9965145b7ae26c53a32ad74b1195bbf5" + integrity sha512-iGW8U/wiCSR0+6syrPioVGoSzQFt4Z91SsCRbgNKTAk7D+XQv6OI78jvvYg4o0c2FOlwGhqz147HZV5utoSLxA== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.12.1" + "@jimp/utils" "^0.16.1" -"@jimp/plugins@^0.12.1": - version "0.12.1" - resolved "https://registry.yarnpkg.com/@jimp/plugins/-/plugins-0.12.1.tgz#450a1312184f649d81b75fc1aeff265e99c8f2b3" - integrity sha512-7+Yp29T6BbYo+Oqnc+m7A5AH+O+Oy5xnxvxlfmsp48+SuwEZ4akJp13Gu2PSmRlylENzR7MlWOxzhas5ERNlIg== +"@jimp/plugins@^0.16.1": + version "0.16.1" + resolved "https://registry.yarnpkg.com/@jimp/plugins/-/plugins-0.16.1.tgz#9f08544c97226d6460a16ced79f57e85bec3257b" + integrity sha512-c+lCqa25b+4q6mJZSetlxhMoYuiltyS+ValLzdwK/47+aYsq+kcJNl+TuxIEKf59yr9+5rkbpsPkZHLF/V7FFA== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/plugin-blit" "^0.12.1" - "@jimp/plugin-blur" "^0.12.1" - "@jimp/plugin-circle" "^0.12.1" - "@jimp/plugin-color" "^0.12.1" - "@jimp/plugin-contain" "^0.12.1" - "@jimp/plugin-cover" "^0.12.1" - "@jimp/plugin-crop" "^0.12.1" - "@jimp/plugin-displace" "^0.12.1" - "@jimp/plugin-dither" "^0.12.1" - "@jimp/plugin-fisheye" "^0.12.1" - "@jimp/plugin-flip" "^0.12.1" - "@jimp/plugin-gaussian" "^0.12.1" - "@jimp/plugin-invert" "^0.12.1" - "@jimp/plugin-mask" "^0.12.1" - "@jimp/plugin-normalize" "^0.12.1" - "@jimp/plugin-print" "^0.12.1" - "@jimp/plugin-resize" "^0.12.1" - "@jimp/plugin-rotate" "^0.12.1" - "@jimp/plugin-scale" "^0.12.1" - "@jimp/plugin-shadow" "^0.12.1" - "@jimp/plugin-threshold" "^0.12.1" + "@jimp/plugin-blit" "^0.16.1" + "@jimp/plugin-blur" "^0.16.1" + "@jimp/plugin-circle" "^0.16.1" + "@jimp/plugin-color" "^0.16.1" + "@jimp/plugin-contain" "^0.16.1" + "@jimp/plugin-cover" "^0.16.1" + "@jimp/plugin-crop" "^0.16.1" + "@jimp/plugin-displace" "^0.16.1" + "@jimp/plugin-dither" "^0.16.1" + "@jimp/plugin-fisheye" "^0.16.1" + "@jimp/plugin-flip" "^0.16.1" + "@jimp/plugin-gaussian" "^0.16.1" + "@jimp/plugin-invert" "^0.16.1" + "@jimp/plugin-mask" "^0.16.1" + "@jimp/plugin-normalize" "^0.16.1" + "@jimp/plugin-print" "^0.16.1" + "@jimp/plugin-resize" "^0.16.1" + "@jimp/plugin-rotate" "^0.16.1" + "@jimp/plugin-scale" "^0.16.1" + "@jimp/plugin-shadow" "^0.16.1" + "@jimp/plugin-threshold" "^0.16.1" timm "^1.6.1" -"@jimp/png@^0.12.1": - version "0.12.1" - resolved "https://registry.yarnpkg.com/@jimp/png/-/png-0.12.1.tgz#85d99ed6304e7d37f8e5279b3b4b058ed28a7f67" - integrity sha512-tOUSJMJzcMAN82F9/Q20IToquIVWzvOe/7NIpVQJn6m+Lq6TtVmd7d8gdcna9AEFm2FIza5lhq2Kta6Xj0KXhQ== +"@jimp/png@^0.16.1": + version "0.16.1" + resolved "https://registry.yarnpkg.com/@jimp/png/-/png-0.16.1.tgz#f24cfc31529900b13a2dd9d4fdb4460c1e4d814e" + integrity sha512-iyWoCxEBTW0OUWWn6SveD4LePW89kO7ZOy5sCfYeDM/oTPLpR8iMIGvZpZUz1b8kvzFr27vPst4E5rJhGjwsdw== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/utils" "^0.12.1" + "@jimp/utils" "^0.16.1" pngjs "^3.3.3" -"@jimp/tiff@^0.12.1": - version "0.12.1" - resolved "https://registry.yarnpkg.com/@jimp/tiff/-/tiff-0.12.1.tgz#ce2cd058d0f3a9fe43564866b6a64a815c141a9e" - integrity sha512-bzWDgv3202TKhaBGzV9OFF0PVQWEb4194h9kv5js348SSnbCusz/tzTE1EwKrnbDZThZPgTB1ryKs7D+Q9Mhmg== +"@jimp/tiff@^0.16.1": + version "0.16.1" + resolved "https://registry.yarnpkg.com/@jimp/tiff/-/tiff-0.16.1.tgz#0e8756695687d7574b6bc73efab0acd4260b7a12" + integrity sha512-3K3+xpJS79RmSkAvFMgqY5dhSB+/sxhwTFA9f4AVHUK0oKW+u6r52Z1L0tMXHnpbAdR9EJ+xaAl2D4x19XShkQ== dependencies: "@babel/runtime" "^7.7.2" utif "^2.0.1" -"@jimp/types@^0.12.1": - version "0.12.1" - resolved "https://registry.yarnpkg.com/@jimp/types/-/types-0.12.1.tgz#2671e228bd1abc7f086e2f4316097c15aa4b41c0" - integrity sha512-hg5OKXpWWeKGuDrfibrjWWhr7hqb7f552wqnPWSLQpVrdWgjH+hpOv6cOzdo9bsU78qGTelZJPxr0ERRoc+MhQ== +"@jimp/types@^0.16.1": + version "0.16.1" + resolved "https://registry.yarnpkg.com/@jimp/types/-/types-0.16.1.tgz#0dbab37b3202315c91010f16c31766d35a2322cc" + integrity sha512-g1w/+NfWqiVW4CaXSJyD28JQqZtm2eyKMWPhBBDCJN9nLCN12/Az0WFF3JUAktzdsEC2KRN2AqB1a2oMZBNgSQ== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/bmp" "^0.12.1" - "@jimp/gif" "^0.12.1" - "@jimp/jpeg" "^0.12.1" - "@jimp/png" "^0.12.1" - "@jimp/tiff" "^0.12.1" + "@jimp/bmp" "^0.16.1" + "@jimp/gif" "^0.16.1" + "@jimp/jpeg" "^0.16.1" + "@jimp/png" "^0.16.1" + "@jimp/tiff" "^0.16.1" timm "^1.6.1" -"@jimp/utils@^0.12.1": - version "0.12.1" - resolved "https://registry.yarnpkg.com/@jimp/utils/-/utils-0.12.1.tgz#e9ab43dcd55f88a8fdf250a84bcf43d09713bd9d" - integrity sha512-EjPkDQOzV/oZfbolEUgFT6SE++PtCccVBvjuACkttyCfl0P2jnpR49SwstyVLc2u8AwBAZEHHAw9lPYaMjtbXQ== +"@jimp/utils@^0.16.1": + version "0.16.1" + resolved "https://registry.yarnpkg.com/@jimp/utils/-/utils-0.16.1.tgz#2f51e6f14ff8307c4aa83d5e1a277da14a9fe3f7" + integrity sha512-8fULQjB0x4LzUSiSYG6ZtQl355sZjxbv8r9PPAuYHzS9sGiSHJQavNqK/nKnpDsVkU88/vRGcE7t3nMU0dEnVw== dependencies: "@babel/runtime" "^7.7.2" regenerator-runtime "^0.13.3" @@ -2594,10 +2745,10 @@ dependencies: any-observable "^0.3.0" -"@stylelint/postcss-css-in-js@^0.37.1": - version "0.37.1" - resolved "https://registry.yarnpkg.com/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.1.tgz#41e5e7660f73d88227610e18c6ebb262d56ac125" - integrity sha512-UMf2Rni3JGKi3ZwYRGMYJ5ipOA5ENJSKMtYA/pE1ZLURwdh7B5+z2r73RmWvub+N0UuH1Lo+TGfCgYwPvqpXNw== +"@stylelint/postcss-css-in-js@^0.37.2": + version "0.37.2" + resolved "https://registry.yarnpkg.com/@stylelint/postcss-css-in-js/-/postcss-css-in-js-0.37.2.tgz#7e5a84ad181f4234a2480803422a47b8749af3d2" + integrity sha512-nEhsFoJurt8oUmieT8qy4nk81WRHmJynmVwn/Vts08PL9fhgIsMhk1GId5yAN643OzqEEb5S/6At2TZW7pqPDA== dependencies: "@babel/core" ">=7.9.0" @@ -2712,6 +2863,20 @@ "@svgr/plugin-svgo" "^4.3.1" loader-utils "^1.2.3" +"@tootallnate/once@1": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82" + integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw== + +"@types/apollo-upload-client@^14.1.0": + version "14.1.0" + resolved "https://registry.yarnpkg.com/@types/apollo-upload-client/-/apollo-upload-client-14.1.0.tgz#21a57d7e3f29ff946ba51a53b3d7da46ddd21fbc" + integrity sha512-ZLvcEqu+l9qKGdrIpASt/A2WY1ghAC9L3qaoegkiBOccjxvQmWN9liZzVFiuHTuWseWpVbMklqbs/z+KEjll9Q== + dependencies: + "@apollo/client" "^3.1.3" + "@types/extract-files" "*" + graphql "^15.3.0" + "@types/babel__core@^7.1.0": version "7.1.3" resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.3.tgz#e441ea7df63cd080dfcd02ab199e6d16a735fc30" @@ -2756,11 +2921,6 @@ dependencies: "@babel/types" "^7.3.0" -"@types/chai@^4.2.11": - version "4.2.11" - resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.11.tgz#d3614d6c5f500142358e6ed24e1bf16657536c50" - integrity sha512-t7uW6eFafjO+qJ3BIV2gGUyZs27egcNRkUdalkud+Qa3+kg//f129iuOFivHDXQ+vnU3fDXuwgv0cqMCbcE8sw== - "@types/classnames@^2.2.10": version "2.2.10" resolved "https://registry.yarnpkg.com/@types/classnames/-/classnames-2.2.10.tgz#cc658ca319b6355399efc1f5b9e818f1a24bf999" @@ -2786,6 +2946,11 @@ resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" integrity sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g== +"@types/extract-files@*": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@types/extract-files/-/extract-files-8.1.0.tgz#4728440e1d92a6d1d11ac47f5a10e3f9ce47f044" + integrity sha512-ulxvlFU71yLVV3JxdBgryASAIp+aZQuQOpkhU1SznJlcWz0qsJCWHqdJqP6Lprs3blqGS5FH5GbBkU0977+Wew== + "@types/fs-extra@^8.1.0": version "8.1.0" resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-8.1.0.tgz#1114834b53c3914806cd03b3304b37b3bd221a4d" @@ -2793,6 +2958,13 @@ dependencies: "@types/node" "*" +"@types/fslightbox-react@^1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@types/fslightbox-react/-/fslightbox-react-1.4.0.tgz#d2b20486830745ae80318c1c478c9beae5f2cd8a" + integrity sha512-ocIiZqFQ3BWBZB8Bp0fuNma7Eb0aOjkgk/nEUfW0omdRw4ciaVivabfsWldNuR69KwRJrvs6MZQuvVV6JEqlFg== + dependencies: + "@types/react" "*" + "@types/glob@^7.1.1": version "7.1.1" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.1.1.tgz#aa59a1c6e3fbc421e07ccd31a944c30eba521575" @@ -2815,10 +2987,17 @@ "@types/react" "*" hoist-non-react-statics "^3.3.0" -"@types/invariant@^2.2.31": - version "2.2.31" - resolved "https://registry.yarnpkg.com/@types/invariant/-/invariant-2.2.31.tgz#4444c03004f215289dbca3856538434317dd28b2" - integrity sha512-jMlgg9pIURvy9jgBHCjQp/CyBjYHUwj91etVcDdXkFl2CwTFiQlB+8tcsMeXpXf2PFE5X2pjk4Gm43hQSMHAdA== +"@types/http-proxy-agent@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@types/http-proxy-agent/-/http-proxy-agent-2.0.2.tgz#942c1f35c7e1f0edd1b6ffae5d0f9051cfb32be1" + integrity sha512-2S6IuBRhqUnH1/AUx9k8KWtY3Esg4eqri946MnxTG5HwehF1S5mqLln8fcyMiuQkY72p2gH3W+rIPqp5li0LyQ== + dependencies: + "@types/node" "*" + +"@types/invariant@^2.2.33": + version "2.2.34" + resolved "https://registry.yarnpkg.com/@types/invariant/-/invariant-2.2.34.tgz#05e4f79f465c2007884374d4795452f995720bbe" + integrity sha512-lYUtmJ9BqUN688fGY1U1HZoWT1/Jrmgigx2loq4ZcJpICECm/Om3V314BxdzypO0u5PORKGMM6x0OXaljV1YFg== "@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0": version "2.0.1" @@ -2840,15 +3019,37 @@ "@types/istanbul-lib-coverage" "*" "@types/istanbul-lib-report" "*" +"@types/js-yaml@^3.12.5": + version "3.12.5" + resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-3.12.5.tgz#136d5e6a57a931e1cce6f9d8126aa98a9c92a6bb" + integrity sha512-JCcp6J0GV66Y4ZMDAQCXot4xprYB+Zfd3meK9+INSJeVZwJmHAW30BBEEkPzXswMXuiyReUGOP3GxrADc9wPww== + "@types/json-schema@^7.0.3": version "7.0.4" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.4.tgz#38fd73ddfd9b55abb1e1b2ed578cb55bd7b7d339" integrity sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA== -"@types/lodash@^4.14.150": - version "4.14.150" - resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.150.tgz#649fe44684c3f1fcb6164d943c5a61977e8cf0bd" - integrity sha512-kMNLM5JBcasgYscD9x/Gvr6lTAv2NVgsKtet/hm93qMyf/D1pt+7jeEZklKJKxMVmXjxbRVQQGfqDSfipYCO6w== +"@types/json-stable-stringify@^1.0.32": + version "1.0.32" + resolved "https://registry.yarnpkg.com/@types/json-stable-stringify/-/json-stable-stringify-1.0.32.tgz#121f6917c4389db3923640b2e68de5fa64dda88e" + integrity sha512-q9Q6+eUEGwQkv4Sbst3J4PNgDOvpuVuKj79Hl/qnmBMEIPzB5QoFRUtjcgcg2xNUZyYUGXBk5wYIBKHt0A+Mxw== + +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha1-7ihweulOEdK4J7y+UnC86n8+ce4= + +"@types/jsonwebtoken@^8.5.0": + version "8.5.0" + resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-8.5.0.tgz#2531d5e300803aa63279b232c014acf780c981c5" + integrity sha512-9bVao7LvyorRGZCw0VmH/dr7Og+NdjYSsKAxB43OQoComFbBgsEpoR9JW6+qSq/ogwVBg8GI2MfAlk4SYI4OLg== + dependencies: + "@types/node" "*" + +"@types/lodash@^4.14.161": + version "4.14.161" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.161.tgz#a21ca0777dabc6e4f44f3d07f37b765f54188b18" + integrity sha512-EP6O3Jkr7bXvZZSZYlsgt5DIjiGr0dXP1/jVEwVLTFgg0d+3lWVQkRavYVQszV7dYUwvg0B8R0MBDpcmXg7XIA== "@types/minimatch@*": version "3.0.3" @@ -2870,15 +3071,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-13.1.2.tgz#fe94285bf5e0782e1a9e5a8c482b1c34465fa385" integrity sha512-B8emQA1qeKerqd1dmIsQYnXi+mmAzTB7flExjmy5X1aVAKFNNNDubkavwR13kR6JnpeLp3aLoJhwn9trWPAyFQ== -"@types/node@13.13.4": - version "13.13.4" - resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.4.tgz#1581d6c16e3d4803eb079c87d4ac893ee7501c2c" - integrity sha512-x26ur3dSXgv5AwKS0lNfbjpCakGIduWU1DU91Zz58ONRWrIKGunmZBNv4P7N+e27sJkiGDsw/3fT4AtsqQBrBA== - -"@types/node@>=6": - version "13.1.8" - resolved "https://registry.yarnpkg.com/@types/node/-/node-13.1.8.tgz#1d590429fe8187a02707720ecf38a6fe46ce294b" - integrity sha512-6XzyyNM9EKQW4HKuzbo/CkOIjn/evtCmsU+MUM1xDfJ+3/rNjBttM1NgN7AOQvN6tP1Sl1D1PIKMreTArnxM9A== +"@types/node@14.6.4": + version "14.6.4" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.6.4.tgz#a145cc0bb14ef9c4777361b7bbafa5cf8e3acb5a" + integrity sha512-Wk7nG1JSaMfMpoMJDKUsWYugliB2Vy55pdjLpmLixeyMi7HizW2I/9QoxsPCkXl3dO+ZOVqPumKaDUv5zJu2uQ== "@types/normalize-package-data@^2.4.0": version "2.4.0" @@ -2895,7 +3091,7 @@ resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== -"@types/prop-types@*": +"@types/prop-types@*", "@types/prop-types@^15.7.3": version "15.7.3" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw== @@ -2912,17 +3108,17 @@ dependencies: "@types/react" "*" -"@types/react-dom@^16.9.7": - version "16.9.7" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.7.tgz#60844d48ce252d7b2dccf0c7bb937130e27c0cd2" - integrity sha512-GHTYhM8/OwUCf254WO5xqR/aqD3gC9kSTLpopWGpQLpnw23jk44RvMHsyUSEplvRJZdHxhJGMMLF0kCPYHPhQA== +"@types/react-dom@^16.9.8": + version "16.9.8" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.8.tgz#fe4c1e11dfc67155733dfa6aa65108b4971cb423" + integrity sha512-ykkPQ+5nFknnlU6lDd947WbQ6TE3NNzbQAkInC2EKY1qeYdTKp7onFusmYZb+ityzx2YviqT6BXSu+LyWWJwcA== dependencies: "@types/react" "*" -"@types/react-images@^0.5.1": - version "0.5.1" - resolved "https://registry.yarnpkg.com/@types/react-images/-/react-images-0.5.1.tgz#e00ccce7221a03ed7b43072cefdc0e5a66fc5a08" - integrity sha512-n2guyR+kblfNEAr1TA3GnrpEdt0/2dHxMOFaFcCgI62NRQaQClaNdgidsim3JRVWLlUiWilDjzZnJUSR0kMncQ== +"@types/react-images@^0.5.3": + version "0.5.3" + resolved "https://registry.yarnpkg.com/@types/react-images/-/react-images-0.5.3.tgz#328d34bb05936f855ad636c97a49abd6aae3f991" + integrity sha512-sVXMlktxgleh8xrZJc+abvH62ggzYVByoM0056OHPDZzLPqAYX7v7Ph3kgE7EAV/qM/POylCIqgjkqriybMBZw== dependencies: "@types/react" "*" @@ -2952,6 +3148,14 @@ "@types/react" "*" "@types/react-router" "*" +"@types/react-router-hash-link@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@types/react-router-hash-link/-/react-router-hash-link-1.2.1.tgz#fba7dc351cef2985791023018b7a5dbd0653c843" + integrity sha512-jdzPGE8jFGq7fHUpPaKrJvLW1Yhoe5MQCrmgeesC+eSLseMj3cGCTYMDA4BNWG8JQmwO8NTYt/oT3uBZ77pmBA== + dependencies: + "@types/react" "*" + "@types/react-router-dom" "*" + "@types/react-router@*": version "5.1.3" resolved "https://registry.yarnpkg.com/@types/react-router/-/react-router-5.1.3.tgz#7c7ca717399af64d8733d8cb338dd43641b96f2d" @@ -2960,10 +3164,10 @@ "@types/history" "*" "@types/react" "*" -"@types/react-select@^3.0.12": - version "3.0.12" - resolved "https://registry.yarnpkg.com/@types/react-select/-/react-select-3.0.12.tgz#84f6929b0e3aeae949a5abc851002d206c26f714" - integrity sha512-3NVEc1sbaNtI1b06smzr9dlNKTkYWttL27CdEsorMvd2EgTOM/PJmrzkClaVQmBDg52MzQO05xVwNZruEUKpHw== +"@types/react-select@3.0.19": + version "3.0.19" + resolved "https://registry.yarnpkg.com/@types/react-select/-/react-select-3.0.19.tgz#f73b04b8113451b0597df8a8315f9bf8ce03eb44" + integrity sha512-d+6qtfFXZeIOAABlVL1e50RZn8ctOABE4tFDxM6KW4lKuXgTTgLVrSik5AX9XjBjV7N80FtS6GTN/WeoXL9Jww== dependencies: "@types/react" "*" "@types/react-dom" "*" @@ -2976,6 +3180,13 @@ dependencies: "@types/react" "*" +"@types/react-transition-group@^4.4.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.0.tgz#882839db465df1320e4753e6e9f70ca7e9b4d46d" + integrity sha512-/QfLHGpu+2fQOqQaXh8MG9q03bFENooTb/it4jr5kKaZlDQfWvjqWZg48AwzPVMBHlRuTRAY7hRHCEOXz5kV6w== + dependencies: + "@types/react" "*" + "@types/react@*": version "16.9.19" resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.19.tgz#c842aa83ea490007d29938146ff2e4d9e4360c40" @@ -2984,10 +3195,10 @@ "@types/prop-types" "*" csstype "^2.2.0" -"@types/react@16.9.34", "@types/react@^16.9.23": - version "16.9.34" - resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.34.tgz#f7d5e331c468f53affed17a8a4d488cd44ea9349" - integrity sha512-8AJlYMOfPe1KGLKyHpflCg5z46n0b5DbRfqDksxBLBTUpB75ypDBAO9eCUcjNwE6LCUslwTz00yyG/X9gaVtow== +"@types/react@16.9.43": + version "16.9.43" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.43.tgz#c287f23f6189666ee3bebc2eb8d0f84bcb6cdb6b" + integrity sha512-PxshAFcnJqIWYpJbLPriClH53Z2WlJcVZE+NP2etUtWQs2s7yIMj3/LDKZT/5CHJ/F62iyjVCDu2H3jHEXIxSg== dependencies: "@types/prop-types" "*" csstype "^2.2.0" @@ -3000,6 +3211,14 @@ "@types/prop-types" "*" csstype "^2.2.0" +"@types/react@^16.9.35": + version "16.9.49" + resolved "https://registry.yarnpkg.com/@types/react/-/react-16.9.49.tgz#09db021cf8089aba0cdb12a49f8021a69cce4872" + integrity sha512-DtLFjSj0OYAdVLBbyjhuV9CdGVHCkHn2R+xr3XkBvK2rS1Y1tkc14XSGjYgm5Fjjr90AxH9tiSzc1pCFMGO06g== + dependencies: + "@types/prop-types" "*" + csstype "^3.0.2" + "@types/schema-utils@^2.4.0": version "2.4.0" resolved "https://registry.yarnpkg.com/@types/schema-utils/-/schema-utils-2.4.0.tgz#9983012045d541dcee053e685a27c9c87c840fcd" @@ -3022,6 +3241,13 @@ resolved "https://registry.yarnpkg.com/@types/warning/-/warning-3.0.0.tgz#0d2501268ad8f9962b740d387c4654f5f8e23e52" integrity sha1-DSUBJorY+ZYrdA04fEZU9fjiPlI= +"@types/websocket@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@types/websocket/-/websocket-1.0.1.tgz#039272c196c2c0e4868a0d8a1a27bbb86e9e9138" + integrity sha512-f5WLMpezwVxCLm1xQe/kdPpQIOmL0TXYx2O15VYfYzc7hTIdxiOoOvez+McSIw3b7z/1zGovew9YSL7+h4h7/Q== + dependencies: + "@types/node" "*" + "@types/yargs-parser@*": version "13.1.0" resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-13.1.0.tgz#c563aa192f39350a1d18da36c5a8da382bbd8228" @@ -3051,11 +3277,11 @@ tsutils "^3.17.1" "@typescript-eslint/eslint-plugin@^2.30.0": - version "2.30.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.30.0.tgz#312a37e80542a764d96e8ad88a105316cdcd7b05" - integrity sha512-PGejii0qIZ9Q40RB2jIHyUpRWs1GJuHP1pkoCiaeicfwO9z7Fx03NQzupuyzAmv+q9/gFNHu7lo1ByMXe8PNyg== + version "2.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.34.0.tgz#6f8ce8a46c7dea4a6f1d171d2bb8fbae6dac2be9" + integrity sha512-4zY3Z88rEE99+CNvTbXSyovv2z9PNOVffTWD2W8QF5s2prBQtwN2zadqERcrHpcR7O/+KMI3fcTAmUUhK/iQcQ== dependencies: - "@typescript-eslint/experimental-utils" "2.30.0" + "@typescript-eslint/experimental-utils" "2.34.0" functional-red-black-tree "^1.0.1" regexpp "^3.0.0" tsutils "^3.17.1" @@ -3069,13 +3295,13 @@ "@typescript-eslint/typescript-estree" "2.18.0" eslint-scope "^5.0.0" -"@typescript-eslint/experimental-utils@2.30.0": - version "2.30.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.30.0.tgz#9845e868c01f3aed66472c561d4b6bac44809dd0" - integrity sha512-L3/tS9t+hAHksy8xuorhOzhdefN0ERPDWmR9CclsIGOUqGKy6tqc/P+SoXeJRye5gazkuPO0cK9MQRnolykzkA== +"@typescript-eslint/experimental-utils@2.34.0": + version "2.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.34.0.tgz#d3524b644cdb40eebceca67f8cf3e4cc9c8f980f" + integrity sha512-eS6FTkq+wuMJ+sgtuNTtcqavWXqsflWcfBnlYhg/nS4aZ1leewkXGbvBhaapn1q6qf4M71bsR1tez5JTRMuqwA== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/typescript-estree" "2.30.0" + "@typescript-eslint/typescript-estree" "2.34.0" eslint-scope "^5.0.0" eslint-utils "^2.0.0" @@ -3090,13 +3316,13 @@ eslint-visitor-keys "^1.1.0" "@typescript-eslint/parser@^2.24.0", "@typescript-eslint/parser@^2.30.0": - version "2.30.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.30.0.tgz#7681c305a6f4341ae2579f5e3a75846c29eee9ce" - integrity sha512-9kDOxzp0K85UnpmPJqUzdWaCNorYYgk1yZmf4IKzpeTlSAclnFsrLjfwD9mQExctLoLoGAUXq1co+fbr+3HeFw== + version "2.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.34.0.tgz#50252630ca319685420e9a39ca05fe185a256bc8" + integrity sha512-03ilO0ucSD0EPTw2X4PntSIRFtDPWjrVq7C3/Z3VQHRC7+13YB55rcJI3Jt+YgeHbjUdJPcPa7b23rXCBokuyA== dependencies: "@types/eslint-visitor-keys" "^1.0.0" - "@typescript-eslint/experimental-utils" "2.30.0" - "@typescript-eslint/typescript-estree" "2.30.0" + "@typescript-eslint/experimental-utils" "2.34.0" + "@typescript-eslint/typescript-estree" "2.34.0" eslint-visitor-keys "^1.1.0" "@typescript-eslint/typescript-estree@2.18.0": @@ -3112,17 +3338,17 @@ semver "^6.3.0" tsutils "^3.17.1" -"@typescript-eslint/typescript-estree@2.30.0": - version "2.30.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.30.0.tgz#1b8e848b55144270255ffbfe4c63291f8f766615" - integrity sha512-nI5WOechrA0qAhnr+DzqwmqHsx7Ulr/+0H7bWCcClDhhWkSyZR5BmTvnBEyONwJCTWHfc5PAQExX24VD26IAVw== +"@typescript-eslint/typescript-estree@2.34.0": + version "2.34.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.34.0.tgz#14aeb6353b39ef0732cc7f1b8285294937cf37d5" + integrity sha512-OMAr+nJWKdlVM9LOqCqh3pQQPwxHAN7Du8DR6dmwCrAmxtiXQnhHJ6tBNtf+cggqfo51SG/FCwnKhXCIM7hnVg== dependencies: debug "^4.1.1" eslint-visitor-keys "^1.1.0" glob "^7.1.6" is-glob "^4.0.1" lodash "^4.17.15" - semver "^6.3.0" + semver "^7.3.2" tsutils "^3.17.1" "@webassemblyjs/ast@1.8.5": @@ -3271,18 +3497,17 @@ "@webassemblyjs/wast-parser" "1.8.5" "@xtuc/long" "4.2.2" -"@wry/context@^0.4.0": - version "0.4.4" - resolved "https://registry.yarnpkg.com/@wry/context/-/context-0.4.4.tgz#e50f5fa1d6cfaabf2977d1fda5ae91717f8815f8" - integrity sha512-LrKVLove/zw6h2Md/KZyWxIkFM6AoyKp71OqpH9Hiip1csjPVoD3tPxlbQUNxEnHENks3UGgNpSBCAfq9KWuag== +"@wry/context@^0.5.2": + version "0.5.2" + resolved "https://registry.yarnpkg.com/@wry/context/-/context-0.5.2.tgz#f2a5d5ab9227343aa74c81e06533c1ef84598ec7" + integrity sha512-B/JLuRZ/vbEKHRUiGj6xiMojST1kHhu4WcreLfNN7q9DqQFrb97cWgf/kiYsPSUCAMVN0HzfFc8XjJdzgZzfjw== dependencies: - "@types/node" ">=6" tslib "^1.9.3" -"@wry/equality@^0.1.2", "@wry/equality@^0.1.9": - version "0.1.9" - resolved "https://registry.yarnpkg.com/@wry/equality/-/equality-0.1.9.tgz#b13e18b7a8053c6858aa6c85b54911fb31e3a909" - integrity sha512-mB6ceGjpMGz1ZTza8HYnrPGos2mC6So4NhS1PtZ8s4Qt0K7fBiIGhpSxUbQmhwcSWE3no+bYxmI2OL6KuXYmoQ== +"@wry/equality@^0.2.0": + version "0.2.0" + resolved "https://registry.yarnpkg.com/@wry/equality/-/equality-0.2.0.tgz#a312d1b6a682d0909904c2bcd355b02303104fb7" + integrity sha512-Y4d+WH6hs+KZJUC8YKLYGarjGekBrhslDbf/R20oV+AakHPINSitHfDRQz3EGcEWc1luXYNUvMhawWtZVWNGvQ== dependencies: tslib "^1.9.3" @@ -3363,14 +3588,14 @@ adjust-sourcemap-loader@2.0.0: object-path "0.11.4" regex-parser "2.2.10" -agent-base@4, agent-base@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee" - integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg== +agent-base@6: + version "6.0.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.1.tgz#808007e4e5867decb0ab6ab2f928fbdb5a596db4" + integrity sha512-01q25QQDwLSsyfhrKbn8yuur+JNw0H+0Y4JiGIKd3z9aYk/w/2kxD/Upc+t2ZBBSUNff50VjPsSW2YxM8QYKVg== dependencies: - es6-promisify "^5.0.0" + debug "4" -aggregate-error@3.0.1, aggregate-error@^3.0.0: +aggregate-error@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.0.1.tgz#db2fe7246e536f40d9b5442a39e117d7dd6a24e0" integrity sha512-quoaXsZ9/BLNae5yiNoUz+Nhkwz83GhWwtYFglcjEQB2NDHCIpApbqXxIFnm4Pq/Nvhrsq5sYJFyohrrxnTGAA== @@ -3388,16 +3613,6 @@ ajv-keywords@^3.1.0, ajv-keywords@^3.4.1: resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da" integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ== -ajv@5: - version "5.5.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" - integrity sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU= - dependencies: - co "^4.6.0" - fast-deep-equal "^1.0.0" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.3.0" - ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2: version "6.11.0" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.11.0.tgz#c3607cbc8ae392d8a5a536f25b21f8e5f3f87fe9" @@ -3418,6 +3633,16 @@ ajv@^6.12.0: json-schema-traverse "^0.4.1" uri-js "^4.2.2" +ajv@^6.12.4: + version "6.12.4" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.4.tgz#0614facc4522127fa713445c6bfd3ebd376e2234" + integrity sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + ajv@^6.5.5: version "6.10.2" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.2.tgz#d3cea04d6b017b2894ad69040fec8b623eb4bd52" @@ -3443,13 +3668,6 @@ ansi-colors@^3.0.0: resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf" integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA== -ansi-escapes@4.3.1: - version "4.3.1" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61" - integrity sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA== - dependencies: - type-fest "^0.11.0" - ansi-escapes@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" @@ -3462,6 +3680,13 @@ ansi-escapes@^4.2.1: dependencies: type-fest "^0.8.1" +ansi-escapes@^4.3.1: + version "4.3.1" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61" + integrity sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA== + dependencies: + type-fest "^0.11.0" + ansi-html@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e" @@ -3541,113 +3766,14 @@ aphrodite@^0.5.0: asap "^2.0.3" inline-style-prefixer "^2.0.0" -apollo-cache-inmemory@^1.6.5: - version "1.6.5" - resolved "https://registry.yarnpkg.com/apollo-cache-inmemory/-/apollo-cache-inmemory-1.6.5.tgz#2ccaa3827686f6ed7fb634203dbf2b8d7015856a" - integrity sha512-koB76JUDJaycfejHmrXBbWIN9pRKM0Z9CJGQcBzIOtmte1JhEBSuzsOUu7NQgiXKYI4iGoMREcnaWffsosZynA== +apollo-upload-client@^14.1.2: + version "14.1.2" + resolved "https://registry.yarnpkg.com/apollo-upload-client/-/apollo-upload-client-14.1.2.tgz#7a72b000f1cd67eaf8f12b4bda2796d0898c0dae" + integrity sha512-ozaW+4tnVz1rpfwiQwG3RCdCcZ93RV/37ZQbRnObcQ9mjb+zur58sGDPVg9Ef3fiujLmiE/Fe9kdgvIMA3VOjA== dependencies: - apollo-cache "^1.3.4" - apollo-utilities "^1.3.3" - optimism "^0.10.0" - ts-invariant "^0.4.0" - tslib "^1.10.0" - -apollo-cache@1.3.4, apollo-cache@^1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/apollo-cache/-/apollo-cache-1.3.4.tgz#0c9f63c793e1cd6e34c450f7668e77aff58c9a42" - integrity sha512-7X5aGbqaOWYG+SSkCzJNHTz2ZKDcyRwtmvW4mGVLRqdQs+HxfXS4dUS2CcwrAj449se6tZ6NLUMnjko4KMt3KA== - dependencies: - apollo-utilities "^1.3.3" - tslib "^1.10.0" - -apollo-client@^2.6.8: - version "2.6.8" - resolved "https://registry.yarnpkg.com/apollo-client/-/apollo-client-2.6.8.tgz#01cebc18692abf90c6b3806414e081696b0fa537" - integrity sha512-0zvJtAcONiozpa5z5zgou83iEKkBaXhhSSXJebFHRXs100SecDojyUWKjwTtBPn9HbM6o5xrvC5mo9VQ5fgAjw== - dependencies: - "@types/zen-observable" "^0.8.0" - apollo-cache "1.3.4" - apollo-link "^1.0.0" - apollo-utilities "1.3.3" - symbol-observable "^1.0.2" - ts-invariant "^0.4.0" - tslib "^1.10.0" - zen-observable "^0.8.0" - -apollo-link-error@^1.1.13: - version "1.1.13" - resolved "https://registry.yarnpkg.com/apollo-link-error/-/apollo-link-error-1.1.13.tgz#c1a1bb876ffe380802c8df0506a32c33aad284cd" - integrity sha512-jAZOOahJU6bwSqb2ZyskEK1XdgUY9nkmeclCrW7Gddh1uasHVqmoYc4CKdb0/H0Y1J9lvaXKle2Wsw/Zx1AyUg== - dependencies: - apollo-link "^1.2.14" - apollo-link-http-common "^0.2.16" - tslib "^1.9.3" - -apollo-link-http-common@^0.2.14, apollo-link-http-common@^0.2.16: - version "0.2.16" - resolved "https://registry.yarnpkg.com/apollo-link-http-common/-/apollo-link-http-common-0.2.16.tgz#756749dafc732792c8ca0923f9a40564b7c59ecc" - integrity sha512-2tIhOIrnaF4UbQHf7kjeQA/EmSorB7+HyJIIrUjJOKBgnXwuexi8aMecRlqTIDWcyVXCeqLhUnztMa6bOH/jTg== - dependencies: - apollo-link "^1.2.14" - ts-invariant "^0.4.0" - tslib "^1.9.3" - -apollo-link-http@^1.5.17: - version "1.5.17" - resolved "https://registry.yarnpkg.com/apollo-link-http/-/apollo-link-http-1.5.17.tgz#499e9f1711bf694497f02c51af12d82de5d8d8ba" - integrity sha512-uWcqAotbwDEU/9+Dm9e1/clO7hTB2kQ/94JYcGouBVLjoKmTeJTUPQKcJGpPwUjZcSqgYicbFqQSoJIW0yrFvg== - dependencies: - apollo-link "^1.2.14" - apollo-link-http-common "^0.2.16" - tslib "^1.9.3" - -apollo-link-ws@^1.0.20: - version "1.0.20" - resolved "https://registry.yarnpkg.com/apollo-link-ws/-/apollo-link-ws-1.0.20.tgz#dfad44121f8445c6d7b7f8101a1b24813ba008ed" - integrity sha512-mjSFPlQxmoLArpHBeUb2Xj+2HDYeTaJqFGOqQ+I8NVJxgL9lJe84PDWcPah/yMLv3rB7QgBDSuZ0xoRFBPlySw== - dependencies: - apollo-link "^1.2.14" - tslib "^1.9.3" - -apollo-link@^1.0.0: - version "1.2.13" - resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.13.tgz#dff00fbf19dfcd90fddbc14b6a3f9a771acac6c4" - integrity sha512-+iBMcYeevMm1JpYgwDEIDt/y0BB7VWyvlm/7x+TIPNLHCTCMgcEgDuW5kH86iQZWo0I7mNwQiTOz+/3ShPFmBw== - dependencies: - apollo-utilities "^1.3.0" - ts-invariant "^0.4.0" - tslib "^1.9.3" - zen-observable-ts "^0.8.20" - -apollo-link@^1.2.12, apollo-link@^1.2.14: - version "1.2.14" - resolved "https://registry.yarnpkg.com/apollo-link/-/apollo-link-1.2.14.tgz#3feda4b47f9ebba7f4160bef8b977ba725b684d9" - integrity sha512-p67CMEFP7kOG1JZ0ZkYZwRDa369w5PIjtMjvrQd/HnIV8FRsHRqLqK+oAZQnFa1DDdZtOtHTi+aMIW6EatC2jg== - dependencies: - apollo-utilities "^1.3.0" - ts-invariant "^0.4.0" - tslib "^1.9.3" - zen-observable-ts "^0.8.21" - -apollo-upload-client@^13.0.0: - version "13.0.0" - resolved "https://registry.yarnpkg.com/apollo-upload-client/-/apollo-upload-client-13.0.0.tgz#146d1ddd85d711fcac8ca97a72d3ca6787f2b71b" - integrity sha512-lJ9/bk1BH1lD15WhWRha2J3+LrXrPIX5LP5EwiOUHv8PCORp4EUrcujrA3rI5hZeZygrTX8bshcuMdpqpSrvtA== - dependencies: - "@babel/runtime" "^7.9.2" - apollo-link "^1.2.12" - apollo-link-http-common "^0.2.14" - extract-files "^8.0.0" - -apollo-utilities@1.3.3, apollo-utilities@^1.3.0, apollo-utilities@^1.3.3: - version "1.3.3" - resolved "https://registry.yarnpkg.com/apollo-utilities/-/apollo-utilities-1.3.3.tgz#f1854715a7be80cd810bc3ac95df085815c0787c" - integrity sha512-F14aX2R/fKNYMvhuP2t9GD9fggID7zp5I96MF5QeKYWDWTrkRdHRp4+SVfXUVN+cXOaB/IebfvRtzPf25CM0zw== - dependencies: - "@wry/equality" "^0.1.2" - fast-json-stable-stringify "^2.0.0" - ts-invariant "^0.4.0" - tslib "^1.10.0" + "@apollo/client" "^3.1.5" + "@babel/runtime" "^7.11.2" + extract-files "^9.0.0" aproba@^1.0.3, aproba@^1.1.1: version "1.2.0" @@ -3677,6 +3803,14 @@ aria-query@^3.0.0: ast-types-flow "0.0.7" commander "^2.11.0" +aria-query@^4.2.2: + version "4.2.2" + resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b" + integrity sha512-o/HelwhuKpTj/frsOsbNLNgnNGVIFsVP/SW2BSF14gVl7kAfMOJ6/8wUAUvG1R1NHKrfG+2sHZTu0yauT1qBrA== + dependencies: + "@babel/runtime" "^7.10.2" + "@babel/runtime-corejs3" "^7.10.2" + arity-n@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/arity-n/-/arity-n-1.0.4.tgz#d9e76b11733e08569c0847ae7b39b2860b30b745" @@ -3748,7 +3882,7 @@ array-unique@^0.3.2: resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= -array.prototype.flat@^1.2.1: +array.prototype.flat@^1.2.1, array.prototype.flat@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.2.3.tgz#0de82b426b0318dbfdb940089e38b043d37f6c7b" integrity sha512-gBlRZV0VSmfPIeWfuuy56XZMvbVfbEUnOXUvt3F/eUUUSyzlgLxhEX4YAEpxNAogRGehPSnfXyPtYyKAhkzQhQ== @@ -3756,7 +3890,7 @@ array.prototype.flat@^1.2.1: define-properties "^1.1.3" es-abstract "^1.17.0-next.1" -array.prototype.flatmap@1.2.3: +array.prototype.flatmap@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/array.prototype.flatmap/-/array.prototype.flatmap-1.2.3.tgz#1c13f84a178566042dd63de4414440db9222e443" integrity sha512-OOEk+lkePcg+ODXIpvuU9PAryCikCJyo7GlDG1upleEpQRx6mzL9puEBkozQ5iAx20KV0l3DbyQwqciJtqe5Pg== @@ -3811,11 +3945,6 @@ assert@^1.1.1: object-assign "^4.1.1" util "0.10.3" -assertion-error@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.1.0.tgz#e60b6b0e8f301bd97e5375215bda406c85118c0b" - integrity sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw== - assign-symbols@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" @@ -3831,6 +3960,11 @@ astral-regex@^1.0.0: resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-1.0.0.tgz#6c8c3fb827dd43ee3918f27b82782ab7658a6fd9" integrity sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg== +astral-regex@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" + integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== + async-each@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.3.tgz#b727dbf87d7651602f06f4d4ac387f47d91b0cbf" @@ -3886,18 +4020,18 @@ autoprefixer@^9.6.1: postcss "^7.0.26" postcss-value-parser "^4.0.2" -autoprefixer@^9.7.6: - version "9.7.6" - resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.7.6.tgz#63ac5bbc0ce7934e6997207d5bb00d68fa8293a4" - integrity sha512-F7cYpbN7uVVhACZTeeIeealwdGM6wMtfWARVLTy5xmKtgVdBNJvbDRoCK3YO1orcs7gv/KwYlb3iXwu9Ug9BkQ== +autoprefixer@^9.8.6: + version "9.8.6" + resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-9.8.6.tgz#3b73594ca1bf9266320c5acf1588d74dea74210f" + integrity sha512-XrvP4VVHdRBCdX1S3WXVD8+RyG9qeb1D5Sn1DeLiG2xfSpzellk5k54xbUERJ3M5DggQxes39UGOTP8CFrEGbg== dependencies: - browserslist "^4.11.1" - caniuse-lite "^1.0.30001039" - chalk "^2.4.2" + browserslist "^4.12.0" + caniuse-lite "^1.0.30001109" + colorette "^1.2.1" normalize-range "^0.1.2" num2fraction "^1.2.2" - postcss "^7.0.27" - postcss-value-parser "^4.0.3" + postcss "^7.0.32" + postcss-value-parser "^4.1.0" aws-sign2@~0.7.0: version "0.7.0" @@ -3909,12 +4043,17 @@ aws4@^1.8.0: resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.9.0.tgz#24390e6ad61386b0a747265754d2a17219de862c" integrity sha512-Uvq6hVe90D0B2WEnUqtdgY1bATGz3mw33nH9Y+dmA+w5DHvUmBgkr5rM/KCHpCsiFNRUfokW/szpPPgMK2hm4A== -axios@0.19.2: - version "0.19.2" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27" - integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA== +axe-core@^3.5.4: + version "3.5.5" + resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-3.5.5.tgz#84315073b53fa3c0c51676c588d59da09a192227" + integrity sha512-5P0QZ6J5xGikH780pghEdbEKijCTrruK9KxtPZCFWUpef0f6GipO+xEZ5GKCb020mmqgbiNO6TcA55CriL784Q== + +axios@0.20.0: + version "0.20.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.20.0.tgz#057ba30f04884694993a8cd07fa394cff11c50bd" + integrity sha512-ANA4rr2BDcmmAQLOKft2fufrtuvlqR+cXNNinUmvfeSNCOF98PZL+7M/v1zIdGo7OLjEA9J2gXJL+j4zGsl0bA== dependencies: - follow-redirects "1.5.10" + follow-redirects "^1.10.0" axobject-query@^2.0.2: version "2.1.1" @@ -3924,6 +4063,16 @@ axobject-query@^2.0.2: "@babel/runtime" "^7.7.4" "@babel/runtime-corejs3" "^7.7.4" +axobject-query@^2.1.2: + version "2.2.0" + resolved "https://registry.yarnpkg.com/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be" + integrity sha512-Td525n+iPOOyUQIeBfcASuG6uJsDOITl7Mds5gFyerkWiX7qhUTdYUBlSgNMyVqtSJqwpt1kXGLdUt6SykLMRA== + +b64-to-blob@^1.2.19: + version "1.2.19" + resolved "https://registry.yarnpkg.com/b64-to-blob/-/b64-to-blob-1.2.19.tgz#157d85fdc8811665b9a35d29ffbc6a522ba28fbe" + integrity sha512-L3nSu8GgF4iEyNYakCQSfL2F5GI5aCXcot9mNTf+4N0/BMhpxqqHyOb6jIR24iq2xLjQZLG8FOt3gnUcV+9NVg== + babel-code-frame@^6.22.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" @@ -4170,6 +4319,13 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= +base64-blob@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/base64-blob/-/base64-blob-1.4.1.tgz#f8dfc16c22b24ee499e2782719bcce800132c18a" + integrity sha512-n5Ov4cPTbLBTX1PiFbaB5AmK7LMigO9HWh5Lzx+Kcx/yx1MppeeLYtAH8aLv1m++WNoHQnr+xbGSqcZinopwlw== + dependencies: + b64-to-blob "^1.2.19" + base64-js@^1.0.2: version "1.3.1" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1" @@ -4229,7 +4385,7 @@ block-stream@*: dependencies: inherits "~2.0.0" -bluebird@^3.5.1, bluebird@^3.5.5: +bluebird@^3.5.5, bluebird@^3.7.2: version "3.7.2" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.2.tgz#9f229c15be272454ffa973ace0dbee79a1b0c36f" integrity sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg== @@ -4277,10 +4433,10 @@ boolbase@^1.0.0, boolbase@~1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= -bootstrap@^4.4.1: - version "4.4.1" - resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.4.1.tgz#8582960eea0c5cd2bede84d8b0baf3789c3e8b01" - integrity sha512-tbx5cHubwE6e2ZG7nqM3g/FZ5PQEDMWmMGNrCUBVRPHXTJaH7CBDdsLeu3eCh3B1tzAxTnAbtmrzvWEvT2NNEA== +bootstrap@^4.5.2: + version "4.5.2" + resolved "https://registry.yarnpkg.com/bootstrap/-/bootstrap-4.5.2.tgz#a85c4eda59155f0d71186b6e6ad9b875813779ab" + integrity sha512-vlGn0bcySYl/iV+BGA544JkkZP5LB3jsmkeKLFQakCOwCM3AOk7VkldBz4jrzSe+Z0Ezn99NVXa1o45cQY4R6A== bowser@^1.0.0: version "1.9.4" @@ -4423,6 +4579,16 @@ browserslist@^4.11.1, browserslist@^4.9.1: node-releases "^1.1.53" pkg-up "^2.0.0" +browserslist@^4.12.0: + version "4.14.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.14.1.tgz#cb2b490ba881d45dc3039078c7ed04411eaf3fa3" + integrity sha512-zyBTIHydW37pnb63c7fHFXUG6EcqWOqoMdDx6cdyaDFriZ20EoVxcE95S54N+heRqY8m8IUgB5zYta/gCwSaaA== + dependencies: + caniuse-lite "^1.0.30001124" + electron-to-chromium "^1.3.562" + escalade "^3.0.2" + node-releases "^1.1.60" + bser@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" @@ -4472,6 +4638,13 @@ buffer@^5.2.0: base64-js "^1.0.2" ieee754 "^1.1.4" +bufferutil@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/bufferutil/-/bufferutil-4.0.1.tgz#3a177e8e5819a1243fe16b63a199951a7ad8d4a7" + integrity sha512-xowrxvpxojqkagPcWRQVXZl0YXhRhAtBEIq3VoER1NH5Mw1n1o0ojdspp+GS2J//2gCVyrzQDApQ4unGF+QOoA== + dependencies: + node-gyp-build "~3.7.0" + builtin-status-codes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" @@ -4576,7 +4749,7 @@ callsites@^3.0.0: resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73" integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ== -camel-case@4.1.1: +camel-case@4.1.1, camel-case@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/camel-case/-/camel-case-4.1.1.tgz#1fc41c854f00e2f7d0139dfeba1542d6896fe547" integrity sha512-7fa2WcG4fYFkclIvEmxBbTvmibwF2/agfEBc6q3lOpVu0A13ltLsA+Hr/8Hp6kp5f+G7hKi6t8lys6XxP+1K6Q== @@ -4624,11 +4797,6 @@ camelcase@^2.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-2.1.1.tgz#7c1d16d679a1bbe59ca02cacecfb011e201f5a1f" integrity sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8= -camelcase@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" - integrity sha1-MvxLn82vhF/N9+c7uXysImHwqwo= - caniuse-api@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/caniuse-api/-/caniuse-api-3.0.0.tgz#5e4d90e2274961d46291997df599e3ed008ee4c0" @@ -4644,11 +4812,16 @@ caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001020, can resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001023.tgz#b82155827f3f5009077bdd2df3d8968bcbcc6fc4" integrity sha512-C5TDMiYG11EOhVOA62W1p3UsJ2z4DsHtMBQtjzp3ZsUglcQn62WOUgW0y795c7A5uZ+GCEIvzkMatLIlAsbNTA== -caniuse-lite@^1.0.30001035, caniuse-lite@^1.0.30001039, caniuse-lite@^1.0.30001043: +caniuse-lite@^1.0.30001035, caniuse-lite@^1.0.30001043: version "1.0.30001048" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001048.tgz#4bb4f1bc2eb304e5e1154da80b93dee3f1cf447e" integrity sha512-g1iSHKVxornw0K8LG9LLdf+Fxnv7T1Z+mMsf0/YYLclQX4Cd522Ap0Lrw6NFqHgezit78dtyWxzlV2Xfc7vgRg== +caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001124: + version "1.0.30001124" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001124.tgz#5d9998190258e11630d674fc50ea8e579ae0ced2" + integrity sha512-zQW8V3CdND7GHRH6rxm6s59Ww4g/qGWTheoboW9nfeMg7sUoopIfKCcNZUjwYRCOrvereh3kwDpZj4VLQ7zGtA== + capture-exit@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4" @@ -4671,19 +4844,7 @@ ccount@^1.0.0: resolved "https://registry.yarnpkg.com/ccount/-/ccount-1.0.5.tgz#ac82a944905a65ce204eb03023157edf29425c17" integrity sha512-MOli1W+nfbPLlKEhInaxhRdp7KVLFxLN5ykwzHgLsLI3H3gs5jjFAK4Eoj3OzzcxCtumDaI8onoVDeQyWaNTkw== -chai@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/chai/-/chai-4.2.0.tgz#760aa72cf20e3795e84b12877ce0e83737aa29e5" - integrity sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw== - dependencies: - assertion-error "^1.1.0" - check-error "^1.0.2" - deep-eql "^3.0.1" - get-func-name "^2.0.0" - pathval "^1.1.0" - type-detect "^4.0.5" - -chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.4.1, chalk@^2.4.2: +chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.4.1, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -4692,14 +4853,6 @@ chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.4. escape-string-regexp "^1.0.5" supports-color "^5.3.0" -chalk@4.0.0, chalk@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.0.0.tgz#6e98081ed2d17faab615eb52ac66ec1fe6209e72" - integrity sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A== - dependencies: - ansi-styles "^4.1.0" - supports-color "^7.1.0" - chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" @@ -4711,10 +4864,18 @@ chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" -chalk@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/chalk/-/chalk-3.0.0.tgz#3f73c2bf526591f574cc492c51e2456349f844e4" - integrity sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg== +chalk@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.0.0.tgz#6e98081ed2d17faab615eb52ac66ec1fe6209e72" + integrity sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chalk@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.0.tgz#4e14870a618d9e2edd97dd8345fd9d9dc315646a" + integrity sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A== dependencies: ansi-styles "^4.1.0" supports-color "^7.1.0" @@ -4744,26 +4905,6 @@ chardet@^0.7.0: resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA== -check-error@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/check-error/-/check-error-1.0.2.tgz#574d312edd88bb5dd8912e9286dd6c0aed4aac82" - integrity sha1-V00xLt2Iu13YkS6Sht1sCu1KrII= - -chokidar@3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.0.tgz#b30611423ce376357c765b9b8f904b9fba3c0be8" - integrity sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ== - dependencies: - anymatch "~3.1.1" - braces "~3.0.2" - glob-parent "~5.1.0" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.4.0" - optionalDependencies: - fsevents "~2.1.2" - chokidar@^2.0.2, chokidar@^2.1.8: version "2.1.8" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.8.tgz#804b3a7b6a99358c3c5c61e71d8728f041cff917" @@ -4798,6 +4939,21 @@ chokidar@^3.3.0: optionalDependencies: fsevents "~2.1.2" +chokidar@^3.4.2: + version "3.4.2" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.2.tgz#38dc8e658dec3809741eb3ef7bb0a47fe424232d" + integrity sha512-IZHaDeBeI+sZJRX7lGcXsdzgvZqKv6sECqsbErJA4mHWfpRrD8B97kSFN4cQz6nGBGiuFia1MKR4d6c1o8Cv7A== + dependencies: + anymatch "~3.1.1" + braces "~3.0.2" + glob-parent "~5.1.0" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.4.0" + optionalDependencies: + fsevents "~2.1.2" + chownr@^1.1.1, chownr@^1.1.2: version "1.1.3" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.3.tgz#42d837d5239688d55f303003a508230fa6727142" @@ -4877,23 +5033,10 @@ cli-width@^2.0.0: resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639" integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk= -cliui@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" - integrity sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0= - dependencies: - string-width "^1.0.1" - strip-ansi "^3.0.1" - wrap-ansi "^2.0.0" - -cliui@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-4.1.0.tgz#348422dbe82d800b3022eef4f6ac10bf2e4d1b49" - integrity sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ== - dependencies: - string-width "^2.1.1" - strip-ansi "^4.0.0" - wrap-ansi "^2.0.0" +cli-width@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" + integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== cliui@^5.0.0: version "5.0.0" @@ -4904,6 +5047,15 @@ cliui@^5.0.0: strip-ansi "^5.2.0" wrap-ansi "^5.1.0" +cliui@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" + integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^6.2.0" + clone-deep@^0.2.4: version "0.2.4" resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-0.2.4.tgz#4e73dd09e9fb971cc38670c5dced9c1896481cc6" @@ -5003,6 +5155,11 @@ color@^3.0.0: color-convert "^1.9.1" color-string "^1.5.2" +colorette@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b" + integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw== + combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" @@ -5010,11 +5167,6 @@ combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: dependencies: delayed-stream "~1.0.0" -commander@5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-5.1.0.tgz#46abbd1652f8e059bddaef99bbdcb2ad9cf179ae" - integrity sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg== - commander@^2.11.0, commander@^2.20.0: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" @@ -5102,7 +5254,7 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0: resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= -constant-case@3.0.3: +constant-case@3.0.3, constant-case@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/constant-case/-/constant-case-3.0.3.tgz#ac910a99caf3926ac5112f352e3af599d8c5fc0a" integrity sha512-FXtsSnnrFYpzDmvwDGQW+l8XK3GV1coLyBN0eBz16ZUzGaZcT2ANVCJmLeuw2GQgxKHQIe9e0w2dzkSfaRlUmA== @@ -5221,6 +5373,17 @@ cosmiconfig@^5.0.0, cosmiconfig@^5.2.1: js-yaml "^3.13.1" parse-json "^4.0.0" +cosmiconfig@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.0.tgz#ef9b44d773959cae63ddecd122de23853b60f8d3" + integrity sha512-pondGvTuVYDk++upghXJabWzL6Kxu6f26ljFw64Swq9v6sQPUL3EUlVDV56diOjpCayKihL6hVe8exIACU4XcA== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.2.1" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.10.0" + create-ecdh@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff" @@ -5252,21 +5415,12 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: safe-buffer "^5.0.1" sha.js "^2.4.8" -cross-fetch@2.2.2: - version "2.2.2" - resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-2.2.2.tgz#a47ff4f7fc712daba8f6a695a11c948440d45723" - integrity sha1-pH/09/xxLauo9qaVoRyUhEDUVyM= - dependencies: - node-fetch "2.1.2" - whatwg-fetch "2.0.4" - -cross-fetch@3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.0.4.tgz#7bef7020207e684a7638ef5f2f698e24d9eb283c" - integrity sha512-MSHgpjQqgbT/94D4CyADeNoYh52zMkCX4pcJvPP5WqPsLFMKjr2TCMg381ox5qI0ii2dPwaLx/00477knXqXVw== +cross-fetch@3.0.5, cross-fetch@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-3.0.5.tgz#2739d2981892e7ab488a7ad03b92df2816e03f4c" + integrity sha512-FFLcLtraisj5eteosnX1gf01qYDCOc4fDy0+euOt8Kn9YBY2NtXL/pCoYPavw24NIQkQqm5ZOLsGD5Zzj0gyew== dependencies: node-fetch "2.6.0" - whatwg-fetch "3.0.0" cross-spawn@7.0.1: version "7.0.1" @@ -5531,6 +5685,11 @@ csstype@^2.2.0, csstype@^2.5.7, csstype@^2.6.7: resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.8.tgz#0fb6fc2417ffd2816a418c9336da74d7f07db431" integrity sha512-msVS9qTuMT5zwAGCVm4mxfrZ18BNc6Csd0oJAtiFMZ1FAx1CCvy2+5MDmYoix63LM/6NDbNtodCiGYGmFgO0dA== +csstype@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.3.tgz#2b410bbeba38ba9633353aff34b05d9755d065f8" + integrity sha512-jPl+wbWPOWJ7SXsWyqGRk3lGecbar0Cb0OvZF/r/ZU011R4YqiRehgkQ9p4eQfo9DSDLqLL3wHwfxeJiuIsNag== + currently-unhandled@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" @@ -5556,6 +5715,11 @@ damerau-levenshtein@^1.0.4: resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.5.tgz#780cf7144eb2e8dbd1c3bb83ae31100ccc31a414" integrity sha512-CBCRqFnpu715iPmw1KrdOrzRqbdFwQTwAWyyyYS42+iAgHCuXZ+/TdMgQkUENPomxEz9z1BEzuQU2Xw0kUuAgA== +damerau-levenshtein@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz#143c1641cb3d85c60c32329e26899adea8701791" + integrity sha512-JVrozIeElnj3QzfUIt8tB8YMluBJom4Vw9qTPpjGYQ9fYlB3D/rb6OordUxf3xeFB35LKWs0xqcO5U6ySvBtug== + dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" @@ -5572,6 +5736,11 @@ data-urls@^1.0.0, data-urls@^1.1.0: whatwg-mimetype "^2.2.0" whatwg-url "^7.0.0" +dataloader@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dataloader/-/dataloader-2.0.0.tgz#41eaf123db115987e21ca93c005cd7753c55fe6f" + integrity sha512-YzhyDAwA4TaQIhM5go+vCLmU0UikghC/t9DTQYZR2M/UvZ1MdOhPezSDZcjj9uqQJOMqjLcpWtyW2iNINdlatQ== + date-fns@^1.27.2: version "1.30.1" resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c" @@ -5582,7 +5751,7 @@ de-indent@^1.0.2: resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d" integrity sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0= -debounce@1.2.0: +debounce@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/debounce/-/debounce-1.2.0.tgz#44a540abc0ea9943018dc0eaa95cce87f65cd131" integrity sha512-mYtLl1xfZLi1m4RtQYlZgJUNQjl4ZxVnHzIR8nLLgi4q1YT8o/WM+MK/f8yfcc9s5Ir5zRaPZyZU6xs1Syoocg== @@ -5594,27 +5763,20 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6.9: dependencies: ms "2.0.0" -debug@3.1.0, debug@=3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" - integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== - dependencies: - ms "2.0.0" - -debug@^3.0.0, debug@^3.1.0, debug@^3.1.1, debug@^3.2.5: - version "3.2.6" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" - integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== - dependencies: - ms "^2.1.1" - -debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: +debug@4, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== dependencies: ms "^2.1.1" +debug@^3.0.0, debug@^3.1.1, debug@^3.2.5: + version "3.2.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" + integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== + dependencies: + ms "^2.1.1" + decamelize-keys@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/decamelize-keys/-/decamelize-keys-1.1.0.tgz#d171a87933252807eb3cb61dc1c1445d078df2d9" @@ -5623,7 +5785,7 @@ decamelize-keys@^1.1.0: decamelize "^1.1.0" map-obj "^1.0.0" -decamelize@^1.1.0, decamelize@^1.1.1, decamelize@^1.1.2, decamelize@^1.2.0: +decamelize@^1.1.0, decamelize@^1.1.2, decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= @@ -5633,13 +5795,6 @@ decode-uri-component@^0.2.0: resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= -deep-eql@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-3.0.1.tgz#dfc9404400ad1c8fe023e7da1df1c147c4b444df" - integrity sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw== - dependencies: - type-detect "^4.0.0" - deep-equal@^1.0.1: version "1.1.1" resolved "https://registry.yarnpkg.com/deep-equal/-/deep-equal-1.1.1.tgz#b5c98c942ceffaf7cb051e24e1434a25a2e6076a" @@ -5657,11 +5812,6 @@ deep-is@~0.1.3: resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ= -deepmerge@4.2.2: - version "4.2.2" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" - integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== - deepmerge@^2.1.1: version "2.2.1" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-2.2.1.tgz#5d3ff22a01c00f645405a2fbc17d0778a1801170" @@ -5732,16 +5882,11 @@ depd@~1.1.2: resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= -dependency-graph@0.9.0: +dependency-graph@^0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/dependency-graph/-/dependency-graph-0.9.0.tgz#11aed7e203bc8b00f48356d92db27b265c445318" integrity sha512-9YLIBURXj4DJMFALxXw9K3Y3rwb5Fk0X5/8ipCzaN84+gKxoHK43tVKRNakCQbiEx07E8Uwhuq21BpUagFhZ8w== -deprecated-decorator@^0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/deprecated-decorator/-/deprecated-decorator-0.1.6.tgz#00966317b7a12fe92f3cc831f7583af329b86c37" - integrity sha1-AJZjF7ehL+kvPMgx91g68ym4bDc= - des.js@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.1.tgz#5382142e1bdc53f85d86d53e5f4aa7deb91e0843" @@ -5755,7 +5900,7 @@ destroy@~1.0.4: resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA= -detect-indent@6.0.0, detect-indent@^6.0.0: +detect-indent@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.0.0.tgz#0abd0f549f69fc6659a254fe96786186b6f528fd" integrity sha512-oSyFlqaTHCItVRGK5RmrmjB+CmaMOW7IaNA/kdxqhoa6d17j/5ce9O9eWXmV/KEdRwqpQA+Vqe8a8Bsybu4YnA== @@ -5970,16 +6115,11 @@ dotenv-expand@5.1.0: resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== -dotenv@8.2.0: +dotenv@8.2.0, dotenv@^8.2.0: version "8.2.0" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a" integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw== -dotenv@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-4.0.0.tgz#864ef1379aced55ce6f95debecdce179f7a0cd1d" - integrity sha1-hk7xN5rO1Vzm+V3r7NzhefegzR0= - duplexer@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" @@ -6025,6 +6165,11 @@ electron-to-chromium@^1.3.378, electron-to-chromium@^1.3.413: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.427.tgz#ea43d02908a8c71f47ebb46e09de5a3cf8236f04" integrity sha512-/rG5G7Opcw68/Yrb4qYkz07h3bESVRJjUl4X/FrKLXzoUJleKm6D7K7rTTz8V5LUWnd+BbTOyxJX2XprRqHD8A== +electron-to-chromium@^1.3.562: + version "1.3.562" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.562.tgz#79c20277ee1c8d0173a22af00e38433b752bc70f" + integrity sha512-WhRe6liQ2q/w1MZc8mD8INkenHivuHdrr4r5EQHNomy3NJux+incP6M6lDMd0paShP3MD0WGe5R1TWmEClf+Bg== + elegant-spinner@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e" @@ -6053,6 +6198,11 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== +emoji-regex@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.0.0.tgz#48a2309cc8a1d2e9d23bc6a67c39b63032e76ea4" + integrity sha512-6p1NII1Vm62wni/VR/cUMauVQoxmLVb9csqQlvLz+hO2gk8U2UYDfXHQSUYIBKmZwAKz867IDqG7B+u0mj+M6w== + emojis-list@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" @@ -6149,6 +6299,23 @@ es-abstract@^1.17.2: string.prototype.trimleft "^2.1.1" string.prototype.trimright "^2.1.1" +es-abstract@^1.17.5: + version "1.17.6" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.6.tgz#9142071707857b2cacc7b89ecb670316c3e2d52a" + integrity sha512-Fr89bON3WFyUi5EvAeI48QTWX0AyekGgLA8H+c+7fbfCkJwRWRMLd8CQedNEyJuoYYhmtEqY92pgte1FAhBlhw== + dependencies: + es-to-primitive "^1.2.1" + function-bind "^1.1.1" + has "^1.0.3" + has-symbols "^1.0.1" + is-callable "^1.2.0" + is-regex "^1.1.0" + object-inspect "^1.7.0" + object-keys "^1.1.1" + object.assign "^4.1.0" + string.prototype.trimend "^1.0.1" + string.prototype.trimstart "^1.0.1" + es-to-primitive@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" @@ -6176,18 +6343,6 @@ es6-iterator@2.0.3, es6-iterator@~2.0.3: es5-ext "^0.10.35" es6-symbol "^3.1.1" -es6-promise@^4.0.3: - version "4.2.8" - resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a" - integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w== - -es6-promisify@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" - integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= - dependencies: - es6-promise "^4.0.3" - es6-symbol@^3.1.1, es6-symbol@~3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" @@ -6196,6 +6351,11 @@ es6-symbol@^3.1.1, es6-symbol@~3.1.3: d "^1.0.1" ext "^1.1.2" +escalade@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.0.2.tgz#6a580d70edb87880f22b4c91d0d56078df6962c4" + integrity sha512-gPYAU37hYCUhW5euPeR+Y74F7BL+IBsV93j5cvGriSaD1aG6MGsqsV1yamRdrWrb2j3aiZvb0X+UBOWpx3JWtQ== + escape-html@~1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" @@ -6223,14 +6383,14 @@ escodegen@^1.11.0, escodegen@^1.9.1: optionalDependencies: source-map "~0.6.1" -eslint-config-airbnb-base@^14.1.0: - version "14.1.0" - resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.1.0.tgz#2ba4592dd6843258221d9bff2b6831bd77c874e4" - integrity sha512-+XCcfGyCnbzOnktDVhwsCAx+9DmrzEmuwxyHUJpw+kqBVT744OUBrB09khgFKlK1lshVww6qXGsYPZpavoNjJw== +eslint-config-airbnb-base@^14.1.0, eslint-config-airbnb-base@^14.2.0: + version "14.2.0" + resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-14.2.0.tgz#fe89c24b3f9dc8008c9c0d0d88c28f95ed65e9c4" + integrity sha512-Snswd5oC6nJaevs3nZoLSTvGJBvzTfnBqOIArkf3cbyTyq9UD79wOk8s+RiL6bhca0p/eRO6veczhf6A/7Jy8Q== dependencies: confusing-browser-globals "^1.0.9" object.assign "^4.1.0" - object.entries "^1.1.1" + object.entries "^1.1.2" eslint-config-airbnb-typescript@^7.2.1: version "7.2.1" @@ -6242,13 +6402,13 @@ eslint-config-airbnb-typescript@^7.2.1: eslint-config-airbnb-base "^14.1.0" eslint-config-airbnb@^18.1.0: - version "18.1.0" - resolved "https://registry.yarnpkg.com/eslint-config-airbnb/-/eslint-config-airbnb-18.1.0.tgz#724d7e93dadd2169492ff5363c5aaa779e01257d" - integrity sha512-kZFuQC/MPnH7KJp6v95xsLBf63G/w7YqdPfQ0MUanxQ7zcKUNG8j+sSY860g3NwCBOa62apw16J6pRN+AOgXzw== + version "18.2.0" + resolved "https://registry.yarnpkg.com/eslint-config-airbnb/-/eslint-config-airbnb-18.2.0.tgz#8a82168713effce8fc08e10896a63f1235499dcd" + integrity sha512-Fz4JIUKkrhO0du2cg5opdyPKQXOI2MvF8KUvN2710nJMT6jaRUpRE2swrJftAjVGL7T1otLM5ieo5RqS1v9Udg== dependencies: - eslint-config-airbnb-base "^14.1.0" + eslint-config-airbnb-base "^14.2.0" object.assign "^4.1.0" - object.entries "^1.1.1" + object.entries "^1.1.2" eslint-config-prettier@^6.11.0: version "6.11.0" @@ -6272,6 +6432,14 @@ eslint-import-resolver-node@^0.3.2: debug "^2.6.9" resolve "^1.13.1" +eslint-import-resolver-node@^0.3.3: + version "0.3.4" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.4.tgz#85ffa81942c25012d8231096ddf679c03042c717" + integrity sha512-ogtf+5AB/O+nM6DIeBUNr2fuT7ot9Qg/1harBfBtaP13ekEWFQEEMP94BCB7zaNW3gyY+8SHYF00rnqYwXKWOA== + dependencies: + debug "^2.6.9" + resolve "^1.13.1" + eslint-loader@3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/eslint-loader/-/eslint-loader-3.0.3.tgz#e018e3d2722381d982b1201adb56819c73b480ca" @@ -6291,6 +6459,14 @@ eslint-module-utils@^2.4.1: debug "^2.6.9" pkg-dir "^2.0.0" +eslint-module-utils@^2.6.0: + version "2.6.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz#579ebd094f56af7797d19c9866c9c9486629bfa6" + integrity sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA== + dependencies: + debug "^2.6.9" + pkg-dir "^2.0.0" + eslint-plugin-flowtype@4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-4.6.0.tgz#82b2bd6f21770e0e5deede0228e456cb35308451" @@ -6317,24 +6493,25 @@ eslint-plugin-import@2.20.1: resolve "^1.12.0" eslint-plugin-import@^2.20.2: - version "2.20.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.20.2.tgz#91fc3807ce08be4837141272c8b99073906e588d" - integrity sha512-FObidqpXrR8OnCh4iNsxy+WACztJLXAHBO5hK79T1Hc77PgQZkyDGA5Ag9xAvRpglvLNxhH/zSmZ70/pZ31dHg== + version "2.22.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.22.0.tgz#92f7736fe1fde3e2de77623c838dd992ff5ffb7e" + integrity sha512-66Fpf1Ln6aIS5Gr/55ts19eUuoDhAbZgnr6UxK5hbDx6l/QgQgx61AePq+BV4PP2uXQFClgMVzep5zZ94qqsxg== dependencies: - array-includes "^3.0.3" - array.prototype.flat "^1.2.1" + array-includes "^3.1.1" + array.prototype.flat "^1.2.3" contains-path "^0.1.0" debug "^2.6.9" doctrine "1.5.0" - eslint-import-resolver-node "^0.3.2" - eslint-module-utils "^2.4.1" + eslint-import-resolver-node "^0.3.3" + eslint-module-utils "^2.6.0" has "^1.0.3" minimatch "^3.0.4" - object.values "^1.1.0" + object.values "^1.1.1" read-pkg-up "^2.0.0" - resolve "^1.12.0" + resolve "^1.17.0" + tsconfig-paths "^3.9.0" -eslint-plugin-jsx-a11y@6.2.3, eslint-plugin-jsx-a11y@^6.2.3: +eslint-plugin-jsx-a11y@6.2.3: version "6.2.3" resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.2.3.tgz#b872a09d5de51af70a97db1eea7dc933043708aa" integrity sha512-CawzfGt9w83tyuVekn0GDPU9ytYtxyxyFZ3aSWROmnRRFQFT2BiPJd7jvRdzNDi6oLWaS2asMeYSNMjWTV4eNg== @@ -6349,17 +6526,34 @@ eslint-plugin-jsx-a11y@6.2.3, eslint-plugin-jsx-a11y@^6.2.3: has "^1.0.3" jsx-ast-utils "^2.2.1" +eslint-plugin-jsx-a11y@^6.2.3: + version "6.3.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.3.1.tgz#99ef7e97f567cc6a5b8dd5ab95a94a67058a2660" + integrity sha512-i1S+P+c3HOlBJzMFORRbC58tHa65Kbo8b52/TwCwSKLohwvpfT5rm2GjGWzOHTEuq4xxf2aRlHHTtmExDQOP+g== + dependencies: + "@babel/runtime" "^7.10.2" + aria-query "^4.2.2" + array-includes "^3.1.1" + ast-types-flow "^0.0.7" + axe-core "^3.5.4" + axobject-query "^2.1.2" + damerau-levenshtein "^1.0.6" + emoji-regex "^9.0.0" + has "^1.0.3" + jsx-ast-utils "^2.4.1" + language-tags "^1.0.5" + eslint-plugin-react-hooks@^1.6.1: version "1.7.0" resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-1.7.0.tgz#6210b6d5a37205f0b92858f895a4e827020a7d04" integrity sha512-iXTCFcOmlWvw4+TOE8CLWj6yX1GwzT0Y6cUfHHZqWnSk144VmVIRcVGtUAzrLES7C798lmvnt02C7rxaOX1HNA== eslint-plugin-react-hooks@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.0.0.tgz#81196b990043cde339e25c6662aeebe32ac52d01" - integrity sha512-YKBY+kilK5wrwIdQnCF395Ya6nDro3EAMoe+2xFkmyklyhF16fH83TrQOo9zbZIDxBsXFgBbywta/0JKRNFDkw== + version "4.1.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.1.0.tgz#6323fbd5e650e84b2987ba76370523a60f4e7925" + integrity sha512-36zilUcDwDReiORXmcmTc6rRumu9JIM3WjSvV0nclHoUQ0CNrX866EwONvLR/UqaeqFutbAnVu8PEmctdo2SRQ== -eslint-plugin-react@7.19.0, eslint-plugin-react@^7.19.0: +eslint-plugin-react@7.19.0: version "7.19.0" resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.19.0.tgz#6d08f9673628aa69c5559d33489e855d83551666" integrity sha512-SPT8j72CGuAP+JFbT0sJHOB80TX/pu44gQ4vXH/cq+hQTiY2PuZ6IHkqXJV6x1b28GDdo1lbInjKUrrdUf0LOQ== @@ -6377,6 +6571,23 @@ eslint-plugin-react@7.19.0, eslint-plugin-react@^7.19.0: string.prototype.matchall "^4.0.2" xregexp "^4.3.0" +eslint-plugin-react@^7.19.0: + version "7.20.6" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.20.6.tgz#4d7845311a93c463493ccfa0a19c9c5d0fd69f60" + integrity sha512-kidMTE5HAEBSLu23CUDvj8dc3LdBU0ri1scwHBZjI41oDv4tjsWZKU7MQccFzH1QYPYhsnTF2ovh7JlcIcmxgg== + dependencies: + array-includes "^3.1.1" + array.prototype.flatmap "^1.2.3" + doctrine "^2.1.0" + has "^1.0.3" + jsx-ast-utils "^2.4.1" + object.entries "^1.1.2" + object.fromentries "^2.0.2" + object.values "^1.1.1" + prop-types "^15.7.2" + resolve "^1.17.0" + string.prototype.matchall "^4.0.2" + eslint-scope@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" @@ -6679,10 +6890,10 @@ extglob@^2.0.4: snapdragon "^0.8.1" to-regex "^3.0.1" -extract-files@^8.0.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/extract-files/-/extract-files-8.1.0.tgz#46a0690d0fe77411a2e3804852adeaa65cd59288" - integrity sha512-PTGtfthZK79WUMk+avLmwx3NGdU8+iVFXC2NMGxKsn0MnihOG2lvumj+AZo8CTwTrwjXDgZ5tztbRlEdRjBonQ== +extract-files@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/extract-files/-/extract-files-9.0.0.tgz#8a7744f2437f81f5ed3250ed9f1550de902fe54a" + integrity sha512-CvdFfHkC95B4bBBk36hcEmvdR2awOdhhVUYH6S/zrVj3477zven/fJMYg7121h4T1xHZC+tetUpubpAhxwI7hQ== extract-react-intl-messages@^4.1.1: version "4.1.1" @@ -6715,11 +6926,6 @@ extsprintf@^1.2.0: resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= -fast-deep-equal@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614" - integrity sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ= - fast-deep-equal@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" @@ -6730,7 +6936,7 @@ fast-deep-equal@^3.1.1: resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4" integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA== -fast-glob@^2.0.2, fast-glob@^2.2.2: +fast-glob@^2.0.2: version "2.2.7" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-2.2.7.tgz#6953857c3afa475fff92ee6015d52da70a4cd39d" integrity sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw== @@ -6753,6 +6959,18 @@ fast-glob@^3.1.1: merge2 "^1.3.0" micromatch "^4.0.2" +fast-glob@^3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.4.tgz#d20aefbf99579383e7f3cc66529158c9b98554d3" + integrity sha512-kr/Oo6PX51265qeuCYsyGypiO5uJFgBS0jksyG7FUeCyQzNwYnzrNIMR1NXfkZXsMYXYLRAHgISHBz8gQcxKHQ== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.0" + merge2 "^1.3.0" + micromatch "^4.0.2" + picomatch "^2.2.1" + fast-json-stable-stringify@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" @@ -6763,6 +6981,16 @@ fast-levenshtein@~2.0.6: resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= +fast-memoize@^2.5.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/fast-memoize/-/fast-memoize-2.5.2.tgz#79e3bb6a4ec867ea40ba0e7146816f6cdce9b57e" + integrity sha512-Ue0LwpDYErFbmNnZSF0UH6eImUwDmogUO1jyE+JbN2gsQz/jICm1Ve7t9QT0rNSsfJt+Hs4/S3GnsDVjL4HVrw== + +fastest-levenshtein@^1.0.12: + version "1.0.12" + resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz#9990f7d3a88cc5a9ffd1f1745745251700d497e2" + integrity sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow== + fastq@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.6.0.tgz#4ec8a38f4ac25f21492673adb7eae9cfef47d1c2" @@ -6915,13 +7143,13 @@ find-cache-dir@^2.1.0: make-dir "^2.0.0" pkg-dir "^3.0.0" -find-cache-dir@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.2.0.tgz#e7fe44c1abc1299f516146e563108fd1006c1874" - integrity sha512-1JKclkYYsf1q9WIJKLZa9S9muC+08RIjzAlLrK4QcYLJMS6mk9yombQ9qf+zJ7H9LS800k0s44L4sDq9VYzqyg== +find-cache-dir@^3.3.1: + version "3.3.1" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.1.tgz#89b33fad4a4670daa94f855f7fbe31d6d84fe880" + integrity sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ== dependencies: commondir "^1.0.1" - make-dir "^3.0.0" + make-dir "^3.0.2" pkg-dir "^4.1.0" find-root@^1.1.0: @@ -6959,10 +7187,10 @@ find-up@^3.0.0: dependencies: locate-path "^3.0.0" -flag-icon-css@^3.4.6: - version "3.4.6" - resolved "https://registry.yarnpkg.com/flag-icon-css/-/flag-icon-css-3.4.6.tgz#7e51099c85648c65f86d9ebb9c0ec6f5d8826714" - integrity sha512-rF69rt19Hr63SRQTiPBzQABaYB20LAgZhDkr/AxqSdgmCIN+tC5PRMz56Y0gxehFXJmdRwv55+GMi7R1fCRTwg== +flag-icon-css@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/flag-icon-css/-/flag-icon-css-3.5.0.tgz#430747d5cb91e60babf85494de99173c16dc7cf2" + integrity sha512-pgJnJLrtb0tcDgU1fzGaQXmR8h++nXvILJ+r5SmOXaaL/2pocunQo2a8TAXhjQnBpRLPtZ1KCz/TYpqeNuE2ew== flat-cache@^2.0.1: version "2.0.1" @@ -6990,6 +7218,11 @@ flatten@^1.0.2: resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b" integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg== +flexbin@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/flexbin/-/flexbin-0.2.0.tgz#0126306d3d595fcb7dfcb87149b9c9599ff8f4e9" + integrity sha1-ASYwbT1ZX8t9/LhxSbnJWZ/49Ok= + flush-write-stream@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8" @@ -6998,13 +7231,6 @@ flush-write-stream@^1.0.0: inherits "^2.0.3" readable-stream "^2.3.6" -follow-redirects@1.5.10: - version "1.5.10" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a" - integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ== - dependencies: - debug "=3.1.0" - follow-redirects@^1.0.0: version "1.10.0" resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.10.0.tgz#01f5263aee921c6a54fb91667f08f4155ce169eb" @@ -7012,6 +7238,11 @@ follow-redirects@^1.0.0: dependencies: debug "^3.0.0" +follow-redirects@^1.10.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db" + integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA== + for-in@^0.1.3: version "0.1.8" resolved "https://registry.yarnpkg.com/for-in/-/for-in-0.1.8.tgz#d8773908e31256109952b1fdb9b3fa867d2775e1" @@ -7066,10 +7297,10 @@ form-data@~2.3.2: combined-stream "^1.0.6" mime-types "^2.1.12" -formik@^2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/formik/-/formik-2.1.4.tgz#8deef07ec845ea98f75e03da4aad7aab4ac46570" - integrity sha512-oKz8S+yQBzuQVSEoxkqqJrKQS5XJASWGVn6mrs+oTWrBoHgByVwwI1qHiVc9GKDpZBU9vAxXYAKz2BvujlwunA== +formik@^2.1.5: + version "2.1.5" + resolved "https://registry.yarnpkg.com/formik/-/formik-2.1.5.tgz#de5bbbe35543fa6d049fe96b8ee329d6cd6892b8" + integrity sha512-bWpo3PiqVDYslvrRjTq0Isrm0mFXHiO33D8MS6t6dWcqSFGeYF52nlpCM2xwOJ6tRVRznDkL+zz/iHPL4LDuvQ== dependencies: deepmerge "^2.1.1" hoist-non-react-statics "^3.3.0" @@ -7105,6 +7336,16 @@ from2@^2.1.0: inherits "^2.0.1" readable-stream "^2.0.0" +fs-extra@9.0.1: + version "9.0.1" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.0.1.tgz#910da0062437ba4c39fedd863f1675ccfefcb9fc" + integrity sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ== + dependencies: + at-least-node "^1.0.0" + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^1.0.0" + fs-extra@^4.0.2: version "4.0.3" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.3.tgz#0d852122e5bc5beb453fb028e9c0c9bf36340c94" @@ -7177,6 +7418,11 @@ fsevents@^1.2.7: bindings "^1.5.0" nan "^2.12.1" +fslightbox-react@^1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/fslightbox-react/-/fslightbox-react-1.5.0.tgz#07cf41d7ff8b02a79a0886d13519550b79dc50e5" + integrity sha512-xBe1K06pa3opWar/xBtArsHMnxMJWsmg5EmNdDtheDL9nMCqk2AXYlNnstfYVqtJJjqNReqeL21wc52Yy4rwWg== + fstream@^1.0.0, fstream@^1.0.12: version "1.0.12" resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045" @@ -7223,21 +7469,11 @@ gensync@^1.0.0-beta.1: resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269" integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg== -get-caller-file@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" - integrity sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w== - get-caller-file@^2.0.1: version "2.0.5" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== -get-func-name@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41" - integrity sha1-6td0q+5y4gQJQzoGY2YCPdaIekE= - get-own-enumerable-property-symbols@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664" @@ -7253,10 +7489,10 @@ get-stdin@^6.0.0: resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b" integrity sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g== -get-stdin@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-7.0.0.tgz#8d5de98f15171a125c5e516643c7a6d0ea8a96f6" - integrity sha512-zRKcywvrXlXsA0v0i9Io4KDRaAw7+a1ZpjRwl9Wox8PFlVCCHra7E9c4kqXCoCM9nR5tBkaTTZRBoCm60bFqTQ== +get-stdin@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-8.0.0.tgz#cbad6a73feb75f6eeb22ba9e01f89aa28aa97a53" + integrity sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg== get-stream@^4.0.0: version "4.1.0" @@ -7277,6 +7513,14 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" +gifwrap@^0.9.2: + version "0.9.2" + resolved "https://registry.yarnpkg.com/gifwrap/-/gifwrap-0.9.2.tgz#348e286e67d7cf57942172e1e6f05a71cee78489" + integrity sha512-fcIswrPaiCDAyO8xnWvHSZdWChjKXUanKKpAiWWJ/UTkEi/aYKn5+90e7DE820zbEaVR9CE2y4z9bzhQijZ0BA== + dependencies: + image-q "^1.1.1" + omggif "^1.0.10" + glob-parent@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" @@ -7297,7 +7541,7 @@ glob-to-regexp@^0.3.0: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" integrity sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs= -glob@7.1.6, glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@~7.1.1: +glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@~7.1.1: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== @@ -7345,10 +7589,10 @@ globals@^12.1.0: dependencies: type-fest "^0.8.1" -globby@11.0.0, globby@^11.0.0: - version "11.0.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.0.tgz#56fd0e9f0d4f8fb0c456f1ab0dee96e1380bc154" - integrity sha512-iuehFnR3xu5wBBtm4xi0dMe92Ob87ufyu/dHwpDYfbcpYpIbrO5OnS8M1vWvrBhSGEJ3/Ecj7gnX76P8YxpPEg== +globby@11.0.1, globby@^11.0.1: + version "11.0.1" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.1.tgz#9a2bf107a068f3ffeabc49ad702c79ede8cfd357" + integrity sha512-iH9RmgwCmUJHi2z5o2l3eTtGBtXek1OYlHrbcxOYugyHLmAsZrPj43OtHThd62Buh/Vv6VyCBD2bdyWcGNQqoQ== dependencies: array-union "^2.1.0" dir-glob "^3.0.1" @@ -7407,64 +7651,46 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6 resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== -graphql-config@3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/graphql-config/-/graphql-config-3.0.1.tgz#13101175c31b0ec2d893fe699af33bbb2f38c7ee" - integrity sha512-RKktfOcMAh/Lg7jpXXR/u1yOlgWF+bvSUP1wT2aGeUnKfm0B4tu9SBoOAAtt2Vf/bIyOGUSKYkMzqQOiBLTuFw== +graphql-config@^3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/graphql-config/-/graphql-config-3.0.3.tgz#58907c65ed7d6e04132321450b60e57863ea9a5f" + integrity sha512-MBY0wEjvcgJtZUyoqpPvOE1e5qPI0hJaa1gKTqjonSFiCsNHX2lykNjpOPcodmAgH1V06ELxhGnm9kcVzqvi/g== dependencies: - "@graphql-toolkit/common" "~0.10.6" - "@graphql-toolkit/core" "~0.10.6" - "@graphql-toolkit/graphql-file-loader" "~0.10.6" - "@graphql-toolkit/json-file-loader" "~0.10.6" - "@graphql-toolkit/schema-merging" "~0.10.6" - "@graphql-toolkit/url-loader" "~0.10.6" + "@graphql-tools/graphql-file-loader" "^6.0.0" + "@graphql-tools/json-file-loader" "^6.0.0" + "@graphql-tools/load" "^6.0.0" + "@graphql-tools/merge" "^6.0.0" + "@graphql-tools/url-loader" "^6.0.0" + "@graphql-tools/utils" "^6.0.0" cosmiconfig "6.0.0" minimatch "3.0.4" - tslib "^1.11.1" + string-env-interpolation "1.0.1" + tslib "^2.0.0" -graphql-request@^1.5.0: - version "1.8.2" - resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-1.8.2.tgz#398d10ae15c585676741bde3fc01d5ca948f8fbe" - integrity sha512-dDX2M+VMsxXFCmUX0Vo0TopIZIX4ggzOtiCsThgtrKR4niiaagsGTDIHj3fsOMFETpa064vzovI+4YV4QnMbcg== +graphql-request@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/graphql-request/-/graphql-request-3.1.0.tgz#c487488a1aa7b9a0f02335026b4ec897d645f9d4" + integrity sha512-Flg2Bd4Ek9BDJ5qacZC/iYuiS3LroHxQTmlUnfqjo/6jKwowY25FVtoLTnssMCBrYspRYEYEIfF1GN8J3/o5JQ== dependencies: - cross-fetch "2.2.2" - -graphql-tag@2.10.3, graphql-tag@^2.10.3: - version "2.10.3" - resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.10.3.tgz#ea1baba5eb8fc6339e4c4cf049dabe522b0edf03" - integrity sha512-4FOv3ZKfA4WdOKJeHdz6B3F/vxBLSgmBcGeAFPf4n1F64ltJUvOOerNj0rsJxONQGdhUMynQIvd6LzB+1J5oKA== - -graphql-tools@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/graphql-tools/-/graphql-tools-5.0.0.tgz#67281c834a0e29f458adba8018f424816fa627e9" - integrity sha512-5zn3vtn//382b7G3Wzz3d5q/sh+f7tVrnxeuhTMTJ7pWJijNqLxH7VEzv8VwXCq19zAzHYEosFHfXiK7qzvk7w== - dependencies: - apollo-link "^1.2.14" - apollo-upload-client "^13.0.0" - deprecated-decorator "^0.1.6" + cross-fetch "^3.0.5" + extract-files "^9.0.0" form-data "^3.0.0" - iterall "^1.3.0" - node-fetch "^2.6.0" - tslib "^1.11.1" - uuid "^7.0.3" -graphql@^14.5.8: - version "14.6.0" - resolved "https://registry.yarnpkg.com/graphql/-/graphql-14.6.0.tgz#57822297111e874ea12f5cd4419616930cd83e49" - integrity sha512-VKzfvHEKybTKjQVpTFrA5yUq2S9ihcZvfJAtsDBBCuV6wauPu1xl/f9ehgVf0FcEJJs4vz6ysb/ZMkGigQZseg== - dependencies: - iterall "^1.2.2" +graphql-tag@^2.11.0: + version "2.11.0" + resolved "https://registry.yarnpkg.com/graphql-tag/-/graphql-tag-2.11.0.tgz#1deb53a01c46a7eb401d6cb59dec86fa1cccbffd" + integrity sha512-VmsD5pJqWJnQZMUeRwrDhfgoyqcfwEkvtpANqcoUG8/tOLkwNgU9mzub/Mc78OJMhHjx7gfAMTxzdG43VGg3bA== + +graphql@^15.3.0: + version "15.3.0" + resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.3.0.tgz#3ad2b0caab0d110e3be4a5a9b2aa281e362b5278" + integrity sha512-GTCJtzJmkFLWRfFJuoo9RWWa/FfamUHgiFosxi/X1Ani4AVWbeyBenZTNX6dM+7WSbbFfTo/25eh0LLkwHMw2w== growly@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE= -gud@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/gud/-/gud-1.0.0.tgz#a489581b17e6a70beca9abe3ae57de7a499852c0" - integrity sha512-zGEOVKFM5sVPPrYs7J5/hYEw2Pof8KCyOwyhG8sAF26mCAeUFAcYPu1mwB7hhpIP29zOIBaDqwuHdLp0jvZXjw== - gzip-size@5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/gzip-size/-/gzip-size-5.1.1.tgz#cb9bee692f87c0612b232840a873904e4c135274" @@ -7664,10 +7890,10 @@ html-encoding-sniffer@^1.0.2: dependencies: whatwg-encoding "^1.0.1" -html-entities@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.2.1.tgz#0df29351f0721163515dfb9e5543e5f6eed5162f" - integrity sha1-DfKTUfByEWNRXfueVUPl9u7VFi8= +html-entities@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-1.3.1.tgz#fb9a1a4b5b14c5daba82d3e34c6ae4fe701a0e44" + integrity sha512-rhE/4Z3hIhzHAUKbW8jVcCyuT5oJCXXqhN/6mXXVCpzTmvJnoH2HL/bt3EZ6p55jbFJBeAe1ZNpL5BugLujxNA== html-escaper@^2.0.0: version "2.0.0" @@ -7778,13 +8004,14 @@ http-errors@~1.7.2: resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.10.tgz#92c9c1374c35085f75db359ec56cc257cbb93fa4" integrity sha1-ksnBN0w1CF912zWexWzCV8u5P6Q= -http-proxy-agent@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405" - integrity sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg== +http-proxy-agent@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" + integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== dependencies: - agent-base "4" - debug "3.1.0" + "@tootallnate/once" "1" + agent-base "6" + debug "4" http-proxy-middleware@0.19.1: version "0.19.1" @@ -7819,23 +8046,23 @@ https-browserify@^1.0.0: resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= -https-proxy-agent@^2.2.1: - version "2.2.4" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b" - integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg== +https-proxy-agent@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2" + integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA== dependencies: - agent-base "^4.3.0" - debug "^3.1.0" + agent-base "6" + debug "4" hyphenate-style-name@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.3.tgz#097bb7fa0b8f1a9cf0bd5c734cf95899981a9b48" integrity sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ== -i18n-iso-countries@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/i18n-iso-countries/-/i18n-iso-countries-5.2.0.tgz#3384c5b91c09b76035c7726e54fd941f9b61e9e1" - integrity sha512-U14vanOZJrNNm6yAL26qyZcOhlyJLmondS4rSWzT+JQzotkyTAkRv8mrJzzJf4EewqrRBVtzqFAH0PnSJm2AQw== +i18n-iso-countries@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/i18n-iso-countries/-/i18n-iso-countries-6.0.0.tgz#b707941d4cce34c9d5f21033c6cc733acbbbd7b8" + integrity sha512-2YOLRUNrnOq/sVchB6PfOgZ4E0rRMfxxy+QMhjv+2fiJHMidmmmb24UPHwXmzxyybB8mFPU+/uE46ebLOJIrpQ== dependencies: diacritics "1.3.0" @@ -7885,6 +8112,16 @@ ignore@^5.1.4: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.4.tgz#84b7b3dbe64552b6ef0eca99f6743dbec6d97adf" integrity sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A== +ignore@^5.1.8: + version "5.1.8" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.8.tgz#f150a8b50a34289b33e22f5889abd4d8016f0e57" + integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== + +image-q@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/image-q/-/image-q-1.1.1.tgz#fc84099664460b90ca862d9300b6bfbbbfbf8056" + integrity sha1-/IQJlmRGC5DKhi2TALa/u7+/gFY= + immediate@~3.0.5: version "3.0.6" resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b" @@ -7915,7 +8152,7 @@ import-fresh@^2.0.0: caller-path "^2.0.0" resolve-from "^3.0.0" -import-fresh@^3.0.0, import-fresh@^3.1.0: +import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66" integrity sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ== @@ -7923,7 +8160,7 @@ import-fresh@^3.0.0, import-fresh@^3.1.0: parent-module "^1.0.0" resolve-from "^4.0.0" -import-from@3.0.0, import-from@^3.0.0: +import-from@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/import-from/-/import-from-3.0.0.tgz#055cfec38cd5a27d8057ca51376d7d3bf0891966" integrity sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ== @@ -7960,11 +8197,6 @@ in-publish@^2.0.0: resolved "https://registry.yarnpkg.com/in-publish/-/in-publish-2.0.0.tgz#e20ff5e3a2afc2690320b6dc552682a9c7fadf51" integrity sha1-4g/146KvwmkDILbcVSaCqcf631E= -indent-string@4.0.0, indent-string@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" - integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== - indent-string@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" @@ -7977,6 +8209,11 @@ indent-string@^3.0.0: resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289" integrity sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok= +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + indexes-of@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/indexes-of/-/indexes-of-1.0.1.tgz#f30f716c8e2bd346c7b67d3df3915566a7c05607" @@ -8042,25 +8279,6 @@ inquirer@7.0.4: strip-ansi "^5.1.0" through "^2.3.6" -inquirer@7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.1.0.tgz#1298a01859883e17c7264b82870ae1034f92dd29" - integrity sha512-5fJMWEmikSYu0nv/flMc475MhGbB7TSPd/2IpFV4I4rMklboCH2rQjYY5kKiYGHqUF9gvaambupcJFFG9dvReg== - dependencies: - ansi-escapes "^4.2.1" - chalk "^3.0.0" - cli-cursor "^3.1.0" - cli-width "^2.0.0" - external-editor "^3.0.3" - figures "^3.0.0" - lodash "^4.17.15" - mute-stream "0.0.8" - run-async "^2.4.0" - rxjs "^6.5.3" - string-width "^4.1.0" - strip-ansi "^6.0.0" - through "^2.3.6" - inquirer@^7.0.0: version "7.0.3" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.0.3.tgz#f9b4cd2dff58b9f73e8d43759436ace15bed4567" @@ -8080,6 +8298,25 @@ inquirer@^7.0.0: strip-ansi "^5.1.0" through "^2.3.6" +inquirer@^7.3.3: + version "7.3.3" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.3.3.tgz#04d176b2af04afc157a83fd7c100e98ee0aad003" + integrity sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA== + dependencies: + ansi-escapes "^4.2.1" + chalk "^4.1.0" + cli-cursor "^3.1.0" + cli-width "^3.0.0" + external-editor "^3.0.3" + figures "^3.0.0" + lodash "^4.17.19" + mute-stream "0.0.8" + run-async "^2.4.0" + rxjs "^6.6.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + through "^2.3.6" + internal-ip@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-4.3.0.tgz#845452baad9d2ca3b69c635a137acb9a0dad0907" @@ -8097,14 +8334,6 @@ internal-slot@^1.0.2: has "^1.0.3" side-channel "^1.0.2" -intl-format-cache@^4.2.26: - version "4.2.26" - resolved "https://registry.yarnpkg.com/intl-format-cache/-/intl-format-cache-4.2.26.tgz#ba5e2ee6cec25217f688b68ecdd58eec3703a827" - integrity sha512-RalEzK89R3rJrOo7vcGY8h1WLypF1ZRQQldIsrQM6FTEPixvHb+pAEhd2QkdUk972hFjAEBJR02GdHhaEw9v2g== - dependencies: - "@types/chai" "^4.2.11" - chai "^4.2.0" - intl-messageformat-parser@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/intl-messageformat-parser/-/intl-messageformat-parser-5.0.2.tgz#878c0d66459b366f4135a812007a873789875b95" @@ -8112,13 +8341,20 @@ intl-messageformat-parser@^5.0.2: dependencies: "@formatjs/intl-unified-numberformat" "^3.3.5" -intl-messageformat@^8.3.9: - version "8.3.9" - resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-8.3.9.tgz#fa57e6f5abdd4b5ad03dd767c965435bd38cbd78" - integrity sha512-WHIopaMiZ14UJ76d14FfqbeNE3knGJT7pJg6eJVxh1G5ziL656BqfQk6dYxPZ2VvoaY7wnT3dLlIXy1MTE0blw== +intl-messageformat-parser@^6.0.5: + version "6.0.5" + resolved "https://registry.yarnpkg.com/intl-messageformat-parser/-/intl-messageformat-parser-6.0.5.tgz#098b052ac2714101b4da06fd45d68199d3abd131" + integrity sha512-4aO/RTUtzWiV/naqif4ubwz8P7THOxhraN6XmQpgXj4mdGjtPNO2j3vKlEDgAvv4BEi12R/JCHfLf7SUyfPKog== dependencies: - intl-format-cache "^4.2.26" - intl-messageformat-parser "^5.0.2" + "@formatjs/ecma402-abstract" "^1.2.0" + +intl-messageformat@^9.3.6: + version "9.3.6" + resolved "https://registry.yarnpkg.com/intl-messageformat/-/intl-messageformat-9.3.6.tgz#6b15bca5ebbd81808cf703423c34fb789cf1da8e" + integrity sha512-ZmaPVtB1i0Ao64sI+kCl+uAqlHGn1KyHHPYw2W/cd4q00ACDBpdeqeD3y4tQnMXMGZriwbSn90dJ+bvSkQr1dA== + dependencies: + fast-memoize "^2.5.2" + intl-messageformat-parser "^6.0.5" invariant@^2.2.2, invariant@^2.2.4: version "2.2.4" @@ -8127,16 +8363,6 @@ invariant@^2.2.2, invariant@^2.2.4: dependencies: loose-envify "^1.0.0" -invert-kv@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" - integrity sha1-EEqOSqym09jNFXqO+L+rLXo//bY= - -invert-kv@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02" - integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA== - ip-regex@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" @@ -8251,6 +8477,11 @@ is-callable@^1.1.4, is-callable@^1.1.5: resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab" integrity sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q== +is-callable@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.0.tgz#83336560b54a38e35e3a2df7afd0454d691468bb" + integrity sha512-pyVD9AaGLxtg6srb2Ng6ynWJqkHU9bEM087AKck0w8QwDarTfNcpIYoU8x8Hv2Icm8u6kFJM18Dag8lyqGkviw== + is-ci@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" @@ -8452,6 +8683,11 @@ is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" +is-promise@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-4.0.0.tgz#42ff9f84206c1991d26debf520dd5c01042dd2f3" + integrity sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ== + is-promise@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" @@ -8464,6 +8700,13 @@ is-regex@^1.0.4, is-regex@^1.0.5: dependencies: has "^1.0.3" +is-regex@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.1.tgz#c6f98aacc546f6cec5468a07b7b153ab564a57b9" + integrity sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg== + dependencies: + has-symbols "^1.0.1" + is-regexp@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-regexp/-/is-regexp-1.0.0.tgz#fd2d883545c46bac5a633e7b9a09e87fa2cb5069" @@ -8649,11 +8892,6 @@ iterall@^1.2.1: resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.2.2.tgz#92d70deb8028e0c39ff3164fdbf4d8b088130cd7" integrity sha512-yynBb1g+RFUPY64fTrFv7nsjRrENBQJaX2UL+2Szc9REFrSNm1rpSXHGzhmAy7a9uv3vlvgBlXnf9RqmPH1/DA== -iterall@^1.2.2, iterall@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.3.0.tgz#afcb08492e2915cbd8a0884eb93a8c94d0d72fea" - integrity sha512-QZ9qOMdF+QLHxy1QIpUHUU1D5pS2CG2P69LF6L6CPjPYA/XMOmKV3PZpawHoAjHNyB0swdVTRxdYT4tbBbxqwg== - jest-changed-files@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-24.9.0.tgz#08d8c15eb79a7fa3fc98269bc14b451ee82f8039" @@ -9025,10 +9263,10 @@ jest-worker@^24.6.0, jest-worker@^24.9.0: merge-stream "^2.0.0" supports-color "^6.1.0" -jest-worker@^25.1.0: - version "25.1.0" - resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-25.1.0.tgz#75d038bad6fdf58eba0d2ec1835856c497e3907a" - integrity sha512-ZHhHtlxOWSxCoNOKHGbiLzXnl42ga9CxDr27H36Qn+15pQZd3R/F24jrmjDelw9j/iHUIWMWs08/u2QN50HHOg== +jest-worker@^25.4.0: + version "25.5.0" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-25.5.0.tgz#2611d071b79cea0f43ee57a3d118593ac1547db1" + integrity sha512-/dsSmUkIy5EBGfv/IjjqmFxrNAUpBERfGs1oHROyD7yxjG/w+t0GOJDX8O1k32ySmd7+a5IhnJU2qQFcJ4n1vw== dependencies: merge-stream "^2.0.0" supports-color "^7.0.0" @@ -9041,21 +9279,21 @@ jest@24.9.0: import-local "^2.0.0" jest-cli "^24.9.0" -jimp@^0.12.1: - version "0.12.1" - resolved "https://registry.yarnpkg.com/jimp/-/jimp-0.12.1.tgz#3e58fdd16ebb2b8f00a09be3dd5c54f79ffae04a" - integrity sha512-0soPJif+yjmzmOF+4cF2hyhxUWWpXpQntsm2joJXFFoRcQiPzsG4dbLKYqYPT3Fc6PjZ8MaLtCkDqqckVSfmRw== +jimp@^0.16.1: + version "0.16.1" + resolved "https://registry.yarnpkg.com/jimp/-/jimp-0.16.1.tgz#192f851a30e5ca11112a3d0aa53137659a78ca7a" + integrity sha512-+EKVxbR36Td7Hfd23wKGIeEyHbxShZDX6L8uJkgVW3ESA9GiTEPK08tG1XI2r/0w5Ch0HyJF5kPqF9K7EmGjaw== dependencies: "@babel/runtime" "^7.7.2" - "@jimp/custom" "^0.12.1" - "@jimp/plugins" "^0.12.1" - "@jimp/types" "^0.12.1" + "@jimp/custom" "^0.16.1" + "@jimp/plugins" "^0.16.1" + "@jimp/types" "^0.16.1" regenerator-runtime "^0.13.3" -jpeg-js@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.0.tgz#39adab7245b6d11e918ba5d4b49263ff2fc6a2f9" - integrity sha512-960VHmtN1vTpasX/1LupLohdP5odwAT7oK/VSm6mW0M58LbrBnowLAPWAZhWGhDAGjzbMnPXZxzB/QYgBwkN0w== +jpeg-js@0.4.2: + version "0.4.2" + resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.4.2.tgz#8b345b1ae4abde64c2da2fe67ea216a114ac279d" + integrity sha512-+az2gi/hvex7eLTMTlbRLOhH6P6WFdk2ITI8HJsaH2VqYO0I594zXSYEP+tf4FW+8Cy68ScDXoAsQdyQanv3sw== js-base64@^2.1.8: version "2.5.1" @@ -9072,7 +9310,7 @@ js-tokens@^3.0.2: resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls= -js-yaml@^3.10.0, js-yaml@^3.13.1: +js-yaml@^3.13.1: version "3.13.1" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== @@ -9080,6 +9318,14 @@ js-yaml@^3.10.0, js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" +js-yaml@^3.14.0: + version "3.14.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" + integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A== + dependencies: + argparse "^1.0.7" + esprima "^4.0.0" + jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" @@ -9164,11 +9410,6 @@ json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== -json-schema-traverse@^0.3.0: - version "0.3.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" - integrity sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A= - json-schema-traverse@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" @@ -9196,7 +9437,7 @@ json-stringify-safe@~5.0.1: resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= -json-to-pretty-yaml@1.2.2: +json-to-pretty-yaml@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/json-to-pretty-yaml/-/json-to-pretty-yaml-1.2.2.tgz#f4cd0bd0a5e8fe1df25aaf5ba118b099fd992d5b" integrity sha1-9M0L0KXo/h3yWq9boRiwmf2ZLVs= @@ -9251,7 +9492,7 @@ jsonify@~0.0.0: resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" integrity sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM= -jsonwebtoken@^8.1.0: +jsonwebtoken@^8.5.1: version "8.5.1" resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz#00e71e0b8df54c2121a1f26137df2280673bcc0d" integrity sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w== @@ -9285,6 +9526,14 @@ jsx-ast-utils@^2.2.1, jsx-ast-utils@^2.2.3: array-includes "^3.0.3" object.assign "^4.1.0" +jsx-ast-utils@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.4.1.tgz#1114a4c1209481db06c690c2b4f488cc665f657e" + integrity sha512-z1xSldJ6imESSzOjd3NNkieVJKRlKYSOtMG8SFyCj2FIrvSaSuli/WjpBkEzCBoR9bYYYFgqJw61Xhu7Lcgk+w== + dependencies: + array-includes "^3.1.1" + object.assign "^4.1.0" + jwa@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" @@ -9338,15 +9587,32 @@ kind-of@^6.0.0, kind-of@^6.0.2: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA== +kind-of@^6.0.3: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + kleur@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e" integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w== -known-css-properties@^0.18.0: - version "0.18.0" - resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.18.0.tgz#d6e00b56ee1d5b0d171fd86df1583cfb012c521f" - integrity sha512-69AgJ1rQa7VvUsd2kpvVq+VeObDuo3zrj0CzM5Slmf6yduQFAI2kXPDQJR2IE/u6MSAUOJrwSzjg5vlz8qcMiw== +known-css-properties@^0.19.0: + version "0.19.0" + resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.19.0.tgz#5d92b7fa16c72d971bda9b7fe295bdf61836ee5b" + integrity sha512-eYboRV94Vco725nKMlpkn3nV2+96p9c3gKXRsYqAJSswSENvBhN7n5L+uDhY58xQa0UukWsDMTGELzmD8Q+wTA== + +language-subtag-registry@~0.3.2: + version "0.3.20" + resolved "https://registry.yarnpkg.com/language-subtag-registry/-/language-subtag-registry-0.3.20.tgz#a00a37121894f224f763268e431c55556b0c0755" + integrity sha512-KPMwROklF4tEx283Xw0pNKtfTj1gZ4UByp4EsIFWLgBavJltF4TiYPc39k06zSTsLzxTVXXDSpbwaQXaFB4Qeg== + +language-tags@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/language-tags/-/language-tags-1.0.5.tgz#d321dbc4da30ba8bf3024e040fa5c14661f9193a" + integrity sha1-0yHbxNowuovzAk4ED6XBRmH5GTo= + dependencies: + language-subtag-registry "~0.3.2" last-call-webpack-plugin@^3.0.0: version "3.0.0" @@ -9366,20 +9632,6 @@ lazy-cache@^1.0.3: resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" integrity sha1-odePw6UEdMuAhF07O24dpJpEbo4= -lcid@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" - integrity sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU= - dependencies: - invert-kv "^1.0.0" - -lcid@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/lcid/-/lcid-2.0.0.tgz#6ef5d2df60e52f82eb228a4c373e8d1f397253cf" - integrity sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA== - dependencies: - invert-kv "^2.0.0" - left-pad@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e" @@ -9422,7 +9674,7 @@ listr-silent-renderer@^1.1.1: resolved "https://registry.yarnpkg.com/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz#924b5a3757153770bf1a8e3fbf74b8bbf3f9242e" integrity sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4= -listr-update-renderer@0.5.0, listr-update-renderer@^0.5.0: +listr-update-renderer@^0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/listr-update-renderer/-/listr-update-renderer-0.5.0.tgz#4ea8368548a7b8aecb7e06d8c95cb45ae2ede6a2" integrity sha512-tKRsZpKz8GSGqoI/+caPmfrypiaq+OQCbd+CovEC24uk1h952lVj5sC7SqyFUm+OaJ5HN/a1YLt5cit2FMNsFA== @@ -9446,7 +9698,7 @@ listr-verbose-renderer@^0.5.0: date-fns "^1.27.2" figures "^2.0.0" -listr@0.14.3: +listr@^0.14.3: version "0.14.3" resolved "https://registry.yarnpkg.com/listr/-/listr-0.14.3.tgz#2fea909604e434be464c50bddba0d496928fa586" integrity sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA== @@ -9547,10 +9799,10 @@ loader-utils@^1.4.0: emojis-list "^3.0.0" json5 "^1.0.1" -localforage@1.7.3: - version "1.7.3" - resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.7.3.tgz#0082b3ca9734679e1bd534995bdd3b24cf10f204" - integrity sha512-1TulyYfc4udS7ECSBT2vwJksWbkwwTX8BzeUIiq8Y07Riy7bDAAnxDaPU/tWyOVmQAcWJIEIFP9lPfBGqVoPgQ== +localforage@1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/localforage/-/localforage-1.9.0.tgz#f3e4d32a8300b362b4634cc4e066d9d00d2f09d1" + integrity sha512-rR1oyNrKulpe+VM9cYmcFn6tsHuokyVHFaCM3+osEmxaHTbEk8oQu6eGDfS6DQLWi/N67XRmB8ECG37OES368g== dependencies: lie "3.1.1" @@ -9672,17 +9924,15 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@4.17.15, "lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4, lodash@^4.17.5, lodash@~4.17.10: +"lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.5, lodash@~4.17.10: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== -log-symbols@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920" - integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA== - dependencies: - chalk "^4.0.0" +lodash@^4.17.19, lodash@^4.17.20, lodash@~4.17.15: + version "4.17.20" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" + integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== log-symbols@^1.0.2: version "1.0.2" @@ -9691,19 +9941,12 @@ log-symbols@^1.0.2: dependencies: chalk "^1.0.0" -log-symbols@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" - integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg== +log-symbols@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920" + integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA== dependencies: - chalk "^2.0.1" - -log-symbols@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz#f3a08516a5dea893336a7dee14d18a1cfdab77c4" - integrity sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ== - dependencies: - chalk "^2.4.2" + chalk "^4.0.0" log-update@^2.3.0: version "2.3.0" @@ -9714,10 +9957,10 @@ log-update@^2.3.0: cli-cursor "^2.0.0" wrap-ansi "^3.0.1" -loglevel@^1.6.6: - version "1.6.6" - resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.6.6.tgz#0ee6300cc058db6b3551fa1c4bf73b83bb771312" - integrity sha512-Sgr5lbboAUBo3eXCSPL4/KoVz3ROKquOjcctxmHIt+vol2DrqTQe3SwkKKuYhEiWB5kYa13YyopJ69deJ1irzQ== +loglevel@^1.6.8: + version "1.7.0" + resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.0.tgz#728166855a740d59d38db01cf46f042caa041bb0" + integrity sha512-i2sY04nal5jDcagM3FMfG++T69GEEM8CYuOfeOIvmXzOIcwE9a/CJPR0MFM97pYMj/u10lzz7/zd7+qwhrBTqQ== longest-streak@^2.0.1: version "2.0.4" @@ -9781,6 +10024,13 @@ make-dir@^3.0.0: dependencies: semver "^6.0.0" +make-dir@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + makeerror@1.0.x: version "1.0.11" resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c" @@ -9793,13 +10043,6 @@ mamacro@^0.0.3: resolved "https://registry.yarnpkg.com/mamacro/-/mamacro-0.0.3.tgz#ad2c9576197c9f1abf308d0787865bd975a3f3e4" integrity sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA== -map-age-cleaner@^0.1.1: - version "0.1.3" - resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a" - integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w== - dependencies: - p-defer "^1.0.0" - map-cache@^0.2.0, map-cache@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" @@ -9872,15 +10115,6 @@ media-typer@0.3.0: resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= -mem@^4.0.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/mem/-/mem-4.3.0.tgz#461af497bc4ae09608cdb2e60eefb69bff744178" - integrity sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w== - dependencies: - map-age-cleaner "^0.1.1" - mimic-fn "^2.0.0" - p-is-promise "^2.0.0" - memoize-one@^5.0.0: version "5.1.1" resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.1.1.tgz#047b6e3199b508eaec03504de71229b8eb1d75c0" @@ -9935,6 +10169,23 @@ meow@^6.1.0: type-fest "^0.13.1" yargs-parser "^18.1.3" +meow@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/meow/-/meow-7.1.1.tgz#7c01595e3d337fcb0ec4e8eed1666ea95903d306" + integrity sha512-GWHvA5QOcS412WCo8vwKDlTelGLsCGBVevQB5Kva961rmNfun0PCbv5+xta2kUMFJyR8/oWnn7ddeKdosbAPbA== + dependencies: + "@types/minimist" "^1.2.0" + camelcase-keys "^6.2.2" + decamelize-keys "^1.1.0" + hard-rejection "^2.1.0" + minimist-options "4.1.0" + normalize-package-data "^2.5.0" + read-pkg-up "^7.0.1" + redent "^3.0.0" + trim-newlines "^3.0.0" + type-fest "^0.13.1" + yargs-parser "^18.1.3" + merge-deep@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/merge-deep/-/merge-deep-3.0.2.tgz#f39fa100a4f1bd34ff29f7d2bf4508fbb8d83ad2" @@ -10043,7 +10294,7 @@ mimic-fn@^1.0.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" integrity sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ== -mimic-fn@^2.0.0, mimic-fn@^2.1.0: +mimic-fn@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== @@ -10060,14 +10311,13 @@ min-indent@^1.0.0: resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.0.tgz#cfc45c37e9ec0d8f0a0ec3dd4ef7f7c3abe39256" integrity sha1-z8RcN+nsDY8KDsPdTvf3w6vjklY= -mini-create-react-context@^0.3.0: - version "0.3.2" - resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.3.2.tgz#79fc598f283dd623da8e088b05db8cddab250189" - integrity sha512-2v+OeetEyliMt5VHMXsBhABoJ0/M4RCe7fatd/fBy6SMiKazUSEt3gxxypfnk2SHMkdBYvorHRoQxuGoiwbzAw== +mini-create-react-context@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/mini-create-react-context/-/mini-create-react-context-0.4.0.tgz#df60501c83151db69e28eac0ef08b4002efab040" + integrity sha512-b0TytUgFSbgFJGzJqXPKCFCBWigAjpjo+Fl7Vf7ZbKRDptszpppKxXH6DRXEABZ/gcEQczeb0iZ7JvL8e8jjCA== dependencies: - "@babel/runtime" "^7.4.0" - gud "^1.0.0" - tiny-warning "^1.0.2" + "@babel/runtime" "^7.5.5" + tiny-warning "^1.0.3" mini-css-extract-plugin@0.9.0: version "0.9.0" @@ -10096,6 +10346,15 @@ minimatch@3.0.4, minimatch@^3.0.4, minimatch@~3.0.2: dependencies: brace-expansion "^1.1.7" +minimist-options@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" + integrity sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A== + dependencies: + arrify "^1.0.1" + is-plain-obj "^1.1.0" + kind-of "^6.0.3" + minimist-options@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.0.2.tgz#29c4021373ded40d546186725e57761e4b1984a7" @@ -10186,22 +10445,27 @@ mkdirp@0.5.1, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.1: dependencies: minimist "0.0.8" -mkdirp@1.0.4, mkdirp@^1.0.3: - version "1.0.4" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" - integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== - -mkdirp@^0.5.3: +mkdirp@^0.5.3, mkdirp@^0.5.5: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== dependencies: minimist "^1.2.5" -moment@~2.25.0: - version "2.25.1" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.25.1.tgz#1cb546dca1eccdd607c9324747842200b683465d" - integrity sha512-nRKMf9wDS4Fkyd0C9LXh2FFXinD+iwbJ5p/lh3CHitW9kZbRbJ8hCruiadiIXZVbeAqKZzqcTvHnK3mRhFjb6w== +mkdirp@^1.0.3, mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +moment@~2.27.0: + version "2.27.0" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.27.0.tgz#8bff4e3e26a236220dfe3e36de756b6ebaa0105d" + integrity sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ== + +mousetrap-pause@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/mousetrap-pause/-/mousetrap-pause-1.0.0.tgz#91c429f2f5f9ad71508fa0561bb53be3fdf9a9d0" + integrity sha1-kcQp8vX5rXFQj6BWG7U74/35qdA= mousetrap@^1.6.5: version "1.6.5" @@ -10315,12 +10579,7 @@ no-case@^3.0.3: lower-case "^2.0.1" tslib "^1.10.0" -node-fetch@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.1.2.tgz#ab884e8e7e57e38a944753cec706f788d1768bb5" - integrity sha1-q4hOjn5X44qUR1POxwb3iNF2i7U= - -node-fetch@2.6.0, node-fetch@^2.6.0: +node-fetch@2.6.0: version "2.6.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd" integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA== @@ -10338,6 +10597,11 @@ node-forge@0.9.0: resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.0.tgz#d624050edbb44874adca12bb9a52ec63cb782579" integrity sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ== +node-gyp-build@~3.7.0: + version "3.7.0" + resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-3.7.0.tgz#daa77a4f547b9aed3e2aac779eaf151afd60ec8d" + integrity sha512-L/Eg02Epx6Si2NXmedx+Okg+4UHqmaf3TNcxd50SF9NQGcJaON3AtU++kax69XV7YWz4tUspqZSAsVofhFKG2w== + node-gyp@^3.8.0: version "3.8.0" resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.8.0.tgz#540304261c330e80d0d5edce253a68cb3964218c" @@ -10418,10 +10682,15 @@ node-releases@^1.1.52, node-releases@^1.1.53: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.53.tgz#2d821bfa499ed7c5dffc5e2f28c88e78a08ee3f4" integrity sha512-wp8zyQVwef2hpZ/dJH7SfSrIPD6YoJz6BDQDpGEkcA0s3LpAQoxBIYmfIq6QAhC1DhwsyCgTaTTcONwX8qzCuQ== -node-sass@4.14.0: - version "4.14.0" - resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.14.0.tgz#a8e9d7720f8e15b4a1072719dcf04006f5648eeb" - integrity sha512-AxqU+DFpk0lEz95sI6jO0hU0Rwyw7BXVEv6o9OItoXLyeygPeaSpiV4rwQb10JiTghHaa0gZeD21sz+OsQluaw== +node-releases@^1.1.60: + version "1.1.60" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.60.tgz#6948bdfce8286f0b5d0e5a88e8384e954dfe7084" + integrity sha512-gsO4vjEdQaTusZAEebUWp2a5d7dF5DYoIpDG7WySnk7BuZDW+GPpHXoXXuYawRBr/9t5q54tirPz79kFIWg4dA== + +node-sass@4.14.1: + version "4.14.1" + resolved "https://registry.yarnpkg.com/node-sass/-/node-sass-4.14.1.tgz#99c87ec2efb7047ed638fb4c9db7f3a42e2217b5" + integrity sha512-sjCuOlvGyCJS40R8BscF5vhVlQjNN069NtQ1gSxyK1u9iqvn6tf7O1R4GNowVZfiZUCRt5MmMs1xd+4V/7Yr0g== dependencies: async-foreach "^0.1.3" chalk "^1.1.1" @@ -10437,7 +10706,7 @@ node-sass@4.14.0: node-gyp "^3.8.0" npmlog "^4.0.0" request "^2.88.0" - sass-graph "^2.2.4" + sass-graph "2.2.5" stdout-stream "^1.4.0" "true-case-path" "^1.0.2" @@ -10610,6 +10879,15 @@ object.entries@^1.1.0, object.entries@^1.1.1: function-bind "^1.1.1" has "^1.0.3" +object.entries@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/object.entries/-/object.entries-1.1.2.tgz#bc73f00acb6b6bb16c203434b10f9a7e797d3add" + integrity sha512-BQdB9qKmb/HyNdMNWVr7O3+z5MUIx3aiegEIJqjMBbBf0YT9RRxTJSim4mzFqtyr7PDAHigq0N9dO0m0tRakQA== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + has "^1.0.3" + object.fromentries@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.2.tgz#4a09c9b9bb3843dd0f89acdb517a794d4f355ac9" @@ -10650,7 +10928,7 @@ obuf@^1.0.0, obuf@^1.1.2: resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== -omggif@^1.0.9: +omggif@^1.0.10, omggif@^1.0.9: version "1.0.10" resolved "https://registry.yarnpkg.com/omggif/-/omggif-1.0.10.tgz#ddaaf90d4a42f532e9e7cb3a95ecdd47f17c7b19" integrity sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw== @@ -10703,12 +10981,12 @@ opn@^5.5.0: dependencies: is-wsl "^1.1.0" -optimism@^0.10.0: - version "0.10.3" - resolved "https://registry.yarnpkg.com/optimism/-/optimism-0.10.3.tgz#163268fdc741dea2fb50f300bedda80356445fd7" - integrity sha512-9A5pqGoQk49H6Vhjb9kPgAeeECfUDF6aIICbMDL23kDLStBn1MWk3YvcZ4xWF9CsSf6XEgvRLkXy4xof/56vVw== +optimism@^0.12.1: + version "0.12.1" + resolved "https://registry.yarnpkg.com/optimism/-/optimism-0.12.1.tgz#933f9467b9aef0e601655adb9638f893e486ad02" + integrity sha512-t8I7HM1dw0SECitBYAqFOVHoBAHEQBTeKjIL9y9ImHzAVkdyPK4ifTgM4VJRDtTUY4r/u5Eqxs4XcGPHaoPkeQ== dependencies: - "@wry/context" "^0.4.0" + "@wry/context" "^0.5.2" optimize-css-assets-webpack-plugin@5.0.3: version "5.0.3" @@ -10747,22 +11025,6 @@ os-homedir@^1.0.0: resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= -os-locale@^1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" - integrity sha1-IPnxeuKe00XoveWDsT0gCYA8FNk= - dependencies: - lcid "^1.0.0" - -os-locale@^3.0.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-3.1.0.tgz#a802a6ee17f24c10483ab9935719cef4ed16bf1a" - integrity sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q== - dependencies: - execa "^1.0.0" - lcid "^2.0.0" - mem "^4.0.0" - os-tmpdir@^1.0.0, os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" @@ -10776,11 +11038,6 @@ osenv@0: os-homedir "^1.0.0" os-tmpdir "^1.0.0" -p-defer@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" - integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww= - p-each-series@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-1.0.0.tgz#930f3d12dd1f50e7434457a22cd6f04ac6ad7f71" @@ -10793,15 +11050,10 @@ p-finally@^1.0.0: resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= -p-is-promise@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.1.0.tgz#918cebaea248a62cf7ffab8e3bca8c5f882fc42e" - integrity sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg== - -p-limit@2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== +p-limit@3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.0.2.tgz#1664e010af3cadc681baafd3e2a437be7b0fb5fe" + integrity sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg== dependencies: p-try "^2.0.0" @@ -10812,13 +11064,20 @@ p-limit@^1.1.0: dependencies: p-try "^1.0.0" -p-limit@^2.0.0, p-limit@^2.2.0, p-limit@^2.2.2: +p-limit@^2.0.0, p-limit@^2.2.0: version "2.2.2" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.2.tgz#61279b67721f5287aa1c13a9a7fbbc48c9291b1e" integrity sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ== dependencies: p-try "^2.0.0" +p-limit@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + p-locate@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" @@ -10964,7 +11223,7 @@ parse-entities@^2.0.0: is-decimal "^1.0.0" is-hexadecimal "^1.0.0" -parse-filepath@1.0.2: +parse-filepath@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/parse-filepath/-/parse-filepath-1.0.2.tgz#a632127f53aaf3d15876f5872f3ffac763d6c891" integrity sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE= @@ -11135,11 +11394,6 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -pathval@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0" - integrity sha1-uULm1L3mUwBe9rcTYd74cn0GReA= - pbkdf2@^3.0.3: version "3.0.17" resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.17.tgz#976c206530617b14ebb32114239f7b09336e93a6" @@ -11276,14 +11530,14 @@ pnp-webpack-plugin@1.6.4: dependencies: ts-pnp "^1.1.6" -portfinder@^1.0.25: - version "1.0.25" - resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.25.tgz#254fd337ffba869f4b9d37edc298059cb4d35eca" - integrity sha512-6ElJnHBbxVA1XSLgBp7G1FiCkQdlqGzuF7DswL5tcea+E8UpuvPU7beVAjjRwCioTS9ZluNbu+ZyRvgTsmqEBg== +portfinder@^1.0.26: + version "1.0.28" + resolved "https://registry.yarnpkg.com/portfinder/-/portfinder-1.0.28.tgz#67c4622852bd5374dd1dd900f779f53462fac778" + integrity sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA== dependencies: async "^2.6.2" debug "^3.1.1" - mkdirp "^0.5.1" + mkdirp "^0.5.5" posix-character-classes@^0.1.0: version "0.1.1" @@ -11865,16 +12119,6 @@ postcss-replace-overflow-wrap@^3.0.0: dependencies: postcss "^7.0.2" -postcss-reporter@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/postcss-reporter/-/postcss-reporter-6.0.1.tgz#7c055120060a97c8837b4e48215661aafb74245f" - integrity sha512-LpmQjfRWyabc+fRygxZjpRxfhRf9u/fdlKf4VHG4TSPbV2XNsuISzYW1KL+1aQzx53CAppa1bKG4APIB/DOXXw== - dependencies: - chalk "^2.4.1" - lodash "^4.17.11" - log-symbols "^2.2.0" - postcss "^7.0.7" - postcss-resolve-nested-selector@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/postcss-resolve-nested-selector/-/postcss-resolve-nested-selector-0.1.1.tgz#29ccbc7c37dedfac304e9fff0bf1596b3f6a0e4e" @@ -11902,12 +12146,12 @@ postcss-sass@^0.4.4: gonzales-pe "^4.3.0" postcss "^7.0.21" -postcss-scss@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/postcss-scss/-/postcss-scss-2.0.0.tgz#248b0a28af77ea7b32b1011aba0f738bda27dea1" - integrity sha512-um9zdGKaDZirMm+kZFKKVsnKPF7zF7qBAtIfTSnZXD1jZ0JNZIxdB6TxQOjCnlSzLRInVl2v3YdBh/M881C4ug== +postcss-scss@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/postcss-scss/-/postcss-scss-2.1.1.tgz#ec3a75fa29a55e016b90bf3269026c53c1d2b383" + integrity sha512-jQmGnj0hSGLd9RscFw9LyuSVAa5Bl1/KBPqG1NQw9w8ND55nY4ZEsdlVuYJvLPpV+y0nwTV5v/4rHPzZRihQbA== dependencies: - postcss "^7.0.0" + postcss "^7.0.6" postcss-selector-matches@^4.0.0: version "4.0.0" @@ -11994,7 +12238,7 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2: resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.0.2.tgz#482282c09a42706d1fc9a069b73f44ec08391dc9" integrity sha512-LmeoohTpp/K4UiyQCwuGWlONxXamGzCMtFxLq4W1nZVGIQLYvMCJx3yAF9qyyuFpflABI9yVdtJAqbihOsCsJQ== -postcss-value-parser@^4.0.3: +postcss-value-parser@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb" integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ== @@ -12017,7 +12261,7 @@ postcss@7.0.21: source-map "^0.6.1" supports-color "^6.1.0" -postcss@^7, postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.21, postcss@^7.0.23, postcss@^7.0.26, postcss@^7.0.5, postcss@^7.0.6, postcss@^7.0.7: +postcss@^7, postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.17, postcss@^7.0.2, postcss@^7.0.21, postcss@^7.0.23, postcss@^7.0.26, postcss@^7.0.5, postcss@^7.0.6: version "7.0.26" resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.26.tgz#5ed615cfcab35ba9bbb82414a4fa88ea10429587" integrity sha512-IY4oRjpXWYshuTDFxMVkJDtWIk2LhsTlu8bZnbEJA4+bYT16Lvpo8Qv6EvDumhYRgzjZl489pmsY3qVgJQ08nA== @@ -12026,10 +12270,10 @@ postcss@^7, postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.16, po source-map "^0.6.1" supports-color "^6.1.0" -postcss@^7.0.27: - version "7.0.28" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.28.tgz#d349ced7743475717ba91f6810efb58c51fb5dbb" - integrity sha512-YU6nVhyWIsVtlNlnAj1fHTsUKW5qxm3KEgzq2Jj6KTEFOTK8QWR12eIDvrlWhiSTK8WIBFTBhOJV4DY6dUuEbw== +postcss@^7.0.31, postcss@^7.0.32: + version "7.0.32" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-7.0.32.tgz#4310d6ee347053da3433db2be492883d62cec59d" + integrity sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw== dependencies: chalk "^2.4.2" source-map "^0.6.1" @@ -12045,10 +12289,10 @@ prepend-http@^1.0.0: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw= -prettier@2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.5.tgz#d6d56282455243f2f92cc1716692c08aa31522d4" - integrity sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg== +prettier@2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.1.1.tgz#d9485dd5e499daa6cb547023b87a6cf51bee37d6" + integrity sha512-9bY+5ZWCfqj3ghYBLxApy2zf6m+NJo5GzmLTpr9FsApsfjriNnS2dahWReHMi7qNPhhHl9SYHJs2cHZLgexNIw== pretty-bytes@^5.1.0: version "5.3.0" @@ -12073,35 +12317,6 @@ pretty-format@^24.9.0: ansi-styles "^3.2.0" react-is "^16.8.4" -prisma-json-schema@0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/prisma-json-schema/-/prisma-json-schema-0.1.3.tgz#6c302db8f464f8b92e8694d3f7dd3f41ac9afcbe" - integrity sha512-XZrf2080oR81mY8/OC8al68HiwBm0nXlFE727JIia0ZbNqwuV4MyRYk6E0+OIa6/9KEYxZrcAmoBs3EW1cCvnA== - -prisma-yml@1.34.10: - version "1.34.10" - resolved "https://registry.yarnpkg.com/prisma-yml/-/prisma-yml-1.34.10.tgz#0ef1ad3a125f54f200289cba56bdd9597f17f410" - integrity sha512-N9on+Cf/XQKFGUULk/681tnpfqiZ19UBTurFMm+/9rnml37mteDaFr2k8yz+K8Gt2xpEJ7kBu7ikG5PrXI1uoA== - dependencies: - ajv "5" - bluebird "^3.5.1" - chalk "^2.3.0" - debug "^3.1.0" - dotenv "^4.0.0" - fs-extra "^7.0.0" - graphql-request "^1.5.0" - http-proxy-agent "^2.1.0" - https-proxy-agent "^2.2.1" - isomorphic-fetch "^2.2.1" - js-yaml "^3.10.0" - json-stable-stringify "^1.0.1" - jsonwebtoken "^8.1.0" - lodash "^4.17.4" - prisma-json-schema "0.1.3" - replaceall "^0.1.6" - scuid "^1.0.2" - yaml-ast-parser "^0.0.40" - private@^0.1.6, private@^0.1.8: version "0.1.8" resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" @@ -12261,10 +12476,10 @@ qs@~6.5.2: resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== -query-string@6.12.1: - version "6.12.1" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.12.1.tgz#2ae4d272db4fba267141665374e49a1de09e8a7c" - integrity sha512-OHj+zzfRMyj3rmo/6G8a5Ifvw3AleL/EbcHMD27YA31Q+cO5lfmQxECkImuNVjcskLcvBRVHNAB3w6udMs1eAA== +query-string@6.13.1: + version "6.13.1" + resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.13.1.tgz#d913ccfce3b4b3a713989fe6d39466d92e71ccad" + integrity sha512-RfoButmcK+yCta1+FuU8REvisx1oEzhMKwhLUNcepQTPGcNMp1sIqjnfCtfnvGSQZQEhaBHvccujtWoUV3TTbA== dependencies: decode-uri-component "^0.2.0" split-on-first "^1.0.0" @@ -12310,7 +12525,7 @@ ramda@^0.26: resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.26.1.tgz#8d41351eb8111c55353617fc3bbffad8e4d35d06" integrity sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ== -randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: +randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== @@ -12340,17 +12555,6 @@ raw-body@2.4.0: iconv-lite "0.4.24" unpipe "1.0.0" -react-apollo@^3.1.5: - version "3.1.5" - resolved "https://registry.yarnpkg.com/react-apollo/-/react-apollo-3.1.5.tgz#36692d393c47e7ccc37f0a885c7cc5a8b4961c91" - integrity sha512-xOxMqxORps+WHrUYbjVHPliviomefOpu5Sh35oO3osuOyPTxvrljdfTLGCggMhcXBsDljtS5Oy4g+ijWg3D4JQ== - dependencies: - "@apollo/react-common" "^3.1.4" - "@apollo/react-components" "^3.1.5" - "@apollo/react-hoc" "^3.1.5" - "@apollo/react-hooks" "^3.1.5" - "@apollo/react-ssr" "^3.1.5" - react-app-polyfill@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/react-app-polyfill/-/react-app-polyfill-1.0.6.tgz#890f8d7f2842ce6073f030b117de9130a5f385f0" @@ -12363,22 +12567,27 @@ react-app-polyfill@^1.0.6: regenerator-runtime "^0.13.3" whatwg-fetch "^3.0.0" -react-bootstrap@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/react-bootstrap/-/react-bootstrap-1.0.1.tgz#044b51f34a9db8e17dbfb321a71267a8d6ad11b4" - integrity sha512-xMHwsvDN7sIv26P9wWiosWjITZije2dRCjEJHVfV2KFoSJY+8uv2zttEw0XMB7xviQcW3zuIGLJXuj8vf6lYEg== +react-bootstrap@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/react-bootstrap/-/react-bootstrap-1.3.0.tgz#d9dde4ad554e9cd21d1465e8b5e5ef6679cae6a1" + integrity sha512-GYj0c6FO9mx7DaO8Xyz2zs0IcQ6CGCtM3O6/feIoCaG4N8B0+l4eqL7stlMcLpqO4d8NG2PoMO/AbUOD+MO7mg== dependencies: "@babel/runtime" "^7.4.2" "@restart/context" "^2.1.4" "@restart/hooks" "^0.3.21" - "@types/react" "^16.9.23" + "@types/classnames" "^2.2.10" + "@types/invariant" "^2.2.33" + "@types/prop-types" "^15.7.3" + "@types/react" "^16.9.35" + "@types/react-transition-group" "^4.4.0" + "@types/warning" "^3.0.0" classnames "^2.2.6" dom-helpers "^5.1.2" invariant "^2.2.4" prop-types "^15.7.2" prop-types-extra "^1.1.0" - react-overlays "^3.1.2" - react-transition-group "^4.0.0" + react-overlays "^4.1.0" + react-transition-group "^4.4.1" uncontrollable "^7.0.0" warning "^4.0.3" @@ -12449,22 +12658,21 @@ react-input-autosize@^2.2.2: dependencies: prop-types "^15.5.8" -react-intl@^4.5.1: - version "4.5.1" - resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-4.5.1.tgz#b1583c853eaf652ba3579e605aec66eeb915195f" - integrity sha512-Zhe5rn+AYtcnzgAuL2ZgqOCMpFwco9aQ0OMdobe/g1l+hUTEEt7gQyZO0cHXd4lsfJIyBtjGnVlR9V2YwC1dDw== +react-intl@^5.8.0: + version "5.8.0" + resolved "https://registry.yarnpkg.com/react-intl/-/react-intl-5.8.0.tgz#4d365ee992b35cdb81576abd2fb06e4d78a8e461" + integrity sha512-03FHg9u9gW+fc9zyVQS0WwZc3AkIzwRVE73O6FJx10ZCJ5XDDHWzgNCK6H65rX0Hq9+Hw9m7IJiU6YIvV3xLFw== dependencies: - "@formatjs/intl-displaynames" "^1.2.8" - "@formatjs/intl-listformat" "^1.4.6" - "@formatjs/intl-relativetimeformat" "^4.5.14" - "@formatjs/intl-unified-numberformat" "^3.3.5" - "@formatjs/intl-utils" "^2.2.4" + "@formatjs/ecma402-abstract" "^1.2.0" + "@formatjs/intl" "^1.3.0" + "@formatjs/intl-displaynames" "^3.3.6" + "@formatjs/intl-listformat" "^4.2.5" + "@formatjs/intl-relativetimeformat" "^7.2.5" "@types/hoist-non-react-statics" "^3.3.1" - "@types/invariant" "^2.2.31" + fast-memoize "^2.5.2" hoist-non-react-statics "^3.3.2" - intl-format-cache "^4.2.26" - intl-messageformat "^8.3.9" - intl-messageformat-parser "^5.0.2" + intl-messageformat "^9.3.6" + intl-messageformat-parser "^6.0.5" shallow-equal "^1.2.1" react-is@^16.3.2, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1, react-is@^16.8.4: @@ -12503,10 +12711,10 @@ react-markdown@^4.3.1: unist-util-visit "^1.3.0" xtend "^4.0.1" -react-overlays@^3.1.2: - version "3.1.3" - resolved "https://registry.yarnpkg.com/react-overlays/-/react-overlays-3.1.3.tgz#e6ac2b43fd2179924491bd794508072399940128" - integrity sha512-FH82W0R9lFJm/YCTDeSvEKQxXyTaZmjMEQlAjRhgjQhknTkyMsft+X4Wep5l95QveqdxGVxl/P41WUOzTGJUcw== +react-overlays@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/react-overlays/-/react-overlays-4.1.0.tgz#755a890519b02e3904845172d5223ff2dfb1bb29" + integrity sha512-vdRpnKe0ckWOOD9uWdqykLUPHLPndIiUV7XfEKsi5008xiyHCfL8bxsx4LbMrfnxW1LzRthLyfy50XYRFNQqqw== dependencies: "@babel/runtime" "^7.4.5" "@popperjs/core" "^2.0.0" @@ -12537,39 +12745,46 @@ react-router-bootstrap@^0.25.0: dependencies: prop-types "^15.5.10" -react-router-dom@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.1.2.tgz#06701b834352f44d37fbb6311f870f84c76b9c18" - integrity sha512-7BPHAaIwWpZS074UKaw1FjVdZBSVWEk8IuDXdB+OkLb8vd/WRQIpA4ag9WQk61aEfQs47wHyjWUoUGGZxpQXew== +react-router-dom@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/react-router-dom/-/react-router-dom-5.2.0.tgz#9e65a4d0c45e13289e66c7b17c7e175d0ea15662" + integrity sha512-gxAmfylo2QUjcwxI63RhQ5G85Qqt4voZpUXSEqCwykV0baaOTQDR1f0PmY8AELqIyVc0NEZUj0Gov5lNGcXgsA== dependencies: "@babel/runtime" "^7.1.2" history "^4.9.0" loose-envify "^1.3.1" prop-types "^15.6.2" - react-router "5.1.2" + react-router "5.2.0" tiny-invariant "^1.0.2" tiny-warning "^1.0.0" -react-router@5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.1.2.tgz#6ea51d789cb36a6be1ba5f7c0d48dd9e817d3418" - integrity sha512-yjEuMFy1ONK246B+rsa0cUam5OeAQ8pyclRDgpxuSCrAlJ1qN9uZ5IgyKC7gQg0w8OM50NXHEegPh/ks9YuR2A== +react-router-hash-link@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/react-router-hash-link/-/react-router-hash-link-2.1.0.tgz#69cc93df0945480adff14e9e501aea5f356896a8" + integrity sha512-U/WizkZwV2IoxLScRJX5CHJWreXjv/kCmjT/LpfYiFdXGnrKgPd0KqcA4KfmQbkwO411OwDmUKKz+bOKoMkzKg== + dependencies: + prop-types "^15.6.0" + +react-router@5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/react-router/-/react-router-5.2.0.tgz#424e75641ca8747fbf76e5ecca69781aa37ea293" + integrity sha512-smz1DUuFHRKdcJC0jobGo8cVbhO3x50tCL4icacOlcwDOEQPq4TMqwx3sY1TP+DvtTgz4nm3thuo7A+BK2U0Dw== dependencies: "@babel/runtime" "^7.1.2" history "^4.9.0" hoist-non-react-statics "^3.1.0" loose-envify "^1.3.1" - mini-create-react-context "^0.3.0" + mini-create-react-context "^0.4.0" path-to-regexp "^1.7.0" prop-types "^15.6.2" react-is "^16.6.0" tiny-invariant "^1.0.2" tiny-warning "^1.0.0" -react-scripts@^3.4.1: - version "3.4.1" - resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-3.4.1.tgz#f551298b5c71985cc491b9acf3c8e8c0ae3ada0a" - integrity sha512-JpTdi/0Sfd31mZA6Ukx+lq5j1JoKItX7qqEK4OiACjVQletM1P38g49d9/D0yTxp9FrSF+xpJFStkGgKEIRjlQ== +react-scripts@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/react-scripts/-/react-scripts-3.4.3.tgz#21de5eb93de41ee92cd0b85b0e1298d0bb2e6c51" + integrity sha512-oSnoWmii/iKdeQiwaO6map1lUaZLmG0xIUyb/HwCVFLT7gNbj8JZ9RmpvMCZ4fB98ZUMRfNmp/ft8uy/xD1RLA== dependencies: "@babel/core" "7.9.0" "@svgr/webpack" "4.3.3" @@ -12616,11 +12831,11 @@ react-scripts@^3.4.1: sass-loader "8.0.2" semver "6.3.0" style-loader "0.23.1" - terser-webpack-plugin "2.3.5" + terser-webpack-plugin "2.3.8" ts-pnp "1.1.6" url-loader "2.3.0" webpack "4.42.0" - webpack-dev-server "3.10.3" + webpack-dev-server "3.11.0" webpack-manifest-plugin "2.2.0" workbox-webpack-plugin "4.3.1" optionalDependencies: @@ -12658,7 +12873,7 @@ react-transition-group@2: prop-types "^15.6.2" react-lifecycles-compat "^3.0.4" -react-transition-group@^4.0.0, react-transition-group@^4.3.0: +react-transition-group@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.3.0.tgz#fea832e386cf8796c58b61874a3319704f5ce683" integrity sha512-1qRV1ZuVSdxPlPf4O8t7inxUGpdyO5zG9IoNfJxSO0ImU2A1YWkEQvFPuIPZmMLkg5hYs7vv5mMOyfgSkvAwvw== @@ -12668,6 +12883,16 @@ react-transition-group@^4.0.0, react-transition-group@^4.3.0: loose-envify "^1.4.0" prop-types "^15.6.2" +react-transition-group@^4.4.1: + version "4.4.1" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9" + integrity sha512-Djqr7OQ2aPUiYurhPalTrVy9ddmFCCzwhqQmtN+J3+3DzLO209Fdr70QrN8Z3DsglWql6iY1lDWAfpFiBtuKGw== + dependencies: + "@babel/runtime" "^7.5.5" + dom-helpers "^5.0.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" + react@16.13.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e" @@ -12971,10 +13196,10 @@ relateurl@^0.2.7: resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk= -relay-compiler@9.1.0: - version "9.1.0" - resolved "https://registry.yarnpkg.com/relay-compiler/-/relay-compiler-9.1.0.tgz#e2975de85192e2470daad78e30052bf9614d22ed" - integrity sha512-jsJx0Ux5RoxM+JFm3M3xl7UfZAJ0kUTY/r6jqOpcYgVI3GLJthvNI4IoziFRlWbhizEzGFbpkdshZcu9IObJYA== +relay-compiler@10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/relay-compiler/-/relay-compiler-10.0.1.tgz#d3029a5121cad52e1e55073210365b827cee5f3b" + integrity sha512-hrTqh81XXxPB4EgvxPmvojICr0wJnRoumxOsMZnS9dmhDHSqcBAT7+C3+rdGm5sSdNH8mbMcZM7YSPDh8ABxQw== dependencies: "@babel/core" "^7.0.0" "@babel/generator" "^7.5.0" @@ -12983,20 +13208,20 @@ relay-compiler@9.1.0: "@babel/traverse" "^7.0.0" "@babel/types" "^7.0.0" babel-preset-fbjs "^3.3.0" - chalk "^2.4.1" - fast-glob "^2.2.2" + chalk "^4.0.0" fb-watchman "^2.0.0" fbjs "^1.0.0" + glob "^7.1.1" immutable "~3.7.6" nullthrows "^1.1.1" - relay-runtime "9.1.0" + relay-runtime "10.0.1" signedsource "^1.0.0" - yargs "^14.2.0" + yargs "^15.3.1" -relay-runtime@9.1.0: - version "9.1.0" - resolved "https://registry.yarnpkg.com/relay-runtime/-/relay-runtime-9.1.0.tgz#d0534007d5c43e7b9653c6f5cc112ffac09c5020" - integrity sha512-6FE5YlZpR/b3R/HzGly85V+c4MdtLJhFY/outQARgxXonomrwqEik0Cr34LnPK4DmGS36cMLUliqhCs/DZyPVw== +relay-runtime@10.0.1: + version "10.0.1" + resolved "https://registry.yarnpkg.com/relay-runtime/-/relay-runtime-10.0.1.tgz#c83bd7e6e37234ece2a62a254e37dd199a4f74f9" + integrity sha512-sPYiuosq+5gQ7zXs2EKg2O8qRSsF8vmMYo6SIHEi4juBLg1HrdTEvqcaNztc2ZFmUc4vYZpTbbS4j/TZCtHuyA== dependencies: "@babel/runtime" "^7.0.0" fbjs "^1.0.0" @@ -13142,32 +13367,6 @@ request-promise-native@^1.0.5: stealthy-require "^1.1.1" tough-cookie "^2.3.3" -request@2.88.2: - version "2.88.2" - resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" - integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.8.0" - caseless "~0.12.0" - combined-stream "~1.0.6" - extend "~3.0.2" - forever-agent "~0.6.1" - form-data "~2.3.2" - har-validator "~5.1.3" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.19" - oauth-sign "~0.9.0" - performance-now "^2.1.0" - qs "~6.5.2" - safe-buffer "^5.1.2" - tough-cookie "~2.5.0" - tunnel-agent "^0.6.0" - uuid "^3.3.2" - request@^2.87.0, request@^2.88.0: version "2.88.0" resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" @@ -13194,16 +13393,37 @@ request@^2.87.0, request@^2.88.0: tunnel-agent "^0.6.0" uuid "^3.3.2" +request@^2.88.2: + version "2.88.2" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" + integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.3" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.5.0" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= -require-main-filename@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" - integrity sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE= - require-main-filename@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" @@ -13286,7 +13506,7 @@ resolve@^1.10.0, resolve@^1.12.0, resolve@^1.3.2: dependencies: path-parse "^1.0.6" -resolve@^1.15.1: +resolve@^1.15.1, resolve@^1.17.0: version "1.17.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444" integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w== @@ -13405,6 +13625,13 @@ rxjs@^6.3.3, rxjs@^6.5.3: dependencies: tslib "^1.9.0" +rxjs@^6.6.0: + version "6.6.2" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.2.tgz#8096a7ac03f2cc4fe5860ef6e572810d9e01c0d2" + integrity sha512-BHdBMVoWC2sL26w//BCu3YzKT4s2jip/WhwsGEDmeKYBhKDZeYezVUnHatYB7L85v5xs0BAQmg6BEYJEKxBabg== + dependencies: + tslib "^1.9.0" + safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" @@ -13447,15 +13674,15 @@ sanitize.css@^10.0.0: resolved "https://registry.yarnpkg.com/sanitize.css/-/sanitize.css-10.0.0.tgz#b5cb2547e96d8629a60947544665243b1dc3657a" integrity sha512-vTxrZz4dX5W86M6oVWVdOVe72ZiPs41Oi7Z6Km4W5Turyz28mrXSJhhEBZoRtzJWIv3833WKVwLSDWWkEfupMg== -sass-graph@^2.2.4: - version "2.2.4" - resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.2.4.tgz#13fbd63cd1caf0908b9fd93476ad43a51d1e0b49" - integrity sha1-E/vWPNHK8JCLn9k0dq1DpR0eC0k= +sass-graph@2.2.5: + version "2.2.5" + resolved "https://registry.yarnpkg.com/sass-graph/-/sass-graph-2.2.5.tgz#a981c87446b8319d96dce0671e487879bd24c2e8" + integrity sha512-VFWDAHOe6mRuT4mZRd4eKE+d8Uedrk6Xnh7Sh9b4NGufQLQjOrvf/MQoOdx+0s92L89FeyUUNfU597j/3uNpag== dependencies: glob "^7.0.0" lodash "^4.0.0" scss-tokenizer "^0.2.3" - yargs "^7.0.0" + yargs "^13.3.2" sass-loader@8.0.2: version "8.0.2" @@ -13513,7 +13740,7 @@ schema-utils@^1.0.0: ajv-errors "^1.0.0" ajv-keywords "^3.1.0" -schema-utils@^2.5.0, schema-utils@^2.6.0, schema-utils@^2.6.1, schema-utils@^2.6.4: +schema-utils@^2.5.0, schema-utils@^2.6.0, schema-utils@^2.6.1: version "2.6.4" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.6.4.tgz#a27efbf6e4e78689d91872ee3ccfa57d7bdd0f53" integrity sha512-VNjcaUxVnEeun6B2fiiUDjXXBtD4ZSH7pdbfIu1pOFwgptDPLMo/z9jr4sUfsjFVPqDCEin/F7IYlq7/E6yDbQ== @@ -13529,7 +13756,7 @@ scss-tokenizer@^0.2.3: js-base64 "^2.1.8" source-map "^0.4.2" -scuid@^1.0.2: +scuid@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/scuid/-/scuid-1.1.0.tgz#d3f9f920956e737a60f72d0e4ad280bf324d5dab" integrity sha512-MuCAyrGZcTLfQoH2XoBlQ8C6bzwN88XT/0slOGz0pn8+gIP85BOAfYa44ZXQUTOwRwPU0QvgU+V+OSajl/59Xg== @@ -13561,6 +13788,11 @@ semver@7.0.0: resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A== +semver@^7.3.2: + version "7.3.2" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" + integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== + semver@~5.3.0: version "5.3.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" @@ -13590,6 +13822,13 @@ serialize-javascript@^2.1.2: resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-2.1.2.tgz#ecec53b0e0317bdc95ef76ab7074b7384785fa61" integrity sha512-rs9OggEUF0V4jUSecXazOYsLfu7OGK2qIn3c7IPBiffz32XniEp/TX9Xmc9LQfK2nQ2QKHvZ2oygKUGU0lG4jQ== +serialize-javascript@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa" + integrity sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw== + dependencies: + randombytes "^2.1.0" + serve-index@^1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239" @@ -13725,13 +13964,6 @@ signedsource@^1.0.0: resolved "https://registry.yarnpkg.com/signedsource/-/signedsource-1.0.0.tgz#1ddace4981798f93bd833973803d80d52e93ad6a" integrity sha1-HdrOSYF5j5O9gzlzgD2A1S6TrWo= -simple-git@1.132.0: - version "1.132.0" - resolved "https://registry.yarnpkg.com/simple-git/-/simple-git-1.132.0.tgz#53ac4c5ec9e74e37c2fd461e23309f22fcdf09b1" - integrity sha512-xauHm1YqCTom1sC9eOjfq3/9RKiUA9iPnxBbrY2DdL8l4ADMu0jjM5l5lphQP5YWNqAL2aXC/OeuQ76vHtW5fg== - dependencies: - debug "^4.0.1" - simple-swizzle@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/simple-swizzle/-/simple-swizzle-0.2.2.tgz#a4da6b635ffcccca33f70d17cb92592de95e557a" @@ -13773,6 +14005,15 @@ slice-ansi@^2.1.0: astral-regex "^1.0.0" is-fullwidth-code-point "^2.0.0" +slice-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-4.0.0.tgz#500e8dd0fd55b05815086255b3195adf2a45fe6b" + integrity sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" + snapdragon-node@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" @@ -13815,13 +14056,14 @@ sockjs-client@1.4.0: json3 "^3.3.2" url-parse "^1.4.3" -sockjs@0.3.19: - version "0.3.19" - resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.19.tgz#d976bbe800af7bd20ae08598d582393508993c0d" - integrity sha512-V48klKZl8T6MzatbLlzzRNhMepEys9Y4oGFpypBFFn1gLI/QQ9HtLLyWJNbPlwGLelOVOEijUbTTJeLLI59jLw== +sockjs@0.3.20: + version "0.3.20" + resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.20.tgz#b26a283ec562ef8b2687b44033a4eeceac75d855" + integrity sha512-SpmVOVpdq0DJc0qArhF3E5xsxvaiqGNb73XfgBpK1y3UD5gs8DSo8aCTsuT5pX8rssdc2NDIzANwP9eCAiSdTA== dependencies: faye-websocket "^0.10.0" - uuid "^3.0.1" + uuid "^3.4.0" + websocket-driver "0.6.5" sort-keys@^1.0.0: version "1.1.2" @@ -13921,10 +14163,10 @@ spdy-transport@^3.0.0: readable-stream "^3.0.6" wbuf "^1.7.3" -spdy@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.1.tgz#6f12ed1c5db7ea4f24ebb8b89ba58c87c08257f2" - integrity sha512-HeZS3PBdMA+sZSu0qwpCxl3DeALD5ASx8pAX0jZdKXSpPWbQ6SYGnlg3BBmYLx5LtiZrmkAZfErCm2oECBcioA== +spdy@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.2.tgz#b74f466203a3eda452c02492b91fb9e84a27677b" + integrity sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA== dependencies: debug "^4.1.0" handle-thing "^2.0.0" @@ -14066,6 +14308,11 @@ strict-uri-encode@^2.0.0: resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz#b9c7330c7042862f6b142dc274bbcc5866ce3546" integrity sha1-ucczDHBChi9rFC3CdLvMWGbONUY= +string-env-interpolation@1.0.1, string-env-interpolation@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/string-env-interpolation/-/string-env-interpolation-1.0.1.tgz#ad4397ae4ac53fe6c91d1402ad6f6a52862c7152" + integrity sha512-78lwMoCcn0nNu8LszbP1UA7g55OeE4v7rCeWnM5B453rnNr4aq+5it3FEYtZrSEiMvHZOZ9Jlqb0OD0M2VInqg== + string-length@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/string-length/-/string-length-2.0.0.tgz#d40dbb686a3ace960c1cffca562bf2c45f8363ed" @@ -14082,7 +14329,7 @@ string-length@^3.1.0: astral-regex "^1.0.0" strip-ansi "^5.2.0" -string-width@^1.0.1, string-width@^1.0.2: +string-width@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= @@ -14091,7 +14338,7 @@ string-width@^1.0.1, string-width@^1.0.2: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -"string-width@^1.0.2 || 2", string-width@^2.0.0, string-width@^2.1.1: +"string-width@^1.0.2 || 2", string-width@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== @@ -14129,6 +14376,25 @@ string.prototype.matchall@^4.0.2: regexp.prototype.flags "^1.3.0" side-channel "^1.0.2" +string.prototype.replaceall@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/string.prototype.replaceall/-/string.prototype.replaceall-1.0.3.tgz#f7839a016a290b60769138f4d4938238cbb5a809" + integrity sha512-GF8JS9jtHSDkIsVMsYBPR4dItwaU6xOSPsMcRGTAbBr12ZDfyKMtgxdC2HDFbsMogGel29pmwxioJoXeu9ztIg== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.0-next.1" + function-bind "^1.1.1" + has-symbols "^1.0.1" + is-regex "^1.0.4" + +string.prototype.trimend@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.1.tgz#85812a6b847ac002270f5808146064c995fb6913" + integrity sha512-LRPxFUaTtpqYsTeNKaFOw3R4bxIzWOnbQ837QfBylo8jIxtcbK/A/sMV7Q+OAV/vWo+7s25pOE10KYSjaSO06g== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + string.prototype.trimleft@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz#9bdb8ac6abd6d602b17a4ed321870d2f8dcefc74" @@ -14145,6 +14411,14 @@ string.prototype.trimright@^2.1.1: define-properties "^1.1.3" function-bind "^1.1.1" +string.prototype.trimstart@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.1.tgz#14af6d9f34b053f7cfc89b72f8f2ee14b9039a54" + integrity sha512-XxZn+QpvrBI1FOcg6dIpxUPgWCPuNXvMD72aaRaUQv1eD4e/Qy8i/hFTe0BUmD60p/QA6bh1avmuPTfNjqVWRw== + dependencies: + define-properties "^1.1.3" + es-abstract "^1.17.5" + string_decoder@^1.0.0, string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -14279,61 +14553,61 @@ stylehacks@^4.0.0: postcss-selector-parser "^3.0.0" stylelint-config-prettier@^8.0.1: - version "8.0.1" - resolved "https://registry.yarnpkg.com/stylelint-config-prettier/-/stylelint-config-prettier-8.0.1.tgz#ec7cdd7faabaff52ebfa56c28fed3d995ebb8cab" - integrity sha512-RcjNW7MUaNVqONhJH4+rtlAE3ow/9SsAM0YWV0Lgu3dbTKdWTa/pQXRdFWgoHWpzUKn+9oBKR5x8JdH+20wmgw== + version "8.0.2" + resolved "https://registry.yarnpkg.com/stylelint-config-prettier/-/stylelint-config-prettier-8.0.2.tgz#da9de33da4c56893cbe7e26df239a7374045e14e" + integrity sha512-TN1l93iVTXpF9NJstlvP7nOu9zY2k+mN0NSFQ/VEGz15ZIP9ohdDZTtCWHs5LjctAhSAzaILULGbgiM0ItId3A== stylelint-order@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/stylelint-order/-/stylelint-order-4.0.0.tgz#2a945c2198caac3ff44687d7c8582c81d044b556" - integrity sha512-bXV0v+jfB0+JKsqIn3mLglg1Dj2QCYkFHNfL1c+rVMEmruZmW5LUqT/ARBERfBm8SFtCuXpEdatidw/3IkcoiA== + version "4.1.0" + resolved "https://registry.yarnpkg.com/stylelint-order/-/stylelint-order-4.1.0.tgz#692d05b7d0c235ac66fcf5ea1d9e5f08a76747f6" + integrity sha512-sVTikaDvMqg2aJjh4r48jsdfmqLT+nqB1MOsaBnvM3OwLx4S+WXcsxsgk5w18h/OZoxZCxuyXMh61iBHcj9Qiw== dependencies: lodash "^4.17.15" - postcss "^7.0.26" + postcss "^7.0.31" postcss-sorting "^5.0.1" stylelint@^13.3.3: - version "13.3.3" - resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-13.3.3.tgz#e267a628ebfc1adad6f5a1fe818724c34171402b" - integrity sha512-j8Oio2T1YNiJc6iXDaPYd74Jg4zOa1bByNm/g9/Nvnq4tDPsIjMi46jhRZyPPktGPwjJ5FwcmCqIRlH6PVP8mA== + version "13.7.0" + resolved "https://registry.yarnpkg.com/stylelint/-/stylelint-13.7.0.tgz#8d7a4233063b2f06e9f28b3405ff189e334547b5" + integrity sha512-1wStd4zVetnlHO98VjcHQbjSDmvcA39smkZQMct2cf+hom40H0xlQNdzzbswoG/jGBh61/Ue9m7Lu99PY51O6A== dependencies: - "@stylelint/postcss-css-in-js" "^0.37.1" + "@stylelint/postcss-css-in-js" "^0.37.2" "@stylelint/postcss-markdown" "^0.36.1" - autoprefixer "^9.7.6" + autoprefixer "^9.8.6" balanced-match "^1.0.0" - chalk "^4.0.0" - cosmiconfig "^6.0.0" + chalk "^4.1.0" + cosmiconfig "^7.0.0" debug "^4.1.1" execall "^2.0.0" + fast-glob "^3.2.4" + fastest-levenshtein "^1.0.12" file-entry-cache "^5.0.1" - get-stdin "^7.0.0" + get-stdin "^8.0.0" global-modules "^2.0.0" - globby "^11.0.0" + globby "^11.0.1" globjoin "^0.1.4" html-tags "^3.1.0" - ignore "^5.1.4" + ignore "^5.1.8" import-lazy "^4.0.0" imurmurhash "^0.1.4" - known-css-properties "^0.18.0" - leven "^3.1.0" - lodash "^4.17.15" - log-symbols "^3.0.0" + known-css-properties "^0.19.0" + lodash "^4.17.20" + log-symbols "^4.0.0" mathml-tag-names "^2.1.3" - meow "^6.1.0" + meow "^7.1.1" micromatch "^4.0.2" normalize-selector "^0.2.0" - postcss "^7.0.27" + postcss "^7.0.32" postcss-html "^0.36.0" postcss-less "^3.1.4" postcss-media-query-parser "^0.2.3" - postcss-reporter "^6.0.1" postcss-resolve-nested-selector "^0.1.1" postcss-safe-parser "^4.0.2" postcss-sass "^0.4.4" - postcss-scss "^2.0.0" + postcss-scss "^2.1.1" postcss-selector-parser "^6.0.2" postcss-syntax "^0.36.2" - postcss-value-parser "^4.0.3" + postcss-value-parser "^4.1.0" resolve-from "^5.0.0" slash "^3.0.0" specificity "^0.4.1" @@ -14342,14 +14616,14 @@ stylelint@^13.3.3: style-search "^0.1.0" sugarss "^2.0.0" svg-tags "^1.0.0" - table "^5.4.6" - v8-compile-cache "^2.1.0" + table "^6.0.1" + v8-compile-cache "^2.1.1" write-file-atomic "^3.0.3" -subscriptions-transport-ws@^0.9.16: - version "0.9.16" - resolved "https://registry.yarnpkg.com/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.16.tgz#90a422f0771d9c32069294c08608af2d47f596ec" - integrity sha512-pQdoU7nC+EpStXnCfh/+ho0zE0Z+ma+i7xvj7bkXKb1dvYHSZxgRPaU6spRP+Bjzow67c/rRDoix5RT0uU9omw== +subscriptions-transport-ws@0.9.18, subscriptions-transport-ws@^0.9.18: + version "0.9.18" + resolved "https://registry.yarnpkg.com/subscriptions-transport-ws/-/subscriptions-transport-ws-0.9.18.tgz#bcf02320c911fbadb054f7f928e51c6041a37b97" + integrity sha512-tztzcBTNoEbuErsVQpTN2xUNN/efAZXyCyL5m3x4t6SKrEiTL2N8SaKWBFWM4u56pL79ULif3zjyeq+oV+nOaA== dependencies: backo2 "^1.0.2" eventemitter3 "^3.1.0" @@ -14419,17 +14693,22 @@ svgo@^1.0.0, svgo@^1.2.2: unquote "~1.1.1" util.promisify "~1.0.0" -symbol-observable@^1.0.2, symbol-observable@^1.0.4, symbol-observable@^1.1.0: +symbol-observable@^1.0.4, symbol-observable@^1.1.0, symbol-observable@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== +symbol-observable@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-2.0.1.tgz#ce66c36a04ed0f3056e7293184749a6fdd7063ea" + integrity sha512-QrfHrrEUMadQCgMijc3YpfA4ncwgqGv58Xgvdu3JZVQB7iY7cAkiqobZEZbaA863jof8AdpR01CPnZ5UWeqZBQ== + symbol-tree@^3.2.2: version "3.2.4" resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2" integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== -table@^5.2.3, table@^5.4.6: +table@^5.2.3: version "5.4.6" resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e" integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug== @@ -14439,6 +14718,16 @@ table@^5.2.3, table@^5.4.6: slice-ansi "^2.1.0" string-width "^3.0.0" +table@^6.0.1: + version "6.0.3" + resolved "https://registry.yarnpkg.com/table/-/table-6.0.3.tgz#e5b8a834e37e27ad06de2e0fda42b55cfd8a0123" + integrity sha512-8321ZMcf1B9HvVX/btKv8mMZahCjn2aYrDlpqHaBFCfnox64edeH9kEid0vTLTRR8gWR2A20aDgeuTTea4sVtw== + dependencies: + ajv "^6.12.4" + lodash "^4.17.20" + slice-ansi "^4.0.0" + string-width "^4.2.0" + tapable@^1.0.0, tapable@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.3.tgz#a1fccc06b58db61fd7a45da2da44f5f3a3e67ba2" @@ -14453,19 +14742,19 @@ tar@^2.0.0: fstream "^1.0.12" inherits "2" -terser-webpack-plugin@2.3.5: - version "2.3.5" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-2.3.5.tgz#5ad971acce5c517440ba873ea4f09687de2f4a81" - integrity sha512-WlWksUoq+E4+JlJ+h+U+QUzXpcsMSSNXkDy9lBVkSqDn1w23Gg29L/ary9GeJVYCGiNJJX7LnVc4bwL1N3/g1w== +terser-webpack-plugin@2.3.8: + version "2.3.8" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-2.3.8.tgz#894764a19b0743f2f704e7c2a848c5283a696724" + integrity sha512-/fKw3R+hWyHfYx7Bv6oPqmk4HGQcrWLtV3X6ggvPuwPNHSnzvVV51z6OaaCOus4YLjutYGOz3pEpbhe6Up2s1w== dependencies: cacache "^13.0.1" - find-cache-dir "^3.2.0" - jest-worker "^25.1.0" - p-limit "^2.2.2" - schema-utils "^2.6.4" - serialize-javascript "^2.1.2" + find-cache-dir "^3.3.1" + jest-worker "^25.4.0" + p-limit "^2.3.0" + schema-utils "^2.6.6" + serialize-javascript "^4.0.0" source-map "^0.6.1" - terser "^4.4.3" + terser "^4.6.12" webpack-sources "^1.4.3" terser-webpack-plugin@^1.4.3: @@ -14483,7 +14772,7 @@ terser-webpack-plugin@^1.4.3: webpack-sources "^1.4.0" worker-farm "^1.7.0" -terser@^4.1.2, terser@^4.3.9, terser@^4.4.3: +terser@^4.1.2, terser@^4.3.9: version "4.6.3" resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.3.tgz#e33aa42461ced5238d352d2df2a67f21921f8d87" integrity sha512-Lw+ieAXmY69d09IIc/yqeBqXpEQIpDGZqT34ui1QWXIUpR2RjbqEkT8X7Lgex19hslSqcWM5iMN2kM11eMsESQ== @@ -14492,6 +14781,24 @@ terser@^4.1.2, terser@^4.3.9, terser@^4.4.3: source-map "~0.6.1" source-map-support "~0.5.12" +terser@^4.6.12: + version "4.8.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-4.8.0.tgz#63056343d7c70bb29f3af665865a46fe03a0df17" + integrity sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw== + dependencies: + commander "^2.20.0" + source-map "~0.6.1" + source-map-support "~0.5.12" + +terser@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.3.0.tgz#c481f4afecdcc182d5e2bdd2ff2dc61555161e81" + integrity sha512-XTT3D3AwxC54KywJijmY2mxZ8nJiEjBHVYzq8l9OaYuRFWeQNBwvipuzzYEP4e+/AVcd1hqG/CqgsdIRyT45Fg== + dependencies: + commander "^2.20.0" + source-map "~0.6.1" + source-map-support "~0.5.12" + test-exclude@^5.2.3: version "5.2.3" resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-5.2.3.tgz#c3d3e1e311eb7ee405e092dac10aefd09091eac0" @@ -14552,7 +14859,7 @@ tiny-invariant@^1.0.2: resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.0.6.tgz#b3f9b38835e36a41c843a3b0907a5a7b3755de73" integrity sha512-FOyLWWVjG+aC0UqG76V53yAWdXfH8bO6FNmyZOuUrzDzK8DI3/JRY25UD7+g49JWM1LXwymsKERB+DzI0dTEQA== -tiny-warning@^1.0.0, tiny-warning@^1.0.2: +tiny-warning@^1.0.0, tiny-warning@^1.0.2, tiny-warning@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.3.tgz#94a30db453df4c643d0fd566060d60a875d84754" integrity sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA== @@ -14676,14 +14983,14 @@ trough@^1.0.0: dependencies: glob "^7.1.2" -ts-invariant@^0.4.0, ts-invariant@^0.4.4: +ts-invariant@^0.4.4: version "0.4.4" resolved "https://registry.yarnpkg.com/ts-invariant/-/ts-invariant-0.4.4.tgz#97a523518688f93aafad01b0e80eb803eb2abd86" integrity sha512-uEtWkFM/sdZvRNNDL3Ehu4WVpwaulhwQszV8mrtcdeE8nN00BV9mAmQ88RkrBhFgl9gMgvjJLAQcZbnPXI9mlA== dependencies: tslib "^1.9.3" -ts-log@2.1.4: +ts-log@^2.1.4: version "2.1.4" resolved "https://registry.yarnpkg.com/ts-log/-/ts-log-2.1.4.tgz#063c5ad1cbab5d49d258d18015963489fb6fb59a" integrity sha512-P1EJSoyV+N3bR/IWFeAqXzKPZwHpnLY6j7j58mAvewHRipo+BQM2Y1f9Y9BjEQznKwgqqZm7H8iuixmssU7tYQ== @@ -14698,16 +15005,26 @@ ts-pnp@^1.1.6: resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92" integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw== -tslib@1.11.1, tslib@^1.11.1, tslib@~1.11.1: - version "1.11.1" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35" - integrity sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA== +tsconfig-paths@^3.9.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.9.0.tgz#098547a6c4448807e8fcb8eae081064ee9a3c90b" + integrity sha512-dRcuzokWhajtZWkQsDVKbWyY+jgcLC5sqJhg2PSgf4ZkH2aHPvaOY8YWGhmjb68b5qqTfasSsDO9k7RUiEmZAw== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.1" + minimist "^1.2.0" + strip-bom "^3.0.0" tslib@^1.10.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: version "1.10.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a" integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ== +tslib@^2.0.0, tslib@~2.0.0, tslib@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.0.1.tgz#410eb0d113e5b6356490eec749603725b021b43e" + integrity sha512-SgIkNheinmEBgx1IUNirK0TUD4X9yjjBRTqqjggWCU3pUEqIk3/Uwl3yRixYKT6WjQuGiwDv4NomL3wqRCj+CQ== + tsutils@^3.17.1: version "3.17.1" resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.17.1.tgz#ed719917f11ca0dee586272b2ac49e015a2dd759" @@ -14739,11 +15056,6 @@ type-check@~0.3.2: dependencies: prelude-ls "~1.1.2" -type-detect@^4.0.0, type-detect@^4.0.5: - version "4.0.8" - resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" - integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g== - type-fest@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1" @@ -14794,10 +15106,10 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@^3.8.3: - version "3.8.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061" - integrity sha512-MYlEfn5VrLNsgudQTVJeNaQFUAI7DkhnOjdpAp4T+ku1TfQClewlbSuTVHiA+8skNBgaf02TL/kLOvig4y3G8w== +typescript@^3.9.7: + version "3.9.7" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa" + integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw== ua-parser-js@^0.7.18: version "0.7.21" @@ -15096,6 +15408,13 @@ use@^3.1.0: resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== +utf-8-validate@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-5.0.2.tgz#63cfbccd85dc1f2b66cf7a1d0eebc08ed056bfb3" + integrity sha512-SwV++i2gTD5qh2XqaPzBnNX88N6HdyhQrNNRykvcS0QKvItV9u3vPEJr+X5Hhfb1JC0r0e1alL0iB09rY8+nmw== + dependencies: + node-gyp-build "~3.7.0" + utif@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/utif/-/utif-2.0.1.tgz#9e1582d9bbd20011a6588548ed3266298e711759" @@ -15150,27 +15469,27 @@ utils-merge@1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= -uuid@^3.0.1: - version "3.4.0" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" - integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== - uuid@^3.3.2: version "3.3.3" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.3.tgz#4568f0216e78760ee1dbf3a4d2cf53e224112866" integrity sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ== -uuid@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b" - integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg== +uuid@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" + integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== -v8-compile-cache@^2.0.3, v8-compile-cache@^2.1.0: +v8-compile-cache@^2.0.3: version "2.1.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e" integrity sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g== -valid-url@1.0.9: +v8-compile-cache@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745" + integrity sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ== + +valid-url@1.0.9, valid-url@^1.0.9: version "1.0.9" resolved "https://registry.yarnpkg.com/valid-url/-/valid-url-1.0.9.tgz#1c14479b40f1397a75782f115e4086447433a200" integrity sha1-HBRHm0DxOXp1eC8RXkCGRHQzogA= @@ -15258,10 +15577,10 @@ vm-browserify@^1.0.1: resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-1.1.2.tgz#78641c488b8e6ca91a75f511e7a3b32a86e5dda0" integrity sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ== -vue-template-compiler@^2.6.11: - version "2.6.11" - resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.11.tgz#c04704ef8f498b153130018993e56309d4698080" - integrity sha512-KIq15bvQDrcCjpGjrAhx4mUlyyHfdmTaoNfeoATHLAiWB+MU3cx4lOzMwrnUh9cCxy0Lt1T11hAFY6TQgroUAA== +vue-template-compiler@^2.6.12: + version "2.6.12" + resolved "https://registry.yarnpkg.com/vue-template-compiler/-/vue-template-compiler-2.6.12.tgz#947ed7196744c8a5285ebe1233fe960437fcc57e" + integrity sha512-OzzZ52zS41YUbkCBfdXShQTe69j1gQDZ9HIX8miuC9C3rBCk9wIRjLiZZLrmX9V+Ftq/YEyv1JaVr5Y/hNtByg== dependencies: de-indent "^1.0.2" he "^1.1.0" @@ -15335,10 +15654,10 @@ webpack-dev-middleware@^3.7.2: range-parser "^1.2.1" webpack-log "^2.0.0" -webpack-dev-server@3.10.3: - version "3.10.3" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.10.3.tgz#f35945036813e57ef582c2420ef7b470e14d3af0" - integrity sha512-e4nWev8YzEVNdOMcNzNeCN947sWJNd43E5XvsJzbAL08kGc2frm1tQ32hTJslRS+H65LCb/AaUCYU7fjHCpDeQ== +webpack-dev-server@3.11.0: + version "3.11.0" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.11.0.tgz#8f154a3bce1bcfd1cc618ef4e703278855e7ff8c" + integrity sha512-PUxZ+oSTxogFQgkTtFndEtJIPNmml7ExwufBZ9L2/Xyyd5PnOL5UreWe5ZT7IU25DSdykL9p1MLQzmLh2ljSeg== dependencies: ansi-html "0.0.7" bonjour "^3.5.0" @@ -15348,31 +15667,31 @@ webpack-dev-server@3.10.3: debug "^4.1.1" del "^4.1.1" express "^4.17.1" - html-entities "^1.2.1" + html-entities "^1.3.1" http-proxy-middleware "0.19.1" import-local "^2.0.0" internal-ip "^4.3.0" ip "^1.1.5" is-absolute-url "^3.0.3" killable "^1.0.1" - loglevel "^1.6.6" + loglevel "^1.6.8" opn "^5.5.0" p-retry "^3.0.1" - portfinder "^1.0.25" + portfinder "^1.0.26" schema-utils "^1.0.0" selfsigned "^1.10.7" semver "^6.3.0" serve-index "^1.9.1" - sockjs "0.3.19" + sockjs "0.3.20" sockjs-client "1.4.0" - spdy "^4.0.1" + spdy "^4.0.2" strip-ansi "^3.0.1" supports-color "^6.1.0" url "^0.11.0" webpack-dev-middleware "^3.7.2" webpack-log "^2.0.0" ws "^6.2.1" - yargs "12.0.5" + yargs "^13.3.2" webpack-log@^2.0.0: version "2.0.0" @@ -15429,6 +15748,13 @@ webpack@4.42.0: watchpack "^1.6.0" webpack-sources "^1.4.1" +websocket-driver@0.6.5: + version "0.6.5" + resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.6.5.tgz#5cb2556ceb85f4373c6d8238aa691c8454e13a36" + integrity sha1-XLJVbOuF9Dc8bYI4qmkchFThOjY= + dependencies: + websocket-extensions ">=0.1.1" + websocket-driver@>=0.5.1: version "0.7.3" resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.3.tgz#a2d4e0d4f4f116f1e6297eba58b05d430100e9f9" @@ -15443,6 +15769,18 @@ websocket-extensions@>=0.1.1: resolved "https://registry.yarnpkg.com/websocket-extensions/-/websocket-extensions-0.1.3.tgz#5d2ff22977003ec687a4b87073dfbbac146ccf29" integrity sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg== +websocket@1.0.32: + version "1.0.32" + resolved "https://registry.yarnpkg.com/websocket/-/websocket-1.0.32.tgz#1f16ddab3a21a2d929dec1687ab21cfdc6d3dbb1" + integrity sha512-i4yhcllSP4wrpoPMU2N0TQ/q0O94LRG/eUQjEAamRltjQ1oT1PFFKOG4i877OlJgCG8rw6LrrowJp+TYCEWF7Q== + dependencies: + bufferutil "^4.0.1" + debug "^2.2.0" + es5-ext "^0.10.50" + typedarray-to-buffer "^3.1.5" + utf-8-validate "^5.0.2" + yaeti "^0.0.6" + whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0" @@ -15450,12 +15788,7 @@ whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.3, whatwg-encoding@^1.0.5: dependencies: iconv-lite "0.4.24" -whatwg-fetch@2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f" - integrity sha512-dcQ1GWpOD/eEQ97k66aiEVpNnapVj90/+R+SXTPYGHpYBBypfKJEQjLrvMZ7YXbKm21gXd4NcuxUTjiv1YtLng== - -whatwg-fetch@3.0.0, whatwg-fetch@>=0.10.0, whatwg-fetch@^3.0.0: +whatwg-fetch@>=0.10.0, whatwg-fetch@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb" integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q== @@ -15483,11 +15816,6 @@ whatwg-url@^7.0.0: tr46 "^1.0.1" webidl-conversions "^4.0.2" -which-module@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" - integrity sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8= - which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" @@ -15668,23 +15996,6 @@ worker-rpc@^0.1.0: dependencies: microevent.ts "~0.1.1" -wrap-ansi@7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" - integrity sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU= - dependencies: - string-width "^1.0.1" - strip-ansi "^3.0.1" - wrap-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-3.0.1.tgz#288a04d87eda5c286e060dfe8f135ce8d007f8ba" @@ -15702,6 +16013,24 @@ wrap-ansi@^5.1.0: string-width "^3.0.0" strip-ansi "^5.0.0" +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" @@ -15824,16 +16153,16 @@ xtend@^4.0.0, xtend@^4.0.1, xtend@~4.0.1: resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== -y18n@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" - integrity sha1-bRX7qITAhnnA136I53WegR4H+kE= - -"y18n@^3.2.1 || ^4.0.0", y18n@^4.0.0: +y18n@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== +yaeti@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/yaeti/-/yaeti-0.0.6.tgz#f26f484d72684cf42bedfb76970aa1608fbf9577" + integrity sha1-8m9ITXJoTPQr7ft2lwqhYI+/lXc= + yallist@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" @@ -15849,10 +16178,15 @@ yallist@^4.0.0: resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== -yaml-ast-parser@^0.0.40: - version "0.0.40" - resolved "https://registry.yarnpkg.com/yaml-ast-parser/-/yaml-ast-parser-0.0.40.tgz#08536d4e73d322b1c9ce207ab8dd70e04d20ae6e" - integrity sha1-CFNtTnPTIrHJziB6uN1w4E0grm4= +yaml-ast-parser@^0.0.43: + version "0.0.43" + resolved "https://registry.yarnpkg.com/yaml-ast-parser/-/yaml-ast-parser-0.0.43.tgz#e8a23e6fb4c38076ab92995c5dca33f3d3d7c9bb" + integrity sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A== + +yaml@^1.10.0: + version "1.10.0" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e" + integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg== yaml@^1.7.2: version "1.7.2" @@ -15861,14 +16195,6 @@ yaml@^1.7.2: dependencies: "@babel/runtime" "^7.6.3" -yargs-parser@^11.1.1: - version "11.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-11.1.1.tgz#879a0865973bca9f6bab5cbdf3b1c67ec7d3bcf4" - integrity sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - yargs-parser@^13.1.1: version "13.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.1.tgz#d26058532aa06d365fe091f6a1fc06b2f7e5eca0" @@ -15877,15 +16203,15 @@ yargs-parser@^13.1.1: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^15.0.0: - version "15.0.0" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-15.0.0.tgz#cdd7a97490ec836195f59f3f4dbe5ea9e8f75f08" - integrity sha512-xLTUnCMc4JhxrPEPUYD5IBR1mWCK/aT6+RJ/K29JY2y1vD+FhtgKK0AXRWvI262q3QSffAQuTouFIKUuHX89wQ== +yargs-parser@^13.1.2: + version "13.1.2" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" + integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== dependencies: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^18.1.3: +yargs-parser@^18.1.2, yargs-parser@^18.1.3: version "18.1.3" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== @@ -15893,31 +16219,6 @@ yargs-parser@^18.1.3: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-5.0.0.tgz#275ecf0d7ffe05c77e64e7c86e4cd94bf0e1228a" - integrity sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo= - dependencies: - camelcase "^3.0.0" - -yargs@12.0.5: - version "12.0.5" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13" - integrity sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw== - dependencies: - cliui "^4.0.0" - decamelize "^1.2.0" - find-up "^3.0.0" - get-caller-file "^1.0.1" - os-locale "^3.0.0" - require-directory "^2.1.1" - require-main-filename "^1.0.1" - set-blocking "^2.0.0" - string-width "^2.0.0" - which-module "^2.0.0" - y18n "^3.2.1 || ^4.0.0" - yargs-parser "^11.1.1" - yargs@^13.3.0: version "13.3.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.0.tgz#4c657a55e07e5f2cf947f8a366567c04a0dedc83" @@ -15934,13 +16235,12 @@ yargs@^13.3.0: y18n "^4.0.0" yargs-parser "^13.1.1" -yargs@^14.2.0: - version "14.2.2" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-14.2.2.tgz#2769564379009ff8597cdd38fba09da9b493c4b5" - integrity sha512-/4ld+4VV5RnrynMhPZJ/ZpOCGSCeghMykZ3BhdFBDa9Wy/RH6uEGNWDJog+aUlq+9OM1CFTgtYRW5Is1Po9NOA== +yargs@^13.3.2: + version "13.3.2" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" + integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== dependencies: cliui "^5.0.0" - decamelize "^1.2.0" find-up "^3.0.0" get-caller-file "^2.0.1" require-directory "^2.1.1" @@ -15949,44 +16249,26 @@ yargs@^14.2.0: string-width "^3.0.0" which-module "^2.0.0" y18n "^4.0.0" - yargs-parser "^15.0.0" + yargs-parser "^13.1.2" -yargs@^7.0.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8" - integrity sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg= +yargs@^15.3.1, yargs@^15.4.1: + version "15.4.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" + integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== dependencies: - camelcase "^3.0.0" - cliui "^3.2.0" - decamelize "^1.1.1" - get-caller-file "^1.0.1" - os-locale "^1.4.0" - read-pkg-up "^1.0.1" + cliui "^6.0.0" + decamelize "^1.2.0" + find-up "^4.1.0" + get-caller-file "^2.0.1" require-directory "^2.1.1" - require-main-filename "^1.0.1" + require-main-filename "^2.0.0" set-blocking "^2.0.0" - string-width "^1.0.2" - which-module "^1.0.0" - y18n "^3.2.1" - yargs-parser "^5.0.0" + string-width "^4.2.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^18.1.2" -zen-observable-ts@^0.8.20: - version "0.8.20" - resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.8.20.tgz#44091e335d3fcbc97f6497e63e7f57d5b516b163" - integrity sha512-2rkjiPALhOtRaDX6pWyNqK1fnP5KkJJybYebopNSn6wDG1lxBoFs2+nwwXKoA6glHIrtwrfBBy6da0stkKtTAA== - dependencies: - tslib "^1.9.3" - zen-observable "^0.8.0" - -zen-observable-ts@^0.8.21: - version "0.8.21" - resolved "https://registry.yarnpkg.com/zen-observable-ts/-/zen-observable-ts-0.8.21.tgz#85d0031fbbde1eba3cd07d3ba90da241215f421d" - integrity sha512-Yj3yXweRc8LdRMrCC8nIc4kkjWecPAUVh0TI0OUrWXx6aX790vLcDlWca6I4vsyCGH3LpWxq0dJRcMOFoVqmeg== - dependencies: - tslib "^1.9.3" - zen-observable "^0.8.0" - -zen-observable@^0.8.0: +zen-observable@^0.8.14: version "0.8.15" resolved "https://registry.yarnpkg.com/zen-observable/-/zen-observable-0.8.15.tgz#96415c512d8e3ffd920afd3889604e30b9eaac15" integrity sha512-PQ2PC7R9rslx84ndNBZB/Dkv8V8fZEpk83RLgXtYd0fwUgEjseMn1Dgajh2x6S8QbZAFa9p2qVCEuYZNgve0dQ== diff --git a/vendor/github.com/99designs/gqlgen/.gitignore b/vendor/github.com/99designs/gqlgen/.gitignore index 3dbd96815..b918d6a6f 100644 --- a/vendor/github.com/99designs/gqlgen/.gitignore +++ b/vendor/github.com/99designs/gqlgen/.gitignore @@ -4,6 +4,8 @@ /integration/node_modules /integration/schema-fetched.graphql /example/chat/package-lock.json +/example/federation/package-lock.json +/example/federation/node_modules /codegen/gen /gen diff --git a/vendor/github.com/99designs/gqlgen/.golangci.yml b/vendor/github.com/99designs/gqlgen/.golangci.yml index 2239d630a..8d5138680 100644 --- a/vendor/github.com/99designs/gqlgen/.golangci.yml +++ b/vendor/github.com/99designs/gqlgen/.golangci.yml @@ -1,3 +1,39 @@ +run: + tests: true + skip-dirs: + - bin + linters-settings: errcheck: ignore: fmt:.*,[rR]ead|[wW]rite|[cC]lose,io:Copy + +linters: + disable-all: true + enable: + - bodyclose + - deadcode + - depguard + - dupl + - errcheck + - gocritic + - gofmt + - goimports + - gosimple + - govet + - ineffassign + - interfacer + - misspell + - nakedret + - prealloc + - staticcheck + - structcheck + - unconvert + - unused + - varcheck + +issues: + exclude-rules: + # Exclude some linters from running on tests files. + - path: _test\.go + linters: + - dupl diff --git a/vendor/github.com/99designs/gqlgen/LICENSE b/vendor/github.com/99designs/gqlgen/LICENSE index 18e1b2493..10bb21c07 100644 --- a/vendor/github.com/99designs/gqlgen/LICENSE +++ b/vendor/github.com/99designs/gqlgen/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2018 Adam Scarr +Copyright (c) 2020 gqlgen authors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/vendor/github.com/99designs/gqlgen/README.md b/vendor/github.com/99designs/gqlgen/README.md index 0b302bdd5..0543bc373 100644 --- a/vendor/github.com/99designs/gqlgen/README.md +++ b/vendor/github.com/99designs/gqlgen/README.md @@ -1,20 +1,24 @@ -# gqlgen [![CircleCI](https://badgen.net/circleci/github/99designs/gqlgen/master)](https://circleci.com/gh/99designs/gqlgen) [![Read the Docs](https://badgen.net/badge/docs/available/green)](http://gqlgen.com/) +# gqlgen [![Continuous Integration](https://github.com/99designs/gqlgen/workflows/Continuous%20Integration/badge.svg)](https://github.com/99designs/gqlgen/actions) [![Read the Docs](https://badgen.net/badge/docs/available/green)](http://gqlgen.com/) [![GoDoc](https://godoc.org/github.com/99designs/gqlgen?status.svg)](https://godoc.org/github.com/99designs/gqlgen) + +![gqlgen](https://user-images.githubusercontent.com/46195831/89802919-0bb8ef00-db2a-11ea-8ba4-88e7a58b2fd2.png) ## What is gqlgen? -[gqlgen](https://github.com/99designs/gqlgen) is a Go library for building GraphQL servers without any fuss. gqlgen is: +[gqlgen](https://github.com/99designs/gqlgen) is a Go library for building GraphQL servers without any fuss.
    - - **Schema first** — Define your API using the GraphQL [Schema Definition Language](http://graphql.org/learn/schema/). - - **Type safe** — You should never see `map[string]interface{}` here. - - **Codegen** — Let us generate the boring bits, so you can build your app quickly. +- **gqlgen is based on a Schema first approach** — You get to Define your API using the GraphQL [Schema Definition Language](http://graphql.org/learn/schema/). +- **gqlgen priortizes Type safety** — You should never see `map[string]interface{}` here. +- **gqlgen enables Codegen** — We generate the boring bits, so you can focus on building your app quickly. -[Feature Comparison](https://gqlgen.com/feature-comparison/) +Still not convinced enough to use **gqlgen**? Compare **gqlgen** with other Go graphql [implementations](https://gqlgen.com/feature-comparison/) ## Getting Started +- To install gqlgen run the comand `go get github.com/99designs/gqlgen` in your project directory.
    +- You could initialize a new project using the recommended folder structure by running this command `go run github.com/99designs/gqlgen init`. -First work your way through the [Getting Started](https://gqlgen.com/getting-started/) tutorial. - -If you can't find what your looking for, look at our [examples](https://github.com/99designs/gqlgen/tree/master/example) for example usage of gqlgen. +You could find a more comprehensive guide to help you get started [here](https://gqlgen.com/getting-started/).
    +We also have a couple of real-world [examples](https://github.com/99designs/gqlgen/tree/master/example) that show how to GraphQL applicatons with **gqlgen** seamlessly, +You can see these [examples](https://github.com/99designs/gqlgen/tree/master/example) here or visit [godoc](https://godoc.org/github.com/99designs/gqlgen). ## Reporting Issues @@ -22,10 +26,88 @@ If you think you've found a bug, or something isn't behaving the way you think i ## Contributing -Read our [Contribution Guidelines](https://github.com/99designs/gqlgen/blob/master/CONTRIBUTING.md) for information on how you can help out gqlgen. +We welcome contributions, Read our [Contribution Guidelines](https://github.com/99designs/gqlgen/blob/master/CONTRIBUTING.md) to learn more about contributing to **gqlgen** +## Frequently asked questions + +### How do I prevent fetching child objects that might not be used? + +When you have nested or recursive schema like this: + +```graphql +type User { + id: ID! + name: String! + friends: [User!]! +} +``` + +You need to tell gqlgen that it should only fetch friends if the user requested it. There are two ways to do this; + +- #### Using Custom Models + +Write a custom model that omits the friends field: + +```go +type User struct { + ID int + Name string +} +``` + +And reference the model in `gqlgen.yml`: + +```yaml +# gqlgen.yml +models: + User: + model: github.com/you/pkg/model.User # go import path to the User struct above +``` + +- #### Using Explicit Resolvers + +If you want to Keep using the generated model, mark the field as requiring a resolver explicitly in `gqlgen.yml` like this: + +```yaml +# gqlgen.yml +models: + User: + fields: + friends: + resolver: true # force a resolver to be generated +``` + +After doing either of the above and running generate we will need to provide a resolver for friends: + +```go +func (r *userResolver) Friends(ctx context.Context, obj *User) ([]*User, error) { + // select * from user where friendid = obj.ID + return friends, nil +} +``` + +### Can I change the type of the ID from type String to Type Int? + +Yes! You can by remapping it in config as seen below: + +```yaml +models: + ID: # The GraphQL type ID is backed by + model: + - github.com/99designs/gqlgen/graphql.IntID # An go integer + - github.com/99designs/gqlgen/graphql.ID # or a go string +``` + +This means gqlgen will be able to automatically bind to strings or ints for models you have written yourself, but the +first model in this list is used as the default type and it will always be used when: + +- Generating models based on schema +- As arguments in resolvers + +There isnt any way around this, gqlgen has no way to know what you want in a given context. ## Other Resources - - [Christopher Biscardi @ Gophercon UK 2018](https://youtu.be/FdURVezcdcw) - - [Introducing gqlgen: a GraphQL Server Generator for Go](https://99designs.com.au/blog/engineering/gqlgen-a-graphql-server-generator-for-go/) - - [GraphQL workshop for Golang developers by Iván Corrales Solera](https://graphql-go.wesovilabs.com) +- [Christopher Biscardi @ Gophercon UK 2018](https://youtu.be/FdURVezcdcw) +- [Introducing gqlgen: a GraphQL Server Generator for Go](https://99designs.com.au/blog/engineering/gqlgen-a-graphql-server-generator-for-go/) +- [Dive into GraphQL by Iván Corrales Solera](https://medium.com/@ivan.corrales.solera/dive-into-graphql-9bfedf22e1a) +- [Sample Project built on gqlgen with Postgres by Oleg Shalygin](https://github.com/oshalygin/gqlgen-pg-todo-example) diff --git a/vendor/github.com/99designs/gqlgen/api/generate.go b/vendor/github.com/99designs/gqlgen/api/generate.go index 3dd083f52..3a19c017d 100644 --- a/vendor/github.com/99designs/gqlgen/api/generate.go +++ b/vendor/github.com/99designs/gqlgen/api/generate.go @@ -6,25 +6,60 @@ import ( "github.com/99designs/gqlgen/codegen" "github.com/99designs/gqlgen/codegen/config" "github.com/99designs/gqlgen/plugin" + "github.com/99designs/gqlgen/plugin/federation" "github.com/99designs/gqlgen/plugin/modelgen" "github.com/99designs/gqlgen/plugin/resolvergen" "github.com/pkg/errors" - "golang.org/x/tools/go/packages" ) func Generate(cfg *config.Config, option ...Option) error { _ = syscall.Unlink(cfg.Exec.Filename) - _ = syscall.Unlink(cfg.Model.Filename) + if cfg.Model.IsDefined() { + _ = syscall.Unlink(cfg.Model.Filename) + } - plugins := []plugin.Plugin{ - modelgen.New(), - resolvergen.New(), + plugins := []plugin.Plugin{} + if cfg.Model.IsDefined() { + plugins = append(plugins, modelgen.New()) + } + plugins = append(plugins, resolvergen.New()) + if cfg.Federation.IsDefined() { + plugins = append([]plugin.Plugin{federation.New()}, plugins...) } for _, o := range option { o(cfg, &plugins) } + for _, p := range plugins { + if inj, ok := p.(plugin.EarlySourceInjector); ok { + if s := inj.InjectSourceEarly(); s != nil { + cfg.Sources = append(cfg.Sources, s) + } + } + } + + if err := cfg.LoadSchema(); err != nil { + return errors.Wrap(err, "failed to load schema") + } + + for _, p := range plugins { + if inj, ok := p.(plugin.LateSourceInjector); ok { + if s := inj.InjectSourceLate(cfg.Schema); s != nil { + cfg.Sources = append(cfg.Sources, s) + } + } + } + + // LoadSchema again now we have everything + if err := cfg.LoadSchema(); err != nil { + return errors.Wrap(err, "failed to load schema") + } + + if err := cfg.Init(); err != nil { + return errors.Wrap(err, "generating core failed") + } + for _, p := range plugins { if mut, ok := p.(plugin.ConfigMutator); ok { err := mut.MutateConfig(cfg) @@ -36,7 +71,7 @@ func Generate(cfg *config.Config, option ...Option) error { // Merge again now that the generated models have been injected into the typemap data, err := codegen.BuildData(cfg) if err != nil { - return errors.Wrap(err, "merging failed") + return errors.Wrap(err, "merging type systems failed") } if err = codegen.GenerateCode(data); err != nil { @@ -52,8 +87,14 @@ func Generate(cfg *config.Config, option ...Option) error { } } - if err := validate(cfg); err != nil { - return errors.Wrap(err, "validation failed") + if err = codegen.GenerateCode(data); err != nil { + return errors.Wrap(err, "generating core failed") + } + + if !cfg.SkipValidation { + if err := validate(cfg); err != nil { + return errors.Wrap(err, "validation failed") + } } return nil @@ -68,9 +109,11 @@ func validate(cfg *config.Config) error { if cfg.Resolver.IsDefined() { roots = append(roots, cfg.Resolver.ImportPath()) } - _, err := packages.Load(&packages.Config{Mode: packages.LoadTypes | packages.LoadSyntax}, roots...) - if err != nil { - return errors.Wrap(err, "validation failed") + + cfg.Packages.LoadAll(roots...) + errs := cfg.Packages.Errors() + if len(errs) > 0 { + return errs } return nil } diff --git a/vendor/github.com/99designs/gqlgen/cmd/ambient.go b/vendor/github.com/99designs/gqlgen/cmd/ambient.go index 7838fdf16..0f3655d34 100644 --- a/vendor/github.com/99designs/gqlgen/cmd/ambient.go +++ b/vendor/github.com/99designs/gqlgen/cmd/ambient.go @@ -5,6 +5,6 @@ import ( // don't prune unused code for us. Both lists should be kept in sync. _ "github.com/99designs/gqlgen/graphql" _ "github.com/99designs/gqlgen/graphql/introspection" - _ "github.com/vektah/gqlparser" - _ "github.com/vektah/gqlparser/ast" + _ "github.com/vektah/gqlparser/v2" + _ "github.com/vektah/gqlparser/v2/ast" ) diff --git a/vendor/github.com/99designs/gqlgen/cmd/gen.go b/vendor/github.com/99designs/gqlgen/cmd/gen.go index c69858b44..b875bb43d 100644 --- a/vendor/github.com/99designs/gqlgen/cmd/gen.go +++ b/vendor/github.com/99designs/gqlgen/cmd/gen.go @@ -1,44 +1,43 @@ package cmd import ( - "fmt" "os" "github.com/99designs/gqlgen/api" "github.com/99designs/gqlgen/codegen/config" "github.com/pkg/errors" - "github.com/urfave/cli" + "github.com/urfave/cli/v2" ) -var genCmd = cli.Command{ +var genCmd = &cli.Command{ Name: "generate", Usage: "generate a graphql server based on schema", Flags: []cli.Flag{ - cli.BoolFlag{Name: "verbose, v", Usage: "show logs"}, - cli.StringFlag{Name: "config, c", Usage: "the config filename"}, + &cli.BoolFlag{Name: "verbose, v", Usage: "show logs"}, + &cli.StringFlag{Name: "config, c", Usage: "the config filename"}, }, - Action: func(ctx *cli.Context) { + Action: func(ctx *cli.Context) error { var cfg *config.Config var err error if configFilename := ctx.String("config"); configFilename != "" { cfg, err = config.LoadConfig(configFilename) if err != nil { - fmt.Fprintln(os.Stderr, err.Error()) - os.Exit(1) + return err } } else { cfg, err = config.LoadConfigFromDefaultLocations() if os.IsNotExist(errors.Cause(err)) { - cfg = config.DefaultConfig() - } else if err != nil { - fmt.Fprintln(os.Stderr, err.Error()) - os.Exit(2) + cfg, err = config.LoadDefaultConfig() + } + + if err != nil { + return err } } if err = api.Generate(cfg); err != nil { - fmt.Fprintln(os.Stderr, err.Error()) - os.Exit(3) + return err } + return nil }, } diff --git a/vendor/github.com/99designs/gqlgen/cmd/init.go b/vendor/github.com/99designs/gqlgen/cmd/init.go index e07bed970..121805af5 100644 --- a/vendor/github.com/99designs/gqlgen/cmd/init.go +++ b/vendor/github.com/99designs/gqlgen/cmd/init.go @@ -3,28 +3,79 @@ package cmd import ( "bytes" "fmt" + "html/template" "io/ioutil" "os" + "path/filepath" "strings" "github.com/99designs/gqlgen/api" - "github.com/99designs/gqlgen/plugin/servergen" - "github.com/99designs/gqlgen/codegen/config" - "github.com/pkg/errors" - "github.com/urfave/cli" - yaml "gopkg.in/yaml.v2" + "github.com/99designs/gqlgen/internal/code" + "github.com/99designs/gqlgen/plugin/servergen" + "github.com/urfave/cli/v2" ) -var configComment = ` -# .gqlgen.yml example -# -# Refer to https://gqlgen.com/config/ -# for detailed .gqlgen.yml documentation. -` +var configTemplate = template.Must(template.New("name").Parse( + `# Where are all the schema files located? globs are supported eg src/**/*.graphqls +schema: + - graph/*.graphqls -var schemaDefault = ` -# GraphQL schema example +# Where should the generated server code go? +exec: + filename: graph/generated/generated.go + package: generated + +# Uncomment to enable federation +# federation: +# filename: graph/generated/federation.go +# package: generated + +# Where should any generated models go? +model: + filename: graph/model/models_gen.go + package: model + +# Where should the resolver implementations go? +resolver: + layout: follow-schema + dir: graph + package: graph + +# Optional: turn on use ` + "`" + `gqlgen:"fieldName"` + "`" + ` tags in your models +# struct_tag: json + +# Optional: turn on to use []Thing instead of []*Thing +# omit_slice_element_pointers: false + +# Optional: set to speed up generation time by not performing a final validation pass. +# skip_validation: true + +# gqlgen will search for any type names in the schema in these go packages +# if they match it will use them, otherwise it will generate them. +autobind: + - "{{.}}/graph/model" + +# This section declares type mapping between the GraphQL and go type systems +# +# The first line in each type will be used as defaults for resolver arguments and +# modelgen, the others will be allowed when binding to fields. Configure them to +# your liking +models: + ID: + model: + - github.com/99designs/gqlgen/graphql.ID + - github.com/99designs/gqlgen/graphql.Int + - github.com/99designs/gqlgen/graphql.Int64 + - github.com/99designs/gqlgen/graphql.Int32 + Int: + model: + - github.com/99designs/gqlgen/graphql.Int + - github.com/99designs/gqlgen/graphql.Int64 + - github.com/99designs/gqlgen/graphql.Int32 +`)) + +var schemaDefault = `# GraphQL schema example # # https://gqlgen.com/getting-started/ @@ -54,91 +105,95 @@ type Mutation { } ` -var initCmd = cli.Command{ +var initCmd = &cli.Command{ Name: "init", Usage: "create a new gqlgen project", Flags: []cli.Flag{ - cli.BoolFlag{Name: "verbose, v", Usage: "show logs"}, - cli.StringFlag{Name: "config, c", Usage: "the config filename"}, - cli.StringFlag{Name: "server", Usage: "where to write the server stub to", Value: "server/server.go"}, - cli.StringFlag{Name: "schema", Usage: "where to write the schema stub to", Value: "schema.graphql"}, + &cli.BoolFlag{Name: "verbose, v", Usage: "show logs"}, + &cli.StringFlag{Name: "config, c", Usage: "the config filename"}, + &cli.StringFlag{Name: "server", Usage: "where to write the server stub to", Value: "server.go"}, + &cli.StringFlag{Name: "schema", Usage: "where to write the schema stub to", Value: "graph/schema.graphqls"}, }, - Action: func(ctx *cli.Context) { - initSchema(ctx.String("schema")) - config := initConfig(ctx) + Action: func(ctx *cli.Context) error { + configFilename := ctx.String("config") + serverFilename := ctx.String("server") - GenerateGraphServer(config, ctx.String("server")) + pkgName := code.ImportPathForDir(".") + if pkgName == "" { + return fmt.Errorf("unable to determine import path for current directory, you probably need to run go mod init first") + } + + if err := initSchema(ctx.String("schema")); err != nil { + return err + } + if !configExists(configFilename) { + if err := initConfig(configFilename, pkgName); err != nil { + return err + } + } + + GenerateGraphServer(serverFilename) + return nil }, } -func GenerateGraphServer(cfg *config.Config, serverFilename string) { - err := api.Generate(cfg, api.AddPlugin(servergen.New(serverFilename))) +func GenerateGraphServer(serverFilename string) { + cfg, err := config.LoadConfigFromDefaultLocations() if err != nil { fmt.Fprintln(os.Stderr, err.Error()) } + if err := api.Generate(cfg, api.AddPlugin(servergen.New(serverFilename))); err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + } + fmt.Fprintf(os.Stdout, "Exec \"go run ./%s\" to start GraphQL server\n", serverFilename) } -func initConfig(ctx *cli.Context) *config.Config { +func configExists(configFilename string) bool { var cfg *config.Config - var err error - configFilename := ctx.String("config") + if configFilename != "" { - cfg, err = config.LoadConfig(configFilename) + cfg, _ = config.LoadConfig(configFilename) } else { - cfg, err = config.LoadConfigFromDefaultLocations() - } - - if cfg != nil { - fmt.Fprintf(os.Stderr, "init failed: a configuration file already exists\n") - os.Exit(1) - } - - if !os.IsNotExist(errors.Cause(err)) { - fmt.Fprintln(os.Stderr, err.Error()) - os.Exit(1) + cfg, _ = config.LoadConfigFromDefaultLocations() } + return cfg != nil +} +func initConfig(configFilename string, pkgName string) error { if configFilename == "" { configFilename = "gqlgen.yml" } - cfg = config.DefaultConfig() - cfg.Resolver = config.PackageConfig{ - Filename: "resolver.go", - Type: "Resolver", + if err := os.MkdirAll(filepath.Dir(configFilename), 0755); err != nil { + return fmt.Errorf("unable to create config dir: " + err.Error()) } var buf bytes.Buffer - buf.WriteString(strings.TrimSpace(configComment)) - buf.WriteString("\n\n") - var b []byte - b, err = yaml.Marshal(cfg) - if err != nil { - fmt.Fprintln(os.Stderr, "unable to marshal yaml: "+err.Error()) - os.Exit(1) - } - buf.Write(b) - - err = ioutil.WriteFile(configFilename, buf.Bytes(), 0644) - if err != nil { - fmt.Fprintln(os.Stderr, "unable to write cfg file: "+err.Error()) - os.Exit(1) + if err := configTemplate.Execute(&buf, pkgName); err != nil { + panic(err) } - return cfg + if err := ioutil.WriteFile(configFilename, buf.Bytes(), 0644); err != nil { + return fmt.Errorf("unable to write cfg file: " + err.Error()) + } + + return nil } -func initSchema(schemaFilename string) { +func initSchema(schemaFilename string) error { _, err := os.Stat(schemaFilename) if !os.IsNotExist(err) { - return + return nil } - err = ioutil.WriteFile(schemaFilename, []byte(strings.TrimSpace(schemaDefault)), 0644) - if err != nil { - fmt.Fprintln(os.Stderr, "unable to write schema file: "+err.Error()) - os.Exit(1) + if err := os.MkdirAll(filepath.Dir(schemaFilename), 0755); err != nil { + return fmt.Errorf("unable to create schema dir: " + err.Error()) } + + if err = ioutil.WriteFile(schemaFilename, []byte(strings.TrimSpace(schemaDefault)), 0644); err != nil { + return fmt.Errorf("unable to write schema file: " + err.Error()) + } + return nil } diff --git a/vendor/github.com/99designs/gqlgen/cmd/root.go b/vendor/github.com/99designs/gqlgen/cmd/root.go index dc2970ac8..2776aa284 100644 --- a/vendor/github.com/99designs/gqlgen/cmd/root.go +++ b/vendor/github.com/99designs/gqlgen/cmd/root.go @@ -7,9 +7,10 @@ import ( "os" "github.com/99designs/gqlgen/graphql" - "github.com/urfave/cli" + "github.com/urfave/cli/v2" // Required since otherwise dep will prune away these unused packages before codegen has a chance to run + _ "github.com/99designs/gqlgen/graphql/handler" _ "github.com/99designs/gqlgen/handler" ) @@ -31,7 +32,7 @@ func Execute() { } app.Action = genCmd.Action - app.Commands = []cli.Command{ + app.Commands = []*cli.Command{ genCmd, initCmd, versionCmd, diff --git a/vendor/github.com/99designs/gqlgen/cmd/version.go b/vendor/github.com/99designs/gqlgen/cmd/version.go index 8b7442d4e..d3a05deda 100644 --- a/vendor/github.com/99designs/gqlgen/cmd/version.go +++ b/vendor/github.com/99designs/gqlgen/cmd/version.go @@ -4,13 +4,14 @@ import ( "fmt" "github.com/99designs/gqlgen/graphql" - "github.com/urfave/cli" + "github.com/urfave/cli/v2" ) -var versionCmd = cli.Command{ +var versionCmd = &cli.Command{ Name: "version", Usage: "print the version string", - Action: func(ctx *cli.Context) { + Action: func(ctx *cli.Context) error { fmt.Println(graphql.Version) + return nil }, } diff --git a/vendor/github.com/99designs/gqlgen/codegen/args.go b/vendor/github.com/99designs/gqlgen/codegen/args.go index d1498bddb..20a26e975 100644 --- a/vendor/github.com/99designs/gqlgen/codegen/args.go +++ b/vendor/github.com/99designs/gqlgen/codegen/args.go @@ -8,7 +8,7 @@ import ( "github.com/99designs/gqlgen/codegen/config" "github.com/99designs/gqlgen/codegen/templates" "github.com/pkg/errors" - "github.com/vektah/gqlparser/ast" + "github.com/vektah/gqlparser/v2/ast" ) type ArgSet struct { @@ -26,6 +26,22 @@ type FieldArgument struct { Value interface{} // value set in Data } +//ImplDirectives get not Builtin and location ARGUMENT_DEFINITION directive +func (f *FieldArgument) ImplDirectives() []*Directive { + d := make([]*Directive, 0) + for i := range f.Directives { + if !f.Directives[i].Builtin && f.Directives[i].IsLocation(ast.LocationArgumentDefinition) { + d = append(d, f.Directives[i]) + } + } + + return d +} + +func (f *FieldArgument) DirectiveObjName() string { + return "rawArgs" +} + func (f *FieldArgument) Stream() bool { return f.Object != nil && f.Object.Stream } diff --git a/vendor/github.com/99designs/gqlgen/codegen/args.gotpl b/vendor/github.com/99designs/gqlgen/codegen/args.gotpl index 4c7212182..b25d444b5 100644 --- a/vendor/github.com/99designs/gqlgen/codegen/args.gotpl +++ b/vendor/github.com/99designs/gqlgen/codegen/args.gotpl @@ -5,27 +5,20 @@ func (ec *executionContext) {{ $name }}(ctx context.Context, rawArgs map[string] {{- range $i, $arg := . }} var arg{{$i}} {{ $arg.TypeReference.GO | ref}} if tmp, ok := rawArgs[{{$arg.Name|quote}}]; ok { - {{- if $arg.Directives }} - getArg0 := func(ctx context.Context) (interface{}, error) { return ec.{{ $arg.TypeReference.UnmarshalFunc }}(ctx, tmp) } - - {{- range $i, $directive := $arg.Directives }} - getArg{{add $i 1}} := func(ctx context.Context) (res interface{}, err error) { - {{- range $dArg := $directive.Args }} - {{- if and $dArg.TypeReference.IsPtr ( notNil "Value" $dArg ) }} - {{ $dArg.VarName }} := {{ $dArg.Value | dump }} - {{- end }} - {{- end }} - n := getArg{{$i}} - return ec.directives.{{$directive.Name|ucFirst}}({{$directive.ResolveArgs "tmp" "n" }}) - } - {{- end }} - - tmp, err = getArg{{$arg.Directives|len}}(ctx) + ctx := graphql.WithFieldInputContext(ctx, graphql.NewFieldInputWithField({{$arg.Name|quote}})) + {{- if $arg.ImplDirectives }} + directive0 := func(ctx context.Context) (interface{}, error) { return ec.{{ $arg.TypeReference.UnmarshalFunc }}(ctx, tmp) } + {{ template "implDirectives" $arg }} + tmp, err = directive{{$arg.ImplDirectives|len}}(ctx) if err != nil { return nil, err } if data, ok := tmp.({{ $arg.TypeReference.GO | ref }}) ; ok { arg{{$i}} = data + {{- if $arg.TypeReference.IsNilable }} + } else if tmp == nil { + arg{{$i}} = nil + {{- end }} } else { return nil, fmt.Errorf(`unexpected type %T from directive, should be {{ $arg.TypeReference.GO }}`, tmp) } diff --git a/vendor/github.com/99designs/gqlgen/codegen/config/binder.go b/vendor/github.com/99designs/gqlgen/codegen/config/binder.go index cea904ada..2be7b7bdd 100644 --- a/vendor/github.com/99designs/gqlgen/codegen/config/binder.go +++ b/vendor/github.com/99designs/gqlgen/codegen/config/binder.go @@ -8,37 +8,24 @@ import ( "github.com/99designs/gqlgen/codegen/templates" "github.com/99designs/gqlgen/internal/code" "github.com/pkg/errors" - "github.com/vektah/gqlparser/ast" - "golang.org/x/tools/go/packages" + "github.com/vektah/gqlparser/v2/ast" ) // Binder connects graphql types to golang types using static analysis type Binder struct { - pkgs []*packages.Package + pkgs *code.Packages schema *ast.Schema cfg *Config References []*TypeReference + SawInvalid bool } -func (c *Config) NewBinder(s *ast.Schema) (*Binder, error) { - pkgs, err := packages.Load(&packages.Config{Mode: packages.LoadTypes | packages.LoadSyntax}, c.Models.ReferencedPackages()...) - if err != nil { - return nil, err - } - - for _, p := range pkgs { - for _, e := range p.Errors { - if e.Kind == packages.ListError { - return nil, p.Errors[0] - } - } - } - +func (c *Config) NewBinder() *Binder { return &Binder{ - pkgs: pkgs, - schema: s, + pkgs: c.Packages, + schema: c.Schema, cfg: c, - }, nil + } } func (b *Binder) TypePosition(typ types.Type) token.Position { @@ -58,11 +45,26 @@ func (b *Binder) ObjectPosition(typ types.Object) token.Position { Filename: "unknown", } } - pkg := b.getPkg(typ.Pkg().Path()) + pkg := b.pkgs.Load(typ.Pkg().Path()) return pkg.Fset.Position(typ.Pos()) } +func (b *Binder) FindTypeFromName(name string) (types.Type, error) { + pkgName, typeName := code.PkgAndType(name) + return b.FindType(pkgName, typeName) +} + func (b *Binder) FindType(pkgName string, typeName string) (types.Type, error) { + if pkgName == "" { + if typeName == "map[string]interface{}" { + return MapType, nil + } + + if typeName == "interface{}" { + return InterfaceType, nil + } + } + obj, err := b.FindObject(pkgName, typeName) if err != nil { return nil, err @@ -74,15 +76,6 @@ func (b *Binder) FindType(pkgName string, typeName string) (types.Type, error) { return obj.Type(), nil } -func (b *Binder) getPkg(find string) *packages.Package { - for _, p := range b.pkgs { - if code.NormalizeVendor(find) == code.NormalizeVendor(p.PkgPath) { - return p - } - } - return nil -} - var MapType = types.NewMap(types.Typ[types.String], types.NewInterfaceType(nil, nil).Complete()) var InterfaceType = types.NewInterfaceType(nil, nil) @@ -122,7 +115,7 @@ func (b *Binder) FindObject(pkgName string, typeName string) (types.Object, erro fullName = pkgName + "." + typeName } - pkg := b.getPkg(pkgName) + pkg := b.pkgs.LoadWithTypes(pkgName) if pkg == nil { return nil, errors.Errorf("required package was not loaded: %s", fullName) } @@ -173,7 +166,8 @@ func (b *Binder) PointerTo(ref *TypeReference) *TypeReference { type TypeReference struct { Definition *ast.Definition GQL *ast.Type - GO types.Type + GO types.Type // Type of the field being bound. Could be a pointer or a value type of Target. + Target types.Type // The actual type that we know how to bind to. May require pointer juggling when traversing to fields. CastType types.Type // Before calling marshalling functions cast from/to this base type Marshaler *types.Func // When using external marshalling functions this will point to the Marshal function Unmarshaler *types.Func // When using external marshalling functions this will point to the Unmarshal function @@ -184,6 +178,7 @@ func (ref *TypeReference) Elem() *TypeReference { if p, isPtr := ref.GO.(*types.Pointer); isPtr { return &TypeReference{ GO: p.Elem(), + Target: ref.Target, GQL: ref.GQL, CastType: ref.CastType, Definition: ref.Definition, @@ -196,6 +191,7 @@ func (ref *TypeReference) Elem() *TypeReference { if ref.IsSlice() { return &TypeReference{ GO: ref.GO.(*types.Slice).Elem(), + Target: ref.Target, GQL: ref.GQL.Elem, CastType: ref.CastType, Definition: ref.Definition, @@ -213,10 +209,7 @@ func (t *TypeReference) IsPtr() bool { } func (t *TypeReference) IsNilable() bool { - _, isPtr := t.GO.(*types.Pointer) - _, isMap := t.GO.(*types.Map) - _, isInterface := t.GO.(*types.Interface) - return isPtr || isMap || isInterface + return IsNilable(t.GO) } func (t *TypeReference) IsSlice() bool { @@ -244,7 +237,12 @@ func (t *TypeReference) UniquenessKey() string { nullability = "N" } - return nullability + t.Definition.Name + "2" + templates.TypeIdentifier(t.GO) + var elemNullability = "" + if t.GQL.Elem != nil && t.GQL.Elem.NonNull { + // Fix for #896 + elemNullability = "ᚄ" + } + return nullability + t.Definition.Name + "2" + templates.TypeIdentifier(t.GO) + elemNullability } func (t *TypeReference) MarshalFunc() string { @@ -271,6 +269,10 @@ func (t *TypeReference) UnmarshalFunc() string { return "unmarshal" + t.UniquenessKey() } +func (t *TypeReference) IsTargetNilable() bool { + return IsNilable(t.Target) +} + func (b *Binder) PushRef(ret *TypeReference) { b.References = append(b.References, ret) } @@ -292,6 +294,11 @@ func isIntf(t types.Type) bool { } func (b *Binder) TypeReference(schemaType *ast.Type, bindTarget types.Type) (ret *TypeReference, err error) { + if !isValid(bindTarget) { + b.SawInvalid = true + return nil, fmt.Errorf("%s has an invalid type", schemaType.Name()) + } + var pkgName, typeName string def := b.schema.Types[schemaType.Name()] defer func() { @@ -350,7 +357,7 @@ func (b *Binder) TypeReference(schemaType *ast.Type, bindTarget types.Type) (ret ref.GO = obj.Type() ref.IsMarshaler = true } else if underlying := basicUnderlying(obj.Type()); def.IsLeafType() && underlying != nil && underlying.Kind() == types.String { - // Special case for named types wrapping strings. Used by default enum implementations. + // TODO delete before v1. Backwards compatibility case for named types wrapping strings (see #595) ref.GO = obj.Type() ref.CastType = underlying @@ -366,6 +373,7 @@ func (b *Binder) TypeReference(schemaType *ast.Type, bindTarget types.Type) (ret ref.GO = obj.Type() } + ref.Target = ref.GO ref.GO = b.CopyModifiersFromAst(schemaType, ref.GO) if bindTarget != nil { @@ -378,13 +386,21 @@ func (b *Binder) TypeReference(schemaType *ast.Type, bindTarget types.Type) (ret return ref, nil } - return nil, fmt.Errorf("%s has type compatible with %s", schemaType.Name(), bindTarget.String()) + return nil, fmt.Errorf("%s is incompatible with %s", schemaType.Name(), bindTarget.String()) +} + +func isValid(t types.Type) bool { + basic, isBasic := t.(*types.Basic) + if !isBasic { + return true + } + return basic.Kind() != types.Invalid } func (b *Binder) CopyModifiersFromAst(t *ast.Type, base types.Type) types.Type { if t.Elem != nil { child := b.CopyModifiersFromAst(t.Elem, base) - if _, isStruct := child.Underlying().(*types.Struct); isStruct { + if _, isStruct := child.Underlying().(*types.Struct); isStruct && !b.cfg.OmitSliceElementPointers { child = types.NewPointer(child) } return types.NewSlice(child) @@ -395,13 +411,25 @@ func (b *Binder) CopyModifiersFromAst(t *ast.Type, base types.Type) types.Type { _, isInterface = named.Underlying().(*types.Interface) } - if !isInterface && !t.NonNull { + if !isInterface && !IsNilable(base) && !t.NonNull { return types.NewPointer(base) } return base } +func IsNilable(t types.Type) bool { + if namedType, isNamed := t.(*types.Named); isNamed { + return IsNilable(namedType.Underlying()) + } + _, isPtr := t.(*types.Pointer) + _, isMap := t.(*types.Map) + _, isInterface := t.(*types.Interface) + _, isSlice := t.(*types.Slice) + _, isChan := t.(*types.Chan) + return isPtr || isMap || isInterface || isSlice || isChan +} + func hasMethod(it types.Type, name string) bool { if ptr, isPtr := it.(*types.Pointer); isPtr { it = ptr.Elem() diff --git a/vendor/github.com/99designs/gqlgen/codegen/config/config.go b/vendor/github.com/99designs/gqlgen/codegen/config/config.go index 1725adab0..ba939fcf5 100644 --- a/vendor/github.com/99designs/gqlgen/codegen/config/config.go +++ b/vendor/github.com/99designs/gqlgen/codegen/config/config.go @@ -2,27 +2,38 @@ package config import ( "fmt" - "go/types" "io/ioutil" "os" "path/filepath" + "regexp" "sort" "strings" "github.com/99designs/gqlgen/internal/code" "github.com/pkg/errors" - "github.com/vektah/gqlparser" - "github.com/vektah/gqlparser/ast" - yaml "gopkg.in/yaml.v2" + "github.com/vektah/gqlparser/v2" + "github.com/vektah/gqlparser/v2/ast" + "gopkg.in/yaml.v2" ) type Config struct { - SchemaFilename StringList `yaml:"schema,omitempty"` - Exec PackageConfig `yaml:"exec"` - Model PackageConfig `yaml:"model"` - Resolver PackageConfig `yaml:"resolver,omitempty"` - Models TypeMap `yaml:"models,omitempty"` - StructTag string `yaml:"struct_tag,omitempty"` + SchemaFilename StringList `yaml:"schema,omitempty"` + Exec PackageConfig `yaml:"exec"` + Model PackageConfig `yaml:"model,omitempty"` + Federation PackageConfig `yaml:"federation,omitempty"` + Resolver ResolverConfig `yaml:"resolver,omitempty"` + AutoBind []string `yaml:"autobind"` + Models TypeMap `yaml:"models,omitempty"` + StructTag string `yaml:"struct_tag,omitempty"` + Directives map[string]DirectiveConfig `yaml:"directives,omitempty"` + OmitSliceElementPointers bool `yaml:"omit_slice_element_pointers,omitempty"` + SkipValidation bool `yaml:"skip_validation,omitempty"` + Sources []*ast.Source `yaml:"-"` + Packages *code.Packages `yaml:"-"` + Schema *ast.Schema `yaml:"-"` + + // Deprecated use Federation instead. Will be removed next release + Federated bool `yaml:"federated,omitempty"` } var cfgFilenames = []string{".gqlgen.yml", "gqlgen.yml", "gqlgen.yaml"} @@ -33,9 +44,30 @@ func DefaultConfig() *Config { SchemaFilename: StringList{"schema.graphql"}, Model: PackageConfig{Filename: "models_gen.go"}, Exec: PackageConfig{Filename: "generated.go"}, + Directives: map[string]DirectiveConfig{}, + Models: TypeMap{}, } } +// LoadDefaultConfig loads the default config so that it is ready to be used +func LoadDefaultConfig() (*Config, error) { + config := DefaultConfig() + + for _, filename := range config.SchemaFilename { + filename = filepath.ToSlash(filename) + var err error + var schemaRaw []byte + schemaRaw, err = ioutil.ReadFile(filename) + if err != nil { + return nil, errors.Wrap(err, "unable to open schema") + } + + config.Sources = append(config.Sources, &ast.Source{Name: filename, Input: string(schemaRaw)}) + } + + return config, nil +} + // LoadConfigFromDefaultLocations looks for a config file in the current directory, and all parent directories // walking up the tree. The closest config file will be returned. func LoadConfigFromDefaultLocations() (*Config, error) { @@ -51,6 +83,13 @@ func LoadConfigFromDefaultLocations() (*Config, error) { return LoadConfig(cfgFile) } +var path2regex = strings.NewReplacer( + `.`, `\.`, + `*`, `.+`, + `\`, `[\\/]`, + `/`, `[\\/]`, +) + // LoadConfig reads the gqlgen.yml config file func LoadConfig(filename string) (*Config, error) { config := DefaultConfig() @@ -64,12 +103,50 @@ func LoadConfig(filename string) (*Config, error) { return nil, errors.Wrap(err, "unable to parse config") } + defaultDirectives := map[string]DirectiveConfig{ + "skip": {SkipRuntime: true}, + "include": {SkipRuntime: true}, + "deprecated": {SkipRuntime: true}, + } + + for key, value := range defaultDirectives { + if _, defined := config.Directives[key]; !defined { + config.Directives[key] = value + } + } + preGlobbing := config.SchemaFilename config.SchemaFilename = StringList{} for _, f := range preGlobbing { - matches, err := filepath.Glob(f) - if err != nil { - return nil, errors.Wrapf(err, "failed to glob schema filename %s", f) + var matches []string + + // for ** we want to override default globbing patterns and walk all + // subdirectories to match schema files. + if strings.Contains(f, "**") { + pathParts := strings.SplitN(f, "**", 2) + rest := strings.TrimPrefix(strings.TrimPrefix(pathParts[1], `\`), `/`) + // turn the rest of the glob into a regex, anchored only at the end because ** allows + // for any number of dirs in between and walk will let us match against the full path name + globRe := regexp.MustCompile(path2regex.Replace(rest) + `$`) + + if err := filepath.Walk(pathParts[0], func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + if globRe.MatchString(strings.TrimPrefix(path, pathParts[0])) { + matches = append(matches, path) + } + + return nil + }); err != nil { + return nil, errors.Wrapf(err, "failed to walk schema at root %s", pathParts[0]) + } + } else { + matches, err = filepath.Glob(f) + if err != nil { + return nil, errors.Wrapf(err, "failed to glob schema filename %s", f) + } } for _, m := range matches { @@ -80,13 +157,126 @@ func LoadConfig(filename string) (*Config, error) { } } + for _, filename := range config.SchemaFilename { + filename = filepath.ToSlash(filename) + var err error + var schemaRaw []byte + schemaRaw, err = ioutil.ReadFile(filename) + if err != nil { + return nil, errors.Wrap(err, "unable to open schema") + } + + config.Sources = append(config.Sources, &ast.Source{Name: filename, Input: string(schemaRaw)}) + } + return config, nil } -type PackageConfig struct { - Filename string `yaml:"filename,omitempty"` - Package string `yaml:"package,omitempty"` - Type string `yaml:"type,omitempty"` +func (c *Config) Init() error { + if c.Packages == nil { + c.Packages = &code.Packages{} + } + + if c.Schema == nil { + if err := c.LoadSchema(); err != nil { + return err + } + } + + err := c.injectTypesFromSchema() + if err != nil { + return err + } + + err = c.autobind() + if err != nil { + return err + } + + c.injectBuiltins() + + // prefetch all packages in one big packages.Load call + pkgs := []string{ + "github.com/99designs/gqlgen/graphql", + "github.com/99designs/gqlgen/graphql/introspection", + } + pkgs = append(pkgs, c.Models.ReferencedPackages()...) + pkgs = append(pkgs, c.AutoBind...) + c.Packages.LoadAll(pkgs...) + + // check everything is valid on the way out + err = c.check() + if err != nil { + return err + } + + return nil +} + +func (c *Config) injectTypesFromSchema() error { + c.Directives["goModel"] = DirectiveConfig{ + SkipRuntime: true, + } + + c.Directives["goField"] = DirectiveConfig{ + SkipRuntime: true, + } + + for _, schemaType := range c.Schema.Types { + if schemaType == c.Schema.Query || schemaType == c.Schema.Mutation || schemaType == c.Schema.Subscription { + continue + } + + if bd := schemaType.Directives.ForName("goModel"); bd != nil { + if ma := bd.Arguments.ForName("model"); ma != nil { + if mv, err := ma.Value.Value(nil); err == nil { + c.Models.Add(schemaType.Name, mv.(string)) + } + } + if ma := bd.Arguments.ForName("models"); ma != nil { + if mvs, err := ma.Value.Value(nil); err == nil { + for _, mv := range mvs.([]interface{}) { + c.Models.Add(schemaType.Name, mv.(string)) + } + } + } + } + + if schemaType.Kind == ast.Object || schemaType.Kind == ast.InputObject { + for _, field := range schemaType.Fields { + if fd := field.Directives.ForName("goField"); fd != nil { + forceResolver := c.Models[schemaType.Name].Fields[field.Name].Resolver + fieldName := c.Models[schemaType.Name].Fields[field.Name].FieldName + + if ra := fd.Arguments.ForName("forceResolver"); ra != nil { + if fr, err := ra.Value.Value(nil); err == nil { + forceResolver = fr.(bool) + } + } + + if na := fd.Arguments.ForName("name"); na != nil { + if fr, err := na.Value.Value(nil); err == nil { + fieldName = fr.(string) + } + } + + if c.Models[schemaType.Name].Fields == nil { + c.Models[schemaType.Name] = TypeMapEntry{ + Model: c.Models[schemaType.Name].Model, + Fields: map[string]TypeMapField{}, + } + } + + c.Models[schemaType.Name].Fields[field.Name] = TypeMapField{ + FieldName: fieldName, + Resolver: forceResolver, + } + } + } + } + } + + return nil } type TypeMapEntry struct { @@ -95,8 +285,9 @@ type TypeMapEntry struct { } type TypeMapField struct { - Resolver bool `yaml:"resolver"` - FieldName string `yaml:"fieldName"` + Resolver bool `yaml:"resolver"` + FieldName string `yaml:"fieldName"` + GeneratedMethod string `yaml:"-"` } type StringList []string @@ -128,90 +319,85 @@ func (a StringList) Has(file string) bool { return false } -func (c *PackageConfig) normalize() error { - if c.Filename == "" { - return errors.New("Filename is required") - } - c.Filename = abs(c.Filename) - // If Package is not set, first attempt to load the package at the output dir. If that fails - // fallback to just the base dir name of the output filename. - if c.Package == "" { - c.Package = code.NameForDir(c.Dir()) +func (c *Config) check() error { + if c.Models == nil { + c.Models = TypeMap{} } - return nil -} - -func (c *PackageConfig) ImportPath() string { - return code.ImportPathForDir(c.Dir()) -} - -func (c *PackageConfig) Dir() string { - return filepath.Dir(c.Filename) -} - -func (c *PackageConfig) Check() error { - if strings.ContainsAny(c.Package, "./\\") { - return fmt.Errorf("package should be the output package name only, do not include the output filename") - } - if c.Filename != "" && !strings.HasSuffix(c.Filename, ".go") { - return fmt.Errorf("filename should be path to a go source file") + type FilenamePackage struct { + Filename string + Package string + Declaree string } - return c.normalize() -} + fileList := map[string][]FilenamePackage{} -func (c *PackageConfig) Pkg() *types.Package { - return types.NewPackage(c.ImportPath(), c.Dir()) -} - -func (c *PackageConfig) IsDefined() bool { - return c.Filename != "" -} - -func (c *Config) Check() error { if err := c.Models.Check(); err != nil { return errors.Wrap(err, "config.models") } if err := c.Exec.Check(); err != nil { return errors.Wrap(err, "config.exec") } - if err := c.Model.Check(); err != nil { - return errors.Wrap(err, "config.model") + fileList[c.Exec.ImportPath()] = append(fileList[c.Exec.ImportPath()], FilenamePackage{ + Filename: c.Exec.Filename, + Package: c.Exec.Package, + Declaree: "exec", + }) + + if c.Model.IsDefined() { + if err := c.Model.Check(); err != nil { + return errors.Wrap(err, "config.model") + } + fileList[c.Model.ImportPath()] = append(fileList[c.Model.ImportPath()], FilenamePackage{ + Filename: c.Model.Filename, + Package: c.Model.Package, + Declaree: "model", + }) } if c.Resolver.IsDefined() { if err := c.Resolver.Check(); err != nil { return errors.Wrap(err, "config.resolver") } + fileList[c.Resolver.ImportPath()] = append(fileList[c.Resolver.ImportPath()], FilenamePackage{ + Filename: c.Resolver.Filename, + Package: c.Resolver.Package, + Declaree: "resolver", + }) } - - // check packages names against conflict, if present in the same dir - // and check filenames for uniqueness - packageConfigList := []PackageConfig{ - c.Model, - c.Exec, - c.Resolver, - } - filesMap := make(map[string]bool) - pkgConfigsByDir := make(map[string]PackageConfig) - for _, current := range packageConfigList { - _, fileFound := filesMap[current.Filename] - if fileFound { - return fmt.Errorf("filename %s defined more than once", current.Filename) + if c.Federation.IsDefined() { + if err := c.Federation.Check(); err != nil { + return errors.Wrap(err, "config.federation") } - filesMap[current.Filename] = true - previous, inSameDir := pkgConfigsByDir[current.Dir()] - if inSameDir && current.Package != previous.Package { - return fmt.Errorf("filenames %s and %s are in the same directory but have different package definitions", stripPath(current.Filename), stripPath(previous.Filename)) + fileList[c.Federation.ImportPath()] = append(fileList[c.Federation.ImportPath()], FilenamePackage{ + Filename: c.Federation.Filename, + Package: c.Federation.Package, + Declaree: "federation", + }) + if c.Federation.ImportPath() != c.Exec.ImportPath() { + return fmt.Errorf("federation and exec must be in the same package") } - pkgConfigsByDir[current.Dir()] = current + } + if c.Federated { + return fmt.Errorf("federated has been removed, instead use\nfederation:\n filename: path/to/federated.go") } - return c.normalize() -} + for importPath, pkg := range fileList { + for _, file1 := range pkg { + for _, file2 := range pkg { + if file1.Package != file2.Package { + return fmt.Errorf("%s and %s define the same import path (%s) with different package names (%s vs %s)", + file1.Declaree, + file2.Declaree, + importPath, + file1.Package, + file2.Package, + ) + } + } + } + } -func stripPath(path string) string { - return filepath.Base(path) + return nil } type TypeMap map[string]TypeMapEntry @@ -259,10 +445,14 @@ func (tm TypeMap) ReferencedPackages() []string { return pkgs } -func (tm TypeMap) Add(Name string, goType string) { - modelCfg := tm[Name] +func (tm TypeMap) Add(name string, goType string) { + modelCfg := tm[name] modelCfg.Model = append(modelCfg.Model, goType) - tm[Name] = modelCfg + tm[name] = modelCfg +} + +type DirectiveConfig struct { + SkipRuntime bool `yaml:"skip_runtime"` } func inStrSlice(haystack []string, needle string) bool { @@ -307,29 +497,54 @@ func findCfgInDir(dir string) string { return "" } -func (c *Config) normalize() error { - if err := c.Model.normalize(); err != nil { - return errors.Wrap(err, "model") +func (c *Config) autobind() error { + if len(c.AutoBind) == 0 { + return nil } - if err := c.Exec.normalize(); err != nil { - return errors.Wrap(err, "exec") - } + ps := c.Packages.LoadAll(c.AutoBind...) - if c.Resolver.IsDefined() { - if err := c.Resolver.normalize(); err != nil { - return errors.Wrap(err, "resolver") + for _, t := range c.Schema.Types { + if c.Models.UserDefined(t.Name) { + continue + } + + for i, p := range ps { + if p == nil { + return fmt.Errorf("unable to load %s - make sure you're using an import path to a package that exists", c.AutoBind[i]) + } + if t := p.Types.Scope().Lookup(t.Name); t != nil { + c.Models.Add(t.Name(), t.Pkg().Path()+"."+t.Name()) + break + } } } - if c.Models == nil { - c.Models = TypeMap{} + for i, t := range c.Models { + for j, m := range t.Model { + pkg, typename := code.PkgAndType(m) + + // skip anything that looks like an import path + if strings.Contains(pkg, "/") { + continue + } + + for _, p := range ps { + if p.Name != pkg { + continue + } + if t := p.Types.Scope().Lookup(typename); t != nil { + c.Models[i].Model[j] = t.Pkg().Path() + "." + t.Name() + break + } + } + } } return nil } -func (c *Config) InjectBuiltins(s *ast.Schema) { +func (c *Config) injectBuiltins() { builtins := TypeMap{ "__Directive": {Model: StringList{"github.com/99designs/gqlgen/graphql/introspection.Directive"}}, "__DirectiveLocation": {Model: StringList{"github.com/99designs/gqlgen/graphql.String"}}, @@ -370,35 +585,36 @@ func (c *Config) InjectBuiltins(s *ast.Schema) { } for typeName, entry := range extraBuiltins { - if t, ok := s.Types[typeName]; !c.Models.Exists(typeName) && ok && t.Kind == ast.Scalar { + if t, ok := c.Schema.Types[typeName]; !c.Models.Exists(typeName) && ok && t.Kind == ast.Scalar { c.Models[typeName] = entry } } } -func (c *Config) LoadSchema() (*ast.Schema, map[string]string, error) { - schemaStrings := map[string]string{} - - var sources []*ast.Source - - for _, filename := range c.SchemaFilename { - filename = filepath.ToSlash(filename) - var err error - var schemaRaw []byte - schemaRaw, err = ioutil.ReadFile(filename) - if err != nil { - fmt.Fprintln(os.Stderr, "unable to open schema: "+err.Error()) - os.Exit(1) - } - schemaStrings[filename] = string(schemaRaw) - sources = append(sources, &ast.Source{Name: filename, Input: schemaStrings[filename]}) +func (c *Config) LoadSchema() error { + if c.Packages != nil { + c.Packages = &code.Packages{} } - schema, err := gqlparser.LoadSchema(sources...) + if err := c.check(); err != nil { + return err + } + + schema, err := gqlparser.LoadSchema(c.Sources...) if err != nil { - return nil, nil, err + return err } - return schema, schemaStrings, nil + + if schema.Query == nil { + schema.Query = &ast.Definition{ + Kind: ast.Object, + Name: "Query", + } + schema.Types["Query"] = schema.Query + } + + c.Schema = schema + return nil } func abs(path string) string { diff --git a/vendor/github.com/99designs/gqlgen/codegen/config/package.go b/vendor/github.com/99designs/gqlgen/codegen/config/package.go new file mode 100644 index 000000000..a96459381 --- /dev/null +++ b/vendor/github.com/99designs/gqlgen/codegen/config/package.go @@ -0,0 +1,62 @@ +package config + +import ( + "fmt" + "go/types" + "path/filepath" + "strings" + + "github.com/99designs/gqlgen/internal/code" +) + +type PackageConfig struct { + Filename string `yaml:"filename,omitempty"` + Package string `yaml:"package,omitempty"` +} + +func (c *PackageConfig) ImportPath() string { + if !c.IsDefined() { + return "" + } + return code.ImportPathForDir(c.Dir()) +} + +func (c *PackageConfig) Dir() string { + if !c.IsDefined() { + return "" + } + return filepath.Dir(c.Filename) +} + +func (c *PackageConfig) Pkg() *types.Package { + if !c.IsDefined() { + return nil + } + return types.NewPackage(c.ImportPath(), c.Package) +} + +func (c *PackageConfig) IsDefined() bool { + return c.Filename != "" +} + +func (c *PackageConfig) Check() error { + if strings.ContainsAny(c.Package, "./\\") { + return fmt.Errorf("package should be the output package name only, do not include the output filename") + } + if c.Filename == "" { + return fmt.Errorf("filename must be specified") + } + if !strings.HasSuffix(c.Filename, ".go") { + return fmt.Errorf("filename should be path to a go source file") + } + + c.Filename = abs(c.Filename) + + // If Package is not set, first attempt to load the package at the output dir. If that fails + // fallback to just the base dir name of the output filename. + if c.Package == "" { + c.Package = code.NameForDir(c.Dir()) + } + + return nil +} diff --git a/vendor/github.com/99designs/gqlgen/codegen/config/resolver.go b/vendor/github.com/99designs/gqlgen/codegen/config/resolver.go new file mode 100644 index 000000000..cd03f1887 --- /dev/null +++ b/vendor/github.com/99designs/gqlgen/codegen/config/resolver.go @@ -0,0 +1,100 @@ +package config + +import ( + "fmt" + "go/types" + "path/filepath" + "strings" + + "github.com/99designs/gqlgen/internal/code" +) + +type ResolverConfig struct { + Filename string `yaml:"filename,omitempty"` + FilenameTemplate string `yaml:"filename_template,omitempty"` + Package string `yaml:"package,omitempty"` + Type string `yaml:"type,omitempty"` + Layout ResolverLayout `yaml:"layout,omitempty"` + DirName string `yaml:"dir"` +} + +type ResolverLayout string + +var ( + LayoutSingleFile ResolverLayout = "single-file" + LayoutFollowSchema ResolverLayout = "follow-schema" +) + +func (r *ResolverConfig) Check() error { + if r.Layout == "" { + r.Layout = LayoutSingleFile + } + if r.Type == "" { + r.Type = "Resolver" + } + + switch r.Layout { + case LayoutSingleFile: + if r.Filename == "" { + return fmt.Errorf("filename must be specified with layout=%s", r.Layout) + } + if !strings.HasSuffix(r.Filename, ".go") { + return fmt.Errorf("filename should be path to a go source file with layout=%s", r.Layout) + } + r.Filename = abs(r.Filename) + case LayoutFollowSchema: + if r.DirName == "" { + return fmt.Errorf("dirname must be specified with layout=%s", r.Layout) + } + r.DirName = abs(r.DirName) + if r.Filename == "" { + r.Filename = filepath.Join(r.DirName, "resolver.go") + } else { + r.Filename = abs(r.Filename) + } + default: + return fmt.Errorf("invalid layout %s. must be %s or %s", r.Layout, LayoutSingleFile, LayoutFollowSchema) + } + + if strings.ContainsAny(r.Package, "./\\") { + return fmt.Errorf("package should be the output package name only, do not include the output filename") + } + + if r.Package == "" && r.Dir() != "" { + r.Package = code.NameForDir(r.Dir()) + } + + return nil +} + +func (r *ResolverConfig) ImportPath() string { + if r.Dir() == "" { + return "" + } + return code.ImportPathForDir(r.Dir()) +} + +func (r *ResolverConfig) Dir() string { + switch r.Layout { + case LayoutSingleFile: + if r.Filename == "" { + return "" + } + return filepath.Dir(r.Filename) + case LayoutFollowSchema: + return r.DirName + default: + panic("invalid layout " + r.Layout) + } +} + +func (r *ResolverConfig) Pkg() *types.Package { + if r.Dir() == "" { + return nil + } + return types.NewPackage(r.ImportPath(), r.Package) +} + +func (r *ResolverConfig) IsDefined() bool { + return r.Filename != "" || r.DirName != "" +} diff --git a/vendor/github.com/99designs/gqlgen/codegen/data.go b/vendor/github.com/99designs/gqlgen/codegen/data.go index f2ea70b4a..bedbef9d4 100644 --- a/vendor/github.com/99designs/gqlgen/codegen/data.go +++ b/vendor/github.com/99designs/gqlgen/codegen/data.go @@ -4,9 +4,10 @@ import ( "fmt" "sort" - "github.com/99designs/gqlgen/codegen/config" "github.com/pkg/errors" - "github.com/vektah/gqlparser/ast" + "github.com/vektah/gqlparser/v2/ast" + + "github.com/99designs/gqlgen/codegen/config" ) // Data is a unified model of the code to be generated. Plugins may modify this structure to do things like implement @@ -14,8 +15,7 @@ import ( type Data struct { Config *config.Config Schema *ast.Schema - SchemaStr map[string]string - Directives map[string]*Directive + Directives DirectiveList Objects Objects Inputs Objects Interfaces map[string]*Interface @@ -30,7 +30,6 @@ type Data struct { type builder struct { Config *config.Config Schema *ast.Schema - SchemaStr map[string]string Binder *config.Binder Directives map[string]*Directive } @@ -38,26 +37,12 @@ type builder struct { func BuildData(cfg *config.Config) (*Data, error) { b := builder{ Config: cfg, + Schema: cfg.Schema, } + b.Binder = b.Config.NewBinder() + var err error - b.Schema, b.SchemaStr, err = cfg.LoadSchema() - if err != nil { - return nil, err - } - - err = cfg.Check() - if err != nil { - return nil, err - } - - cfg.InjectBuiltins(b.Schema) - - b.Binder, err = b.Config.NewBinder(b.Schema) - if err != nil { - return nil, err - } - b.Directives, err = b.buildDirectives() if err != nil { return nil, err @@ -74,7 +59,6 @@ func BuildData(cfg *config.Config) (*Data, error) { Config: cfg, Directives: dataDirectives, Schema: b.Schema, - SchemaStr: b.SchemaStr, Interfaces: map[string]*Interface{}, } @@ -96,7 +80,10 @@ func BuildData(cfg *config.Config) (*Data, error) { s.Inputs = append(s.Inputs, input) case ast.Union, ast.Interface: - s.Interfaces[schemaType.Name] = b.buildInterface(schemaType) + s.Interfaces[schemaType.Name], err = b.buildInterface(schemaType) + if err != nil { + return nil, errors.Wrap(err, "unable to bind to interface") + } } } @@ -118,10 +105,7 @@ func BuildData(cfg *config.Config) (*Data, error) { return nil, err } - s.ReferencedTypes, err = b.buildTypes() - if err != nil { - return nil, err - } + s.ReferencedTypes = b.buildTypes() sort.Slice(s.Objects, func(i, j int) bool { return s.Objects[i].Definition.Name < s.Objects[j].Definition.Name @@ -131,6 +115,17 @@ func BuildData(cfg *config.Config) (*Data, error) { return s.Inputs[i].Definition.Name < s.Inputs[j].Definition.Name }) + if b.Binder.SawInvalid { + // if we have a syntax error, show it + err := cfg.Packages.Errors() + if len(err) > 0 { + return nil, err + } + + // otherwise show a generic error message + return nil, fmt.Errorf("invalid types were encountered while traversing the go source code, this probably means the invalid code generated isnt correct. add try adding -v to debug") + } + return &s, nil } diff --git a/vendor/github.com/99designs/gqlgen/codegen/directive.go b/vendor/github.com/99designs/gqlgen/codegen/directive.go index 5a27e8ace..5d4c038ff 100644 --- a/vendor/github.com/99designs/gqlgen/codegen/directive.go +++ b/vendor/github.com/99designs/gqlgen/codegen/directive.go @@ -7,15 +7,46 @@ import ( "github.com/99designs/gqlgen/codegen/templates" "github.com/pkg/errors" - "github.com/vektah/gqlparser/ast" + "github.com/vektah/gqlparser/v2/ast" ) +type DirectiveList map[string]*Directive + +//LocationDirectives filter directives by location +func (dl DirectiveList) LocationDirectives(location string) DirectiveList { + return locationDirectives(dl, ast.DirectiveLocation(location)) +} + type Directive struct { + *ast.DirectiveDefinition Name string Args []*FieldArgument Builtin bool } +//IsLocation check location directive +func (d *Directive) IsLocation(location ...ast.DirectiveLocation) bool { + for _, l := range d.Locations { + for _, a := range location { + if l == a { + return true + } + } + } + + return false +} + +func locationDirectives(directives DirectiveList, location ...ast.DirectiveLocation) map[string]*Directive { + mDirectives := make(map[string]*Directive) + for name, d := range directives { + if d.IsLocation(location...) { + mDirectives[name] = d + } + } + return mDirectives +} + func (b *builder) buildDirectives() (map[string]*Directive, error) { directives := make(map[string]*Directive, len(b.Schema.Directives)) @@ -24,11 +55,6 @@ func (b *builder) buildDirectives() (map[string]*Directive, error) { return nil, errors.Errorf("directive with name %s already exists", name) } - var builtin bool - if name == "skip" || name == "include" || name == "deprecated" { - builtin = true - } - var args []*FieldArgument for _, arg := range dir.Arguments { tr, err := b.Binder.TypeReference(arg.Type, nil) @@ -53,9 +79,10 @@ func (b *builder) buildDirectives() (map[string]*Directive, error) { } directives[name] = &Directive{ - Name: name, - Args: args, - Builtin: builtin, + DirectiveDefinition: dir, + Name: name, + Args: args, + Builtin: b.Config.Directives[name].SkipRuntime, } } @@ -92,8 +119,10 @@ func (b *builder) getDirectives(list ast.DirectiveList) ([]*Directive, error) { }) } dirs[i] = &Directive{ - Name: d.Name, - Args: args, + Name: d.Name, + Args: args, + DirectiveDefinition: list[i].Definition, + Builtin: b.Config.Directives[d.Name].SkipRuntime, } } @@ -119,18 +148,12 @@ func (d *Directive) CallArgs() string { return strings.Join(args, ", ") } -func (d *Directive) ResolveArgs(obj string, next string) string { - args := []string{"ctx", obj, next} +func (d *Directive) ResolveArgs(obj string, next int) string { + args := []string{"ctx", obj, fmt.Sprintf("directive%d", next)} for _, arg := range d.Args { - dArg := "&" + arg.VarName - if !arg.TypeReference.IsPtr() { - if arg.Value != nil { - dArg = templates.Dump(arg.Value) - } else { - dArg = templates.Dump(arg.Default) - } - } else if arg.Value == nil && arg.Default == nil { + dArg := arg.VarName + if arg.Value == nil && arg.Default == nil { dArg = "nil" } @@ -144,7 +167,7 @@ func (d *Directive) Declaration() string { res := ucFirst(d.Name) + " func(ctx context.Context, obj interface{}, next graphql.Resolver" for _, arg := range d.Args { - res += fmt.Sprintf(", %s %s", arg.Name, templates.CurrentImports.LookupType(arg.TypeReference.GO)) + res += fmt.Sprintf(", %s %s", templates.ToGoPrivate(arg.Name), templates.CurrentImports.LookupType(arg.TypeReference.GO)) } res += ") (res interface{}, err error)" diff --git a/vendor/github.com/99designs/gqlgen/codegen/directives.gotpl b/vendor/github.com/99designs/gqlgen/codegen/directives.gotpl new file mode 100644 index 000000000..e6d2455f6 --- /dev/null +++ b/vendor/github.com/99designs/gqlgen/codegen/directives.gotpl @@ -0,0 +1,149 @@ +{{ define "implDirectives" }}{{ $in := .DirectiveObjName }} + {{- range $i, $directive := .ImplDirectives -}} + directive{{add $i 1}} := func(ctx context.Context) (interface{}, error) { + {{- range $arg := $directive.Args }} + {{- if notNil "Value" $arg }} + {{ $arg.VarName }}, err := ec.{{ $arg.TypeReference.UnmarshalFunc }}(ctx, {{ $arg.Value | dump }}) + if err != nil{ + return nil, err + } + {{- else if notNil "Default" $arg }} + {{ $arg.VarName }}, err := ec.{{ $arg.TypeReference.UnmarshalFunc }}(ctx, {{ $arg.Default | dump }}) + if err != nil{ + return nil, err + } + {{- end }} + {{- end }} + if ec.directives.{{$directive.Name|ucFirst}} == nil { + return nil, errors.New("directive {{$directive.Name}} is not implemented") + } + return ec.directives.{{$directive.Name|ucFirst}}({{$directive.ResolveArgs $in $i }}) + } + {{ end -}} +{{ end }} + +{{define "queryDirectives"}} + for _, d := range obj.Directives { + switch d.Name { + {{- range $directive := . }} + case "{{$directive.Name}}": + {{- if $directive.Args }} + rawArgs := d.ArgumentMap(ec.Variables) + args, err := ec.{{ $directive.ArgsFunc }}(ctx,rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + {{- end }} + n := next + next = func(ctx context.Context) (interface{}, error) { + if ec.directives.{{$directive.Name|ucFirst}} == nil { + return nil, errors.New("directive {{$directive.Name}} is not implemented") + } + return ec.directives.{{$directive.Name|ucFirst}}({{$directive.CallArgs}}) + } + {{- end }} + } + } + tmp, err := next(ctx) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + if data, ok := tmp.(graphql.Marshaler); ok { + return data + } + ec.Errorf(ctx, `unexpected type %T from directive, should be graphql.Marshaler`, tmp) + return graphql.Null +{{end}} + +{{ if .Directives.LocationDirectives "QUERY" }} +func (ec *executionContext) _queryMiddleware(ctx context.Context, obj *ast.OperationDefinition, next func(ctx context.Context) (interface{}, error)) graphql.Marshaler { + {{ template "queryDirectives" .Directives.LocationDirectives "QUERY" }} +} +{{ end }} + +{{ if .Directives.LocationDirectives "MUTATION" }} +func (ec *executionContext) _mutationMiddleware(ctx context.Context, obj *ast.OperationDefinition, next func(ctx context.Context) (interface{}, error)) graphql.Marshaler { + {{ template "queryDirectives" .Directives.LocationDirectives "MUTATION" }} +} +{{ end }} + +{{ if .Directives.LocationDirectives "SUBSCRIPTION" }} +func (ec *executionContext) _subscriptionMiddleware(ctx context.Context, obj *ast.OperationDefinition, next func(ctx context.Context) (interface{}, error)) func() graphql.Marshaler { + for _, d := range obj.Directives { + switch d.Name { + {{- range $directive := .Directives.LocationDirectives "SUBSCRIPTION" }} + case "{{$directive.Name}}": + {{- if $directive.Args }} + rawArgs := d.ArgumentMap(ec.Variables) + args, err := ec.{{ $directive.ArgsFunc }}(ctx,rawArgs) + if err != nil { + ec.Error(ctx, err) + return func() graphql.Marshaler { + return graphql.Null + } + } + {{- end }} + n := next + next = func(ctx context.Context) (interface{}, error) { + if ec.directives.{{$directive.Name|ucFirst}} == nil { + return nil, errors.New("directive {{$directive.Name}} is not implemented") + } + return ec.directives.{{$directive.Name|ucFirst}}({{$directive.CallArgs}}) + } + {{- end }} + } + } + tmp, err := next(ctx) + if err != nil { + ec.Error(ctx, err) + return func() graphql.Marshaler { + return graphql.Null + } + } + if data, ok := tmp.(func() graphql.Marshaler); ok { + return data + } + ec.Errorf(ctx, `unexpected type %T from directive, should be graphql.Marshaler`, tmp) + return func() graphql.Marshaler { + return graphql.Null + } +} +{{ end }} + +{{ if .Directives.LocationDirectives "FIELD" }} + func (ec *executionContext) _fieldMiddleware(ctx context.Context, obj interface{}, next graphql.Resolver) interface{} { + {{- if .Directives.LocationDirectives "FIELD" }} + fc := graphql.GetFieldContext(ctx) + for _, d := range fc.Field.Directives { + switch d.Name { + {{- range $directive := .Directives.LocationDirectives "FIELD" }} + case "{{$directive.Name}}": + {{- if $directive.Args }} + rawArgs := d.ArgumentMap(ec.Variables) + args, err := ec.{{ $directive.ArgsFunc }}(ctx,rawArgs) + if err != nil { + ec.Error(ctx, err) + return nil + } + {{- end }} + n := next + next = func(ctx context.Context) (interface{}, error) { + if ec.directives.{{$directive.Name|ucFirst}} == nil { + return nil, errors.New("directive {{$directive.Name}} is not implemented") + } + return ec.directives.{{$directive.Name|ucFirst}}({{$directive.CallArgs}}) + } + {{- end }} + } + } + {{- end }} + res, err := ec.ResolverMiddleware(ctx, next) + if err != nil { + ec.Error(ctx, err) + return nil + } + return res + } +{{ end }} diff --git a/vendor/github.com/99designs/gqlgen/codegen/field.go b/vendor/github.com/99designs/gqlgen/codegen/field.go index f5f7b2213..26ed6b551 100644 --- a/vendor/github.com/99designs/gqlgen/codegen/field.go +++ b/vendor/github.com/99designs/gqlgen/codegen/field.go @@ -11,7 +11,7 @@ import ( "github.com/99designs/gqlgen/codegen/config" "github.com/99designs/gqlgen/codegen/templates" "github.com/pkg/errors" - "github.com/vektah/gqlparser/ast" + "github.com/vektah/gqlparser/v2/ast" ) type Field struct { @@ -27,6 +27,7 @@ type Field struct { NoErr bool // If this is bound to a go method, does that method have an error as the second argument Object *Object // A link back to the parent object Default interface{} // The default value + Stream bool // does this field return a channel? Directives []*Directive } @@ -73,17 +74,26 @@ func (b *builder) buildField(obj *Object, field *ast.FieldDefinition) (*Field, e return &f, nil } -func (b *builder) bindField(obj *Object, f *Field) error { +func (b *builder) bindField(obj *Object, f *Field) (errret error) { defer func() { if f.TypeReference == nil { tr, err := b.Binder.TypeReference(f.Type, nil) if err != nil { - panic(err) + errret = err } f.TypeReference = tr } + if f.TypeReference != nil { + dirs, err := b.getDirectives(f.TypeReference.Definition.Directives) + if err != nil { + errret = err + } + f.Directives = append(dirs, f.Directives...) + } }() + f.Stream = obj.Stream + switch { case f.Name == "__schema": f.GoFieldType = GoFieldMethod @@ -95,6 +105,18 @@ func (b *builder) bindField(obj *Object, f *Field) error { f.GoReceiverName = "ec" f.GoFieldName = "introspectType" return nil + case f.Name == "_entities": + f.GoFieldType = GoFieldMethod + f.GoReceiverName = "ec" + f.GoFieldName = "__resolve_entities" + f.MethodHasContext = true + return nil + case f.Name == "_service": + f.GoFieldType = GoFieldMethod + f.GoReceiverName = "ec" + f.GoFieldName = "__resolve__service" + f.MethodHasContext = true + return nil case obj.Root: f.IsResolver = true return nil @@ -181,75 +203,155 @@ func (b *builder) bindField(obj *Object, f *Field) error { } } -// findField attempts to match the name to a struct field with the following -// priorites: -// 1. Any method with a matching name -// 2. Any Fields with a struct tag (see config.StructTag) -// 3. Any fields with a matching name -// 4. Same logic again for embedded fields -func (b *builder) findBindTarget(named *types.Named, name string) (types.Object, error) { - for i := 0; i < named.NumMethods(); i++ { - method := named.Method(i) - if !method.Exported() { - continue - } - - if !strings.EqualFold(method.Name(), name) { - continue - } - - return method, nil +// findBindTarget attempts to match the name to a field or method on a Type +// with the following priorites: +// 1. Any Fields with a struct tag (see config.StructTag). Errors if more than one match is found +// 2. Any method or field with a matching name. Errors if more than one match is found +// 3. Same logic again for embedded fields +func (b *builder) findBindTarget(t types.Type, name string) (types.Object, error) { + // NOTE: a struct tag will override both methods and fields + // Bind to struct tag + found, err := b.findBindStructTagTarget(t, name) + if found != nil || err != nil { + return found, err } - strukt, ok := named.Underlying().(*types.Struct) - if !ok { - return nil, fmt.Errorf("not a struct") + // Search for a method to bind to + foundMethod, err := b.findBindMethodTarget(t, name) + if err != nil { + return nil, err } - return b.findBindStructTarget(strukt, name) + + // Search for a field to bind to + foundField, err := b.findBindFieldTarget(t, name) + if err != nil { + return nil, err + } + + switch { + case foundField == nil && foundMethod != nil: + // Bind to method + return foundMethod, nil + case foundField != nil && foundMethod == nil: + // Bind to field + return foundField, nil + case foundField != nil && foundMethod != nil: + // Error + return nil, errors.Errorf("found more than one way to bind for %s", name) + } + + // Search embeds + return b.findBindEmbedsTarget(t, name) } -func (b *builder) findBindStructTarget(strukt *types.Struct, name string) (types.Object, error) { - // struct tags have the highest priority - if b.Config.StructTag != "" { - var foundField *types.Var - for i := 0; i < strukt.NumFields(); i++ { - field := strukt.Field(i) - if !field.Exported() { +func (b *builder) findBindStructTagTarget(in types.Type, name string) (types.Object, error) { + if b.Config.StructTag == "" { + return nil, nil + } + + switch t := in.(type) { + case *types.Named: + return b.findBindStructTagTarget(t.Underlying(), name) + case *types.Struct: + var found types.Object + for i := 0; i < t.NumFields(); i++ { + field := t.Field(i) + if !field.Exported() || field.Embedded() { continue } - tags := reflect.StructTag(strukt.Tag(i)) + tags := reflect.StructTag(t.Tag(i)) if val, ok := tags.Lookup(b.Config.StructTag); ok && equalFieldName(val, name) { - if foundField != nil { + if found != nil { return nil, errors.Errorf("tag %s is ambigious; multiple fields have the same tag value of %s", b.Config.StructTag, val) } - foundField = field + found = field } } - if foundField != nil { - return foundField, nil - } + + return found, nil } - // Then matching field names - for i := 0; i < strukt.NumFields(); i++ { - field := strukt.Field(i) - if !field.Exported() { - continue - } - if equalFieldName(field.Name(), name) { // aqui! - return field, nil + return nil, nil +} + +func (b *builder) findBindMethodTarget(in types.Type, name string) (types.Object, error) { + switch t := in.(type) { + case *types.Named: + if _, ok := t.Underlying().(*types.Interface); ok { + return b.findBindMethodTarget(t.Underlying(), name) } + + return b.findBindMethoderTarget(t.Method, t.NumMethods(), name) + case *types.Interface: + // FIX-ME: Should use ExplicitMethod here? What's the difference? + return b.findBindMethoderTarget(t.Method, t.NumMethods(), name) } - // Then look in embedded structs - for i := 0; i < strukt.NumFields(); i++ { - field := strukt.Field(i) - if !field.Exported() { + return nil, nil +} + +func (b *builder) findBindMethoderTarget(methodFunc func(i int) *types.Func, methodCount int, name string) (types.Object, error) { + var found types.Object + for i := 0; i < methodCount; i++ { + method := methodFunc(i) + if !method.Exported() || !strings.EqualFold(method.Name(), name) { continue } - if !field.Anonymous() { + if found != nil { + return nil, errors.Errorf("found more than one matching method to bind for %s", name) + } + + found = method + } + + return found, nil +} + +func (b *builder) findBindFieldTarget(in types.Type, name string) (types.Object, error) { + switch t := in.(type) { + case *types.Named: + return b.findBindFieldTarget(t.Underlying(), name) + case *types.Struct: + var found types.Object + for i := 0; i < t.NumFields(); i++ { + field := t.Field(i) + if !field.Exported() || !equalFieldName(field.Name(), name) { + continue + } + + if found != nil { + return nil, errors.Errorf("found more than one matching field to bind for %s", name) + } + + found = field + } + + return found, nil + } + + return nil, nil +} + +func (b *builder) findBindEmbedsTarget(in types.Type, name string) (types.Object, error) { + switch t := in.(type) { + case *types.Named: + return b.findBindEmbedsTarget(t.Underlying(), name) + case *types.Struct: + return b.findBindStructEmbedsTarget(t, name) + case *types.Interface: + return b.findBindInterfaceEmbedsTarget(t, name) + } + + return nil, nil +} + +func (b *builder) findBindStructEmbedsTarget(strukt *types.Struct, name string) (types.Object, error) { + var found types.Object + for i := 0; i < strukt.NumFields(); i++ { + field := strukt.Field(i) + if !field.Embedded() { continue } @@ -258,33 +360,68 @@ func (b *builder) findBindStructTarget(strukt *types.Struct, name string) (types fieldType = ptr.Elem() } - switch fieldType := fieldType.(type) { - case *types.Named: - f, err := b.findBindTarget(fieldType, name) - if err != nil { - return nil, err - } - if f != nil { - return f, nil - } - case *types.Struct: - f, err := b.findBindStructTarget(fieldType, name) - if err != nil { - return nil, err - } - if f != nil { - return f, nil - } - default: - panic(fmt.Errorf("unknown embedded field type %T", field.Type())) + f, err := b.findBindTarget(fieldType, name) + if err != nil { + return nil, err + } + + if f != nil && found != nil { + return nil, errors.Errorf("found more than one way to bind for %s", name) + } + + if f != nil { + found = f } } - return nil, nil + return found, nil +} + +func (b *builder) findBindInterfaceEmbedsTarget(iface *types.Interface, name string) (types.Object, error) { + var found types.Object + for i := 0; i < iface.NumEmbeddeds(); i++ { + embeddedType := iface.EmbeddedType(i) + + f, err := b.findBindTarget(embeddedType, name) + if err != nil { + return nil, err + } + + if f != nil && found != nil { + return nil, errors.Errorf("found more than one way to bind for %s", name) + } + + if f != nil { + found = f + } + } + + return found, nil } func (f *Field) HasDirectives() bool { - return len(f.Directives) > 0 + return len(f.ImplDirectives()) > 0 +} + +func (f *Field) DirectiveObjName() string { + if f.Object.Root { + return "nil" + } + return f.GoReceiverName +} + +func (f *Field) ImplDirectives() []*Directive { + var d []*Directive + loc := ast.LocationFieldDefinition + if f.Object.IsInputType() { + loc = ast.LocationInputFieldDefinition + } + for i := range f.Directives { + if !f.Directives[i].Builtin && f.Directives[i].IsLocation(loc, ast.LocationObject) { + d = append(d, f.Directives[i]) + } + } + return d } func (f *Field) IsReserved() bool { @@ -338,7 +475,7 @@ func (f *Field) ShortResolverDeclaration() string { res := "(ctx context.Context" if !f.Object.Root { - res += fmt.Sprintf(", obj *%s", templates.CurrentImports.LookupType(f.Object.Type)) + res += fmt.Sprintf(", obj %s", templates.CurrentImports.LookupType(f.Object.Reference())) } for _, arg := range f.Args { res += fmt.Sprintf(", %s %s", arg.VarName, templates.CurrentImports.LookupType(arg.TypeReference.GO)) @@ -354,7 +491,7 @@ func (f *Field) ShortResolverDeclaration() string { } func (f *Field) ComplexitySignature() string { - res := fmt.Sprintf("func(childComplexity int") + res := "func(childComplexity int" for _, arg := range f.Args { res += fmt.Sprintf(", %s %s", arg.VarName, templates.CurrentImports.LookupType(arg.TypeReference.GO)) } @@ -363,16 +500,16 @@ func (f *Field) ComplexitySignature() string { } func (f *Field) ComplexityArgs() string { - var args []string - for _, arg := range f.Args { - args = append(args, "args["+strconv.Quote(arg.Name)+"].("+templates.CurrentImports.LookupType(arg.TypeReference.GO)+")") + args := make([]string, len(f.Args)) + for i, arg := range f.Args { + args[i] = "args[" + strconv.Quote(arg.Name) + "].(" + templates.CurrentImports.LookupType(arg.TypeReference.GO) + ")" } return strings.Join(args, ", ") } func (f *Field) CallArgs() string { - var args []string + args := make([]string, 0, len(f.Args)+2) if f.IsResolver { args = append(args, "rctx") @@ -380,10 +517,8 @@ func (f *Field) CallArgs() string { if !f.Object.Root { args = append(args, "obj") } - } else { - if f.MethodHasContext { - args = append(args, "ctx") - } + } else if f.MethodHasContext { + args = append(args, "ctx") } for _, arg := range f.Args { diff --git a/vendor/github.com/99designs/gqlgen/codegen/field.gotpl b/vendor/github.com/99designs/gqlgen/codegen/field.gotpl index 9718a08aa..993625b74 100644 --- a/vendor/github.com/99designs/gqlgen/codegen/field.gotpl +++ b/vendor/github.com/99designs/gqlgen/codegen/field.gotpl @@ -1,29 +1,57 @@ {{- range $object := .Objects }}{{- range $field := $object.Fields }} -{{- if $object.Stream }} - func (ec *executionContext) _{{$object.Name}}_{{$field.Name}}(ctx context.Context, field graphql.CollectedField) func() graphql.Marshaler { - ctx = graphql.WithResolverContext(ctx, &graphql.ResolverContext{ - Field: field, - Args: nil, - }) - {{- if $field.Args }} - rawArgs := field.ArgumentMap(ec.Variables) - args, err := ec.{{ $field.ArgsFunc }}(ctx,rawArgs) - if err != nil { - ec.Error(ctx, err) - return nil - } - {{- end }} - // FIXME: subscriptions are missing request middleware stack https://github.com/99designs/gqlgen/issues/259 - // and Tracer stack - rctx := ctx - results, err := ec.resolvers.{{ $field.ShortInvocation }} +func (ec *executionContext) _{{$object.Name}}_{{$field.Name}}(ctx context.Context, field graphql.CollectedField{{ if not $object.Root }}, obj {{$object.Reference | ref}}{{end}}) (ret {{ if $object.Stream }}func(){{ end }}graphql.Marshaler) { + {{- $null := "graphql.Null" }} + {{- if $object.Stream }} + {{- $null = "nil" }} + {{- end }} + defer func () { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = {{ $null }} + } + }() + fc := &graphql.FieldContext{ + Object: {{$object.Name|quote}}, + Field: field, + Args: nil, + IsMethod: {{or $field.IsMethod $field.IsResolver}}, + } + + ctx = graphql.WithFieldContext(ctx, fc) + {{- if $field.Args }} + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.{{ $field.ArgsFunc }}(ctx,rawArgs) if err != nil { ec.Error(ctx, err) - return nil + return {{ $null }} } + fc.Args = args + {{- end }} + {{- if $.Directives.LocationDirectives "FIELD" }} + resTmp := ec._fieldMiddleware(ctx, {{if $object.Root}}nil{{else}}obj{{end}}, func(rctx context.Context) (interface{}, error) { + {{ template "field" $field }} + }) + {{ else }} + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + {{ template "field" $field }} + }) + if err != nil { + ec.Error(ctx, err) + return {{ $null }} + } + {{- end }} + if resTmp == nil { + {{- if $field.TypeReference.GQL.NonNull }} + if !graphql.HasFieldError(ctx, fc) { + ec.Errorf(ctx, "must not be null") + } + {{- end }} + return {{ $null }} + } + {{- if $object.Stream }} return func() graphql.Marshaler { - res, ok := <-results + res, ok := <-resTmp.(<-chan {{$field.TypeReference.GO | ref}}) if !ok { return nil } @@ -35,66 +63,60 @@ w.Write([]byte{'}'}) }) } - } -{{ else }} - func (ec *executionContext) _{{$object.Name}}_{{$field.Name}}(ctx context.Context, field graphql.CollectedField{{ if not $object.Root }}, obj {{$object.Reference | ref}}{{end}}) graphql.Marshaler { - ctx = ec.Tracer.StartFieldExecution(ctx, field) - defer func () { ec.Tracer.EndFieldExecution(ctx) }() - rctx := &graphql.ResolverContext{ - Object: {{$object.Name|quote}}, - Field: field, - Args: nil, - IsMethod: {{or $field.IsMethod $field.IsResolver}}, - } - ctx = graphql.WithResolverContext(ctx, rctx) - {{- if $field.Args }} - rawArgs := field.ArgumentMap(ec.Variables) - args, err := ec.{{ $field.ArgsFunc }}(ctx,rawArgs) - if err != nil { - ec.Error(ctx, err) - return graphql.Null - } - rctx.Args = args - {{- end }} - ctx = ec.Tracer.StartFieldResolverExecution(ctx, rctx) - resTmp := ec.FieldMiddleware(ctx, {{if $object.Root}}nil{{else}}obj{{end}}, func(rctx context.Context) (interface{}, error) { - ctx = rctx // use context from middleware stack in children - {{- if $field.IsResolver }} - return ec.resolvers.{{ $field.ShortInvocation }} - {{- else if $field.IsMap }} - switch v := {{$field.GoReceiverName}}[{{$field.Name|quote}}].(type) { - case {{$field.TypeReference.GO | ref}}: - return v, nil - case {{$field.TypeReference.Elem.GO | ref}}: - return &v, nil - case nil: - return ({{$field.TypeReference.GO | ref}})(nil), nil - default: - return nil, fmt.Errorf("unexpected type %T for field %s", v, {{ $field.Name | quote}}) - } - {{- else if $field.IsMethod }} - {{- if $field.NoErr }} - return {{$field.GoReceiverName}}.{{$field.GoFieldName}}({{ $field.CallArgs }}), nil - {{- else }} - return {{$field.GoReceiverName}}.{{$field.GoFieldName}}({{ $field.CallArgs }}) - {{- end }} - {{- else if $field.IsVariable }} - return {{$field.GoReceiverName}}.{{$field.GoFieldName}}, nil - {{- end }} - }) - if resTmp == nil { - {{- if $field.TypeReference.GQL.NonNull }} - if !ec.HasError(rctx) { - ec.Errorf(ctx, "must not be null") - } - {{- end }} - return graphql.Null - } + {{- else }} res := resTmp.({{$field.TypeReference.GO | ref}}) - rctx.Result = res - ctx = ec.Tracer.StartFieldChildExecution(ctx) + fc.Result = res return ec.{{ $field.TypeReference.MarshalFunc }}(ctx, field.Selections, res) - } -{{ end }} + {{- end }} +} {{- end }}{{- end}} + +{{ define "field" }} + {{- if .HasDirectives -}} + directive0 := func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + {{ template "fieldDefinition" . }} + } + {{ template "implDirectives" . }} + tmp, err := directive{{.ImplDirectives|len}}(rctx) + if err != nil { + return nil, err + } + if tmp == nil { + return nil, nil + } + if data, ok := tmp.({{if .Stream}}<-chan {{end}}{{ .TypeReference.GO | ref }}) ; ok { + return data, nil + } + return nil, fmt.Errorf(`unexpected type %T from directive, should be {{if .Stream}}<-chan {{end}}{{ .TypeReference.GO }}`, tmp) + {{- else -}} + ctx = rctx // use context from middleware stack in children + {{ template "fieldDefinition" . }} + {{- end -}} +{{ end }} + +{{ define "fieldDefinition" }} + {{- if .IsResolver -}} + return ec.resolvers.{{ .ShortInvocation }} + {{- else if .IsMap -}} + switch v := {{.GoReceiverName}}[{{.Name|quote}}].(type) { + case {{if .Stream}}<-chan {{end}}{{.TypeReference.GO | ref}}: + return v, nil + case {{if .Stream}}<-chan {{end}}{{.TypeReference.Elem.GO | ref}}: + return &v, nil + case nil: + return ({{.TypeReference.GO | ref}})(nil), nil + default: + return nil, fmt.Errorf("unexpected type %T for field %s", v, {{ .Name | quote}}) + } + {{- else if .IsMethod -}} + {{- if .NoErr -}} + return {{.GoReceiverName}}.{{.GoFieldName}}({{ .CallArgs }}), nil + {{- else -}} + return {{.GoReceiverName}}.{{.GoFieldName}}({{ .CallArgs }}) + {{- end -}} + {{- else if .IsVariable -}} + return {{.GoReceiverName}}.{{.GoFieldName}}, nil + {{- end }} +{{- end }} diff --git a/vendor/github.com/99designs/gqlgen/codegen/generate.go b/vendor/github.com/99designs/gqlgen/codegen/generate.go index eafa3f874..f1ed2ca27 100644 --- a/vendor/github.com/99designs/gqlgen/codegen/generate.go +++ b/vendor/github.com/99designs/gqlgen/codegen/generate.go @@ -11,5 +11,6 @@ func GenerateCode(data *Data) error { Data: data, RegionTags: true, GeneratedHeader: true, + Packages: data.Config.Packages, }) } diff --git a/vendor/github.com/99designs/gqlgen/codegen/generated!.gotpl b/vendor/github.com/99designs/gqlgen/codegen/generated!.gotpl index 5753f1d13..864d15deb 100644 --- a/vendor/github.com/99designs/gqlgen/codegen/generated!.gotpl +++ b/vendor/github.com/99designs/gqlgen/codegen/generated!.gotpl @@ -8,8 +8,8 @@ {{ reserveImport "errors" }} {{ reserveImport "bytes" }} -{{ reserveImport "github.com/vektah/gqlparser" }} -{{ reserveImport "github.com/vektah/gqlparser/ast" }} +{{ reserveImport "github.com/vektah/gqlparser/v2" "gqlparser" }} +{{ reserveImport "github.com/vektah/gqlparser/v2/ast" }} {{ reserveImport "github.com/99designs/gqlgen/graphql" }} {{ reserveImport "github.com/99designs/gqlgen/graphql/introspection" }} @@ -39,7 +39,7 @@ type ResolverRoot interface { type DirectiveRoot struct { {{ range $directive := .Directives }} - {{ $directive.Declaration }} + {{- $directive.Declaration }} {{ end }} } @@ -112,129 +112,86 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return 0, false } -func (e *executableSchema) Query(ctx context.Context, op *ast.OperationDefinition) *graphql.Response { - {{- if .QueryRoot }} - ec := executionContext{graphql.GetRequestContext(ctx), e} +func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { + rc := graphql.GetOperationContext(ctx) + ec := executionContext{rc, e} + first := true - buf := ec.RequestMiddleware(ctx, func(ctx context.Context) []byte { - data := ec._{{.QueryRoot.Name}}(ctx, op.SelectionSet) + switch rc.Operation.Operation { + {{- if .QueryRoot }} case ast.Query: + return func(ctx context.Context) *graphql.Response { + if !first { return nil } + first = false + {{ if .Directives.LocationDirectives "QUERY" -}} + data := ec._queryMiddleware(ctx, rc.Operation, func(ctx context.Context) (interface{}, error){ + return ec._{{.QueryRoot.Name}}(ctx, rc.Operation.SelectionSet), nil + }) + {{- else -}} + data := ec._{{.QueryRoot.Name}}(ctx, rc.Operation.SelectionSet) + {{- end }} var buf bytes.Buffer data.MarshalGQL(&buf) - return buf.Bytes() - }) - - return &graphql.Response{ - Data: buf, - Errors: ec.Errors, - Extensions: ec.Extensions, - } - {{- else }} - return graphql.ErrorResponse(ctx, "queries are not supported") - {{- end }} -} - -func (e *executableSchema) Mutation(ctx context.Context, op *ast.OperationDefinition) *graphql.Response { - {{- if .MutationRoot }} - ec := executionContext{graphql.GetRequestContext(ctx), e} - - buf := ec.RequestMiddleware(ctx, func(ctx context.Context) []byte { - data := ec._{{.MutationRoot.Name}}(ctx, op.SelectionSet) - var buf bytes.Buffer - data.MarshalGQL(&buf) - return buf.Bytes() - }) - - return &graphql.Response{ - Data: buf, - Errors: ec.Errors, - Extensions: ec.Extensions, - } - {{- else }} - return graphql.ErrorResponse(ctx, "mutations are not supported") - {{- end }} -} - -func (e *executableSchema) Subscription(ctx context.Context, op *ast.OperationDefinition) func() *graphql.Response { - {{- if .SubscriptionRoot }} - ec := executionContext{graphql.GetRequestContext(ctx), e} - - next := ec._{{.SubscriptionRoot.Name}}(ctx, op.SelectionSet) - if ec.Errors != nil { - return graphql.OneShot(&graphql.Response{Data: []byte("null"), Errors: ec.Errors}) - } - - var buf bytes.Buffer - return func() *graphql.Response { - buf := ec.RequestMiddleware(ctx, func(ctx context.Context) []byte { - buf.Reset() - data := next() - - if data == nil { - return nil - } - data.MarshalGQL(&buf) - return buf.Bytes() - }) - - if buf == nil { - return nil - } return &graphql.Response{ - Data: buf, - Errors: ec.Errors, - Extensions: ec.Extensions, + Data: buf.Bytes(), } } - {{- else }} - return graphql.OneShot(graphql.ErrorResponse(ctx, "subscriptions are not supported")) - {{- end }} + {{ end }} + + {{- if .MutationRoot }} case ast.Mutation: + return func(ctx context.Context) *graphql.Response { + if !first { return nil } + first = false + {{ if .Directives.LocationDirectives "MUTATION" -}} + data := ec._mutationMiddleware(ctx, rc.Operation, func(ctx context.Context) (interface{}, error){ + return ec._{{.MutationRoot.Name}}(ctx, rc.Operation.SelectionSet), nil + }) + {{- else -}} + data := ec._{{.MutationRoot.Name}}(ctx, rc.Operation.SelectionSet) + {{- end }} + var buf bytes.Buffer + data.MarshalGQL(&buf) + + return &graphql.Response{ + Data: buf.Bytes(), + } + } + {{ end }} + + {{- if .SubscriptionRoot }} case ast.Subscription: + {{ if .Directives.LocationDirectives "SUBSCRIPTION" -}} + next := ec._subscriptionMiddleware(ctx, rc.Operation, func(ctx context.Context) (interface{}, error){ + return ec._{{.SubscriptionRoot.Name}}(ctx, rc.Operation.SelectionSet),nil + }) + {{- else -}} + next := ec._{{.SubscriptionRoot.Name}}(ctx, rc.Operation.SelectionSet) + {{- end }} + + var buf bytes.Buffer + return func(ctx context.Context) *graphql.Response { + buf.Reset() + data := next() + + if data == nil { + return nil + } + data.MarshalGQL(&buf) + + return &graphql.Response{ + Data: buf.Bytes(), + } + } + {{ end }} + default: + return graphql.OneShot(graphql.ErrorResponse(ctx, "unsupported GraphQL operation")) + } } type executionContext struct { - *graphql.RequestContext + *graphql.OperationContext *executableSchema } -func (ec *executionContext) FieldMiddleware(ctx context.Context, obj interface{}, next graphql.Resolver) (ret interface{}) { - defer func() { - if r := recover(); r != nil { - ec.Error(ctx, ec.Recover(ctx, r)) - ret = nil - } - }() - {{- if .Directives }} - rctx := graphql.GetResolverContext(ctx) - for _, d := range rctx.Field.Definition.Directives { - switch d.Name { - {{- range $directive := .Directives }} - case "{{$directive.Name}}": - if ec.directives.{{$directive.Name|ucFirst}} != nil { - {{- if $directive.Args }} - rawArgs := d.ArgumentMap(ec.Variables) - args, err := ec.{{ $directive.ArgsFunc }}(ctx,rawArgs) - if err != nil { - ec.Error(ctx, err) - return nil - } - {{- end }} - n := next - next = func(ctx context.Context) (interface{}, error) { - return ec.directives.{{$directive.Name|ucFirst}}({{$directive.CallArgs}}) - } - } - {{- end }} - } - } - {{- end }} - res, err := ec.ResolverMiddleware(ctx, next) - if err != nil { - ec.Error(ctx, err) - return nil - } - return res -} - func (ec *executionContext) introspectSchema() (*introspection.Schema, error) { if ec.DisableIntrospection { return nil, errors.New("introspection disabled") @@ -249,8 +206,9 @@ func (ec *executionContext) introspectType(name string) (*introspection.Type, er return introspection.WrapTypeFromDef(parsedSchema, parsedSchema.Types[name]), nil } -var parsedSchema = gqlparser.MustLoadSchema( - {{- range $filename, $schema := .SchemaStr }} - &ast.Source{Name: {{$filename|quote}}, Input: {{$schema|rawQuote}}}, - {{- end }} -) +var sources = []*ast.Source{ +{{- range $source := .Config.Sources }} + {Name: {{$source.Name|quote}}, Input: {{$source.Input|rawQuote}}, BuiltIn: {{$source.BuiltIn}}}, +{{- end }} +} +var parsedSchema = gqlparser.MustLoadSchema(sources...) diff --git a/vendor/github.com/99designs/gqlgen/codegen/input.gotpl b/vendor/github.com/99designs/gqlgen/codegen/input.gotpl index c8ac7ad3a..56f9347c4 100644 --- a/vendor/github.com/99designs/gqlgen/codegen/input.gotpl +++ b/vendor/github.com/99designs/gqlgen/codegen/input.gotpl @@ -1,8 +1,8 @@ {{- range $input := .Inputs }} {{- if not .HasUnmarshal }} - func (ec *executionContext) unmarshalInput{{ .Name }}(ctx context.Context, v interface{}) ({{.Type | ref}}, error) { + func (ec *executionContext) unmarshalInput{{ .Name }}(ctx context.Context, obj interface{}) ({{.Type | ref}}, error) { var it {{.Type | ref}} - var asMap = v.(map[string]interface{}) + var asMap = obj.(map[string]interface{}) {{ range $field := .Fields}} {{- if $field.Default}} if _, present := asMap[{{$field.Name|quote}}] ; !present { @@ -16,27 +16,21 @@ {{- range $field := .Fields }} case {{$field.Name|quote}}: var err error - {{- if $field.Directives }} - getField0 := func(ctx context.Context) (interface{}, error) { return ec.{{ $field.TypeReference.UnmarshalFunc }}(ctx, v) } - {{- range $i, $directive := $field.Directives }} - getField{{add $i 1}} := func(ctx context.Context) (res interface{}, err error) { - {{- range $dArg := $directive.Args }} - {{- if and $dArg.TypeReference.IsPtr ( notNil "Value" $dArg ) }} - {{ $dArg.VarName }} := {{ $dArg.Value | dump }} - {{- end }} - {{- end }} - n := getField{{$i}} - return ec.directives.{{$directive.Name|ucFirst}}({{$directive.ResolveArgs "it" "n" }}) - } - {{- end }} - - tmp, err := getField{{$field.Directives|len}}(ctx) + ctx := graphql.WithFieldInputContext(ctx, graphql.NewFieldInputWithField({{$field.Name|quote}})) + {{- if $field.ImplDirectives }} + directive0 := func(ctx context.Context) (interface{}, error) { return ec.{{ $field.TypeReference.UnmarshalFunc }}(ctx, v) } + {{ template "implDirectives" $field }} + tmp, err := directive{{$field.ImplDirectives|len}}(ctx) if err != nil { return it, err } if data, ok := tmp.({{ $field.TypeReference.GO | ref }}) ; ok { it.{{$field.GoFieldName}} = data + {{- if $field.TypeReference.IsNilable }} + } else if tmp == nil { + it.{{$field.GoFieldName}} = nil + {{- end }} } else { return it, fmt.Errorf(`unexpected type %T from directive, should be {{ $field.TypeReference.GO }}`, tmp) } diff --git a/vendor/github.com/99designs/gqlgen/codegen/interface.go b/vendor/github.com/99designs/gqlgen/codegen/interface.go index f59e8ed07..a55ce1e6b 100644 --- a/vendor/github.com/99designs/gqlgen/codegen/interface.go +++ b/vendor/github.com/99designs/gqlgen/codegen/interface.go @@ -1,9 +1,13 @@ package codegen import ( + "fmt" "go/types" - "github.com/vektah/gqlparser/ast" + "github.com/pkg/errors" + "github.com/vektah/gqlparser/v2/ast" + + "github.com/99designs/gqlgen/codegen/config" ) type Interface struct { @@ -16,11 +20,11 @@ type Interface struct { type InterfaceImplementor struct { *ast.Definition - Interface *Interface - Type types.Type + Type types.Type + TakeRef bool } -func (b *builder) buildInterface(typ *ast.Definition) *Interface { +func (b *builder) buildInterface(typ *ast.Definition) (*Interface, error) { obj, err := b.Binder.DefaultUserObject(typ.Name) if err != nil { panic(err) @@ -32,32 +36,53 @@ func (b *builder) buildInterface(typ *ast.Definition) *Interface { InTypemap: b.Config.Models.UserDefined(typ.Name), } + interfaceType, err := findGoInterface(i.Type) + if interfaceType == nil || err != nil { + return nil, fmt.Errorf("%s is not an interface", i.Type) + } + for _, implementor := range b.Schema.GetPossibleTypes(typ) { obj, err := b.Binder.DefaultUserObject(implementor.Name) if err != nil { - panic(err) + return nil, fmt.Errorf("%s has no backing go type", implementor.Name) } - i.Implementors = append(i.Implementors, InterfaceImplementor{ - Definition: implementor, - Type: obj, - Interface: i, - }) + implementorType, err := findGoNamedType(obj) + if err != nil { + return nil, errors.Wrapf(err, "can not find backing go type %s", obj.String()) + } else if implementorType == nil { + return nil, fmt.Errorf("can not find backing go type %s", obj.String()) + } + + anyValid := false + + // first check if the value receiver can be nil, eg can we type switch on case Thing: + if types.Implements(implementorType, interfaceType) { + i.Implementors = append(i.Implementors, InterfaceImplementor{ + Definition: implementor, + Type: obj, + TakeRef: !types.IsInterface(obj), + }) + anyValid = true + } + + // then check if the pointer receiver can be nil, eg can we type switch on case *Thing: + if types.Implements(types.NewPointer(implementorType), interfaceType) { + i.Implementors = append(i.Implementors, InterfaceImplementor{ + Definition: implementor, + Type: types.NewPointer(obj), + }) + anyValid = true + } + + if !anyValid { + return nil, fmt.Errorf("%s does not satisfy the interface %s", implementorType.String(), i.Type.String()) + } } - return i + return i, nil } -func (i *InterfaceImplementor) ValueReceiver() bool { - interfaceType, err := findGoInterface(i.Interface.Type) - if interfaceType == nil || err != nil { - return true - } - - implementorType, err := findGoNamedType(i.Type) - if implementorType == nil || err != nil { - return true - } - - return types.Implements(implementorType, interfaceType) +func (i *InterfaceImplementor) CanBeNil() bool { + return config.IsNilable(i.Type) } diff --git a/vendor/github.com/99designs/gqlgen/codegen/interface.gotpl b/vendor/github.com/99designs/gqlgen/codegen/interface.gotpl index 81a580765..e9d560c8f 100644 --- a/vendor/github.com/99designs/gqlgen/codegen/interface.gotpl +++ b/vendor/github.com/99designs/gqlgen/codegen/interface.gotpl @@ -1,16 +1,17 @@ {{- range $interface := .Interfaces }} -func (ec *executionContext) _{{$interface.Name}}(ctx context.Context, sel ast.SelectionSet, obj *{{$interface.Type | ref}}) graphql.Marshaler { - switch obj := (*obj).(type) { +func (ec *executionContext) _{{$interface.Name}}(ctx context.Context, sel ast.SelectionSet, obj {{$interface.Type | ref}}) graphql.Marshaler { + switch obj := (obj).(type) { case nil: return graphql.Null {{- range $implementor := $interface.Implementors }} - {{- if $implementor.ValueReceiver }} - case {{$implementor.Type | ref}}: - return ec._{{$implementor.Name}}(ctx, sel, &obj) - {{- end}} - case *{{$implementor.Type | ref}}: - return ec._{{$implementor.Name}}(ctx, sel, obj) + case {{$implementor.Type | ref}}: + {{- if $implementor.CanBeNil }} + if obj == nil { + return graphql.Null + } + {{- end }} + return ec._{{$implementor.Name}}(ctx, sel, {{ if $implementor.TakeRef }}&{{ end }}obj) {{- end }} default: panic(fmt.Errorf("unexpected type %T", obj)) diff --git a/vendor/github.com/99designs/gqlgen/codegen/object.go b/vendor/github.com/99designs/gqlgen/codegen/object.go index 539c3164c..7b91c9004 100644 --- a/vendor/github.com/99designs/gqlgen/codegen/object.go +++ b/vendor/github.com/99designs/gqlgen/codegen/object.go @@ -8,7 +8,7 @@ import ( "github.com/99designs/gqlgen/codegen/config" "github.com/pkg/errors" - "github.com/vektah/gqlparser/ast" + "github.com/vektah/gqlparser/v2/ast" ) type GoFieldType int @@ -82,11 +82,9 @@ func (b *builder) buildObject(typ *ast.Definition) (*Object, error) { } func (o *Object) Reference() types.Type { - switch o.Type.(type) { - case *types.Pointer, *types.Slice, *types.Map: + if config.IsNilable(o.Type) { return o.Type } - return types.NewPointer(o.Type) } @@ -114,8 +112,7 @@ func (o *Object) HasUnmarshal() bool { return true } for i := 0; i < o.Type.(*types.Named).NumMethods(); i++ { - switch o.Type.(*types.Named).Method(i).Name() { - case "UnmarshalGQL": + if o.Type.(*types.Named).Method(i).Name() == "UnmarshalGQL" { return true } } diff --git a/vendor/github.com/99designs/gqlgen/codegen/object.gotpl b/vendor/github.com/99designs/gqlgen/codegen/object.gotpl index 98a75740e..33775a0b4 100644 --- a/vendor/github.com/99designs/gqlgen/codegen/object.gotpl +++ b/vendor/github.com/99designs/gqlgen/codegen/object.gotpl @@ -4,8 +4,8 @@ var {{ $object.Name|lcFirst}}Implementors = {{$object.Implementors}} {{- if .Stream }} func (ec *executionContext) _{{$object.Name}}(ctx context.Context, sel ast.SelectionSet) func() graphql.Marshaler { - fields := graphql.CollectFields(ec.RequestContext, sel, {{$object.Name|lcFirst}}Implementors) - ctx = graphql.WithResolverContext(ctx, &graphql.ResolverContext{ + fields := graphql.CollectFields(ec.OperationContext, sel, {{$object.Name|lcFirst}}Implementors) + ctx = graphql.WithFieldContext(ctx, &graphql.FieldContext{ Object: {{$object.Name|quote}}, }) if len(fields) != 1 { @@ -24,9 +24,9 @@ func (ec *executionContext) _{{$object.Name}}(ctx context.Context, sel ast.Selec } {{- else }} func (ec *executionContext) _{{$object.Name}}(ctx context.Context, sel ast.SelectionSet{{ if not $object.Root }},obj {{$object.Reference | ref }}{{ end }}) graphql.Marshaler { - fields := graphql.CollectFields(ec.RequestContext, sel, {{$object.Name|lcFirst}}Implementors) + fields := graphql.CollectFields(ec.OperationContext, sel, {{$object.Name|lcFirst}}Implementors) {{if $object.Root}} - ctx = graphql.WithResolverContext(ctx, &graphql.ResolverContext{ + ctx = graphql.WithFieldContext(ctx, &graphql.FieldContext{ Object: {{$object.Name|quote}}, }) {{end}} diff --git a/vendor/github.com/99designs/gqlgen/codegen/templates/import.go b/vendor/github.com/99designs/gqlgen/codegen/templates/import.go index effe9a0df..17bd96ab2 100644 --- a/vendor/github.com/99designs/gqlgen/codegen/templates/import.go +++ b/vendor/github.com/99designs/gqlgen/codegen/templates/import.go @@ -4,6 +4,7 @@ import ( "fmt" "go/types" "strconv" + "strings" "github.com/99designs/gqlgen/internal/code" ) @@ -15,12 +16,13 @@ type Import struct { } type Imports struct { - imports []*Import - destDir string + imports []*Import + destDir string + packages *code.Packages } func (i *Import) String() string { - if i.Alias == i.Name { + if strings.HasSuffix(i.Path, i.Alias) { return strconv.Quote(i.Path) } @@ -48,7 +50,7 @@ func (s *Imports) Reserve(path string, aliases ...string) (string, error) { return "", nil } - name := code.NameForPackage(path) + name := s.packages.NameForPackage(path) var alias string if len(aliases) != 1 { alias = name @@ -93,7 +95,7 @@ func (s *Imports) Lookup(path string) string { } imp := &Import{ - Name: code.NameForPackage(path), + Name: s.packages.NameForPackage(path), Path: path, } s.imports = append(s.imports, imp) diff --git a/vendor/github.com/99designs/gqlgen/codegen/templates/templates.go b/vendor/github.com/99designs/gqlgen/codegen/templates/templates.go index f2fcb568e..79b0c5c7d 100644 --- a/vendor/github.com/99designs/gqlgen/codegen/templates/templates.go +++ b/vendor/github.com/99designs/gqlgen/codegen/templates/templates.go @@ -15,6 +15,8 @@ import ( "text/template" "unicode" + "github.com/99designs/gqlgen/internal/code" + "github.com/99designs/gqlgen/internal/imports" "github.com/pkg/errors" ) @@ -40,9 +42,16 @@ type Options struct { Filename string RegionTags bool GeneratedHeader bool + // PackageDoc is documentation written above the package line + PackageDoc string + // FileNotice is notice written below the package line + FileNotice string // Data will be passed to the template execution. Data interface{} Funcs template.FuncMap + + // Packages cache, you can find me on config.Config + Packages *code.Packages } // Render renders a gql plugin template from the given Options. Render is an @@ -53,7 +62,7 @@ func Render(cfg Options) error { if CurrentImports != nil { panic(fmt.Errorf("recursive or concurrent call to RenderToFile detected")) } - CurrentImports = &Imports{destDir: filepath.Dir(cfg.Filename)} + CurrentImports = &Imports{packages: cfg.Packages, destDir: filepath.Dir(cfg.Filename)} // load path relative to calling source file _, callerFile, _, _ := runtime.Caller(1) @@ -131,9 +140,16 @@ func Render(cfg Options) error { if cfg.GeneratedHeader { result.WriteString("// Code generated by github.com/99designs/gqlgen, DO NOT EDIT.\n\n") } + if cfg.PackageDoc != "" { + result.WriteString(cfg.PackageDoc + "\n") + } result.WriteString("package ") result.WriteString(cfg.PackageName) result.WriteString("\n\n") + if cfg.FileNotice != "" { + result.WriteString(cfg.FileNotice) + result.WriteString("\n\n") + } result.WriteString("import (\n") result.WriteString(CurrentImports.String()) result.WriteString(")\n") @@ -143,7 +159,13 @@ func Render(cfg Options) error { } CurrentImports = nil - return write(cfg.Filename, result.Bytes()) + err = write(cfg.Filename, result.Bytes(), cfg.Packages) + if err != nil { + return err + } + + cfg.Packages.Evict(code.ImportPathForDir(filepath.Dir(cfg.Filename))) + return nil } func center(width int, pad string, s string) string { @@ -157,8 +179,8 @@ func center(width int, pad string, s string) string { func Funcs() template.FuncMap { return template.FuncMap{ - "ucFirst": ucFirst, - "lcFirst": lcFirst, + "ucFirst": UcFirst, + "lcFirst": LcFirst, "quote": strconv.Quote, "rawQuote": rawQuote, "dump": Dump, @@ -180,7 +202,7 @@ func Funcs() template.FuncMap { } } -func ucFirst(s string) string { +func UcFirst(s string) string { if s == "" { return "" } @@ -189,7 +211,7 @@ func ucFirst(s string) string { return string(r) } -func lcFirst(s string) string { +func LcFirst(s string) string { if s == "" { return "" } @@ -211,6 +233,7 @@ var pkgReplacer = strings.NewReplacer( "/", "ᚋ", ".", "ᚗ", "-", "ᚑ", + "~", "א", ) func TypeIdentifier(t types.Type) string { @@ -260,6 +283,9 @@ func Call(p *types.Func) string { } func ToGo(name string) string { + if name == "_" { + return "_" + } runes := make([]rune, 0, len(name)) wordWalker(name, func(info *wordInfo) { @@ -270,7 +296,7 @@ func ToGo(name string) string { if strings.ToUpper(word) == word || strings.ToLower(word) == word { // FOO or foo → Foo // FOo → FOo - word = ucFirst(strings.ToLower(word)) + word = UcFirst(strings.ToLower(word)) } } runes = append(runes, []rune(word)...) @@ -280,24 +306,28 @@ func ToGo(name string) string { } func ToGoPrivate(name string) string { + if name == "_" { + return "_" + } runes := make([]rune, 0, len(name)) first := true wordWalker(name, func(info *wordInfo) { word := info.Word - if first { + switch { + case first: if strings.ToUpper(word) == word || strings.ToLower(word) == word { // ID → id, CAMEL → camel word = strings.ToLower(info.Word) } else { // ITicket → iTicket - word = lcFirst(info.Word) + word = LcFirst(info.Word) } first = false - } else if info.MatchCommonInitial { + case info.MatchCommonInitial: word = strings.ToUpper(word) - } else if !info.HasCommonInitial { - word = ucFirst(strings.ToLower(word)) + case !info.HasCommonInitial: + word = UcFirst(strings.ToLower(word)) } runes = append(runes, []rune(word)...) }) @@ -314,14 +344,15 @@ type wordInfo struct { // This function is based on the following code. // https://github.com/golang/lint/blob/06c8688daad7faa9da5a0c2f163a3d14aac986ca/lint.go#L679 func wordWalker(str string, f func(*wordInfo)) { - runes := []rune(str) + runes := []rune(strings.TrimFunc(str, isDelimiter)) w, i := 0, 0 // index of start of word, scan hasCommonInitial := false for i+1 <= len(runes) { eow := false // whether we hit the end of a word - if i+1 == len(runes) { + switch { + case i+1 == len(runes): eow = true - } else if isDelimiter(runes[i+1]) { + case isDelimiter(runes[i+1]): // underscore; shift the remainder forward over any run of underscores eow = true n := 1 @@ -336,7 +367,7 @@ func wordWalker(str string, f func(*wordInfo)) { copy(runes[i+1:], runes[i+n+1:]) runes = runes[:len(runes)-n] - } else if unicode.IsLower(runes[i]) && !unicode.IsLower(runes[i+1]) { + case unicode.IsLower(runes[i]) && !unicode.IsLower(runes[i+1]): // lower->non-lower eow = true } @@ -429,6 +460,7 @@ var commonInitialisms = map[string]bool{ "IP": true, "JSON": true, "LHS": true, + "PGP": true, "QPS": true, "RAM": true, "RHS": true, @@ -549,13 +581,13 @@ func render(filename string, tpldata interface{}) (*bytes.Buffer, error) { return buf, t.Execute(buf, tpldata) } -func write(filename string, b []byte) error { +func write(filename string, b []byte, packages *code.Packages) error { err := os.MkdirAll(filepath.Dir(filename), 0755) if err != nil { return errors.Wrap(err, "failed to create directory") } - formatted, err := imports.Prune(filename, b) + formatted, err := imports.Prune(filename, b, packages) if err != nil { fmt.Fprintf(os.Stderr, "gofmt failed on %s: %s\n", filepath.Base(filename), err.Error()) formatted = b diff --git a/vendor/github.com/99designs/gqlgen/codegen/type.go b/vendor/github.com/99designs/gqlgen/codegen/type.go index e00837323..06b370be7 100644 --- a/vendor/github.com/99designs/gqlgen/codegen/type.go +++ b/vendor/github.com/99designs/gqlgen/codegen/type.go @@ -1,18 +1,32 @@ package codegen import ( + "fmt" + "github.com/99designs/gqlgen/codegen/config" ) -func (b *builder) buildTypes() (map[string]*config.TypeReference, error) { +func (b *builder) buildTypes() map[string]*config.TypeReference { ret := map[string]*config.TypeReference{} - for _, ref := range b.Binder.References { - for ref != nil { - ret[ref.UniquenessKey()] = ref + processType(ret, ref) + } + return ret +} - ref = ref.Elem() +func processType(ret map[string]*config.TypeReference, ref *config.TypeReference) { + key := ref.UniquenessKey() + if existing, found := ret[key]; found { + // Simplistic check of content which is obviously different. + existingGQL := fmt.Sprintf("%v", existing.GQL) + newGQL := fmt.Sprintf("%v", ref.GQL) + if existingGQL != newGQL { + panic(fmt.Sprintf("non-unique key \"%s\", trying to replace %s with %s", key, existingGQL, newGQL)) } } - return ret, nil + ret[key] = ref + + if ref.IsSlice() { + processType(ret, ref.Elem()) + } } diff --git a/vendor/github.com/99designs/gqlgen/codegen/type.gotpl b/vendor/github.com/99designs/gqlgen/codegen/type.gotpl index cb2782c39..2bd0c1943 100644 --- a/vendor/github.com/99designs/gqlgen/codegen/type.gotpl +++ b/vendor/github.com/99designs/gqlgen/codegen/type.gotpl @@ -1,13 +1,10 @@ {{- range $type := .ReferencedTypes }} {{ with $type.UnmarshalFunc }} func (ec *executionContext) {{ . }}(ctx context.Context, v interface{}) ({{ $type.GO | ref }}, error) { - {{- if $type.IsNilable }} + {{- if and $type.IsNilable (not $type.GQL.NonNull) }} if v == nil { return nil, nil } {{- end }} - {{- if $type.IsPtr }} - res, err := ec.{{ $type.Elem.UnmarshalFunc }}(ctx, v) - return &res, err - {{- else if $type.IsSlice }} + {{- if $type.IsSlice }} var vSlice []interface{} if v != nil { if tmp1, ok := v.([]interface{}); ok { @@ -19,9 +16,10 @@ var err error res := make([]{{$type.GO.Elem | ref}}, len(vSlice)) for i := range vSlice { + ctx := graphql.WithFieldInputContext(ctx, graphql.NewFieldInputWithIndex(i)) res[i], err = ec.{{ $type.Elem.UnmarshalFunc }}(ctx, vSlice[i]) if err != nil { - return nil, err + return nil, graphql.WrapErrorWithInputPath(ctx, err) } } return res, nil @@ -29,17 +27,38 @@ {{- if $type.Unmarshaler }} {{- if $type.CastType }} tmp, err := {{ $type.Unmarshaler | call }}(v) - return {{ $type.GO | ref }}(tmp), err + {{- if $type.IsNilable }} + res := {{ $type.Elem.GO | ref }}(tmp) + {{- else}} + res := {{ $type.GO | ref }}(tmp) + {{- end }} {{- else}} - return {{ $type.Unmarshaler | call }}(v) + res, err := {{ $type.Unmarshaler | call }}(v) + {{- end }} + {{- if and $type.IsTargetNilable (not $type.IsNilable) }} + return *res, graphql.WrapErrorWithInputPath(ctx, err) + {{- else if and (not $type.IsTargetNilable) $type.IsNilable }} + return &res, graphql.WrapErrorWithInputPath(ctx, err) + {{- else}} + return res, graphql.WrapErrorWithInputPath(ctx, err) {{- end }} {{- else if eq ($type.GO | ref) "map[string]interface{}" }} return v.(map[string]interface{}), nil - {{- else if $type.IsMarshaler -}} - var res {{ $type.GO | ref }} - return res, res.UnmarshalGQL(v) + {{- else if $type.IsMarshaler }} + {{- if $type.IsNilable }} + var res = new({{ $type.Elem.GO | ref }}) + {{- else}} + var res {{ $type.GO | ref }} + {{- end }} + err := res.UnmarshalGQL(v) + return res, graphql.WrapErrorWithInputPath(ctx, err) {{- else }} - return ec.unmarshalInput{{ $type.GQL.Name }}(ctx, v) + res, err := ec.unmarshalInput{{ $type.GQL.Name }}(ctx, v) + {{- if $type.IsNilable }} + return &res, graphql.WrapErrorWithInputPath(ctx, err) + {{- else}} + return res, graphql.WrapErrorWithInputPath(ctx, err) + {{- end }} {{- end }} {{- end }} } @@ -47,17 +66,6 @@ {{ with $type.MarshalFunc }} func (ec *executionContext) {{ . }}(ctx context.Context, sel ast.SelectionSet, v {{ $type.GO | ref }}) graphql.Marshaler { - {{- if $type.IsNilable }} - if v == nil { - {{- if $type.GQL.NonNull }} - if !ec.HasError(graphql.GetResolverContext(ctx)) { - ec.Errorf(ctx, "must not be null") - } - {{- end }} - return graphql.Null - } - {{- end }} - {{- if $type.IsSlice }} {{- if not $type.GQL.NonNull }} if v == nil { @@ -75,11 +83,11 @@ for i := range v { {{- if not $type.IsScalar }} i := i - rctx := &graphql.ResolverContext{ + fc := &graphql.FieldContext{ Index: &i, Result: &v[i], } - ctx := graphql.WithResolverContext(ctx, rctx) + ctx := graphql.WithFieldContext(ctx, fc) f := func(i int) { defer func() { if r := recover(); r != nil { @@ -104,22 +112,35 @@ {{ if not $type.IsScalar }} wg.Wait() {{ end }} return ret {{- else }} - + {{- if $type.IsNilable }} + if v == nil { + {{- if $type.GQL.NonNull }} + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "must not be null") + } + {{- end }} + return graphql.Null + } + {{- end }} {{- if $type.IsMarshaler }} return v {{- else if $type.Marshaler }} - {{- if $type.IsPtr }} - return ec.{{ $type.Elem.MarshalFunc }}(ctx, sel, *v) - {{- else if $type.GQL.NonNull }} - res := {{ $type.Marshaler | call }}({{- if $type.CastType }}{{ $type.CastType | ref }}(v){{else}}v{{- end }}) + {{- $v := "v" }} + {{- if and $type.IsTargetNilable (not $type.IsNilable) }} + {{- $v = "&v" }} + {{- else if and (not $type.IsTargetNilable) $type.IsNilable }} + {{- $v = "*v" }} + {{- end }} + {{- if $type.GQL.NonNull }} + res := {{ $type.Marshaler | call }}({{- if $type.CastType }}{{ $type.CastType | ref }}({{ $v }}){{else}}{{ $v }}{{- end }}) if res == graphql.Null { - if !ec.HasError(graphql.GetResolverContext(ctx)) { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { ec.Errorf(ctx, "must not be null") } } return res {{- else }} - return {{ $type.Marshaler | call }}({{- if $type.CastType }}{{ $type.CastType | ref }}(v){{else}}v{{- end }}) + return {{ $type.Marshaler | call }}({{- if $type.CastType }}{{ $type.CastType | ref }}({{ $v }}){{else}}{{ $v }}{{- end }}) {{- end }} {{- else }} return ec._{{$type.Definition.Name}}(ctx, sel, {{ if not $type.IsNilable}}&{{end}} v) diff --git a/vendor/github.com/99designs/gqlgen/complexity/complexity.go b/vendor/github.com/99designs/gqlgen/complexity/complexity.go index d5b46bf45..1877aae5f 100644 --- a/vendor/github.com/99designs/gqlgen/complexity/complexity.go +++ b/vendor/github.com/99designs/gqlgen/complexity/complexity.go @@ -2,7 +2,7 @@ package complexity import ( "github.com/99designs/gqlgen/graphql" - "github.com/vektah/gqlparser/ast" + "github.com/vektah/gqlparser/v2/ast" ) func Calculate(es graphql.ExecutableSchema, op *ast.OperationDefinition, vars map[string]interface{}) int { diff --git a/vendor/github.com/99designs/gqlgen/go.mod b/vendor/github.com/99designs/gqlgen/go.mod index 9ff313d86..16affcf04 100644 --- a/vendor/github.com/99designs/gqlgen/go.mod +++ b/vendor/github.com/99designs/gqlgen/go.mod @@ -1,14 +1,19 @@ module github.com/99designs/gqlgen +go 1.12 + require ( - github.com/davecgh/go-spew v1.1.1 // indirect + github.com/agnivade/levenshtein v1.0.3 // indirect github.com/go-chi/chi v3.3.2+incompatible github.com/gogo/protobuf v1.0.0 // indirect github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f // indirect github.com/gorilla/mux v1.6.1 // indirect - github.com/gorilla/websocket v1.2.0 + github.com/gorilla/websocket v1.4.2 github.com/hashicorp/golang-lru v0.5.0 - github.com/kr/pretty v0.1.0 // indirect + github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381 + github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007 + github.com/mattn/go-colorable v0.1.4 + github.com/mattn/go-isatty v0.0.12 github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047 github.com/opentracing/basictracer-go v1.0.0 // indirect github.com/opentracing/opentracing-go v1.0.2 @@ -16,13 +21,13 @@ require ( github.com/rs/cors v1.6.0 github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371 // indirect github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0 // indirect - github.com/stretchr/testify v1.3.0 - github.com/urfave/cli v1.20.0 + github.com/stretchr/testify v1.4.0 + github.com/urfave/cli/v2 v2.1.1 github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e - github.com/vektah/gqlparser v1.1.2 - golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd - gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect - gopkg.in/yaml.v2 v2.2.2 + github.com/vektah/gqlparser v1.3.1 + github.com/vektah/gqlparser/v2 v2.0.1 + golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589 + gopkg.in/yaml.v2 v2.2.4 sourcegraph.com/sourcegraph/appdash v0.0.0-20180110180208-2cc67fd64755 sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67 // indirect ) diff --git a/vendor/github.com/99designs/gqlgen/go.sum b/vendor/github.com/99designs/gqlgen/go.sum index b2d54b1f7..02d393c3b 100644 --- a/vendor/github.com/99designs/gqlgen/go.sum +++ b/vendor/github.com/99designs/gqlgen/go.sum @@ -1,11 +1,20 @@ +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/agnivade/levenshtein v1.0.1 h1:3oJU7J3FGFmyhn8KHjmVaZCN5hxTr7GxgRue+sxIXdQ= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= +github.com/agnivade/levenshtein v1.0.3 h1:M5ZnqLOoZR8ygVq0FfkXsNOKzMCk0xRiow0R5+5VkQ0= +github.com/agnivade/levenshtein v1.0.3/go.mod h1:4SFRZbbXWLF4MU1T9Qg0pGgH3Pjs+t6ie5efyrwRJXs= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= +github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c h1:TUuUh0Xgj97tLMNtWtNvI9mIV6isjEb9lBMNv+77IGM= +github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= github.com/go-chi/chi v3.3.2+incompatible h1:uQNcQN3NsV1j4ANsPh42P4ew4t6rnRbJb8frvpp31qQ= github.com/go-chi/chi v3.3.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= github.com/gogo/protobuf v1.0.0 h1:2jyBKDKU/8v3v2xVR2PtiWQviFUyiaGk2rpfyFT8rTM= @@ -14,8 +23,8 @@ github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f h1:9oNbS1z4rVpbnkH github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.1 h1:KOwqsTYZdeuMacU7CxjMNYEKeBvLbxW+psodrbcEa3A= github.com/gorilla/mux v1.6.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/websocket v1.2.0 h1:VJtLvh6VQym50czpZzx07z/kw9EgAxI3x1ZB8taTMQQ= -github.com/gorilla/websocket v1.2.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= @@ -23,6 +32,15 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN 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/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381 h1:bqDmpDG49ZRnB5PcgP0RXtQvnMSgIF14M7CBd2shtXs= +github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007 h1:reVOUXwnhsYv/8UqjvhrMOu5CNT9UapHFLbQ2JcXsmg= +github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= +github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047 h1:zCoDWFD5nrJJVjbXiDZcVhOBSzKn3o9LgRLLMRNuru8= github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/opentracing/basictracer-go v1.0.0 h1:YyUAhaEfjoWXclZVJ9sGoNct7j4TVk7lZWlQw5UXuoo= @@ -35,46 +53,61 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rs/cors v1.6.0 h1:G9tHG9lebljV9mfp9SNPDL36nCDxmo3zTlAf1YgvzmI= github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= -github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371 h1:SWV2fHctRpRrp49VXJ6UZja7gU9QLHwRpIPBN89SKEo= github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0 h1:JJV9CsgM9EC9w2iVkwuz+sMx8yRFe89PJRUrv6hPCIA= github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.1 h1:52QO5WkIUcHGIR7EnGagH88x1bUzqGXTC5/1bDTUQ7U= github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw= -github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/vektah/dataloaden v0.2.0 h1:lhynDrG7c8mNLahboCo0Wq82tMjmu5yOUv2ds/tBmss= -github.com/vektah/dataloaden v0.2.0/go.mod h1:vxM6NuRlgiR0M6wbVTJeKp9vQIs81ZMfCYO+4yq/jbE= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k= +github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e h1:+w0Zm/9gaWpEAyDlU1eKOuk5twTjAjuevXqcJJw8hrg= github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U= -github.com/vektah/gqlparser v1.1.2 h1:ZsyLGn7/7jDNI+y4SEhI4yAxRChlv15pUHMjijT+e68= -github.com/vektah/gqlparser v1.1.2/go.mod h1:1ycwN7Ij5njmMkPPAOaRFY4rET2Enx7IkVv3vaXspKw= +github.com/vektah/gqlparser v1.3.1 h1:8b0IcD3qZKWJQHSzynbDlrtP3IxVydZ2DZepCGofqfU= +github.com/vektah/gqlparser v1.3.1/go.mod h1:bkVf0FX+Stjg/MHnm8mEyubuaArhNEqfQhF+OTiAL74= +github.com/vektah/gqlparser/v2 v2.0.1 h1:xgl5abVnsd4hkN9rk65OJID9bfcLSMuTaTcZj777q1o= +github.com/vektah/gqlparser/v2 v2.0.1/go.mod h1:SyUiHgLATUR8BiYURfTirrTcGpcE+4XkV2se04Px1Ms= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/net v0.0.0-20180404174746-b3c676e531a6 h1:mge3qS/eMvcfyIAzTMOAy0XUzWG6Lk0N4M8zjuSmdco= -golang.org/x/net v0.0.0-20180404174746-b3c676e531a6/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= 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-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6 h1:iZgcI2DDp6zW5v9Z/5+f0NuqoxNdmzg4hivjk2WLXpY= golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190125232054-dbeab5af4b8d3204d444b78cafaba18a9a062a50/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190511041617-99f201b6807e h1:wTxRxdzKt8fn3IQa3+kVlPJMxK2hJj2Orm+M2Mzw9eg= -golang.org/x/tools v0.0.0-20190511041617-99f201b6807e/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd h1:oMEQDWVXVNpceQoVd1JN3CQ7LYJJzs5qWqZIUcxXHHw= golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589 h1:rjUrONFu4kLchcZTfp3/96bR8bW8dIa8uz3cR5n0cgM= +golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 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-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= sourcegraph.com/sourcegraph/appdash v0.0.0-20180110180208-2cc67fd64755 h1:d2maSb13hr/ArmfK3rW+wNUKKfytCol7W1/vDHxMPiE= sourcegraph.com/sourcegraph/appdash v0.0.0-20180110180208-2cc67fd64755/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67 h1:e1sMhtVq9AfcEy8AXNb8eSg6gbzfdpYhoNqnPJa+GzI= diff --git a/vendor/github.com/99designs/gqlgen/graphql/cache.go b/vendor/github.com/99designs/gqlgen/graphql/cache.go new file mode 100644 index 000000000..fe86ca350 --- /dev/null +++ b/vendor/github.com/99designs/gqlgen/graphql/cache.go @@ -0,0 +1,29 @@ +package graphql + +import "context" + +// Cache is a shared store for APQ and query AST caching +type Cache interface { + // Get looks up a key's value from the cache. + Get(ctx context.Context, key string) (value interface{}, ok bool) + + // Add adds a value to the cache. + Add(ctx context.Context, key string, value interface{}) +} + +// MapCache is the simplest implementation of a cache, because it can not evict it should only be used in tests +type MapCache map[string]interface{} + +// Get looks up a key's value from the cache. +func (m MapCache) Get(ctx context.Context, key string) (value interface{}, ok bool) { + v, ok := m[key] + return v, ok +} + +// Add adds a value to the cache. +func (m MapCache) Add(ctx context.Context, key string, value interface{}) { m[key] = value } + +type NoCache struct{} + +func (n NoCache) Get(ctx context.Context, key string) (value interface{}, ok bool) { return nil, false } +func (n NoCache) Add(ctx context.Context, key string, value interface{}) {} diff --git a/vendor/github.com/99designs/gqlgen/graphql/context.go b/vendor/github.com/99designs/gqlgen/graphql/context.go deleted file mode 100644 index 356f5175b..000000000 --- a/vendor/github.com/99designs/gqlgen/graphql/context.go +++ /dev/null @@ -1,274 +0,0 @@ -package graphql - -import ( - "context" - "fmt" - "sync" - - "github.com/vektah/gqlparser/ast" - "github.com/vektah/gqlparser/gqlerror" -) - -type Resolver func(ctx context.Context) (res interface{}, err error) -type FieldMiddleware func(ctx context.Context, next Resolver) (res interface{}, err error) -type RequestMiddleware func(ctx context.Context, next func(ctx context.Context) []byte) []byte -type ComplexityLimitFunc func(ctx context.Context) int - -type RequestContext struct { - RawQuery string - Variables map[string]interface{} - Doc *ast.QueryDocument - - ComplexityLimit int - OperationComplexity int - DisableIntrospection bool - - // ErrorPresenter will be used to generate the error - // message from errors given to Error(). - ErrorPresenter ErrorPresenterFunc - Recover RecoverFunc - ResolverMiddleware FieldMiddleware - DirectiveMiddleware FieldMiddleware - RequestMiddleware RequestMiddleware - Tracer Tracer - - errorsMu sync.Mutex - Errors gqlerror.List - extensionsMu sync.Mutex - Extensions map[string]interface{} -} - -func DefaultResolverMiddleware(ctx context.Context, next Resolver) (res interface{}, err error) { - return next(ctx) -} - -func DefaultDirectiveMiddleware(ctx context.Context, next Resolver) (res interface{}, err error) { - return next(ctx) -} - -func DefaultRequestMiddleware(ctx context.Context, next func(ctx context.Context) []byte) []byte { - return next(ctx) -} - -func NewRequestContext(doc *ast.QueryDocument, query string, variables map[string]interface{}) *RequestContext { - return &RequestContext{ - Doc: doc, - RawQuery: query, - Variables: variables, - ResolverMiddleware: DefaultResolverMiddleware, - DirectiveMiddleware: DefaultDirectiveMiddleware, - RequestMiddleware: DefaultRequestMiddleware, - Recover: DefaultRecover, - ErrorPresenter: DefaultErrorPresenter, - Tracer: &NopTracer{}, - } -} - -type key string - -const ( - request key = "request_context" - resolver key = "resolver_context" -) - -func GetRequestContext(ctx context.Context) *RequestContext { - if val, ok := ctx.Value(request).(*RequestContext); ok { - return val - } - return nil -} - -func WithRequestContext(ctx context.Context, rc *RequestContext) context.Context { - return context.WithValue(ctx, request, rc) -} - -type ResolverContext struct { - Parent *ResolverContext - // The name of the type this field belongs to - Object string - // These are the args after processing, they can be mutated in middleware to change what the resolver will get. - Args map[string]interface{} - // The raw field - Field CollectedField - // The index of array in path. - Index *int - // The result object of resolver - Result interface{} - // IsMethod indicates if the resolver is a method - IsMethod bool -} - -func (r *ResolverContext) Path() []interface{} { - var path []interface{} - for it := r; it != nil; it = it.Parent { - if it.Index != nil { - path = append(path, *it.Index) - } else if it.Field.Field != nil { - path = append(path, it.Field.Alias) - } - } - - // because we are walking up the chain, all the elements are backwards, do an inplace flip. - for i := len(path)/2 - 1; i >= 0; i-- { - opp := len(path) - 1 - i - path[i], path[opp] = path[opp], path[i] - } - - return path -} - -func GetResolverContext(ctx context.Context) *ResolverContext { - if val, ok := ctx.Value(resolver).(*ResolverContext); ok { - return val - } - return nil -} - -func WithResolverContext(ctx context.Context, rc *ResolverContext) context.Context { - rc.Parent = GetResolverContext(ctx) - return context.WithValue(ctx, resolver, rc) -} - -// This is just a convenient wrapper method for CollectFields -func CollectFieldsCtx(ctx context.Context, satisfies []string) []CollectedField { - resctx := GetResolverContext(ctx) - return CollectFields(GetRequestContext(ctx), resctx.Field.Selections, satisfies) -} - -// CollectAllFields returns a slice of all GraphQL field names that were selected for the current resolver context. -// The slice will contain the unique set of all field names requested regardless of fragment type conditions. -func CollectAllFields(ctx context.Context) []string { - resctx := GetResolverContext(ctx) - collected := CollectFields(GetRequestContext(ctx), resctx.Field.Selections, nil) - uniq := make([]string, 0, len(collected)) -Next: - for _, f := range collected { - for _, name := range uniq { - if name == f.Name { - continue Next - } - } - uniq = append(uniq, f.Name) - } - return uniq -} - -// Errorf sends an error string to the client, passing it through the formatter. -func (c *RequestContext) Errorf(ctx context.Context, format string, args ...interface{}) { - c.errorsMu.Lock() - defer c.errorsMu.Unlock() - - c.Errors = append(c.Errors, c.ErrorPresenter(ctx, fmt.Errorf(format, args...))) -} - -// Error sends an error to the client, passing it through the formatter. -func (c *RequestContext) Error(ctx context.Context, err error) { - c.errorsMu.Lock() - defer c.errorsMu.Unlock() - - c.Errors = append(c.Errors, c.ErrorPresenter(ctx, err)) -} - -// HasError returns true if the current field has already errored -func (c *RequestContext) HasError(rctx *ResolverContext) bool { - c.errorsMu.Lock() - defer c.errorsMu.Unlock() - path := rctx.Path() - - for _, err := range c.Errors { - if equalPath(err.Path, path) { - return true - } - } - return false -} - -// GetErrors returns a list of errors that occurred in the current field -func (c *RequestContext) GetErrors(rctx *ResolverContext) gqlerror.List { - c.errorsMu.Lock() - defer c.errorsMu.Unlock() - path := rctx.Path() - - var errs gqlerror.List - for _, err := range c.Errors { - if equalPath(err.Path, path) { - errs = append(errs, err) - } - } - return errs -} - -func equalPath(a []interface{}, b []interface{}) bool { - if len(a) != len(b) { - return false - } - - for i := 0; i < len(a); i++ { - if a[i] != b[i] { - return false - } - } - - return true -} - -// AddError is a convenience method for adding an error to the current response -func AddError(ctx context.Context, err error) { - GetRequestContext(ctx).Error(ctx, err) -} - -// AddErrorf is a convenience method for adding an error to the current response -func AddErrorf(ctx context.Context, format string, args ...interface{}) { - GetRequestContext(ctx).Errorf(ctx, format, args...) -} - -// RegisterExtension registers an extension, returns error if extension has already been registered -func (c *RequestContext) RegisterExtension(key string, value interface{}) error { - c.extensionsMu.Lock() - defer c.extensionsMu.Unlock() - - if c.Extensions == nil { - c.Extensions = make(map[string]interface{}) - } - - if _, ok := c.Extensions[key]; ok { - return fmt.Errorf("extension already registered for key %s", key) - } - - c.Extensions[key] = value - return nil -} - -// ChainFieldMiddleware add chain by FieldMiddleware -func ChainFieldMiddleware(handleFunc ...FieldMiddleware) FieldMiddleware { - n := len(handleFunc) - - if n > 1 { - lastI := n - 1 - return func(ctx context.Context, next Resolver) (interface{}, error) { - var ( - chainHandler Resolver - curI int - ) - chainHandler = func(currentCtx context.Context) (interface{}, error) { - if curI == lastI { - return next(currentCtx) - } - curI++ - res, err := handleFunc[curI](currentCtx, chainHandler) - curI-- - return res, err - - } - return handleFunc[0](ctx, chainHandler) - } - } - - if n == 1 { - return handleFunc[0] - } - - return func(ctx context.Context, next Resolver) (interface{}, error) { - return next(ctx) - } -} diff --git a/vendor/github.com/99designs/gqlgen/graphql/context_field.go b/vendor/github.com/99designs/gqlgen/graphql/context_field.go new file mode 100644 index 000000000..3c3385042 --- /dev/null +++ b/vendor/github.com/99designs/gqlgen/graphql/context_field.go @@ -0,0 +1,92 @@ +package graphql + +import ( + "context" + "time" + + "github.com/vektah/gqlparser/v2/ast" +) + +type key string + +const resolverCtx key = "resolver_context" + +// Deprecated: Use FieldContext instead +type ResolverContext = FieldContext + +type FieldContext struct { + Parent *FieldContext + // The name of the type this field belongs to + Object string + // These are the args after processing, they can be mutated in middleware to change what the resolver will get. + Args map[string]interface{} + // The raw field + Field CollectedField + // The index of array in path. + Index *int + // The result object of resolver + Result interface{} + // IsMethod indicates if the resolver is a method + IsMethod bool +} + +type FieldStats struct { + // When field execution started + Started time.Time + + // When argument marshaling finished + ArgumentsCompleted time.Time + + // When the field completed running all middleware. Not available inside field middleware! + Completed time.Time +} + +func (r *FieldContext) Path() ast.Path { + var path ast.Path + for it := r; it != nil; it = it.Parent { + if it.Index != nil { + path = append(path, ast.PathIndex(*it.Index)) + } else if it.Field.Field != nil { + path = append(path, ast.PathName(it.Field.Alias)) + } + } + + // because we are walking up the chain, all the elements are backwards, do an inplace flip. + for i := len(path)/2 - 1; i >= 0; i-- { + opp := len(path) - 1 - i + path[i], path[opp] = path[opp], path[i] + } + + return path +} + +// Deprecated: Use GetFieldContext instead +func GetResolverContext(ctx context.Context) *ResolverContext { + return GetFieldContext(ctx) +} + +func GetFieldContext(ctx context.Context) *FieldContext { + if val, ok := ctx.Value(resolverCtx).(*FieldContext); ok { + return val + } + return nil +} + +func WithFieldContext(ctx context.Context, rc *FieldContext) context.Context { + rc.Parent = GetFieldContext(ctx) + return context.WithValue(ctx, resolverCtx, rc) +} + +func equalPath(a ast.Path, b ast.Path) bool { + if len(a) != len(b) { + return false + } + + for i := 0; i < len(a); i++ { + if a[i] != b[i] { + return false + } + } + + return true +} diff --git a/vendor/github.com/99designs/gqlgen/graphql/context_field_input.go b/vendor/github.com/99designs/gqlgen/graphql/context_field_input.go new file mode 100644 index 000000000..0ed6f8b66 --- /dev/null +++ b/vendor/github.com/99designs/gqlgen/graphql/context_field_input.go @@ -0,0 +1,85 @@ +package graphql + +import ( + "context" + + "github.com/vektah/gqlparser/v2/ast" + "github.com/vektah/gqlparser/v2/gqlerror" +) + +const fieldInputCtx key = "field_input_context" + +type FieldInputContext struct { + ParentField *FieldContext + ParentInput *FieldInputContext + Field *string + Index *int +} + +func (fic *FieldInputContext) Path() ast.Path { + var inputPath ast.Path + for it := fic; it != nil; it = it.ParentInput { + if it.Index != nil { + inputPath = append(inputPath, ast.PathIndex(*it.Index)) + } else if it.Field != nil { + inputPath = append(inputPath, ast.PathName(*it.Field)) + } + } + + // because we are walking up the chain, all the elements are backwards, do an inplace flip. + for i := len(inputPath)/2 - 1; i >= 0; i-- { + opp := len(inputPath) - 1 - i + inputPath[i], inputPath[opp] = inputPath[opp], inputPath[i] + } + + if fic.ParentField != nil { + fieldPath := fic.ParentField.Path() + return append(fieldPath, inputPath...) + + } + + return inputPath +} + +func NewFieldInputWithField(field string) *FieldInputContext { + return &FieldInputContext{Field: &field} +} + +func NewFieldInputWithIndex(index int) *FieldInputContext { + return &FieldInputContext{Index: &index} +} + +func WithFieldInputContext(ctx context.Context, fic *FieldInputContext) context.Context { + if fieldContext := GetFieldContext(ctx); fieldContext != nil { + fic.ParentField = fieldContext + } + if fieldInputContext := GetFieldInputContext(ctx); fieldInputContext != nil { + fic.ParentInput = fieldInputContext + } + + return context.WithValue(ctx, fieldInputCtx, fic) +} + +func GetFieldInputContext(ctx context.Context) *FieldInputContext { + if val, ok := ctx.Value(fieldInputCtx).(*FieldInputContext); ok { + return val + } + return nil +} + +func WrapErrorWithInputPath(ctx context.Context, err error) error { + if err == nil { + return nil + } + + inputContext := GetFieldInputContext(ctx) + path := inputContext.Path() + if gerr, ok := err.(*gqlerror.Error); ok { + if gerr.Path == nil { + gerr.Path = path + } + return gerr + } else { + return gqlerror.WrapPath(path, err) + } +} diff --git a/vendor/github.com/99designs/gqlgen/graphql/context_operation.go b/vendor/github.com/99designs/gqlgen/graphql/context_operation.go new file mode 100644 index 000000000..d19b9fd34 --- /dev/null +++ b/vendor/github.com/99designs/gqlgen/graphql/context_operation.go @@ -0,0 +1,107 @@ +package graphql + +import ( + "context" + "errors" + + "github.com/vektah/gqlparser/v2/ast" +) + +// Deprecated: Please update all references to OperationContext instead +type RequestContext = OperationContext + +type OperationContext struct { + RawQuery string + Variables map[string]interface{} + OperationName string + Doc *ast.QueryDocument + + Operation *ast.OperationDefinition + DisableIntrospection bool + Recover RecoverFunc + ResolverMiddleware FieldMiddleware + + Stats Stats +} + +func (c *OperationContext) Validate(ctx context.Context) error { + if c.Doc == nil { + return errors.New("field 'Doc'is required") + } + if c.RawQuery == "" { + return errors.New("field 'RawQuery' is required") + } + if c.Variables == nil { + c.Variables = make(map[string]interface{}) + } + if c.ResolverMiddleware == nil { + return errors.New("field 'ResolverMiddleware' is required") + } + if c.Recover == nil { + c.Recover = DefaultRecover + } + + return nil +} + +const operationCtx key = "operation_context" + +// Deprecated: Please update all references to GetOperationContext instead +func GetRequestContext(ctx context.Context) *RequestContext { + return GetOperationContext(ctx) +} + +func GetOperationContext(ctx context.Context) *OperationContext { + if val, ok := ctx.Value(operationCtx).(*OperationContext); ok && val != nil { + return val + } + panic("missing operation context") +} + +func WithOperationContext(ctx context.Context, rc *OperationContext) context.Context { + return context.WithValue(ctx, operationCtx, rc) +} + +// HasOperationContext checks if the given context is part of an ongoing operation +// +// Some errors can happen outside of an operation, eg json unmarshal errors. +func HasOperationContext(ctx context.Context) bool { + _, ok := ctx.Value(operationCtx).(*OperationContext) + return ok +} + +// This is just a convenient wrapper method for CollectFields +func CollectFieldsCtx(ctx context.Context, satisfies []string) []CollectedField { + resctx := GetFieldContext(ctx) + return CollectFields(GetOperationContext(ctx), resctx.Field.Selections, satisfies) +} + +// CollectAllFields returns a slice of all GraphQL field names that were selected for the current resolver context. +// The slice will contain the unique set of all field names requested regardless of fragment type conditions. +func CollectAllFields(ctx context.Context) []string { + resctx := GetFieldContext(ctx) + collected := CollectFields(GetOperationContext(ctx), resctx.Field.Selections, nil) + uniq := make([]string, 0, len(collected)) +Next: + for _, f := range collected { + for _, name := range uniq { + if name == f.Name { + continue Next + } + } + uniq = append(uniq, f.Name) + } + return uniq +} + +// Errorf sends an error string to the client, passing it through the formatter. +// Deprecated: use graphql.AddErrorf(ctx, err) instead +func (c *OperationContext) Errorf(ctx context.Context, format string, args ...interface{}) { + AddErrorf(ctx, format, args...) +} + +// Error sends an error to the client, passing it through the formatter. +// Deprecated: use graphql.AddError(ctx, err) instead +func (c *OperationContext) Error(ctx context.Context, err error) { + AddError(ctx, err) +} diff --git a/vendor/github.com/99designs/gqlgen/graphql/context_response.go b/vendor/github.com/99designs/gqlgen/graphql/context_response.go new file mode 100644 index 000000000..cc952ed72 --- /dev/null +++ b/vendor/github.com/99designs/gqlgen/graphql/context_response.go @@ -0,0 +1,157 @@ +package graphql + +import ( + "context" + "fmt" + "sync" + + "github.com/vektah/gqlparser/v2/gqlerror" +) + +type responseContext struct { + errorPresenter ErrorPresenterFunc + recover RecoverFunc + + errors gqlerror.List + errorsMu sync.Mutex + + extensions map[string]interface{} + extensionsMu sync.Mutex +} + +const resultCtx key = "result_context" + +func getResponseContext(ctx context.Context) *responseContext { + val, ok := ctx.Value(resultCtx).(*responseContext) + if !ok { + panic("missing response context") + } + return val +} + +func WithResponseContext(ctx context.Context, presenterFunc ErrorPresenterFunc, recoverFunc RecoverFunc) context.Context { + return context.WithValue(ctx, resultCtx, &responseContext{ + errorPresenter: presenterFunc, + recover: recoverFunc, + }) +} + +// AddErrorf writes a formatted error to the client, first passing it through the error presenter. +func AddErrorf(ctx context.Context, format string, args ...interface{}) { + c := getResponseContext(ctx) + + c.errorsMu.Lock() + defer c.errorsMu.Unlock() + + c.errors = append(c.errors, c.errorPresenter(ctx, fmt.Errorf(format, args...))) +} + +// AddError sends an error to the client, first passing it through the error presenter. +func AddError(ctx context.Context, err error) { + c := getResponseContext(ctx) + + c.errorsMu.Lock() + defer c.errorsMu.Unlock() + + c.errors = append(c.errors, c.errorPresenter(ctx, err)) +} + +func Recover(ctx context.Context, err interface{}) (userMessage error) { + c := getResponseContext(ctx) + return c.recover(ctx, err) +} + +// HasFieldError returns true if the given field has already errored +func HasFieldError(ctx context.Context, rctx *FieldContext) bool { + c := getResponseContext(ctx) + + c.errorsMu.Lock() + defer c.errorsMu.Unlock() + + if len(c.errors) == 0 { + return false + } + + path := rctx.Path() + for _, err := range c.errors { + if equalPath(err.Path, path) { + return true + } + } + return false +} + +// GetFieldErrors returns a list of errors that occurred in the given field +func GetFieldErrors(ctx context.Context, rctx *FieldContext) gqlerror.List { + c := getResponseContext(ctx) + + c.errorsMu.Lock() + defer c.errorsMu.Unlock() + + if len(c.errors) == 0 { + return nil + } + + path := rctx.Path() + var errs gqlerror.List + for _, err := range c.errors { + if equalPath(err.Path, path) { + errs = append(errs, err) + } + } + return errs +} + +func GetErrors(ctx context.Context) gqlerror.List { + resCtx := getResponseContext(ctx) + resCtx.errorsMu.Lock() + defer resCtx.errorsMu.Unlock() + + if len(resCtx.errors) == 0 { + return nil + } + + errs := resCtx.errors + cpy := make(gqlerror.List, len(errs)) + for i := range errs { + errCpy := *errs[i] + cpy[i] = &errCpy + } + return cpy +} + +// RegisterExtension allows you to add a new extension into the graphql response +func RegisterExtension(ctx context.Context, key string, value interface{}) { + c := getResponseContext(ctx) + c.extensionsMu.Lock() + defer c.extensionsMu.Unlock() + + if c.extensions == nil { + c.extensions = make(map[string]interface{}) + } + + if _, ok := c.extensions[key]; ok { + panic(fmt.Errorf("extension already registered for key %s", key)) + } + + c.extensions[key] = value +} + +// GetExtensions returns any extensions registered in the current result context +func GetExtensions(ctx context.Context) map[string]interface{} { + ext := getResponseContext(ctx).extensions + if ext == nil { + return map[string]interface{}{} + } + + return ext +} + +func GetExtension(ctx context.Context, name string) interface{} { + ext := getResponseContext(ctx).extensions + if ext == nil { + return nil + } + + return ext[name] +} diff --git a/vendor/github.com/99designs/gqlgen/graphql/errcode/codes.go b/vendor/github.com/99designs/gqlgen/graphql/errcode/codes.go new file mode 100644 index 000000000..774ab7a9e --- /dev/null +++ b/vendor/github.com/99designs/gqlgen/graphql/errcode/codes.go @@ -0,0 +1,49 @@ +package errcode + +import ( + "github.com/vektah/gqlparser/v2/gqlerror" +) + +const ValidationFailed = "GRAPHQL_VALIDATION_FAILED" +const ParseFailed = "GRAPHQL_PARSE_FAILED" + +type ErrorKind int + +const ( + // issues with graphql (validation, parsing). 422s in http, GQL_ERROR in websocket + KindProtocol ErrorKind = iota + // user errors, 200s in http, GQL_DATA in websocket + KindUser +) + +var codeType = map[string]ErrorKind{ + ValidationFailed: KindProtocol, + ParseFailed: KindProtocol, +} + +// RegisterErrorType should be called by extensions that want to customize the http status codes for errors they return +func RegisterErrorType(code string, kind ErrorKind) { + codeType[code] = kind +} + +// Set the error code on a given graphql error extension +func Set(err *gqlerror.Error, value string) { + if err.Extensions == nil { + err.Extensions = map[string]interface{}{} + } + + err.Extensions["code"] = value +} + +// get the kind of the first non User error, defaults to User if no errors have a custom extension +func GetErrorKind(errs gqlerror.List) ErrorKind { + for _, err := range errs { + if code, ok := err.Extensions["code"].(string); ok { + if kind, ok := codeType[code]; ok && kind != KindUser { + return kind + } + } + } + + return KindUser +} diff --git a/vendor/github.com/99designs/gqlgen/graphql/error.go b/vendor/github.com/99designs/gqlgen/graphql/error.go index af8b4ce40..201058a2f 100644 --- a/vendor/github.com/99designs/gqlgen/graphql/error.go +++ b/vendor/github.com/99designs/gqlgen/graphql/error.go @@ -3,10 +3,10 @@ package graphql import ( "context" - "github.com/vektah/gqlparser/gqlerror" + "github.com/vektah/gqlparser/v2/gqlerror" ) -type ErrorPresenterFunc func(context.Context, error) *gqlerror.Error +type ErrorPresenterFunc func(ctx context.Context, err error) *gqlerror.Error type ExtendedError interface { Extensions() map[string]interface{} @@ -15,7 +15,7 @@ type ExtendedError interface { func DefaultErrorPresenter(ctx context.Context, err error) *gqlerror.Error { if gqlerr, ok := err.(*gqlerror.Error); ok { if gqlerr.Path == nil { - gqlerr.Path = GetResolverContext(ctx).Path() + gqlerr.Path = GetFieldContext(ctx).Path() } return gqlerr } @@ -27,7 +27,7 @@ func DefaultErrorPresenter(ctx context.Context, err error) *gqlerror.Error { return &gqlerror.Error{ Message: err.Error(), - Path: GetResolverContext(ctx).Path(), + Path: GetFieldContext(ctx).Path(), Extensions: extensions, } } diff --git a/vendor/github.com/99designs/gqlgen/graphql/exec.go b/vendor/github.com/99designs/gqlgen/graphql/executable_schema.go similarity index 77% rename from vendor/github.com/99designs/gqlgen/graphql/exec.go rename to vendor/github.com/99designs/gqlgen/graphql/executable_schema.go index 3e00a4d57..dc53b6881 100644 --- a/vendor/github.com/99designs/gqlgen/graphql/exec.go +++ b/vendor/github.com/99designs/gqlgen/graphql/executable_schema.go @@ -1,29 +1,29 @@ +//go:generate go run github.com/matryer/moq -out executable_schema_mock.go . ExecutableSchema + package graphql import ( "context" "fmt" - "github.com/vektah/gqlparser/ast" + "github.com/vektah/gqlparser/v2/ast" ) type ExecutableSchema interface { Schema() *ast.Schema Complexity(typeName, fieldName string, childComplexity int, args map[string]interface{}) (int, bool) - Query(ctx context.Context, op *ast.OperationDefinition) *Response - Mutation(ctx context.Context, op *ast.OperationDefinition) *Response - Subscription(ctx context.Context, op *ast.OperationDefinition) func() *Response + Exec(ctx context.Context) ResponseHandler } // CollectFields returns the set of fields from an ast.SelectionSet where all collected fields satisfy at least one of the GraphQL types // passed through satisfies. Providing an empty or nil slice for satisfies will return collect all fields regardless of fragment // type conditions. -func CollectFields(reqCtx *RequestContext, selSet ast.SelectionSet, satisfies []string) []CollectedField { +func CollectFields(reqCtx *OperationContext, selSet ast.SelectionSet, satisfies []string) []CollectedField { return collectFields(reqCtx, selSet, satisfies, map[string]bool{}) } -func collectFields(reqCtx *RequestContext, selSet ast.SelectionSet, satisfies []string, visited map[string]bool) []CollectedField { +func collectFields(reqCtx *OperationContext, selSet ast.SelectionSet, satisfies []string, visited map[string]bool) []CollectedField { groupedFields := make([]CollectedField, 0, len(selSet)) for _, sel := range selSet { @@ -32,7 +32,7 @@ func collectFields(reqCtx *RequestContext, selSet ast.SelectionSet, satisfies [] if !shouldIncludeNode(sel.Directives, reqCtx.Variables) { continue } - f := getOrCreateAndAppendField(&groupedFields, sel.Alias, func() CollectedField { + f := getOrCreateAndAppendField(&groupedFields, sel.Alias, sel.ObjectDefinition, func() CollectedField { return CollectedField{Field: sel} }) @@ -45,7 +45,7 @@ func collectFields(reqCtx *RequestContext, selSet ast.SelectionSet, satisfies [] continue } for _, childField := range collectFields(reqCtx, sel.SelectionSet, satisfies, visited) { - f := getOrCreateAndAppendField(&groupedFields, childField.Name, func() CollectedField { return childField }) + f := getOrCreateAndAppendField(&groupedFields, childField.Name, childField.ObjectDefinition, func() CollectedField { return childField }) f.Selections = append(f.Selections, childField.Selections...) } @@ -70,7 +70,7 @@ func collectFields(reqCtx *RequestContext, selSet ast.SelectionSet, satisfies [] } for _, childField := range collectFields(reqCtx, fragment.SelectionSet, satisfies, visited) { - f := getOrCreateAndAppendField(&groupedFields, childField.Name, func() CollectedField { return childField }) + f := getOrCreateAndAppendField(&groupedFields, childField.Name, childField.ObjectDefinition, func() CollectedField { return childField }) f.Selections = append(f.Selections, childField.Selections...) } default: @@ -96,9 +96,9 @@ func instanceOf(val string, satisfies []string) bool { return false } -func getOrCreateAndAppendField(c *[]CollectedField, name string, creator func() CollectedField) *CollectedField { +func getOrCreateAndAppendField(c *[]CollectedField, name string, objectDefinition *ast.Definition, creator func() CollectedField) *CollectedField { for i, cf := range *c { - if cf.Alias == name { + if cf.Alias == name && (cf.ObjectDefinition == objectDefinition || (cf.ObjectDefinition != nil && objectDefinition != nil && cf.ObjectDefinition.Name == objectDefinition.Name)) { return &(*c)[i] } } diff --git a/vendor/github.com/99designs/gqlgen/graphql/executable_schema_mock.go b/vendor/github.com/99designs/gqlgen/graphql/executable_schema_mock.go new file mode 100644 index 000000000..0c021d3d0 --- /dev/null +++ b/vendor/github.com/99designs/gqlgen/graphql/executable_schema_mock.go @@ -0,0 +1,175 @@ +// Code generated by moq; DO NOT EDIT. +// github.com/matryer/moq + +package graphql + +import ( + "context" + "github.com/vektah/gqlparser/v2/ast" + "sync" +) + +var ( + lockExecutableSchemaMockComplexity sync.RWMutex + lockExecutableSchemaMockExec sync.RWMutex + lockExecutableSchemaMockSchema sync.RWMutex +) + +// Ensure, that ExecutableSchemaMock does implement ExecutableSchema. +// If this is not the case, regenerate this file with moq. +var _ ExecutableSchema = &ExecutableSchemaMock{} + +// ExecutableSchemaMock is a mock implementation of ExecutableSchema. +// +// func TestSomethingThatUsesExecutableSchema(t *testing.T) { +// +// // make and configure a mocked ExecutableSchema +// mockedExecutableSchema := &ExecutableSchemaMock{ +// ComplexityFunc: func(typeName string, fieldName string, childComplexity int, args map[string]interface{}) (int, bool) { +// panic("mock out the Complexity method") +// }, +// ExecFunc: func(ctx context.Context) ResponseHandler { +// panic("mock out the Exec method") +// }, +// SchemaFunc: func() *ast.Schema { +// panic("mock out the Schema method") +// }, +// } +// +// // use mockedExecutableSchema in code that requires ExecutableSchema +// // and then make assertions. +// +// } +type ExecutableSchemaMock struct { + // ComplexityFunc mocks the Complexity method. + ComplexityFunc func(typeName string, fieldName string, childComplexity int, args map[string]interface{}) (int, bool) + + // ExecFunc mocks the Exec method. + ExecFunc func(ctx context.Context) ResponseHandler + + // SchemaFunc mocks the Schema method. + SchemaFunc func() *ast.Schema + + // calls tracks calls to the methods. + calls struct { + // Complexity holds details about calls to the Complexity method. + Complexity []struct { + // TypeName is the typeName argument value. + TypeName string + // FieldName is the fieldName argument value. + FieldName string + // ChildComplexity is the childComplexity argument value. + ChildComplexity int + // Args is the args argument value. + Args map[string]interface{} + } + // Exec holds details about calls to the Exec method. + Exec []struct { + // Ctx is the ctx argument value. + Ctx context.Context + } + // Schema holds details about calls to the Schema method. + Schema []struct { + } + } +} + +// Complexity calls ComplexityFunc. +func (mock *ExecutableSchemaMock) Complexity(typeName string, fieldName string, childComplexity int, args map[string]interface{}) (int, bool) { + if mock.ComplexityFunc == nil { + panic("ExecutableSchemaMock.ComplexityFunc: method is nil but ExecutableSchema.Complexity was just called") + } + callInfo := struct { + TypeName string + FieldName string + ChildComplexity int + Args map[string]interface{} + }{ + TypeName: typeName, + FieldName: fieldName, + ChildComplexity: childComplexity, + Args: args, + } + lockExecutableSchemaMockComplexity.Lock() + mock.calls.Complexity = append(mock.calls.Complexity, callInfo) + lockExecutableSchemaMockComplexity.Unlock() + return mock.ComplexityFunc(typeName, fieldName, childComplexity, args) +} + +// ComplexityCalls gets all the calls that were made to Complexity. +// Check the length with: +// len(mockedExecutableSchema.ComplexityCalls()) +func (mock *ExecutableSchemaMock) ComplexityCalls() []struct { + TypeName string + FieldName string + ChildComplexity int + Args map[string]interface{} +} { + var calls []struct { + TypeName string + FieldName string + ChildComplexity int + Args map[string]interface{} + } + lockExecutableSchemaMockComplexity.RLock() + calls = mock.calls.Complexity + lockExecutableSchemaMockComplexity.RUnlock() + return calls +} + +// Exec calls ExecFunc. +func (mock *ExecutableSchemaMock) Exec(ctx context.Context) ResponseHandler { + if mock.ExecFunc == nil { + panic("ExecutableSchemaMock.ExecFunc: method is nil but ExecutableSchema.Exec was just called") + } + callInfo := struct { + Ctx context.Context + }{ + Ctx: ctx, + } + lockExecutableSchemaMockExec.Lock() + mock.calls.Exec = append(mock.calls.Exec, callInfo) + lockExecutableSchemaMockExec.Unlock() + return mock.ExecFunc(ctx) +} + +// ExecCalls gets all the calls that were made to Exec. +// Check the length with: +// len(mockedExecutableSchema.ExecCalls()) +func (mock *ExecutableSchemaMock) ExecCalls() []struct { + Ctx context.Context +} { + var calls []struct { + Ctx context.Context + } + lockExecutableSchemaMockExec.RLock() + calls = mock.calls.Exec + lockExecutableSchemaMockExec.RUnlock() + return calls +} + +// Schema calls SchemaFunc. +func (mock *ExecutableSchemaMock) Schema() *ast.Schema { + if mock.SchemaFunc == nil { + panic("ExecutableSchemaMock.SchemaFunc: method is nil but ExecutableSchema.Schema was just called") + } + callInfo := struct { + }{} + lockExecutableSchemaMockSchema.Lock() + mock.calls.Schema = append(mock.calls.Schema, callInfo) + lockExecutableSchemaMockSchema.Unlock() + return mock.SchemaFunc() +} + +// SchemaCalls gets all the calls that were made to Schema. +// Check the length with: +// len(mockedExecutableSchema.SchemaCalls()) +func (mock *ExecutableSchemaMock) SchemaCalls() []struct { +} { + var calls []struct { + } + lockExecutableSchemaMockSchema.RLock() + calls = mock.calls.Schema + lockExecutableSchemaMockSchema.RUnlock() + return calls +} diff --git a/vendor/github.com/99designs/gqlgen/graphql/executor/executor.go b/vendor/github.com/99designs/gqlgen/graphql/executor/executor.go new file mode 100644 index 000000000..b3163a4bd --- /dev/null +++ b/vendor/github.com/99designs/gqlgen/graphql/executor/executor.go @@ -0,0 +1,191 @@ +package executor + +import ( + "context" + + "github.com/99designs/gqlgen/graphql" + "github.com/99designs/gqlgen/graphql/errcode" + "github.com/vektah/gqlparser/v2/ast" + "github.com/vektah/gqlparser/v2/gqlerror" + "github.com/vektah/gqlparser/v2/parser" + "github.com/vektah/gqlparser/v2/validator" +) + +// Executor executes graphql queries against a schema. +type Executor struct { + es graphql.ExecutableSchema + extensions []graphql.HandlerExtension + ext extensions + + errorPresenter graphql.ErrorPresenterFunc + recoverFunc graphql.RecoverFunc + queryCache graphql.Cache +} + +var _ graphql.GraphExecutor = &Executor{} + +// New creates a new Executor with the given schema, and a default error and +// recovery callbacks, and no query cache or extensions. +func New(es graphql.ExecutableSchema) *Executor { + e := &Executor{ + es: es, + errorPresenter: graphql.DefaultErrorPresenter, + recoverFunc: graphql.DefaultRecover, + queryCache: graphql.NoCache{}, + ext: processExtensions(nil), + } + return e +} + +func (e *Executor) CreateOperationContext(ctx context.Context, params *graphql.RawParams) (*graphql.OperationContext, gqlerror.List) { + rc := &graphql.OperationContext{ + DisableIntrospection: true, + Recover: e.recoverFunc, + ResolverMiddleware: e.ext.fieldMiddleware, + Stats: graphql.Stats{ + Read: params.ReadTime, + OperationStart: graphql.GetStartTime(ctx), + }, + } + ctx = graphql.WithOperationContext(ctx, rc) + + for _, p := range e.ext.operationParameterMutators { + if err := p.MutateOperationParameters(ctx, params); err != nil { + return rc, gqlerror.List{err} + } + } + + rc.RawQuery = params.Query + rc.OperationName = params.OperationName + + var listErr gqlerror.List + rc.Doc, listErr = e.parseQuery(ctx, &rc.Stats, params.Query) + if len(listErr) != 0 { + return rc, listErr + } + + rc.Operation = rc.Doc.Operations.ForName(params.OperationName) + if rc.Operation == nil { + return rc, gqlerror.List{gqlerror.Errorf("operation %s not found", params.OperationName)} + } + + var err *gqlerror.Error + rc.Variables, err = validator.VariableValues(e.es.Schema(), rc.Operation, params.Variables) + if err != nil { + errcode.Set(err, errcode.ValidationFailed) + return rc, gqlerror.List{err} + } + rc.Stats.Validation.End = graphql.Now() + + for _, p := range e.ext.operationContextMutators { + if err := p.MutateOperationContext(ctx, rc); err != nil { + return rc, gqlerror.List{err} + } + } + + return rc, nil +} + +func (e *Executor) DispatchOperation(ctx context.Context, rc *graphql.OperationContext) (graphql.ResponseHandler, context.Context) { + ctx = graphql.WithOperationContext(ctx, rc) + + var innerCtx context.Context + res := e.ext.operationMiddleware(ctx, func(ctx context.Context) graphql.ResponseHandler { + innerCtx = ctx + + tmpResponseContext := graphql.WithResponseContext(ctx, e.errorPresenter, e.recoverFunc) + responses := e.es.Exec(tmpResponseContext) + if errs := graphql.GetErrors(tmpResponseContext); errs != nil { + return graphql.OneShot(&graphql.Response{Errors: errs}) + } + + return func(ctx context.Context) *graphql.Response { + ctx = graphql.WithResponseContext(ctx, e.errorPresenter, e.recoverFunc) + resp := e.ext.responseMiddleware(ctx, func(ctx context.Context) *graphql.Response { + resp := responses(ctx) + if resp == nil { + return nil + } + resp.Errors = append(resp.Errors, graphql.GetErrors(ctx)...) + resp.Extensions = graphql.GetExtensions(ctx) + return resp + }) + if resp == nil { + return nil + } + + return resp + } + }) + + return res, innerCtx +} + +func (e *Executor) DispatchError(ctx context.Context, list gqlerror.List) *graphql.Response { + ctx = graphql.WithResponseContext(ctx, e.errorPresenter, e.recoverFunc) + for _, gErr := range list { + graphql.AddError(ctx, gErr) + } + + resp := e.ext.responseMiddleware(ctx, func(ctx context.Context) *graphql.Response { + resp := &graphql.Response{ + Errors: list, + } + resp.Extensions = graphql.GetExtensions(ctx) + return resp + }) + + return resp +} + +func (e *Executor) PresentRecoveredError(ctx context.Context, err interface{}) *gqlerror.Error { + return e.errorPresenter(ctx, e.recoverFunc(ctx, err)) +} + +func (e *Executor) SetQueryCache(cache graphql.Cache) { + e.queryCache = cache +} + +func (e *Executor) SetErrorPresenter(f graphql.ErrorPresenterFunc) { + e.errorPresenter = f +} + +func (e *Executor) SetRecoverFunc(f graphql.RecoverFunc) { + e.recoverFunc = f +} + +// parseQuery decodes the incoming query and validates it, pulling from cache if present. +// +// NOTE: This should NOT look at variables, they will change per request. It should only parse and validate +// the raw query string. +func (e *Executor) parseQuery(ctx context.Context, stats *graphql.Stats, query string) (*ast.QueryDocument, gqlerror.List) { + stats.Parsing.Start = graphql.Now() + + if doc, ok := e.queryCache.Get(ctx, query); ok { + now := graphql.Now() + + stats.Parsing.End = now + stats.Validation.Start = now + return doc.(*ast.QueryDocument), nil + } + + doc, err := parser.ParseQuery(&ast.Source{Input: query}) + if err != nil { + errcode.Set(err, errcode.ParseFailed) + return nil, gqlerror.List{err} + } + stats.Parsing.End = graphql.Now() + + stats.Validation.Start = graphql.Now() + listErr := validator.Validate(e.es.Schema(), doc) + if len(listErr) != 0 { + for _, e := range listErr { + errcode.Set(e, errcode.ValidationFailed) + } + return nil, listErr + } + + e.queryCache.Add(ctx, query, doc) + + return doc, nil +} diff --git a/vendor/github.com/99designs/gqlgen/graphql/executor/extensions.go b/vendor/github.com/99designs/gqlgen/graphql/executor/extensions.go new file mode 100644 index 000000000..30a48ce80 --- /dev/null +++ b/vendor/github.com/99designs/gqlgen/graphql/executor/extensions.go @@ -0,0 +1,159 @@ +package executor + +import ( + "context" + "fmt" + + "github.com/99designs/gqlgen/graphql" +) + +// Use adds the given extension to this Executor. +func (e *Executor) Use(extension graphql.HandlerExtension) { + if err := extension.Validate(e.es); err != nil { + panic(err) + } + + switch extension.(type) { + case graphql.OperationParameterMutator, + graphql.OperationContextMutator, + graphql.OperationInterceptor, + graphql.FieldInterceptor, + graphql.ResponseInterceptor: + e.extensions = append(e.extensions, extension) + e.ext = processExtensions(e.extensions) + + default: + panic(fmt.Errorf("cannot Use %T as a gqlgen handler extension because it does not implement any extension hooks", extension)) + } +} + +// AroundFields is a convenience method for creating an extension that only implements field middleware +func (e *Executor) AroundFields(f graphql.FieldMiddleware) { + e.Use(aroundFieldFunc(f)) +} + +// AroundOperations is a convenience method for creating an extension that only implements operation middleware +func (e *Executor) AroundOperations(f graphql.OperationMiddleware) { + e.Use(aroundOpFunc(f)) +} + +// AroundResponses is a convenience method for creating an extension that only implements response middleware +func (e *Executor) AroundResponses(f graphql.ResponseMiddleware) { + e.Use(aroundRespFunc(f)) +} + +type extensions struct { + operationMiddleware graphql.OperationMiddleware + responseMiddleware graphql.ResponseMiddleware + fieldMiddleware graphql.FieldMiddleware + operationParameterMutators []graphql.OperationParameterMutator + operationContextMutators []graphql.OperationContextMutator +} + +func processExtensions(exts []graphql.HandlerExtension) extensions { + e := extensions{ + operationMiddleware: func(ctx context.Context, next graphql.OperationHandler) graphql.ResponseHandler { + return next(ctx) + }, + responseMiddleware: func(ctx context.Context, next graphql.ResponseHandler) *graphql.Response { + return next(ctx) + }, + fieldMiddleware: func(ctx context.Context, next graphql.Resolver) (res interface{}, err error) { + return next(ctx) + }, + } + + // this loop goes backwards so the first extension is the outer most middleware and runs first. + for i := len(exts) - 1; i >= 0; i-- { + p := exts[i] + if p, ok := p.(graphql.OperationInterceptor); ok { + previous := e.operationMiddleware + e.operationMiddleware = func(ctx context.Context, next graphql.OperationHandler) graphql.ResponseHandler { + return p.InterceptOperation(ctx, func(ctx context.Context) graphql.ResponseHandler { + return previous(ctx, next) + }) + } + } + + if p, ok := p.(graphql.ResponseInterceptor); ok { + previous := e.responseMiddleware + e.responseMiddleware = func(ctx context.Context, next graphql.ResponseHandler) *graphql.Response { + return p.InterceptResponse(ctx, func(ctx context.Context) *graphql.Response { + return previous(ctx, next) + }) + } + } + + if p, ok := p.(graphql.FieldInterceptor); ok { + previous := e.fieldMiddleware + e.fieldMiddleware = func(ctx context.Context, next graphql.Resolver) (res interface{}, err error) { + return p.InterceptField(ctx, func(ctx context.Context) (res interface{}, err error) { + return previous(ctx, next) + }) + } + } + } + + for _, p := range exts { + if p, ok := p.(graphql.OperationParameterMutator); ok { + e.operationParameterMutators = append(e.operationParameterMutators, p) + } + + if p, ok := p.(graphql.OperationContextMutator); ok { + e.operationContextMutators = append(e.operationContextMutators, p) + } + } + + return e +} + +type aroundOpFunc func(ctx context.Context, next graphql.OperationHandler) graphql.ResponseHandler + +func (r aroundOpFunc) ExtensionName() string { + return "InlineOperationFunc" +} + +func (r aroundOpFunc) Validate(schema graphql.ExecutableSchema) error { + if r == nil { + return fmt.Errorf("OperationFunc can not be nil") + } + return nil +} + +func (r aroundOpFunc) InterceptOperation(ctx context.Context, next graphql.OperationHandler) graphql.ResponseHandler { + return r(ctx, next) +} + +type aroundRespFunc func(ctx context.Context, next graphql.ResponseHandler) *graphql.Response + +func (r aroundRespFunc) ExtensionName() string { + return "InlineResponseFunc" +} + +func (r aroundRespFunc) Validate(schema graphql.ExecutableSchema) error { + if r == nil { + return fmt.Errorf("ResponseFunc can not be nil") + } + return nil +} + +func (r aroundRespFunc) InterceptResponse(ctx context.Context, next graphql.ResponseHandler) *graphql.Response { + return r(ctx, next) +} + +type aroundFieldFunc func(ctx context.Context, next graphql.Resolver) (res interface{}, err error) + +func (f aroundFieldFunc) ExtensionName() string { + return "InlineFieldFunc" +} + +func (f aroundFieldFunc) Validate(schema graphql.ExecutableSchema) error { + if f == nil { + return fmt.Errorf("FieldFunc can not be nil") + } + return nil +} + +func (f aroundFieldFunc) InterceptField(ctx context.Context, next graphql.Resolver) (res interface{}, err error) { + return f(ctx, next) +} diff --git a/vendor/github.com/99designs/gqlgen/graphql/handler.go b/vendor/github.com/99designs/gqlgen/graphql/handler.go new file mode 100644 index 000000000..e74af2f03 --- /dev/null +++ b/vendor/github.com/99designs/gqlgen/graphql/handler.go @@ -0,0 +1,123 @@ +package graphql + +import ( + "context" + "net/http" + "strconv" + "strings" + + "github.com/vektah/gqlparser/v2/gqlerror" +) + +type ( + OperationMiddleware func(ctx context.Context, next OperationHandler) ResponseHandler + OperationHandler func(ctx context.Context) ResponseHandler + + ResponseHandler func(ctx context.Context) *Response + ResponseMiddleware func(ctx context.Context, next ResponseHandler) *Response + + Resolver func(ctx context.Context) (res interface{}, err error) + FieldMiddleware func(ctx context.Context, next Resolver) (res interface{}, err error) + + RawParams struct { + Query string `json:"query"` + OperationName string `json:"operationName"` + Variables map[string]interface{} `json:"variables"` + Extensions map[string]interface{} `json:"extensions"` + + ReadTime TraceTiming `json:"-"` + } + + GraphExecutor interface { + CreateOperationContext(ctx context.Context, params *RawParams) (*OperationContext, gqlerror.List) + DispatchOperation(ctx context.Context, rc *OperationContext) (ResponseHandler, context.Context) + DispatchError(ctx context.Context, list gqlerror.List) *Response + } + + // HandlerExtension adds functionality to the http handler. See the list of possible hook points below + // Its important to understand the lifecycle of a graphql request and the terminology we use in gqlgen + // before working with these + // + // +--- REQUEST POST /graphql --------------------------------------------+ + // | +- OPERATION query OpName { viewer { name } } -----------------------+ | + // | | RESPONSE { "data": { "viewer": { "name": "bob" } } } | | + // | +- OPERATION subscription OpName2 { chat { message } } --------------+ | + // | | RESPONSE { "data": { "chat": { "message": "hello" } } } | | + // | | RESPONSE { "data": { "chat": { "message": "byee" } } } | | + // | +--------------------------------------------------------------------+ | + // +------------------------------------------------------------------------+ + HandlerExtension interface { + // ExtensionName should be a CamelCase string version of the extension which may be shown in stats and logging. + ExtensionName() string + // Validate is called when adding an extension to the server, it allows validation against the servers schema. + Validate(schema ExecutableSchema) error + } + + // OperationParameterMutator is called before creating a request context. allows manipulating the raw query + // on the way in. + OperationParameterMutator interface { + MutateOperationParameters(ctx context.Context, request *RawParams) *gqlerror.Error + } + + // OperationContextMutator is called after creating the request context, but before executing the root resolver. + OperationContextMutator interface { + MutateOperationContext(ctx context.Context, rc *OperationContext) *gqlerror.Error + } + + // OperationInterceptor is called for each incoming query, for basic requests the writer will be invoked once, + // for subscriptions it will be invoked multiple times. + OperationInterceptor interface { + InterceptOperation(ctx context.Context, next OperationHandler) ResponseHandler + } + + // ResponseInterceptor is called around each graphql operation response. This can be called many times for a single + // operation the case of subscriptions. + ResponseInterceptor interface { + InterceptResponse(ctx context.Context, next ResponseHandler) *Response + } + + // FieldInterceptor called around each field + FieldInterceptor interface { + InterceptField(ctx context.Context, next Resolver) (res interface{}, err error) + } + + // Transport provides support for different wire level encodings of graphql requests, eg Form, Get, Post, Websocket + Transport interface { + Supports(r *http.Request) bool + Do(w http.ResponseWriter, r *http.Request, exec GraphExecutor) + } +) + +type Status int + +func (p *RawParams) AddUpload(upload Upload, key, path string) *gqlerror.Error { + if !strings.HasPrefix(path, "variables.") { + return gqlerror.Errorf("invalid operations paths for key %s", key) + } + + var ptr interface{} = p.Variables + parts := strings.Split(path, ".") + + // skip the first part (variables) because we started there + for i, p := range parts[1:] { + last := i == len(parts)-2 + if ptr == nil { + return gqlerror.Errorf("path is missing \"variables.\" prefix, key: %s, path: %s", key, path) + } + if index, parseNbrErr := strconv.Atoi(p); parseNbrErr == nil { + if last { + ptr.([]interface{})[index] = upload + } else { + ptr = ptr.([]interface{})[index] + } + } else { + if last { + ptr.(map[string]interface{})[p] = upload + } else { + ptr = ptr.(map[string]interface{})[p] + } + } + } + + return nil +} diff --git a/vendor/github.com/99designs/gqlgen/graphql/handler/extension/apq.go b/vendor/github.com/99designs/gqlgen/graphql/handler/extension/apq.go new file mode 100644 index 000000000..83f4c1bfc --- /dev/null +++ b/vendor/github.com/99designs/gqlgen/graphql/handler/extension/apq.go @@ -0,0 +1,112 @@ +package extension + +import ( + "context" + "crypto/sha256" + "encoding/hex" + "fmt" + + "github.com/99designs/gqlgen/graphql/errcode" + + "github.com/vektah/gqlparser/v2/gqlerror" + + "github.com/99designs/gqlgen/graphql" + "github.com/mitchellh/mapstructure" +) + +const errPersistedQueryNotFound = "PersistedQueryNotFound" +const errPersistedQueryNotFoundCode = "PERSISTED_QUERY_NOT_FOUND" + +// AutomaticPersistedQuery saves client upload by optimistically sending only the hashes of queries, if the server +// does not yet know what the query is for the hash it will respond telling the client to send the query along with the +// hash in the next request. +// see https://github.com/apollographql/apollo-link-persisted-queries +type AutomaticPersistedQuery struct { + Cache graphql.Cache +} + +type ApqStats struct { + // The hash of the incoming query + Hash string + + // SentQuery is true if the incoming request sent the full query + SentQuery bool +} + +const apqExtension = "APQ" + +var _ interface { + graphql.OperationParameterMutator + graphql.HandlerExtension +} = AutomaticPersistedQuery{} + +func (a AutomaticPersistedQuery) ExtensionName() string { + return "AutomaticPersistedQuery" +} + +func (a AutomaticPersistedQuery) Validate(schema graphql.ExecutableSchema) error { + if a.Cache == nil { + return fmt.Errorf("AutomaticPersistedQuery.Cache can not be nil") + } + return nil +} + +func (a AutomaticPersistedQuery) MutateOperationParameters(ctx context.Context, rawParams *graphql.RawParams) *gqlerror.Error { + if rawParams.Extensions["persistedQuery"] == nil { + return nil + } + + var extension struct { + Sha256 string `mapstructure:"sha256Hash"` + Version int64 `mapstructure:"version"` + } + + if err := mapstructure.Decode(rawParams.Extensions["persistedQuery"], &extension); err != nil { + return gqlerror.Errorf("invalid APQ extension data") + } + + if extension.Version != 1 { + return gqlerror.Errorf("unsupported APQ version") + } + + fullQuery := false + if rawParams.Query == "" { + // client sent optimistic query hash without query string, get it from the cache + query, ok := a.Cache.Get(ctx, extension.Sha256) + if !ok { + err := gqlerror.Errorf(errPersistedQueryNotFound) + errcode.Set(err, errPersistedQueryNotFoundCode) + return err + } + rawParams.Query = query.(string) + } else { + // client sent optimistic query hash with query string, verify and store it + if computeQueryHash(rawParams.Query) != extension.Sha256 { + return gqlerror.Errorf("provided APQ hash does not match query") + } + a.Cache.Add(ctx, extension.Sha256, rawParams.Query) + fullQuery = true + } + + graphql.GetOperationContext(ctx).Stats.SetExtension(apqExtension, &ApqStats{ + Hash: extension.Sha256, + SentQuery: fullQuery, + }) + + return nil +} + +func GetApqStats(ctx context.Context) *ApqStats { + rc := graphql.GetOperationContext(ctx) + if rc == nil { + return nil + } + + s, _ := rc.Stats.GetExtension(apqExtension).(*ApqStats) + return s +} + +func computeQueryHash(query string) string { + b := sha256.Sum256([]byte(query)) + return hex.EncodeToString(b[:]) +} diff --git a/vendor/github.com/99designs/gqlgen/graphql/handler/extension/complexity.go b/vendor/github.com/99designs/gqlgen/graphql/handler/extension/complexity.go new file mode 100644 index 000000000..2d853802b --- /dev/null +++ b/vendor/github.com/99designs/gqlgen/graphql/handler/extension/complexity.go @@ -0,0 +1,88 @@ +package extension + +import ( + "context" + "fmt" + + "github.com/99designs/gqlgen/complexity" + "github.com/99designs/gqlgen/graphql" + "github.com/99designs/gqlgen/graphql/errcode" + "github.com/vektah/gqlparser/v2/gqlerror" +) + +const errComplexityLimit = "COMPLEXITY_LIMIT_EXCEEDED" + +// ComplexityLimit allows you to define a limit on query complexity +// +// If a query is submitted that exceeds the limit, a 422 status code will be returned. +type ComplexityLimit struct { + Func func(ctx context.Context, rc *graphql.OperationContext) int + + es graphql.ExecutableSchema +} + +var _ interface { + graphql.OperationContextMutator + graphql.HandlerExtension +} = &ComplexityLimit{} + +const complexityExtension = "ComplexityLimit" + +type ComplexityStats struct { + // The calculated complexity for this request + Complexity int + + // The complexity limit for this request returned by the extension func + ComplexityLimit int +} + +// FixedComplexityLimit sets a complexity limit that does not change +func FixedComplexityLimit(limit int) *ComplexityLimit { + return &ComplexityLimit{ + Func: func(ctx context.Context, rc *graphql.OperationContext) int { + return limit + }, + } +} + +func (c ComplexityLimit) ExtensionName() string { + return complexityExtension +} + +func (c *ComplexityLimit) Validate(schema graphql.ExecutableSchema) error { + if c.Func == nil { + return fmt.Errorf("ComplexityLimit func can not be nil") + } + c.es = schema + return nil +} + +func (c ComplexityLimit) MutateOperationContext(ctx context.Context, rc *graphql.OperationContext) *gqlerror.Error { + op := rc.Doc.Operations.ForName(rc.OperationName) + complexity := complexity.Calculate(c.es, op, rc.Variables) + + limit := c.Func(ctx, rc) + + rc.Stats.SetExtension(complexityExtension, &ComplexityStats{ + Complexity: complexity, + ComplexityLimit: limit, + }) + + if complexity > limit { + err := gqlerror.Errorf("operation has complexity %d, which exceeds the limit of %d", complexity, limit) + errcode.Set(err, errComplexityLimit) + return err + } + + return nil +} + +func GetComplexityStats(ctx context.Context) *ComplexityStats { + rc := graphql.GetOperationContext(ctx) + if rc == nil { + return nil + } + + s, _ := rc.Stats.GetExtension(complexityExtension).(*ComplexityStats) + return s +} diff --git a/vendor/github.com/99designs/gqlgen/graphql/handler/extension/introspection.go b/vendor/github.com/99designs/gqlgen/graphql/handler/extension/introspection.go new file mode 100644 index 000000000..acc5db2fb --- /dev/null +++ b/vendor/github.com/99designs/gqlgen/graphql/handler/extension/introspection.go @@ -0,0 +1,29 @@ +package extension + +import ( + "context" + + "github.com/99designs/gqlgen/graphql" + "github.com/vektah/gqlparser/v2/gqlerror" +) + +// EnableIntrospection enables clients to reflect all of the types available on the graph. +type Introspection struct{} + +var _ interface { + graphql.OperationContextMutator + graphql.HandlerExtension +} = Introspection{} + +func (c Introspection) ExtensionName() string { + return "Introspection" +} + +func (c Introspection) Validate(schema graphql.ExecutableSchema) error { + return nil +} + +func (c Introspection) MutateOperationContext(ctx context.Context, rc *graphql.OperationContext) *gqlerror.Error { + rc.DisableIntrospection = false + return nil +} diff --git a/vendor/github.com/99designs/gqlgen/graphql/handler/lru/lru.go b/vendor/github.com/99designs/gqlgen/graphql/handler/lru/lru.go new file mode 100644 index 000000000..e2b1561ac --- /dev/null +++ b/vendor/github.com/99designs/gqlgen/graphql/handler/lru/lru.go @@ -0,0 +1,32 @@ +package lru + +import ( + "context" + + "github.com/99designs/gqlgen/graphql" + lru "github.com/hashicorp/golang-lru" +) + +type LRU struct { + lru *lru.Cache +} + +var _ graphql.Cache = &LRU{} + +func New(size int) *LRU { + cache, err := lru.New(size) + if err != nil { + // An error is only returned for non-positive cache size + // and we already checked for that. + panic("unexpected error creating cache: " + err.Error()) + } + return &LRU{cache} +} + +func (l LRU) Get(ctx context.Context, key string) (value interface{}, ok bool) { + return l.lru.Get(key) +} + +func (l LRU) Add(ctx context.Context, key string, value interface{}) { + l.lru.Add(key, value) +} diff --git a/vendor/github.com/99designs/gqlgen/graphql/handler/server.go b/vendor/github.com/99designs/gqlgen/graphql/handler/server.go new file mode 100644 index 000000000..640b2781c --- /dev/null +++ b/vendor/github.com/99designs/gqlgen/graphql/handler/server.go @@ -0,0 +1,180 @@ +package handler + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/99designs/gqlgen/graphql" + "github.com/99designs/gqlgen/graphql/executor" + "github.com/99designs/gqlgen/graphql/handler/extension" + "github.com/99designs/gqlgen/graphql/handler/lru" + "github.com/99designs/gqlgen/graphql/handler/transport" + "github.com/vektah/gqlparser/v2/gqlerror" +) + +type ( + Server struct { + transports []graphql.Transport + exec *executor.Executor + } +) + +func New(es graphql.ExecutableSchema) *Server { + return &Server{ + exec: executor.New(es), + } +} + +func NewDefaultServer(es graphql.ExecutableSchema) *Server { + srv := New(es) + + srv.AddTransport(transport.Websocket{ + KeepAlivePingInterval: 10 * time.Second, + }) + srv.AddTransport(transport.Options{}) + srv.AddTransport(transport.GET{}) + srv.AddTransport(transport.POST{}) + srv.AddTransport(transport.MultipartForm{}) + + srv.SetQueryCache(lru.New(1000)) + + srv.Use(extension.Introspection{}) + srv.Use(extension.AutomaticPersistedQuery{ + Cache: lru.New(100), + }) + + return srv +} + +func (s *Server) AddTransport(transport graphql.Transport) { + s.transports = append(s.transports, transport) +} + +func (s *Server) SetErrorPresenter(f graphql.ErrorPresenterFunc) { + s.exec.SetErrorPresenter(f) +} + +func (s *Server) SetRecoverFunc(f graphql.RecoverFunc) { + s.exec.SetRecoverFunc(f) +} + +func (s *Server) SetQueryCache(cache graphql.Cache) { + s.exec.SetQueryCache(cache) +} + +func (s *Server) Use(extension graphql.HandlerExtension) { + s.exec.Use(extension) +} + +// AroundFields is a convenience method for creating an extension that only implements field middleware +func (s *Server) AroundFields(f graphql.FieldMiddleware) { + s.exec.AroundFields(f) +} + +// AroundOperations is a convenience method for creating an extension that only implements operation middleware +func (s *Server) AroundOperations(f graphql.OperationMiddleware) { + s.exec.AroundOperations(f) +} + +// AroundResponses is a convenience method for creating an extension that only implements response middleware +func (s *Server) AroundResponses(f graphql.ResponseMiddleware) { + s.exec.AroundResponses(f) +} + +func (s *Server) getTransport(r *http.Request) graphql.Transport { + for _, t := range s.transports { + if t.Supports(r) { + return t + } + } + return nil +} + +func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { + defer func() { + if err := recover(); err != nil { + err := s.exec.PresentRecoveredError(r.Context(), err) + resp := &graphql.Response{Errors: []*gqlerror.Error{err}} + b, _ := json.Marshal(resp) + w.WriteHeader(http.StatusUnprocessableEntity) + w.Write(b) + } + }() + + r = r.WithContext(graphql.StartOperationTrace(r.Context())) + + transport := s.getTransport(r) + if transport == nil { + sendErrorf(w, http.StatusBadRequest, "transport not supported") + return + } + + transport.Do(w, r, s.exec) +} + +func sendError(w http.ResponseWriter, code int, errors ...*gqlerror.Error) { + w.WriteHeader(code) + b, err := json.Marshal(&graphql.Response{Errors: errors}) + if err != nil { + panic(err) + } + w.Write(b) +} + +func sendErrorf(w http.ResponseWriter, code int, format string, args ...interface{}) { + sendError(w, code, &gqlerror.Error{Message: fmt.Sprintf(format, args...)}) +} + +type OperationFunc func(ctx context.Context, next graphql.OperationHandler) graphql.ResponseHandler + +func (r OperationFunc) ExtensionName() string { + return "InlineOperationFunc" +} + +func (r OperationFunc) Validate(schema graphql.ExecutableSchema) error { + if r == nil { + return fmt.Errorf("OperationFunc can not be nil") + } + return nil +} + +func (r OperationFunc) InterceptOperation(ctx context.Context, next graphql.OperationHandler) graphql.ResponseHandler { + return r(ctx, next) +} + +type ResponseFunc func(ctx context.Context, next graphql.ResponseHandler) *graphql.Response + +func (r ResponseFunc) ExtensionName() string { + return "InlineResponseFunc" +} + +func (r ResponseFunc) Validate(schema graphql.ExecutableSchema) error { + if r == nil { + return fmt.Errorf("ResponseFunc can not be nil") + } + return nil +} + +func (r ResponseFunc) InterceptResponse(ctx context.Context, next graphql.ResponseHandler) *graphql.Response { + return r(ctx, next) +} + +type FieldFunc func(ctx context.Context, next graphql.Resolver) (res interface{}, err error) + +func (f FieldFunc) ExtensionName() string { + return "InlineFieldFunc" +} + +func (f FieldFunc) Validate(schema graphql.ExecutableSchema) error { + if f == nil { + return fmt.Errorf("FieldFunc can not be nil") + } + return nil +} + +func (f FieldFunc) InterceptField(ctx context.Context, next graphql.Resolver) (res interface{}, err error) { + return f(ctx, next) +} diff --git a/vendor/github.com/99designs/gqlgen/graphql/handler/transport/error.go b/vendor/github.com/99designs/gqlgen/graphql/handler/transport/error.go new file mode 100644 index 000000000..b1aeaf144 --- /dev/null +++ b/vendor/github.com/99designs/gqlgen/graphql/handler/transport/error.go @@ -0,0 +1,26 @@ +package transport + +import ( + "encoding/json" + "fmt" + "net/http" + + "github.com/99designs/gqlgen/graphql" + "github.com/vektah/gqlparser/v2/gqlerror" +) + +// SendError sends a best effort error to a raw response writer. It assumes the client can understand the standard +// json error response +func SendError(w http.ResponseWriter, code int, errors ...*gqlerror.Error) { + w.WriteHeader(code) + b, err := json.Marshal(&graphql.Response{Errors: errors}) + if err != nil { + panic(err) + } + w.Write(b) +} + +// SendErrorf wraps SendError to add formatted messages +func SendErrorf(w http.ResponseWriter, code int, format string, args ...interface{}) { + SendError(w, code, &gqlerror.Error{Message: fmt.Sprintf(format, args...)}) +} diff --git a/vendor/github.com/99designs/gqlgen/graphql/handler/transport/http_form.go b/vendor/github.com/99designs/gqlgen/graphql/handler/transport/http_form.go new file mode 100644 index 000000000..4afc154bd --- /dev/null +++ b/vendor/github.com/99designs/gqlgen/graphql/handler/transport/http_form.go @@ -0,0 +1,208 @@ +package transport + +import ( + "encoding/json" + "io" + "io/ioutil" + "mime" + "net/http" + "os" + "strings" + + "github.com/99designs/gqlgen/graphql" +) + +// MultipartForm the Multipart request spec https://github.com/jaydenseric/graphql-multipart-request-spec +type MultipartForm struct { + // MaxUploadSize sets the maximum number of bytes used to parse a request body + // as multipart/form-data. + MaxUploadSize int64 + + // MaxMemory defines the maximum number of bytes used to parse a request body + // as multipart/form-data in memory, with the remainder stored on disk in + // temporary files. + MaxMemory int64 +} + +var _ graphql.Transport = MultipartForm{} + +func (f MultipartForm) Supports(r *http.Request) bool { + if r.Header.Get("Upgrade") != "" { + return false + } + + mediaType, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) + if err != nil { + return false + } + + return r.Method == "POST" && mediaType == "multipart/form-data" +} + +func (f MultipartForm) maxUploadSize() int64 { + if f.MaxUploadSize == 0 { + return 32 << 20 + } + return f.MaxUploadSize +} + +func (f MultipartForm) maxMemory() int64 { + if f.MaxMemory == 0 { + return 32 << 20 + } + return f.MaxMemory +} + +func (f MultipartForm) Do(w http.ResponseWriter, r *http.Request, exec graphql.GraphExecutor) { + w.Header().Set("Content-Type", "application/json") + + start := graphql.Now() + + var err error + if r.ContentLength > f.maxUploadSize() { + writeJsonError(w, "failed to parse multipart form, request body too large") + return + } + r.Body = http.MaxBytesReader(w, r.Body, f.maxUploadSize()) + if err = r.ParseMultipartForm(f.maxMemory()); err != nil { + w.WriteHeader(http.StatusUnprocessableEntity) + if strings.Contains(err.Error(), "request body too large") { + writeJsonError(w, "failed to parse multipart form, request body too large") + return + } + writeJsonError(w, "failed to parse multipart form") + return + } + defer r.Body.Close() + + var params graphql.RawParams + + if err = jsonDecode(strings.NewReader(r.Form.Get("operations")), ¶ms); err != nil { + w.WriteHeader(http.StatusUnprocessableEntity) + writeJsonError(w, "operations form field could not be decoded") + return + } + + var uploadsMap = map[string][]string{} + if err = json.Unmarshal([]byte(r.Form.Get("map")), &uploadsMap); err != nil { + w.WriteHeader(http.StatusUnprocessableEntity) + writeJsonError(w, "map form field could not be decoded") + return + } + + var upload graphql.Upload + for key, paths := range uploadsMap { + if len(paths) == 0 { + w.WriteHeader(http.StatusUnprocessableEntity) + writeJsonErrorf(w, "invalid empty operations paths list for key %s", key) + return + } + file, header, err := r.FormFile(key) + if err != nil { + w.WriteHeader(http.StatusUnprocessableEntity) + writeJsonErrorf(w, "failed to get key %s from form", key) + return + } + defer file.Close() + + if len(paths) == 1 { + upload = graphql.Upload{ + File: file, + Size: header.Size, + Filename: header.Filename, + ContentType: header.Header.Get("Content-Type"), + } + + if err := params.AddUpload(upload, key, paths[0]); err != nil { + w.WriteHeader(http.StatusUnprocessableEntity) + writeJsonGraphqlError(w, err) + return + } + } else { + if r.ContentLength < f.maxMemory() { + fileBytes, err := ioutil.ReadAll(file) + if err != nil { + w.WriteHeader(http.StatusUnprocessableEntity) + writeJsonErrorf(w, "failed to read file for key %s", key) + return + } + for _, path := range paths { + upload = graphql.Upload{ + File: &bytesReader{s: &fileBytes, i: 0, prevRune: -1}, + Size: header.Size, + Filename: header.Filename, + ContentType: header.Header.Get("Content-Type"), + } + + if err := params.AddUpload(upload, key, path); err != nil { + w.WriteHeader(http.StatusUnprocessableEntity) + writeJsonGraphqlError(w, err) + return + } + } + } else { + tmpFile, err := ioutil.TempFile(os.TempDir(), "gqlgen-") + if err != nil { + w.WriteHeader(http.StatusUnprocessableEntity) + writeJsonErrorf(w, "failed to create temp file for key %s", key) + return + } + tmpName := tmpFile.Name() + defer func() { + _ = os.Remove(tmpName) + }() + _, err = io.Copy(tmpFile, file) + if err != nil { + w.WriteHeader(http.StatusUnprocessableEntity) + if err := tmpFile.Close(); err != nil { + writeJsonErrorf(w, "failed to copy to temp file and close temp file for key %s", key) + return + } + writeJsonErrorf(w, "failed to copy to temp file for key %s", key) + return + } + if err := tmpFile.Close(); err != nil { + w.WriteHeader(http.StatusUnprocessableEntity) + writeJsonErrorf(w, "failed to close temp file for key %s", key) + return + } + for _, path := range paths { + pathTmpFile, err := os.Open(tmpName) + if err != nil { + w.WriteHeader(http.StatusUnprocessableEntity) + writeJsonErrorf(w, "failed to open temp file for key %s", key) + return + } + defer pathTmpFile.Close() + upload = graphql.Upload{ + File: pathTmpFile, + Size: header.Size, + Filename: header.Filename, + ContentType: header.Header.Get("Content-Type"), + } + + if err := params.AddUpload(upload, key, path); err != nil { + w.WriteHeader(http.StatusUnprocessableEntity) + writeJsonGraphqlError(w, err) + return + } + } + } + } + } + + params.ReadTime = graphql.TraceTiming{ + Start: start, + End: graphql.Now(), + } + + rc, gerr := exec.CreateOperationContext(r.Context(), ¶ms) + if gerr != nil { + resp := exec.DispatchError(graphql.WithOperationContext(r.Context(), rc), gerr) + w.WriteHeader(statusFor(gerr)) + writeJson(w, resp) + return + } + responses, ctx := exec.DispatchOperation(r.Context(), rc) + writeJson(w, responses(ctx)) +} diff --git a/vendor/github.com/99designs/gqlgen/graphql/handler/transport/http_get.go b/vendor/github.com/99designs/gqlgen/graphql/handler/transport/http_get.go new file mode 100644 index 000000000..d97c89c63 --- /dev/null +++ b/vendor/github.com/99designs/gqlgen/graphql/handler/transport/http_get.go @@ -0,0 +1,87 @@ +package transport + +import ( + "encoding/json" + "io" + "net/http" + "strings" + + "github.com/99designs/gqlgen/graphql" + "github.com/99designs/gqlgen/graphql/errcode" + "github.com/vektah/gqlparser/v2/ast" + "github.com/vektah/gqlparser/v2/gqlerror" +) + +// GET implements the GET side of the default HTTP transport +// defined in https://github.com/APIs-guru/graphql-over-http#get +type GET struct{} + +var _ graphql.Transport = GET{} + +func (h GET) Supports(r *http.Request) bool { + if r.Header.Get("Upgrade") != "" { + return false + } + + return r.Method == "GET" +} + +func (h GET) Do(w http.ResponseWriter, r *http.Request, exec graphql.GraphExecutor) { + w.Header().Set("Content-Type", "application/json") + + raw := &graphql.RawParams{ + Query: r.URL.Query().Get("query"), + OperationName: r.URL.Query().Get("operationName"), + } + raw.ReadTime.Start = graphql.Now() + + if variables := r.URL.Query().Get("variables"); variables != "" { + if err := jsonDecode(strings.NewReader(variables), &raw.Variables); err != nil { + w.WriteHeader(http.StatusBadRequest) + writeJsonError(w, "variables could not be decoded") + return + } + } + + if extensions := r.URL.Query().Get("extensions"); extensions != "" { + if err := jsonDecode(strings.NewReader(extensions), &raw.Extensions); err != nil { + w.WriteHeader(http.StatusBadRequest) + writeJsonError(w, "extensions could not be decoded") + return + } + } + + raw.ReadTime.End = graphql.Now() + + rc, err := exec.CreateOperationContext(r.Context(), raw) + if err != nil { + w.WriteHeader(statusFor(err)) + resp := exec.DispatchError(graphql.WithOperationContext(r.Context(), rc), err) + writeJson(w, resp) + return + } + op := rc.Doc.Operations.ForName(rc.OperationName) + if op.Operation != ast.Query { + w.WriteHeader(http.StatusNotAcceptable) + writeJsonError(w, "GET requests only allow query operations") + return + } + + responses, ctx := exec.DispatchOperation(r.Context(), rc) + writeJson(w, responses(ctx)) +} + +func jsonDecode(r io.Reader, val interface{}) error { + dec := json.NewDecoder(r) + dec.UseNumber() + return dec.Decode(val) +} + +func statusFor(errs gqlerror.List) int { + switch errcode.GetErrorKind(errs) { + case errcode.KindProtocol: + return http.StatusUnprocessableEntity + default: + return http.StatusOK + } +} diff --git a/vendor/github.com/99designs/gqlgen/graphql/handler/transport/http_post.go b/vendor/github.com/99designs/gqlgen/graphql/handler/transport/http_post.go new file mode 100644 index 000000000..70d971ac8 --- /dev/null +++ b/vendor/github.com/99designs/gqlgen/graphql/handler/transport/http_post.go @@ -0,0 +1,54 @@ +package transport + +import ( + "mime" + "net/http" + + "github.com/99designs/gqlgen/graphql" +) + +// POST implements the POST side of the default HTTP transport +// defined in https://github.com/APIs-guru/graphql-over-http#post +type POST struct{} + +var _ graphql.Transport = POST{} + +func (h POST) Supports(r *http.Request) bool { + if r.Header.Get("Upgrade") != "" { + return false + } + + mediaType, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) + if err != nil { + return false + } + + return r.Method == "POST" && mediaType == "application/json" +} + +func (h POST) Do(w http.ResponseWriter, r *http.Request, exec graphql.GraphExecutor) { + w.Header().Set("Content-Type", "application/json") + + var params *graphql.RawParams + start := graphql.Now() + if err := jsonDecode(r.Body, ¶ms); err != nil { + w.WriteHeader(http.StatusBadRequest) + writeJsonErrorf(w, "json body could not be decoded: "+err.Error()) + return + } + params.ReadTime = graphql.TraceTiming{ + Start: start, + End: graphql.Now(), + } + + rc, err := exec.CreateOperationContext(r.Context(), params) + if err != nil { + w.WriteHeader(statusFor(err)) + resp := exec.DispatchError(graphql.WithOperationContext(r.Context(), rc), err) + writeJson(w, resp) + return + } + ctx := graphql.WithOperationContext(r.Context(), rc) + responses, ctx := exec.DispatchOperation(ctx, rc) + writeJson(w, responses(ctx)) +} diff --git a/vendor/github.com/99designs/gqlgen/graphql/handler/transport/options.go b/vendor/github.com/99designs/gqlgen/graphql/handler/transport/options.go new file mode 100644 index 000000000..674a00c7f --- /dev/null +++ b/vendor/github.com/99designs/gqlgen/graphql/handler/transport/options.go @@ -0,0 +1,26 @@ +package transport + +import ( + "net/http" + + "github.com/99designs/gqlgen/graphql" +) + +// Options responds to http OPTIONS and HEAD requests +type Options struct{} + +var _ graphql.Transport = Options{} + +func (o Options) Supports(r *http.Request) bool { + return r.Method == "HEAD" || r.Method == "OPTIONS" +} + +func (o Options) Do(w http.ResponseWriter, r *http.Request, exec graphql.GraphExecutor) { + switch r.Method { + case http.MethodOptions: + w.WriteHeader(http.StatusOK) + w.Header().Set("Allow", "OPTIONS, GET, POST") + case http.MethodHead: + w.WriteHeader(http.StatusMethodNotAllowed) + } +} diff --git a/vendor/github.com/99designs/gqlgen/graphql/handler/transport/reader.go b/vendor/github.com/99designs/gqlgen/graphql/handler/transport/reader.go new file mode 100644 index 000000000..d3261e283 --- /dev/null +++ b/vendor/github.com/99designs/gqlgen/graphql/handler/transport/reader.go @@ -0,0 +1,25 @@ +package transport + +import ( + "errors" + "io" +) + +type bytesReader struct { + s *[]byte + i int64 // current reading index + prevRune int // index of previous rune; or < 0 +} + +func (r *bytesReader) Read(b []byte) (n int, err error) { + if r.s == nil { + return 0, errors.New("byte slice pointer is nil") + } + if r.i >= int64(len(*r.s)) { + return 0, io.EOF + } + r.prevRune = -1 + n = copy(b, (*r.s)[r.i:]) + r.i += int64(n) + return +} diff --git a/vendor/github.com/99designs/gqlgen/graphql/handler/transport/util.go b/vendor/github.com/99designs/gqlgen/graphql/handler/transport/util.go new file mode 100644 index 000000000..ce845c196 --- /dev/null +++ b/vendor/github.com/99designs/gqlgen/graphql/handler/transport/util.go @@ -0,0 +1,30 @@ +package transport + +import ( + "encoding/json" + "fmt" + "io" + + "github.com/99designs/gqlgen/graphql" + "github.com/vektah/gqlparser/v2/gqlerror" +) + +func writeJson(w io.Writer, response *graphql.Response) { + b, err := json.Marshal(response) + if err != nil { + panic(err) + } + w.Write(b) +} + +func writeJsonError(w io.Writer, msg string) { + writeJson(w, &graphql.Response{Errors: gqlerror.List{{Message: msg}}}) +} + +func writeJsonErrorf(w io.Writer, format string, args ...interface{}) { + writeJson(w, &graphql.Response{Errors: gqlerror.List{{Message: fmt.Sprintf(format, args...)}}}) +} + +func writeJsonGraphqlError(w io.Writer, err ...*gqlerror.Error) { + writeJson(w, &graphql.Response{Errors: err}) +} diff --git a/vendor/github.com/99designs/gqlgen/handler/websocket.go b/vendor/github.com/99designs/gqlgen/graphql/handler/transport/websocket.go similarity index 56% rename from vendor/github.com/99designs/gqlgen/handler/websocket.go rename to vendor/github.com/99designs/gqlgen/graphql/handler/transport/websocket.go index 58f38e5d4..3089a8779 100644 --- a/vendor/github.com/99designs/gqlgen/handler/websocket.go +++ b/vendor/github.com/99designs/gqlgen/graphql/handler/transport/websocket.go @@ -1,4 +1,4 @@ -package handler +package transport import ( "bytes" @@ -11,12 +11,9 @@ import ( "time" "github.com/99designs/gqlgen/graphql" + "github.com/99designs/gqlgen/graphql/errcode" "github.com/gorilla/websocket" - "github.com/hashicorp/golang-lru" - "github.com/vektah/gqlparser" - "github.com/vektah/gqlparser/ast" - "github.com/vektah/gqlparser/gqlerror" - "github.com/vektah/gqlparser/validator" + "github.com/vektah/gqlparser/v2/gqlerror" ) const ( @@ -32,42 +29,53 @@ const ( connectionKeepAliveMsg = "ka" // Server -> Client ) -type operationMessage struct { - Payload json.RawMessage `json:"payload,omitempty"` - ID string `json:"id,omitempty"` - Type string `json:"type"` +type ( + Websocket struct { + Upgrader websocket.Upgrader + InitFunc WebsocketInitFunc + KeepAlivePingInterval time.Duration + } + wsConnection struct { + Websocket + ctx context.Context + conn *websocket.Conn + active map[string]context.CancelFunc + mu sync.Mutex + keepAliveTicker *time.Ticker + exec graphql.GraphExecutor + + initPayload InitPayload + } + operationMessage struct { + Payload json.RawMessage `json:"payload,omitempty"` + ID string `json:"id,omitempty"` + Type string `json:"type"` + } + WebsocketInitFunc func(ctx context.Context, initPayload InitPayload) (context.Context, error) +) + +var _ graphql.Transport = Websocket{} + +func (t Websocket) Supports(r *http.Request) bool { + return r.Header.Get("Upgrade") != "" } -type wsConnection struct { - ctx context.Context - conn *websocket.Conn - exec graphql.ExecutableSchema - active map[string]context.CancelFunc - mu sync.Mutex - cfg *Config - cache *lru.Cache - keepAliveTicker *time.Ticker - - initPayload InitPayload -} - -func connectWs(exec graphql.ExecutableSchema, w http.ResponseWriter, r *http.Request, cfg *Config, cache *lru.Cache) { - ws, err := cfg.upgrader.Upgrade(w, r, http.Header{ +func (t Websocket) Do(w http.ResponseWriter, r *http.Request, exec graphql.GraphExecutor) { + ws, err := t.Upgrader.Upgrade(w, r, http.Header{ "Sec-Websocket-Protocol": []string{"graphql-ws"}, }) if err != nil { log.Printf("unable to upgrade %T to websocket %s: ", w, err.Error()) - sendErrorf(w, http.StatusBadRequest, "unable to upgrade") + SendErrorf(w, http.StatusBadRequest, "unable to upgrade") return } conn := wsConnection{ - active: map[string]context.CancelFunc{}, - exec: exec, - conn: ws, - ctx: r.Context(), - cfg: cfg, - cache: cache, + active: map[string]context.CancelFunc{}, + conn: ws, + ctx: r.Context(), + exec: exec, + Websocket: t, } if !conn.init() { @@ -94,7 +102,18 @@ func (c *wsConnection) init() bool { } } + if c.InitFunc != nil { + ctx, err := c.InitFunc(c.ctx, c.initPayload) + if err != nil { + c.sendConnectionError(err.Error()) + c.close(websocket.CloseNormalClosure, "terminated") + return false + } + c.ctx = ctx + } + c.write(&operationMessage{Type: connectionAckMsg}) + c.write(&operationMessage{Type: connectionKeepAliveMsg}) case connectionTerminateMsg: c.close(websocket.CloseNormalClosure, "terminated") return false @@ -117,18 +136,22 @@ func (c *wsConnection) run() { // We create a cancellation that will shutdown the keep-alive when we leave // this function. ctx, cancel := context.WithCancel(c.ctx) - defer cancel() + defer func() { + cancel() + c.close(websocket.CloseAbnormalClosure, "unexpected closure") + }() // Create a timer that will fire every interval to keep the connection alive. - if c.cfg.connectionKeepAlivePingInterval != 0 { + if c.KeepAlivePingInterval != 0 { c.mu.Lock() - c.keepAliveTicker = time.NewTicker(c.cfg.connectionKeepAlivePingInterval) + c.keepAliveTicker = time.NewTicker(c.KeepAlivePingInterval) c.mu.Unlock() go c.keepAlive(ctx) } for { + start := graphql.Now() message := c.readOp() if message == nil { return @@ -136,19 +159,14 @@ func (c *wsConnection) run() { switch message.Type { case startMsg: - if !c.subscribe(message) { - return - } + c.subscribe(start, message) case stopMsg: c.mu.Lock() closer := c.active[message.ID] c.mu.Unlock() - if closer == nil { - c.sendError(message.ID, gqlerror.Errorf("%s is not running, cannot stop", message.ID)) - continue + if closer != nil { + closer() } - - closer() case connectionTerminateMsg: c.close(websocket.CloseNormalClosure, "terminated") return @@ -172,108 +190,90 @@ func (c *wsConnection) keepAlive(ctx context.Context) { } } -func (c *wsConnection) subscribe(message *operationMessage) bool { - var reqParams params - if err := jsonDecode(bytes.NewReader(message.Payload), &reqParams); err != nil { - c.sendConnectionError("invalid json") - return false +func (c *wsConnection) subscribe(start time.Time, message *operationMessage) { + ctx := graphql.StartOperationTrace(c.ctx) + var params *graphql.RawParams + if err := jsonDecode(bytes.NewReader(message.Payload), ¶ms); err != nil { + c.sendError(message.ID, &gqlerror.Error{Message: "invalid json"}) + c.complete(message.ID) + return } - var ( - doc *ast.QueryDocument - cacheHit bool - ) - if c.cache != nil { - val, ok := c.cache.Get(reqParams.Query) - if ok { - doc = val.(*ast.QueryDocument) - cacheHit = true - } - } - if !cacheHit { - var qErr gqlerror.List - doc, qErr = gqlparser.LoadQuery(c.exec.Schema(), reqParams.Query) - if qErr != nil { - c.sendError(message.ID, qErr...) - return true - } - if c.cache != nil { - c.cache.Add(reqParams.Query, doc) - } + params.ReadTime = graphql.TraceTiming{ + Start: start, + End: graphql.Now(), } - op := doc.Operations.ForName(reqParams.OperationName) - if op == nil { - c.sendError(message.ID, gqlerror.Errorf("operation %s not found", reqParams.OperationName)) - return true - } - - vars, err := validator.VariableValues(c.exec.Schema(), op, reqParams.Variables) + rc, err := c.exec.CreateOperationContext(ctx, params) if err != nil { - c.sendError(message.ID, err) - return true + resp := c.exec.DispatchError(graphql.WithOperationContext(ctx, rc), err) + switch errcode.GetErrorKind(err) { + case errcode.KindProtocol: + c.sendError(message.ID, resp.Errors...) + default: + c.sendResponse(message.ID, &graphql.Response{Errors: err}) + } + + c.complete(message.ID) + return } - reqCtx := c.cfg.newRequestContext(c.exec, doc, op, reqParams.Query, vars) - ctx := graphql.WithRequestContext(c.ctx, reqCtx) + + ctx = graphql.WithOperationContext(ctx, rc) if c.initPayload != nil { ctx = withInitPayload(ctx, c.initPayload) } - if op.Operation != ast.Subscription { - var result *graphql.Response - if op.Operation == ast.Query { - result = c.exec.Query(ctx, op) - } else { - result = c.exec.Mutation(ctx, op) - } - - c.sendData(message.ID, result) - c.write(&operationMessage{ID: message.ID, Type: completeMsg}) - return true - } - ctx, cancel := context.WithCancel(ctx) c.mu.Lock() c.active[message.ID] = cancel c.mu.Unlock() + go func() { defer func() { if r := recover(); r != nil { - userErr := reqCtx.Recover(ctx, r) + userErr := rc.Recover(ctx, r) c.sendError(message.ID, &gqlerror.Error{Message: userErr.Error()}) } }() - next := c.exec.Subscription(ctx, op) - for result := next(); result != nil; result = next() { - c.sendData(message.ID, result) - } + responses, ctx := c.exec.DispatchOperation(ctx, rc) + for { + response := responses(ctx) + if response == nil { + break + } - c.write(&operationMessage{ID: message.ID, Type: completeMsg}) + c.sendResponse(message.ID, response) + } + c.complete(message.ID) c.mu.Lock() delete(c.active, message.ID) c.mu.Unlock() cancel() }() - - return true } -func (c *wsConnection) sendData(id string, response *graphql.Response) { +func (c *wsConnection) sendResponse(id string, response *graphql.Response) { b, err := json.Marshal(response) if err != nil { - c.sendError(id, gqlerror.Errorf("unable to encode json response: %s", err.Error())) - return + panic(err) } + c.write(&operationMessage{ + Payload: b, + ID: id, + Type: dataMsg, + }) +} - c.write(&operationMessage{Type: dataMsg, ID: id, Payload: b}) +func (c *wsConnection) complete(id string) { + c.write(&operationMessage{ID: id, Type: completeMsg}) } func (c *wsConnection) sendError(id string, errors ...*gqlerror.Error) { - var errs []error - for _, err := range errors { - errs = append(errs, err) + errs := make([]error, len(errors)) + for i, err := range errors { + errs[i] = err } b, err := json.Marshal(errs) if err != nil { @@ -293,8 +293,10 @@ func (c *wsConnection) sendConnectionError(format string, args ...interface{}) { func (c *wsConnection) readOp() *operationMessage { _, r, err := c.conn.NextReader() - if err != nil { - c.sendConnectionError("invalid json") + if websocket.IsCloseError(err, websocket.CloseNormalClosure, websocket.CloseNoStatusReceived) { + return nil + } else if err != nil { + c.sendConnectionError("invalid json: %T %s", err, err.Error()) return nil } message := operationMessage{} diff --git a/vendor/github.com/99designs/gqlgen/handler/context.go b/vendor/github.com/99designs/gqlgen/graphql/handler/transport/websocket_init.go similarity index 78% rename from vendor/github.com/99designs/gqlgen/handler/context.go rename to vendor/github.com/99designs/gqlgen/graphql/handler/transport/websocket_init.go index 2992aa3d4..a5f84ba2d 100644 --- a/vendor/github.com/99designs/gqlgen/handler/context.go +++ b/vendor/github.com/99designs/gqlgen/graphql/handler/transport/websocket_init.go @@ -1,4 +1,4 @@ -package handler +package transport import "context" @@ -14,12 +14,12 @@ type InitPayload map[string]interface{} // GetString safely gets a string value from the payload. It returns an empty string if the // payload is nil or the value isn't set. -func (payload InitPayload) GetString(key string) string { - if payload == nil { +func (p InitPayload) GetString(key string) string { + if p == nil { return "" } - if value, ok := payload[key]; ok { + if value, ok := p[key]; ok { res, _ := value.(string) return res } @@ -29,12 +29,12 @@ func (payload InitPayload) GetString(key string) string { // Authorization is a short hand for getting the Authorization header from the // payload. -func (payload InitPayload) Authorization() string { - if value := payload.GetString("Authorization"); value != "" { +func (p InitPayload) Authorization() string { + if value := p.GetString("Authorization"); value != "" { return value } - if value := payload.GetString("authorization"); value != "" { + if value := p.GetString("authorization"); value != "" { return value } diff --git a/vendor/github.com/99designs/gqlgen/graphql/id.go b/vendor/github.com/99designs/gqlgen/graphql/id.go index 4f532037d..2e78a5ec4 100644 --- a/vendor/github.com/99designs/gqlgen/graphql/id.go +++ b/vendor/github.com/99designs/gqlgen/graphql/id.go @@ -20,6 +20,8 @@ func UnmarshalID(v interface{}) (string, error) { return string(v), nil case int: return strconv.Itoa(v), nil + case int64: + return strconv.FormatInt(v, 10), nil case float64: return fmt.Sprintf("%f", v), nil case bool: diff --git a/vendor/github.com/99designs/gqlgen/graphql/introspection/introspection.go b/vendor/github.com/99designs/gqlgen/graphql/introspection/introspection.go index ca0b065f8..5239c9283 100644 --- a/vendor/github.com/99designs/gqlgen/graphql/introspection/introspection.go +++ b/vendor/github.com/99designs/gqlgen/graphql/introspection/introspection.go @@ -1,7 +1,7 @@ // introspection implements the spec defined in https://github.com/facebook/graphql/blob/master/spec/Section%204%20--%20Introspection.md#schema-introspection package introspection -import "github.com/vektah/gqlparser/ast" +import "github.com/vektah/gqlparser/v2/ast" type ( Directive struct { diff --git a/vendor/github.com/99designs/gqlgen/graphql/introspection/schema.go b/vendor/github.com/99designs/gqlgen/graphql/introspection/schema.go index b5d2c4822..044e91d6e 100644 --- a/vendor/github.com/99designs/gqlgen/graphql/introspection/schema.go +++ b/vendor/github.com/99designs/gqlgen/graphql/introspection/schema.go @@ -3,7 +3,7 @@ package introspection import ( "strings" - "github.com/vektah/gqlparser/ast" + "github.com/vektah/gqlparser/v2/ast" ) type Schema struct { @@ -11,7 +11,7 @@ type Schema struct { } func (s *Schema) Types() []Type { - var types []Type + types := make([]Type, 0, len(s.schema.Types)) for _, typ := range s.schema.Types { if strings.HasPrefix(typ.Name, "__") { continue @@ -34,7 +34,7 @@ func (s *Schema) SubscriptionType() *Type { } func (s *Schema) Directives() []Directive { - var res []Directive + res := make([]Directive, 0, len(s.schema.Directives)) for _, d := range s.schema.Directives { res = append(res, s.directiveFromDef(d)) @@ -44,19 +44,19 @@ func (s *Schema) Directives() []Directive { } func (s *Schema) directiveFromDef(d *ast.DirectiveDefinition) Directive { - var locs []string - for _, loc := range d.Locations { - locs = append(locs, string(loc)) + locs := make([]string, len(d.Locations)) + for i, loc := range d.Locations { + locs[i] = string(loc) } - var args []InputValue - for _, arg := range d.Arguments { - args = append(args, InputValue{ + args := make([]InputValue, len(d.Arguments)) + for i, arg := range d.Arguments { + args[i] = InputValue{ Name: arg.Name, Description: arg.Description, DefaultValue: defaultValue(arg.DefaultValue), Type: WrapTypeFromType(s.schema, arg.Type), - }) + } } return Directive{ diff --git a/vendor/github.com/99designs/gqlgen/graphql/introspection/type.go b/vendor/github.com/99designs/gqlgen/graphql/introspection/type.go index 9aceebdc9..f842fa645 100644 --- a/vendor/github.com/99designs/gqlgen/graphql/introspection/type.go +++ b/vendor/github.com/99designs/gqlgen/graphql/introspection/type.go @@ -3,7 +3,7 @@ package introspection import ( "strings" - "github.com/vektah/gqlparser/ast" + "github.com/vektah/gqlparser/v2/ast" ) type Type struct { @@ -152,6 +152,10 @@ func (t *Type) EnumValues(includeDeprecated bool) []EnumValue { res := []EnumValue{} for _, val := range t.def.EnumValues { + if !includeDeprecated && val.Directives.ForName("deprecated") != nil { + continue + } + res = append(res, EnumValue{ Name: val.Name, Description: val.Description, diff --git a/vendor/github.com/99designs/gqlgen/graphql/oneshot.go b/vendor/github.com/99designs/gqlgen/graphql/oneshot.go index dd31f5baa..01fa15f89 100644 --- a/vendor/github.com/99designs/gqlgen/graphql/oneshot.go +++ b/vendor/github.com/99designs/gqlgen/graphql/oneshot.go @@ -1,9 +1,11 @@ package graphql -func OneShot(resp *Response) func() *Response { +import "context" + +func OneShot(resp *Response) ResponseHandler { var oneshot bool - return func() *Response { + return func(context context.Context) *Response { if oneshot { return nil } diff --git a/vendor/github.com/99designs/gqlgen/handler/playground.go b/vendor/github.com/99designs/gqlgen/graphql/playground/playground.go similarity index 92% rename from vendor/github.com/99designs/gqlgen/handler/playground.go rename to vendor/github.com/99designs/gqlgen/graphql/playground/playground.go index 0e1ca7686..45bbbd4f1 100644 --- a/vendor/github.com/99designs/gqlgen/handler/playground.go +++ b/vendor/github.com/99designs/gqlgen/graphql/playground/playground.go @@ -1,4 +1,4 @@ -package handler +package playground import ( "html/template" @@ -11,7 +11,7 @@ var page = template.Must(template.New("graphiql").Parse(` - @@ -33,6 +33,7 @@ var page = template.Must(template.New("graphiql").Parse(` GraphQLPlayground.init(root, { endpoint: location.protocol + '//' + location.host + '{{.endpoint}}', subscriptionsEndpoint: wsProto + '//' + location.host + '{{.endpoint }}', + shareEnabled: true, settings: { 'request.credentials': 'same-origin' } @@ -43,7 +44,7 @@ var page = template.Must(template.New("graphiql").Parse(` `)) -func Playground(title string, endpoint string) http.HandlerFunc { +func Handler(title string, endpoint string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-Type", "text/html") err := page.Execute(w, map[string]string{ diff --git a/vendor/github.com/99designs/gqlgen/graphql/response.go b/vendor/github.com/99designs/gqlgen/graphql/response.go index 6fe55d56d..0d36049a3 100644 --- a/vendor/github.com/99designs/gqlgen/graphql/response.go +++ b/vendor/github.com/99designs/gqlgen/graphql/response.go @@ -5,7 +5,7 @@ import ( "encoding/json" "fmt" - "github.com/vektah/gqlparser/gqlerror" + "github.com/vektah/gqlparser/v2/gqlerror" ) // Errors are intentionally serialized first based on the advice in diff --git a/vendor/github.com/99designs/gqlgen/graphql/stats.go b/vendor/github.com/99designs/gqlgen/graphql/stats.go new file mode 100644 index 000000000..a52e143eb --- /dev/null +++ b/vendor/github.com/99designs/gqlgen/graphql/stats.go @@ -0,0 +1,60 @@ +package graphql + +import ( + "context" + "fmt" + "time" +) + +type Stats struct { + OperationStart time.Time + Read TraceTiming + Parsing TraceTiming + Validation TraceTiming + + // Stats collected by handler extensions. Dont use directly, the extension should provide a type safe way to + // access this. + extension map[string]interface{} +} + +type TraceTiming struct { + Start time.Time + End time.Time +} + +var ctxTraceStart key = "trace_start" + +// StartOperationTrace captures the current time and stores it in context. This will eventually be added to request +// context but we want to grab it as soon as possible. For transports that can only handle a single graphql query +// per http requests you dont need to call this at all, the server will do it for you. For transports that handle +// multiple (eg batching, subscriptions) this should be called before decoding each request. +func StartOperationTrace(ctx context.Context) context.Context { + return context.WithValue(ctx, ctxTraceStart, Now()) +} + +// GetStartTime should only be called by the handler package, it will be set into request context +// as Stats.Start +func GetStartTime(ctx context.Context) time.Time { + t, ok := ctx.Value(ctxTraceStart).(time.Time) + if !ok { + panic(fmt.Sprintf("missing start time: %T", ctx.Value(ctxTraceStart))) + } + return t +} + +func (c *Stats) SetExtension(name string, data interface{}) { + if c.extension == nil { + c.extension = map[string]interface{}{} + } + c.extension[name] = data +} + +func (c *Stats) GetExtension(name string) interface{} { + if c.extension == nil { + return nil + } + return c.extension[name] +} + +// Now is time.Now, except in tests. Then it can be whatever you want it to be. +var Now = time.Now diff --git a/vendor/github.com/99designs/gqlgen/graphql/tracer.go b/vendor/github.com/99designs/gqlgen/graphql/tracer.go deleted file mode 100644 index 0597ce8cc..000000000 --- a/vendor/github.com/99designs/gqlgen/graphql/tracer.go +++ /dev/null @@ -1,58 +0,0 @@ -package graphql - -import ( - "context" -) - -var _ Tracer = (*NopTracer)(nil) - -type Tracer interface { - StartOperationParsing(ctx context.Context) context.Context - EndOperationParsing(ctx context.Context) - StartOperationValidation(ctx context.Context) context.Context - EndOperationValidation(ctx context.Context) - StartOperationExecution(ctx context.Context) context.Context - StartFieldExecution(ctx context.Context, field CollectedField) context.Context - StartFieldResolverExecution(ctx context.Context, rc *ResolverContext) context.Context - StartFieldChildExecution(ctx context.Context) context.Context - EndFieldExecution(ctx context.Context) - EndOperationExecution(ctx context.Context) -} - -type NopTracer struct{} - -func (NopTracer) StartOperationParsing(ctx context.Context) context.Context { - return ctx -} - -func (NopTracer) EndOperationParsing(ctx context.Context) { -} - -func (NopTracer) StartOperationValidation(ctx context.Context) context.Context { - return ctx -} - -func (NopTracer) EndOperationValidation(ctx context.Context) { -} - -func (NopTracer) StartOperationExecution(ctx context.Context) context.Context { - return ctx -} - -func (NopTracer) StartFieldExecution(ctx context.Context, field CollectedField) context.Context { - return ctx -} - -func (NopTracer) StartFieldResolverExecution(ctx context.Context, rc *ResolverContext) context.Context { - return ctx -} - -func (NopTracer) StartFieldChildExecution(ctx context.Context) context.Context { - return ctx -} - -func (NopTracer) EndFieldExecution(ctx context.Context) { -} - -func (NopTracer) EndOperationExecution(ctx context.Context) { -} diff --git a/vendor/github.com/99designs/gqlgen/graphql/upload.go b/vendor/github.com/99designs/gqlgen/graphql/upload.go index 22d610314..62f71c0dc 100644 --- a/vendor/github.com/99designs/gqlgen/graphql/upload.go +++ b/vendor/github.com/99designs/gqlgen/graphql/upload.go @@ -6,9 +6,10 @@ import ( ) type Upload struct { - File io.Reader - Filename string - Size int64 + File io.Reader + Filename string + Size int64 + ContentType string } func MarshalUpload(f Upload) Marshaler { diff --git a/vendor/github.com/99designs/gqlgen/graphql/version.go b/vendor/github.com/99designs/gqlgen/graphql/version.go index 11dc6b019..49954bbb6 100644 --- a/vendor/github.com/99designs/gqlgen/graphql/version.go +++ b/vendor/github.com/99designs/gqlgen/graphql/version.go @@ -1,3 +1,3 @@ package graphql -const Version = "v0.9.0" +const Version = "v0.12.2" diff --git a/vendor/github.com/99designs/gqlgen/handler/graphql.go b/vendor/github.com/99designs/gqlgen/handler/graphql.go deleted file mode 100644 index a22542225..000000000 --- a/vendor/github.com/99designs/gqlgen/handler/graphql.go +++ /dev/null @@ -1,709 +0,0 @@ -package handler - -import ( - "context" - "encoding/json" - "errors" - "fmt" - "io" - "io/ioutil" - "mime" - "net/http" - "os" - "strconv" - "strings" - "time" - - "github.com/99designs/gqlgen/complexity" - "github.com/99designs/gqlgen/graphql" - "github.com/gorilla/websocket" - lru "github.com/hashicorp/golang-lru" - "github.com/vektah/gqlparser/ast" - "github.com/vektah/gqlparser/gqlerror" - "github.com/vektah/gqlparser/parser" - "github.com/vektah/gqlparser/validator" -) - -type params struct { - Query string `json:"query"` - OperationName string `json:"operationName"` - Variables map[string]interface{} `json:"variables"` -} - -type Config struct { - cacheSize int - upgrader websocket.Upgrader - recover graphql.RecoverFunc - errorPresenter graphql.ErrorPresenterFunc - resolverHook graphql.FieldMiddleware - requestHook graphql.RequestMiddleware - tracer graphql.Tracer - complexityLimit int - complexityLimitFunc graphql.ComplexityLimitFunc - disableIntrospection bool - connectionKeepAlivePingInterval time.Duration - uploadMaxMemory int64 - uploadMaxSize int64 -} - -func (c *Config) newRequestContext(es graphql.ExecutableSchema, doc *ast.QueryDocument, op *ast.OperationDefinition, query string, variables map[string]interface{}) *graphql.RequestContext { - reqCtx := graphql.NewRequestContext(doc, query, variables) - reqCtx.DisableIntrospection = c.disableIntrospection - - if hook := c.recover; hook != nil { - reqCtx.Recover = hook - } - - if hook := c.errorPresenter; hook != nil { - reqCtx.ErrorPresenter = hook - } - - if hook := c.resolverHook; hook != nil { - reqCtx.ResolverMiddleware = hook - } - - if hook := c.requestHook; hook != nil { - reqCtx.RequestMiddleware = hook - } - - if hook := c.tracer; hook != nil { - reqCtx.Tracer = hook - } - - if c.complexityLimit > 0 || c.complexityLimitFunc != nil { - reqCtx.ComplexityLimit = c.complexityLimit - operationComplexity := complexity.Calculate(es, op, variables) - reqCtx.OperationComplexity = operationComplexity - } - - return reqCtx -} - -type Option func(cfg *Config) - -func WebsocketUpgrader(upgrader websocket.Upgrader) Option { - return func(cfg *Config) { - cfg.upgrader = upgrader - } -} - -func RecoverFunc(recover graphql.RecoverFunc) Option { - return func(cfg *Config) { - cfg.recover = recover - } -} - -// ErrorPresenter transforms errors found while resolving into errors that will be returned to the user. It provides -// a good place to add any extra fields, like error.type, that might be desired by your frontend. Check the default -// implementation in graphql.DefaultErrorPresenter for an example. -func ErrorPresenter(f graphql.ErrorPresenterFunc) Option { - return func(cfg *Config) { - cfg.errorPresenter = f - } -} - -// IntrospectionEnabled = false will forbid clients from calling introspection endpoints. Can be useful in prod when you dont -// want clients introspecting the full schema. -func IntrospectionEnabled(enabled bool) Option { - return func(cfg *Config) { - cfg.disableIntrospection = !enabled - } -} - -// ComplexityLimit sets a maximum query complexity that is allowed to be executed. -// If a query is submitted that exceeds the limit, a 422 status code will be returned. -func ComplexityLimit(limit int) Option { - return func(cfg *Config) { - cfg.complexityLimit = limit - } -} - -// ComplexityLimitFunc allows you to define a function to dynamically set the maximum query complexity that is allowed -// to be executed. -// If a query is submitted that exceeds the limit, a 422 status code will be returned. -func ComplexityLimitFunc(complexityLimitFunc graphql.ComplexityLimitFunc) Option { - return func(cfg *Config) { - cfg.complexityLimitFunc = complexityLimitFunc - } -} - -// ResolverMiddleware allows you to define a function that will be called around every resolver, -// useful for logging. -func ResolverMiddleware(middleware graphql.FieldMiddleware) Option { - return func(cfg *Config) { - if cfg.resolverHook == nil { - cfg.resolverHook = middleware - return - } - - lastResolve := cfg.resolverHook - cfg.resolverHook = func(ctx context.Context, next graphql.Resolver) (res interface{}, err error) { - return lastResolve(ctx, func(ctx context.Context) (res interface{}, err error) { - return middleware(ctx, next) - }) - } - } -} - -// RequestMiddleware allows you to define a function that will be called around the root request, -// after the query has been parsed. This is useful for logging -func RequestMiddleware(middleware graphql.RequestMiddleware) Option { - return func(cfg *Config) { - if cfg.requestHook == nil { - cfg.requestHook = middleware - return - } - - lastResolve := cfg.requestHook - cfg.requestHook = func(ctx context.Context, next func(ctx context.Context) []byte) []byte { - return lastResolve(ctx, func(ctx context.Context) []byte { - return middleware(ctx, next) - }) - } - } -} - -// Tracer allows you to add a request/resolver tracer that will be called around the root request, -// calling resolver. This is useful for tracing -func Tracer(tracer graphql.Tracer) Option { - return func(cfg *Config) { - if cfg.tracer == nil { - cfg.tracer = tracer - - } else { - lastResolve := cfg.tracer - cfg.tracer = &tracerWrapper{ - tracer1: lastResolve, - tracer2: tracer, - } - } - - opt := RequestMiddleware(func(ctx context.Context, next func(ctx context.Context) []byte) []byte { - ctx = tracer.StartOperationExecution(ctx) - resp := next(ctx) - tracer.EndOperationExecution(ctx) - - return resp - }) - opt(cfg) - } -} - -type tracerWrapper struct { - tracer1 graphql.Tracer - tracer2 graphql.Tracer -} - -func (tw *tracerWrapper) StartOperationParsing(ctx context.Context) context.Context { - ctx = tw.tracer1.StartOperationParsing(ctx) - ctx = tw.tracer2.StartOperationParsing(ctx) - return ctx -} - -func (tw *tracerWrapper) EndOperationParsing(ctx context.Context) { - tw.tracer2.EndOperationParsing(ctx) - tw.tracer1.EndOperationParsing(ctx) -} - -func (tw *tracerWrapper) StartOperationValidation(ctx context.Context) context.Context { - ctx = tw.tracer1.StartOperationValidation(ctx) - ctx = tw.tracer2.StartOperationValidation(ctx) - return ctx -} - -func (tw *tracerWrapper) EndOperationValidation(ctx context.Context) { - tw.tracer2.EndOperationValidation(ctx) - tw.tracer1.EndOperationValidation(ctx) -} - -func (tw *tracerWrapper) StartOperationExecution(ctx context.Context) context.Context { - ctx = tw.tracer1.StartOperationExecution(ctx) - ctx = tw.tracer2.StartOperationExecution(ctx) - return ctx -} - -func (tw *tracerWrapper) StartFieldExecution(ctx context.Context, field graphql.CollectedField) context.Context { - ctx = tw.tracer1.StartFieldExecution(ctx, field) - ctx = tw.tracer2.StartFieldExecution(ctx, field) - return ctx -} - -func (tw *tracerWrapper) StartFieldResolverExecution(ctx context.Context, rc *graphql.ResolverContext) context.Context { - ctx = tw.tracer1.StartFieldResolverExecution(ctx, rc) - ctx = tw.tracer2.StartFieldResolverExecution(ctx, rc) - return ctx -} - -func (tw *tracerWrapper) StartFieldChildExecution(ctx context.Context) context.Context { - ctx = tw.tracer1.StartFieldChildExecution(ctx) - ctx = tw.tracer2.StartFieldChildExecution(ctx) - return ctx -} - -func (tw *tracerWrapper) EndFieldExecution(ctx context.Context) { - tw.tracer2.EndFieldExecution(ctx) - tw.tracer1.EndFieldExecution(ctx) -} - -func (tw *tracerWrapper) EndOperationExecution(ctx context.Context) { - tw.tracer2.EndOperationExecution(ctx) - tw.tracer1.EndOperationExecution(ctx) -} - -// CacheSize sets the maximum size of the query cache. -// If size is less than or equal to 0, the cache is disabled. -func CacheSize(size int) Option { - return func(cfg *Config) { - cfg.cacheSize = size - } -} - -// UploadMaxSize sets the maximum number of bytes used to parse a request body -// as multipart/form-data. -func UploadMaxSize(size int64) Option { - return func(cfg *Config) { - cfg.uploadMaxSize = size - } -} - -// UploadMaxMemory sets the maximum number of bytes used to parse a request body -// as multipart/form-data in memory, with the remainder stored on disk in -// temporary files. -func UploadMaxMemory(size int64) Option { - return func(cfg *Config) { - cfg.uploadMaxMemory = size - } -} - -// WebsocketKeepAliveDuration allows you to reconfigure the keepalive behavior. -// By default, keepalive is enabled with a DefaultConnectionKeepAlivePingInterval -// duration. Set handler.connectionKeepAlivePingInterval = 0 to disable keepalive -// altogether. -func WebsocketKeepAliveDuration(duration time.Duration) Option { - return func(cfg *Config) { - cfg.connectionKeepAlivePingInterval = duration - } -} - -const DefaultCacheSize = 1000 -const DefaultConnectionKeepAlivePingInterval = 25 * time.Second - -// DefaultUploadMaxMemory is the maximum number of bytes used to parse a request body -// as multipart/form-data in memory, with the remainder stored on disk in -// temporary files. -const DefaultUploadMaxMemory = 32 << 20 - -// DefaultUploadMaxSize is maximum number of bytes used to parse a request body -// as multipart/form-data. -const DefaultUploadMaxSize = 32 << 20 - -func GraphQL(exec graphql.ExecutableSchema, options ...Option) http.HandlerFunc { - cfg := &Config{ - cacheSize: DefaultCacheSize, - uploadMaxMemory: DefaultUploadMaxMemory, - uploadMaxSize: DefaultUploadMaxSize, - connectionKeepAlivePingInterval: DefaultConnectionKeepAlivePingInterval, - upgrader: websocket.Upgrader{ - ReadBufferSize: 1024, - WriteBufferSize: 1024, - }, - } - - for _, option := range options { - option(cfg) - } - - var cache *lru.Cache - if cfg.cacheSize > 0 { - var err error - cache, err = lru.New(cfg.cacheSize) - if err != nil { - // An error is only returned for non-positive cache size - // and we already checked for that. - panic("unexpected error creating cache: " + err.Error()) - } - } - if cfg.tracer == nil { - cfg.tracer = &graphql.NopTracer{} - } - - handler := &graphqlHandler{ - cfg: cfg, - cache: cache, - exec: exec, - } - - return handler.ServeHTTP -} - -var _ http.Handler = (*graphqlHandler)(nil) - -type graphqlHandler struct { - cfg *Config - cache *lru.Cache - exec graphql.ExecutableSchema -} - -func (gh *graphqlHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - if r.Method == http.MethodOptions { - w.Header().Set("Allow", "OPTIONS, GET, POST") - w.WriteHeader(http.StatusOK) - return - } - - if strings.Contains(r.Header.Get("Upgrade"), "websocket") { - connectWs(gh.exec, w, r, gh.cfg, gh.cache) - return - } - - w.Header().Set("Content-Type", "application/json") - var reqParams params - switch r.Method { - case http.MethodGet: - reqParams.Query = r.URL.Query().Get("query") - reqParams.OperationName = r.URL.Query().Get("operationName") - - if variables := r.URL.Query().Get("variables"); variables != "" { - if err := jsonDecode(strings.NewReader(variables), &reqParams.Variables); err != nil { - sendErrorf(w, http.StatusBadRequest, "variables could not be decoded") - return - } - } - case http.MethodPost: - mediaType, _, err := mime.ParseMediaType(r.Header.Get("Content-Type")) - if err != nil { - sendErrorf(w, http.StatusBadRequest, "error parsing request Content-Type") - return - } - - switch mediaType { - case "application/json": - if err := jsonDecode(r.Body, &reqParams); err != nil { - sendErrorf(w, http.StatusBadRequest, "json body could not be decoded: "+err.Error()) - return - } - - case "multipart/form-data": - var closers []io.Closer - var tmpFiles []string - defer func() { - for i := len(closers) - 1; 0 <= i; i-- { - _ = closers[i].Close() - } - for _, tmpFile := range tmpFiles { - _ = os.Remove(tmpFile) - } - }() - if err := processMultipart(w, r, &reqParams, &closers, &tmpFiles, gh.cfg.uploadMaxSize, gh.cfg.uploadMaxMemory); err != nil { - sendErrorf(w, http.StatusBadRequest, "multipart body could not be decoded: "+err.Error()) - return - } - default: - sendErrorf(w, http.StatusBadRequest, "unsupported Content-Type: "+mediaType) - return - } - default: - w.WriteHeader(http.StatusMethodNotAllowed) - return - } - - ctx := r.Context() - - var doc *ast.QueryDocument - var cacheHit bool - if gh.cache != nil { - val, ok := gh.cache.Get(reqParams.Query) - if ok { - doc = val.(*ast.QueryDocument) - cacheHit = true - } - } - - ctx, doc, gqlErr := gh.parseOperation(ctx, &parseOperationArgs{ - Query: reqParams.Query, - CachedDoc: doc, - }) - if gqlErr != nil { - sendError(w, http.StatusUnprocessableEntity, gqlErr) - return - } - - ctx, op, vars, listErr := gh.validateOperation(ctx, &validateOperationArgs{ - Doc: doc, - OperationName: reqParams.OperationName, - CacheHit: cacheHit, - R: r, - Variables: reqParams.Variables, - }) - if len(listErr) != 0 { - sendError(w, http.StatusUnprocessableEntity, listErr...) - return - } - - if gh.cache != nil && !cacheHit { - gh.cache.Add(reqParams.Query, doc) - } - - reqCtx := gh.cfg.newRequestContext(gh.exec, doc, op, reqParams.Query, vars) - ctx = graphql.WithRequestContext(ctx, reqCtx) - - defer func() { - if err := recover(); err != nil { - userErr := reqCtx.Recover(ctx, err) - sendErrorf(w, http.StatusUnprocessableEntity, userErr.Error()) - } - }() - - if gh.cfg.complexityLimitFunc != nil { - reqCtx.ComplexityLimit = gh.cfg.complexityLimitFunc(ctx) - } - - if reqCtx.ComplexityLimit > 0 && reqCtx.OperationComplexity > reqCtx.ComplexityLimit { - sendErrorf(w, http.StatusUnprocessableEntity, "operation has complexity %d, which exceeds the limit of %d", reqCtx.OperationComplexity, reqCtx.ComplexityLimit) - return - } - - switch op.Operation { - case ast.Query: - b, err := json.Marshal(gh.exec.Query(ctx, op)) - if err != nil { - panic(err) - } - w.Write(b) - case ast.Mutation: - b, err := json.Marshal(gh.exec.Mutation(ctx, op)) - if err != nil { - panic(err) - } - w.Write(b) - default: - sendErrorf(w, http.StatusBadRequest, "unsupported operation type") - } -} - -type parseOperationArgs struct { - Query string - CachedDoc *ast.QueryDocument -} - -func (gh *graphqlHandler) parseOperation(ctx context.Context, args *parseOperationArgs) (context.Context, *ast.QueryDocument, *gqlerror.Error) { - ctx = gh.cfg.tracer.StartOperationParsing(ctx) - defer func() { gh.cfg.tracer.EndOperationParsing(ctx) }() - - if args.CachedDoc != nil { - return ctx, args.CachedDoc, nil - } - - doc, gqlErr := parser.ParseQuery(&ast.Source{Input: args.Query}) - if gqlErr != nil { - return ctx, nil, gqlErr - } - - return ctx, doc, nil -} - -type validateOperationArgs struct { - Doc *ast.QueryDocument - OperationName string - CacheHit bool - R *http.Request - Variables map[string]interface{} -} - -func (gh *graphqlHandler) validateOperation(ctx context.Context, args *validateOperationArgs) (context.Context, *ast.OperationDefinition, map[string]interface{}, gqlerror.List) { - ctx = gh.cfg.tracer.StartOperationValidation(ctx) - defer func() { gh.cfg.tracer.EndOperationValidation(ctx) }() - - if !args.CacheHit { - listErr := validator.Validate(gh.exec.Schema(), args.Doc) - if len(listErr) != 0 { - return ctx, nil, nil, listErr - } - } - - op := args.Doc.Operations.ForName(args.OperationName) - if op == nil { - return ctx, nil, nil, gqlerror.List{gqlerror.Errorf("operation %s not found", args.OperationName)} - } - - if op.Operation != ast.Query && args.R.Method == http.MethodGet { - return ctx, nil, nil, gqlerror.List{gqlerror.Errorf("GET requests only allow query operations")} - } - - vars, err := validator.VariableValues(gh.exec.Schema(), op, args.Variables) - if err != nil { - return ctx, nil, nil, gqlerror.List{err} - } - - return ctx, op, vars, nil -} - -func jsonDecode(r io.Reader, val interface{}) error { - dec := json.NewDecoder(r) - dec.UseNumber() - return dec.Decode(val) -} - -func sendError(w http.ResponseWriter, code int, errors ...*gqlerror.Error) { - w.WriteHeader(code) - b, err := json.Marshal(&graphql.Response{Errors: errors}) - if err != nil { - panic(err) - } - w.Write(b) -} - -func sendErrorf(w http.ResponseWriter, code int, format string, args ...interface{}) { - sendError(w, code, &gqlerror.Error{Message: fmt.Sprintf(format, args...)}) -} - -type bytesReader struct { - s *[]byte - i int64 // current reading index - prevRune int // index of previous rune; or < 0 -} - -func (r *bytesReader) Read(b []byte) (n int, err error) { - if r.s == nil { - return 0, errors.New("byte slice pointer is nil") - } - if r.i >= int64(len(*r.s)) { - return 0, io.EOF - } - r.prevRune = -1 - n = copy(b, (*r.s)[r.i:]) - r.i += int64(n) - return -} - -func processMultipart(w http.ResponseWriter, r *http.Request, request *params, closers *[]io.Closer, tmpFiles *[]string, uploadMaxSize, uploadMaxMemory int64) error { - var err error - if r.ContentLength > uploadMaxSize { - return errors.New("failed to parse multipart form, request body too large") - } - r.Body = http.MaxBytesReader(w, r.Body, uploadMaxSize) - if err = r.ParseMultipartForm(uploadMaxMemory); err != nil { - if strings.Contains(err.Error(), "request body too large") { - return errors.New("failed to parse multipart form, request body too large") - } - return errors.New("failed to parse multipart form") - } - *closers = append(*closers, r.Body) - - if err = jsonDecode(strings.NewReader(r.Form.Get("operations")), &request); err != nil { - return errors.New("operations form field could not be decoded") - } - - var uploadsMap = map[string][]string{} - if err = json.Unmarshal([]byte(r.Form.Get("map")), &uploadsMap); err != nil { - return errors.New("map form field could not be decoded") - } - - var upload graphql.Upload - for key, paths := range uploadsMap { - if len(paths) == 0 { - return fmt.Errorf("invalid empty operations paths list for key %s", key) - } - file, header, err := r.FormFile(key) - if err != nil { - return fmt.Errorf("failed to get key %s from form", key) - } - *closers = append(*closers, file) - - if len(paths) == 1 { - upload = graphql.Upload{ - File: file, - Size: header.Size, - Filename: header.Filename, - } - err = addUploadToOperations(request, upload, key, paths[0]) - if err != nil { - return err - } - } else { - if r.ContentLength < uploadMaxMemory { - fileBytes, err := ioutil.ReadAll(file) - if err != nil { - return fmt.Errorf("failed to read file for key %s", key) - } - for _, path := range paths { - upload = graphql.Upload{ - File: &bytesReader{s: &fileBytes, i: 0, prevRune: -1}, - Size: header.Size, - Filename: header.Filename, - } - err = addUploadToOperations(request, upload, key, path) - if err != nil { - return err - } - } - } else { - tmpFile, err := ioutil.TempFile(os.TempDir(), "gqlgen-") - if err != nil { - return fmt.Errorf("failed to create temp file for key %s", key) - } - tmpName := tmpFile.Name() - *tmpFiles = append(*tmpFiles, tmpName) - _, err = io.Copy(tmpFile, file) - if err != nil { - if err := tmpFile.Close(); err != nil { - return fmt.Errorf("failed to copy to temp file and close temp file for key %s", key) - } - return fmt.Errorf("failed to copy to temp file for key %s", key) - } - if err := tmpFile.Close(); err != nil { - return fmt.Errorf("failed to close temp file for key %s", key) - } - for _, path := range paths { - pathTmpFile, err := os.Open(tmpName) - if err != nil { - return fmt.Errorf("failed to open temp file for key %s", key) - } - *closers = append(*closers, pathTmpFile) - upload = graphql.Upload{ - File: pathTmpFile, - Size: header.Size, - Filename: header.Filename, - } - err = addUploadToOperations(request, upload, key, path) - if err != nil { - return err - } - } - } - } - } - return nil -} - -func addUploadToOperations(request *params, upload graphql.Upload, key, path string) error { - if !strings.HasPrefix(path, "variables.") { - return fmt.Errorf("invalid operations paths for key %s", key) - } - - var ptr interface{} = request.Variables - parts := strings.Split(path, ".") - - // skip the first part (variables) because we started there - for i, p := range parts[1:] { - last := i == len(parts)-2 - if ptr == nil { - return fmt.Errorf("path is missing \"variables.\" prefix, key: %s, path: %s", key, path) - } - if index, parseNbrErr := strconv.Atoi(p); parseNbrErr == nil { - if last { - ptr.([]interface{})[index] = upload - } else { - ptr = ptr.([]interface{})[index] - } - } else { - if last { - ptr.(map[string]interface{})[p] = upload - } else { - ptr = ptr.(map[string]interface{})[p] - } - } - } - - return nil -} diff --git a/vendor/github.com/99designs/gqlgen/handler/handler.go b/vendor/github.com/99designs/gqlgen/handler/handler.go new file mode 100644 index 000000000..892df5398 --- /dev/null +++ b/vendor/github.com/99designs/gqlgen/handler/handler.go @@ -0,0 +1,247 @@ +package handler + +import ( + "context" + "net/http" + "time" + + "github.com/99designs/gqlgen/graphql" + "github.com/99designs/gqlgen/graphql/handler" + "github.com/99designs/gqlgen/graphql/handler/extension" + "github.com/99designs/gqlgen/graphql/handler/lru" + "github.com/99designs/gqlgen/graphql/handler/transport" + "github.com/99designs/gqlgen/graphql/playground" + "github.com/gorilla/websocket" +) + +// Deprecated: switch to graphql/handler.New +func GraphQL(exec graphql.ExecutableSchema, options ...Option) http.HandlerFunc { + var cfg Config + cfg.cacheSize = 1000 + + for _, option := range options { + option(&cfg) + } + + srv := handler.New(exec) + + srv.AddTransport(transport.Websocket{ + Upgrader: cfg.upgrader, + InitFunc: cfg.websocketInitFunc, + KeepAlivePingInterval: cfg.connectionKeepAlivePingInterval, + }) + srv.AddTransport(transport.Options{}) + srv.AddTransport(transport.GET{}) + srv.AddTransport(transport.POST{}) + srv.AddTransport(transport.MultipartForm{ + MaxUploadSize: cfg.uploadMaxSize, + MaxMemory: cfg.uploadMaxMemory, + }) + + if cfg.cacheSize != 0 { + srv.SetQueryCache(lru.New(cfg.cacheSize)) + } + if cfg.recover != nil { + srv.SetRecoverFunc(cfg.recover) + } + if cfg.errorPresenter != nil { + srv.SetErrorPresenter(cfg.errorPresenter) + } + for _, hook := range cfg.fieldHooks { + srv.AroundFields(hook) + } + for _, hook := range cfg.requestHooks { + srv.AroundResponses(hook) + } + if cfg.complexityLimit != 0 { + srv.Use(extension.FixedComplexityLimit(cfg.complexityLimit)) + } else if cfg.complexityLimitFunc != nil { + srv.Use(&extension.ComplexityLimit{ + Func: func(ctx context.Context, rc *graphql.OperationContext) int { + return cfg.complexityLimitFunc(graphql.WithOperationContext(ctx, rc)) + }, + }) + } + if !cfg.disableIntrospection { + srv.Use(extension.Introspection{}) + } + if cfg.apqCache != nil { + srv.Use(extension.AutomaticPersistedQuery{Cache: apqAdapter{cfg.apqCache}}) + } + return srv.ServeHTTP +} + +// Deprecated: switch to graphql/handler.New +type Config struct { + cacheSize int + upgrader websocket.Upgrader + websocketInitFunc transport.WebsocketInitFunc + connectionKeepAlivePingInterval time.Duration + recover graphql.RecoverFunc + errorPresenter graphql.ErrorPresenterFunc + fieldHooks []graphql.FieldMiddleware + requestHooks []graphql.ResponseMiddleware + complexityLimit int + complexityLimitFunc func(ctx context.Context) int + disableIntrospection bool + uploadMaxMemory int64 + uploadMaxSize int64 + apqCache PersistedQueryCache +} + +// Deprecated: switch to graphql/handler.New +type Option func(cfg *Config) + +// Deprecated: switch to graphql/handler.New +func WebsocketUpgrader(upgrader websocket.Upgrader) Option { + return func(cfg *Config) { + cfg.upgrader = upgrader + } +} + +// Deprecated: switch to graphql/handler.New +func RecoverFunc(recover graphql.RecoverFunc) Option { + return func(cfg *Config) { + cfg.recover = recover + } +} + +// ErrorPresenter transforms errors found while resolving into errors that will be returned to the user. It provides +// a good place to add any extra fields, like error.type, that might be desired by your frontend. Check the default +// implementation in graphql.DefaultErrorPresenter for an example. +// Deprecated: switch to graphql/handler.New +func ErrorPresenter(f graphql.ErrorPresenterFunc) Option { + return func(cfg *Config) { + cfg.errorPresenter = f + } +} + +// IntrospectionEnabled = false will forbid clients from calling introspection endpoints. Can be useful in prod when you dont +// want clients introspecting the full schema. +// Deprecated: switch to graphql/handler.New +func IntrospectionEnabled(enabled bool) Option { + return func(cfg *Config) { + cfg.disableIntrospection = !enabled + } +} + +// ComplexityLimit sets a maximum query complexity that is allowed to be executed. +// If a query is submitted that exceeds the limit, a 422 status code will be returned. +// Deprecated: switch to graphql/handler.New +func ComplexityLimit(limit int) Option { + return func(cfg *Config) { + cfg.complexityLimit = limit + } +} + +// ComplexityLimitFunc allows you to define a function to dynamically set the maximum query complexity that is allowed +// to be executed. +// If a query is submitted that exceeds the limit, a 422 status code will be returned. +// Deprecated: switch to graphql/handler.New +func ComplexityLimitFunc(complexityLimitFunc func(ctx context.Context) int) Option { + return func(cfg *Config) { + cfg.complexityLimitFunc = complexityLimitFunc + } +} + +// ResolverMiddleware allows you to define a function that will be called around every resolver, +// useful for logging. +// Deprecated: switch to graphql/handler.New +func ResolverMiddleware(middleware graphql.FieldMiddleware) Option { + return func(cfg *Config) { + cfg.fieldHooks = append(cfg.fieldHooks, middleware) + } +} + +// RequestMiddleware allows you to define a function that will be called around the root request, +// after the query has been parsed. This is useful for logging +// Deprecated: switch to graphql/handler.New +func RequestMiddleware(middleware graphql.ResponseMiddleware) Option { + return func(cfg *Config) { + cfg.requestHooks = append(cfg.requestHooks, middleware) + } +} + +// WebsocketInitFunc is called when the server receives connection init message from the client. +// This can be used to check initial payload to see whether to accept the websocket connection. +// Deprecated: switch to graphql/handler.New +func WebsocketInitFunc(websocketInitFunc transport.WebsocketInitFunc) Option { + return func(cfg *Config) { + cfg.websocketInitFunc = websocketInitFunc + } +} + +// CacheSize sets the maximum size of the query cache. +// If size is less than or equal to 0, the cache is disabled. +// Deprecated: switch to graphql/handler.New +func CacheSize(size int) Option { + return func(cfg *Config) { + cfg.cacheSize = size + } +} + +// UploadMaxSize sets the maximum number of bytes used to parse a request body +// as multipart/form-data. +// Deprecated: switch to graphql/handler.New +func UploadMaxSize(size int64) Option { + return func(cfg *Config) { + cfg.uploadMaxSize = size + } +} + +// UploadMaxMemory sets the maximum number of bytes used to parse a request body +// as multipart/form-data in memory, with the remainder stored on disk in +// temporary files. +// Deprecated: switch to graphql/handler.New +func UploadMaxMemory(size int64) Option { + return func(cfg *Config) { + cfg.uploadMaxMemory = size + } +} + +// WebsocketKeepAliveDuration allows you to reconfigure the keepalive behavior. +// By default, keepalive is enabled with a DefaultConnectionKeepAlivePingInterval +// duration. Set handler.connectionKeepAlivePingInterval = 0 to disable keepalive +// altogether. +// Deprecated: switch to graphql/handler.New +func WebsocketKeepAliveDuration(duration time.Duration) Option { + return func(cfg *Config) { + cfg.connectionKeepAlivePingInterval = duration + } +} + +// Add cache that will hold queries for automatic persisted queries (APQ) +// Deprecated: switch to graphql/handler.New +func EnablePersistedQueryCache(cache PersistedQueryCache) Option { + return func(cfg *Config) { + cfg.apqCache = cache + } +} + +func GetInitPayload(ctx context.Context) transport.InitPayload { + return transport.GetInitPayload(ctx) +} + +type apqAdapter struct { + PersistedQueryCache +} + +func (a apqAdapter) Get(ctx context.Context, key string) (value interface{}, ok bool) { + return a.PersistedQueryCache.Get(ctx, key) +} +func (a apqAdapter) Add(ctx context.Context, key string, value interface{}) { + a.PersistedQueryCache.Add(ctx, key, value.(string)) +} + +type PersistedQueryCache interface { + Add(ctx context.Context, hash string, query string) + Get(ctx context.Context, hash string) (string, bool) +} + +// Deprecated: use playground.Handler instead +func Playground(title string, endpoint string) http.HandlerFunc { + return playground.Handler(title, endpoint) +} + +// Deprecated: use transport.InitPayload instead +type InitPayload = transport.InitPayload diff --git a/vendor/github.com/99designs/gqlgen/handler/mock.go b/vendor/github.com/99designs/gqlgen/handler/mock.go deleted file mode 100644 index 3e70cf036..000000000 --- a/vendor/github.com/99designs/gqlgen/handler/mock.go +++ /dev/null @@ -1,57 +0,0 @@ -package handler - -import ( - "context" - - "github.com/99designs/gqlgen/graphql" - "github.com/vektah/gqlparser" - "github.com/vektah/gqlparser/ast" -) - -type executableSchemaMock struct { - MutationFunc func(ctx context.Context, op *ast.OperationDefinition) *graphql.Response -} - -var _ graphql.ExecutableSchema = &executableSchemaMock{} - -func (e *executableSchemaMock) Schema() *ast.Schema { - return gqlparser.MustLoadSchema(&ast.Source{Input: ` - schema { query: Query, mutation: Mutation } - type Query { - empty: String! - } - scalar Upload - type File { - id: Int! - } - input UploadFile { - id: Int! - file: Upload! - } - type Mutation { - singleUpload(file: Upload!): File! - singleUploadWithPayload(req: UploadFile!): File! - multipleUpload(files: [Upload!]!): [File!]! - multipleUploadWithPayload(req: [UploadFile!]!): [File!]! - } - `}) -} - -func (e *executableSchemaMock) Complexity(typeName, field string, childComplexity int, args map[string]interface{}) (int, bool) { - return 0, false -} - -func (e *executableSchemaMock) Query(ctx context.Context, op *ast.OperationDefinition) *graphql.Response { - return graphql.ErrorResponse(ctx, "queries are not supported") -} - -func (e *executableSchemaMock) Mutation(ctx context.Context, op *ast.OperationDefinition) *graphql.Response { - return e.MutationFunc(ctx, op) -} - -func (e *executableSchemaMock) Subscription(ctx context.Context, op *ast.OperationDefinition) func() *graphql.Response { - return func() *graphql.Response { - <-ctx.Done() - return nil - } -} diff --git a/vendor/github.com/99designs/gqlgen/handler/stub.go b/vendor/github.com/99designs/gqlgen/handler/stub.go deleted file mode 100644 index d237e1889..000000000 --- a/vendor/github.com/99designs/gqlgen/handler/stub.go +++ /dev/null @@ -1,51 +0,0 @@ -package handler - -import ( - "context" - - "github.com/99designs/gqlgen/graphql" - "github.com/vektah/gqlparser" - "github.com/vektah/gqlparser/ast" -) - -type executableSchemaStub struct { - NextResp chan struct{} -} - -var _ graphql.ExecutableSchema = &executableSchemaStub{} - -func (e *executableSchemaStub) Schema() *ast.Schema { - return gqlparser.MustLoadSchema(&ast.Source{Input: ` - schema { query: Query } - type Query { - me: User! - user(id: Int): User! - } - type User { name: String! } - `}) -} - -func (e *executableSchemaStub) Complexity(typeName, field string, childComplexity int, args map[string]interface{}) (int, bool) { - return 0, false -} - -func (e *executableSchemaStub) Query(ctx context.Context, op *ast.OperationDefinition) *graphql.Response { - return &graphql.Response{Data: []byte(`{"name":"test"}`)} -} - -func (e *executableSchemaStub) Mutation(ctx context.Context, op *ast.OperationDefinition) *graphql.Response { - return graphql.ErrorResponse(ctx, "mutations are not supported") -} - -func (e *executableSchemaStub) Subscription(ctx context.Context, op *ast.OperationDefinition) func() *graphql.Response { - return func() *graphql.Response { - select { - case <-ctx.Done(): - return nil - case <-e.NextResp: - return &graphql.Response{ - Data: []byte(`{"name":"test"}`), - } - } - } -} diff --git a/vendor/github.com/99designs/gqlgen/internal/code/imports.go b/vendor/github.com/99designs/gqlgen/internal/code/imports.go index 75c30fe1e..b56d80fc5 100644 --- a/vendor/github.com/99designs/gqlgen/internal/code/imports.go +++ b/vendor/github.com/99designs/gqlgen/internal/code/imports.go @@ -1,7 +1,6 @@ package code import ( - "errors" "go/build" "go/parser" "go/token" @@ -9,13 +8,8 @@ import ( "path/filepath" "regexp" "strings" - "sync" - - "golang.org/x/tools/go/packages" ) -var nameForPackageCache = sync.Map{} - var gopaths []string func init() { @@ -51,33 +45,50 @@ func NameForDir(dir string) string { return SanitizePackageName(filepath.Base(dir)) } -// ImportPathForDir takes a path and returns a golang import path for the package -func ImportPathForDir(dir string) (res string) { +// goModuleRoot returns the root of the current go module if there is a go.mod file in the directory tree +// If not, it returns false +func goModuleRoot(dir string) (string, bool) { dir, err := filepath.Abs(dir) if err != nil { panic(err) } dir = filepath.ToSlash(dir) - modDir := dir assumedPart := "" for { - f, err := ioutil.ReadFile(filepath.Join(modDir, "/", "go.mod")) + f, err := ioutil.ReadFile(filepath.Join(modDir, "go.mod")) if err == nil { // found it, stop searching - return string(modregex.FindSubmatch(f)[1]) + assumedPart + return string(modregex.FindSubmatch(f)[1]) + assumedPart, true } assumedPart = "/" + filepath.Base(modDir) + assumedPart - modDir, err = filepath.Abs(filepath.Join(modDir, "..")) + parentDir, err := filepath.Abs(filepath.Join(modDir, "..")) if err != nil { panic(err) } - // Walked all the way to the root and didnt find anything :'( - if modDir == "/" { + if parentDir == modDir { + // Walked all the way to the root and didnt find anything :'( break } + modDir = parentDir + } + return "", false +} + +// ImportPathForDir takes a path and returns a golang import path for the package +func ImportPathForDir(dir string) (res string) { + dir, err := filepath.Abs(dir) + + if err != nil { + panic(err) + } + dir = filepath.ToSlash(dir) + + modDir, ok := goModuleRoot(dir) + if ok { + return modDir } for _, gopath := range gopaths { @@ -89,26 +100,4 @@ func ImportPathForDir(dir string) (res string) { return "" } -var modregex = regexp.MustCompile("module (.*)\n") - -// NameForPackage returns the package name for a given import path. This can be really slow. -func NameForPackage(importPath string) string { - if importPath == "" { - panic(errors.New("import path can not be empty")) - } - if v, ok := nameForPackageCache.Load(importPath); ok { - return v.(string) - } - importPath = QualifyPackagePath(importPath) - p, _ := packages.Load(&packages.Config{ - Mode: packages.NeedName, - }, importPath) - - if len(p) != 1 || p[0].Name == "" { - return SanitizePackageName(filepath.Base(importPath)) - } - - nameForPackageCache.Store(importPath, p[0].Name) - - return p[0].Name -} +var modregex = regexp.MustCompile(`module ([^\s]*)`) diff --git a/vendor/github.com/99designs/gqlgen/internal/code/packages.go b/vendor/github.com/99designs/gqlgen/internal/code/packages.go new file mode 100644 index 000000000..b14c45ad2 --- /dev/null +++ b/vendor/github.com/99designs/gqlgen/internal/code/packages.go @@ -0,0 +1,173 @@ +package code + +import ( + "bytes" + "path/filepath" + + "github.com/pkg/errors" + "golang.org/x/tools/go/packages" +) + +var mode = packages.NeedName | + packages.NeedFiles | + packages.NeedImports | + packages.NeedTypes | + packages.NeedSyntax | + packages.NeedTypesInfo + +// Packages is a wrapper around x/tools/go/packages that maintains a (hopefully prewarmed) cache of packages +// that can be invalidated as writes are made and packages are known to change. +type Packages struct { + packages map[string]*packages.Package + importToName map[string]string + loadErrors []error + + numLoadCalls int // stupid test steam. ignore. + numNameCalls int // stupid test steam. ignore. +} + +// LoadAll will call packages.Load and return the package data for the given packages, +// but if the package already have been loaded it will return cached values instead. +func (p *Packages) LoadAll(importPaths ...string) []*packages.Package { + if p.packages == nil { + p.packages = map[string]*packages.Package{} + } + + missing := make([]string, 0, len(importPaths)) + for _, path := range importPaths { + if _, ok := p.packages[path]; ok { + continue + } + missing = append(missing, path) + } + + if len(missing) > 0 { + p.numLoadCalls++ + pkgs, err := packages.Load(&packages.Config{Mode: mode}, missing...) + if err != nil { + p.loadErrors = append(p.loadErrors, err) + } + + for _, pkg := range pkgs { + p.addToCache(pkg) + } + } + + res := make([]*packages.Package, 0, len(importPaths)) + for _, path := range importPaths { + res = append(res, p.packages[NormalizeVendor(path)]) + } + return res +} + +func (p *Packages) addToCache(pkg *packages.Package) { + imp := NormalizeVendor(pkg.PkgPath) + p.packages[imp] = pkg + for _, imp := range pkg.Imports { + if _, found := p.packages[NormalizeVendor(imp.PkgPath)]; !found { + p.addToCache(imp) + } + } +} + +// Load works the same as LoadAll, except a single package at a time. +func (p *Packages) Load(importPath string) *packages.Package { + pkgs := p.LoadAll(importPath) + if len(pkgs) == 0 { + return nil + } + return pkgs[0] +} + +// LoadWithTypes tries a standard load, which may not have enough type info (TypesInfo== nil) available if the imported package is a +// second order dependency. Fortunately this doesnt happen very often, so we can just issue a load when we detect it. +func (p *Packages) LoadWithTypes(importPath string) *packages.Package { + pkg := p.Load(importPath) + if pkg == nil || pkg.TypesInfo == nil { + p.numLoadCalls++ + pkgs, err := packages.Load(&packages.Config{Mode: mode}, importPath) + if err != nil { + p.loadErrors = append(p.loadErrors, err) + return nil + } + p.addToCache(pkgs[0]) + pkg = pkgs[0] + } + return pkg +} + +// NameForPackage looks up the package name from the package stanza in the go files at the given import path. +func (p *Packages) NameForPackage(importPath string) string { + if importPath == "" { + panic(errors.New("import path can not be empty")) + } + if p.importToName == nil { + p.importToName = map[string]string{} + } + + importPath = NormalizeVendor(importPath) + + // if its in the name cache use it + if name := p.importToName[importPath]; name != "" { + return name + } + + // otherwise we might have already loaded the full package data for it cached + pkg := p.packages[importPath] + + if pkg == nil { + // otherwise do a name only lookup for it but dont put it in the package cache. + p.numNameCalls++ + pkgs, err := packages.Load(&packages.Config{Mode: packages.NeedName}, importPath) + if err != nil { + p.loadErrors = append(p.loadErrors, err) + } else { + pkg = pkgs[0] + } + } + + if pkg == nil || pkg.Name == "" { + return SanitizePackageName(filepath.Base(importPath)) + } + + p.importToName[importPath] = pkg.Name + + return pkg.Name +} + +// Evict removes a given package import path from the cache, along with any packages that depend on it. Further calls +// to Load will fetch it from disk. +func (p *Packages) Evict(importPath string) { + delete(p.packages, importPath) + + for _, pkg := range p.packages { + for _, imported := range pkg.Imports { + if imported.PkgPath == importPath { + p.Evict(pkg.PkgPath) + } + } + } +} + +// Errors returns any errors that were returned by Load, either from the call itself or any of the loaded packages. +func (p *Packages) Errors() PkgErrors { + var res []error //nolint:prealloc + res = append(res, p.loadErrors...) + for _, pkg := range p.packages { + for _, err := range pkg.Errors { + res = append(res, err) + } + } + return res +} + +type PkgErrors []error + +func (p PkgErrors) Error() string { + var b bytes.Buffer + b.WriteString("packages.Load: ") + for _, e := range p { + b.WriteString(e.Error() + "\n") + } + return b.String() +} diff --git a/vendor/github.com/99designs/gqlgen/internal/code/util.go b/vendor/github.com/99designs/gqlgen/internal/code/util.go index 2be83a23c..cbe40858e 100644 --- a/vendor/github.com/99designs/gqlgen/internal/code/util.go +++ b/vendor/github.com/99designs/gqlgen/internal/code/util.go @@ -41,6 +41,11 @@ func NormalizeVendor(pkg string) string { func QualifyPackagePath(importPath string) string { wd, _ := os.Getwd() + // in go module mode, the import path doesn't need fixing + if _, ok := goModuleRoot(wd); ok { + return importPath + } + pkg, err := build.Import(importPath, wd, 0) if err != nil { return importPath diff --git a/vendor/github.com/99designs/gqlgen/internal/imports/prune.go b/vendor/github.com/99designs/gqlgen/internal/imports/prune.go index d678870ef..d42a41579 100644 --- a/vendor/github.com/99designs/gqlgen/internal/imports/prune.go +++ b/vendor/github.com/99designs/gqlgen/internal/imports/prune.go @@ -24,7 +24,7 @@ func (fn visitFn) Visit(node ast.Node) ast.Visitor { } // Prune removes any unused imports -func Prune(filename string, src []byte) ([]byte, error) { +func Prune(filename string, src []byte, packages *code.Packages) ([]byte, error) { fset := token.NewFileSet() file, err := parser.ParseFile(fset, filename, src, parser.ParseComments|parser.AllErrors) @@ -32,10 +32,7 @@ func Prune(filename string, src []byte) ([]byte, error) { return nil, err } - unused, err := getUnusedImports(file, filename) - if err != nil { - return nil, err - } + unused := getUnusedImports(file, packages) for ipath, name := range unused { astutil.DeleteNamedImport(fset, file, name, ipath) } @@ -49,7 +46,7 @@ func Prune(filename string, src []byte) ([]byte, error) { return imports.Process(filename, buf.Bytes(), &imports.Options{FormatOnly: true, Comments: true, TabIndent: true, TabWidth: 8}) } -func getUnusedImports(file ast.Node, filename string) (map[string]string, error) { +func getUnusedImports(file ast.Node, packages *code.Packages) map[string]string { imported := map[string]*ast.ImportSpec{} used := map[string]bool{} @@ -68,7 +65,7 @@ func getUnusedImports(file ast.Node, filename string) (map[string]string, error) break } - local := code.NameForPackage(ipath) + local := packages.NameForPackage(ipath) imported[local] = v case *ast.SelectorExpr: @@ -99,5 +96,5 @@ func getUnusedImports(file ast.Node, filename string) (map[string]string, error) } } - return unusedImport, nil + return unusedImport } diff --git a/vendor/github.com/99designs/gqlgen/internal/rewrite/rewriter.go b/vendor/github.com/99designs/gqlgen/internal/rewrite/rewriter.go new file mode 100644 index 000000000..1b9adb171 --- /dev/null +++ b/vendor/github.com/99designs/gqlgen/internal/rewrite/rewriter.go @@ -0,0 +1,195 @@ +package rewrite + +import ( + "bytes" + "fmt" + "go/ast" + "go/token" + "io/ioutil" + "path/filepath" + "strconv" + "strings" + + "github.com/99designs/gqlgen/internal/code" + "golang.org/x/tools/go/packages" +) + +type Rewriter struct { + pkg *packages.Package + files map[string]string + copied map[ast.Decl]bool +} + +func New(dir string) (*Rewriter, error) { + importPath := code.ImportPathForDir(dir) + if importPath == "" { + return nil, fmt.Errorf("import path not found for directory: %q", dir) + } + pkgs, err := packages.Load(&packages.Config{ + Mode: packages.NeedSyntax | packages.NeedTypes, + }, importPath) + if err != nil { + return nil, err + } + if len(pkgs) == 0 { + return nil, fmt.Errorf("package not found for importPath: %s", importPath) + } + + return &Rewriter{ + pkg: pkgs[0], + files: map[string]string{}, + copied: map[ast.Decl]bool{}, + }, nil +} + +func (r *Rewriter) getSource(start, end token.Pos) string { + startPos := r.pkg.Fset.Position(start) + endPos := r.pkg.Fset.Position(end) + + if startPos.Filename != endPos.Filename { + panic("cant get source spanning multiple files") + } + + file := r.getFile(startPos.Filename) + return file[startPos.Offset:endPos.Offset] +} + +func (r *Rewriter) getFile(filename string) string { + if _, ok := r.files[filename]; !ok { + b, err := ioutil.ReadFile(filename) + if err != nil { + panic(fmt.Errorf("unable to load file, already exists: %s", err.Error())) + } + + r.files[filename] = string(b) + + } + + return r.files[filename] +} + +func (r *Rewriter) GetMethodBody(structname string, methodname string) string { + for _, f := range r.pkg.Syntax { + for _, d := range f.Decls { + d, isFunc := d.(*ast.FuncDecl) + if !isFunc { + continue + } + if d.Name.Name != methodname { + continue + } + if d.Recv == nil || len(d.Recv.List) == 0 { + continue + } + recv := d.Recv.List[0].Type + if star, isStar := recv.(*ast.StarExpr); isStar { + recv = star.X + } + ident, ok := recv.(*ast.Ident) + if !ok { + continue + } + + if ident.Name != structname { + continue + } + + r.copied[d] = true + + return r.getSource(d.Body.Pos()+1, d.Body.End()-1) + } + } + + return "" +} + +func (r *Rewriter) MarkStructCopied(name string) { + for _, f := range r.pkg.Syntax { + for _, d := range f.Decls { + d, isGen := d.(*ast.GenDecl) + if !isGen { + continue + } + if d.Tok != token.TYPE || len(d.Specs) == 0 { + continue + } + + spec, isTypeSpec := d.Specs[0].(*ast.TypeSpec) + if !isTypeSpec { + continue + } + + if spec.Name.Name != name { + continue + } + + r.copied[d] = true + } + } +} + +func (r *Rewriter) ExistingImports(filename string) []Import { + filename, err := filepath.Abs(filename) + if err != nil { + panic(err) + } + for _, f := range r.pkg.Syntax { + pos := r.pkg.Fset.Position(f.Pos()) + + if filename != pos.Filename { + continue + } + + var imps []Import + for _, i := range f.Imports { + name := "" + if i.Name != nil { + name = i.Name.Name + } + path, err := strconv.Unquote(i.Path.Value) + if err != nil { + panic(err) + } + imps = append(imps, Import{name, path}) + } + return imps + } + return nil +} + +func (r *Rewriter) RemainingSource(filename string) string { + filename, err := filepath.Abs(filename) + if err != nil { + panic(err) + } + for _, f := range r.pkg.Syntax { + pos := r.pkg.Fset.Position(f.Pos()) + + if filename != pos.Filename { + continue + } + + var buf bytes.Buffer + + for _, d := range f.Decls { + if r.copied[d] { + continue + } + + if d, isGen := d.(*ast.GenDecl); isGen && d.Tok == token.IMPORT { + continue + } + + buf.WriteString(r.getSource(d.Pos(), d.End())) + buf.WriteString("\n") + } + + return strings.TrimSpace(buf.String()) + } + return "" +} + +type Import struct { + Alias string + ImportPath string +} diff --git a/vendor/github.com/99designs/gqlgen/plugin/federation/federation.go b/vendor/github.com/99designs/gqlgen/plugin/federation/federation.go new file mode 100644 index 000000000..7d9abc977 --- /dev/null +++ b/vendor/github.com/99designs/gqlgen/plugin/federation/federation.go @@ -0,0 +1,311 @@ +package federation + +import ( + "fmt" + "sort" + "strings" + + "github.com/vektah/gqlparser/v2/ast" + + "github.com/99designs/gqlgen/codegen" + "github.com/99designs/gqlgen/codegen/config" + "github.com/99designs/gqlgen/codegen/templates" + "github.com/99designs/gqlgen/plugin" +) + +type federation struct { + Entities []*Entity +} + +// New returns a federation plugin that injects +// federated directives and types into the schema +func New() plugin.Plugin { + return &federation{} +} + +// Name returns the plugin name +func (f *federation) Name() string { + return "federation" +} + +// MutateConfig mutates the configuration +func (f *federation) MutateConfig(cfg *config.Config) error { + builtins := config.TypeMap{ + "_Service": { + Model: config.StringList{ + "github.com/99designs/gqlgen/plugin/federation/fedruntime.Service", + }, + }, + "_Entity": { + Model: config.StringList{ + "github.com/99designs/gqlgen/plugin/federation/fedruntime.Entity", + }, + }, + "Entity": { + Model: config.StringList{ + "github.com/99designs/gqlgen/plugin/federation/fedruntime.Entity", + }, + }, + "_Any": { + Model: config.StringList{"github.com/99designs/gqlgen/graphql.Map"}, + }, + } + for typeName, entry := range builtins { + if cfg.Models.Exists(typeName) { + return fmt.Errorf("%v already exists which must be reserved when Federation is enabled", typeName) + } + cfg.Models[typeName] = entry + } + cfg.Directives["external"] = config.DirectiveConfig{SkipRuntime: true} + cfg.Directives["requires"] = config.DirectiveConfig{SkipRuntime: true} + cfg.Directives["provides"] = config.DirectiveConfig{SkipRuntime: true} + cfg.Directives["key"] = config.DirectiveConfig{SkipRuntime: true} + cfg.Directives["extends"] = config.DirectiveConfig{SkipRuntime: true} + + return nil +} + +func (f *federation) InjectSourceEarly() *ast.Source { + return &ast.Source{ + Name: "federation/directives.graphql", + Input: ` +scalar _Any +scalar _FieldSet + +directive @external on FIELD_DEFINITION +directive @requires(fields: _FieldSet!) on FIELD_DEFINITION +directive @provides(fields: _FieldSet!) on FIELD_DEFINITION +directive @key(fields: _FieldSet!) on OBJECT | INTERFACE +directive @extends on OBJECT +`, + BuiltIn: true, + } +} + +// InjectSources creates a GraphQL Entity type with all +// the fields that had the @key directive +func (f *federation) InjectSourceLate(schema *ast.Schema) *ast.Source { + f.setEntities(schema) + + entities := "" + resolvers := "" + for i, e := range f.Entities { + if i != 0 { + entities += " | " + } + entities += e.Name + + if e.ResolverName != "" { + resolverArgs := "" + for _, field := range e.KeyFields { + resolverArgs += fmt.Sprintf("%s: %s,", field.Field.Name, field.Field.Type.String()) + } + resolvers += fmt.Sprintf("\t%s(%s): %s!\n", e.ResolverName, resolverArgs, e.Def.Name) + } + + } + + if len(f.Entities) == 0 { + // It's unusual for a service not to have any entities, but + // possible if it only exports top-level queries and mutations. + return nil + } + + // resolvers can be empty if a service defines only "empty + // extend" types. This should be rare. + if resolvers != "" { + resolvers = ` +# fake type to build resolver interfaces for users to implement +type Entity { + ` + resolvers + ` +} +` + } + + return &ast.Source{ + Name: "federation/entity.graphql", + BuiltIn: true, + Input: ` +# a union of all types that use the @key directive +union _Entity = ` + entities + ` +` + resolvers + ` +type _Service { + sdl: String +} + +extend type Query { + _entities(representations: [_Any!]!): [_Entity]! + _service: _Service! +} +`, + } +} + +// Entity represents a federated type +// that was declared in the GQL schema. +type Entity struct { + Name string // The same name as the type declaration + KeyFields []*KeyField // The fields declared in @key. + ResolverName string // The resolver name, such as FindUserByID + Def *ast.Definition + Requires []*Requires +} + +type KeyField struct { + Field *ast.FieldDefinition + TypeReference *config.TypeReference // The Go representation of that field type +} + +// Requires represents an @requires clause +type Requires struct { + Name string // the name of the field + Fields []*RequireField // the name of the sibling fields +} + +// RequireField is similar to an entity but it is a field not +// an object +type RequireField struct { + Name string // The same name as the type declaration + NameGo string // The Go struct field name + TypeReference *config.TypeReference // The Go representation of that field type +} + +func (e *Entity) allFieldsAreExternal() bool { + for _, field := range e.Def.Fields { + if field.Directives.ForName("external") == nil { + return false + } + } + return true +} + +func (f *federation) GenerateCode(data *codegen.Data) error { + if len(f.Entities) > 0 { + if data.Objects.ByName("Entity") != nil { + data.Objects.ByName("Entity").Root = true + } + for _, e := range f.Entities { + obj := data.Objects.ByName(e.Def.Name) + for _, field := range obj.Fields { + // Storing key fields in a slice rather than a map + // to preserve insertion order at the tradeoff of higher + // lookup complexity. + keyField := f.getKeyField(e.KeyFields, field.Name) + if keyField != nil { + keyField.TypeReference = field.TypeReference + } + for _, r := range e.Requires { + for _, rf := range r.Fields { + if rf.Name == field.Name { + rf.TypeReference = field.TypeReference + rf.NameGo = field.GoFieldName + } + } + } + } + } + } + + return templates.Render(templates.Options{ + PackageName: data.Config.Federation.Package, + Filename: data.Config.Federation.Filename, + Data: f, + GeneratedHeader: true, + Packages: data.Config.Packages, + }) +} + +func (f *federation) getKeyField(keyFields []*KeyField, fieldName string) *KeyField { + for _, field := range keyFields { + if field.Field.Name == fieldName { + return field + } + } + return nil +} + +func (f *federation) setEntities(schema *ast.Schema) { + for _, schemaType := range schema.Types { + if schemaType.Kind == ast.Object { + dir := schemaType.Directives.ForName("key") // TODO: interfaces + if dir != nil { + if len(dir.Arguments) > 1 { + panic("Multiple arguments are not currently supported in @key declaration.") + } + fieldName := dir.Arguments[0].Value.Raw // TODO: multiple arguments + if strings.Contains(fieldName, "{") { + panic("Nested fields are not currently supported in @key declaration.") + } + + requires := []*Requires{} + for _, f := range schemaType.Fields { + dir := f.Directives.ForName("requires") + if dir == nil { + continue + } + fields := strings.Split(dir.Arguments[0].Value.Raw, " ") + requireFields := []*RequireField{} + for _, f := range fields { + requireFields = append(requireFields, &RequireField{ + Name: f, + }) + } + requires = append(requires, &Requires{ + Name: f.Name, + Fields: requireFields, + }) + } + + fieldNames := strings.Split(fieldName, " ") + keyFields := make([]*KeyField, len(fieldNames)) + resolverName := fmt.Sprintf("find%sBy", schemaType.Name) + for i, f := range fieldNames { + field := schemaType.Fields.ForName(f) + + keyFields[i] = &KeyField{Field: field} + if i > 0 { + resolverName += "And" + } + resolverName += templates.ToGo(f) + + } + + e := &Entity{ + Name: schemaType.Name, + KeyFields: keyFields, + Def: schemaType, + ResolverName: resolverName, + Requires: requires, + } + // If our schema has a field with a type defined in + // another service, then we need to define an "empty + // extend" of that type in this service, so this service + // knows what the type is like. But the graphql-server + // will never ask us to actually resolve this "empty + // extend", so we don't require a resolver function for + // it. (Well, it will never ask in practice; it's + // unclear whether the spec guarantees this. See + // https://github.com/apollographql/apollo-server/issues/3852 + // ). Example: + // type MyType { + // myvar: TypeDefinedInOtherService + // } + // // Federation needs this type, but + // // it doesn't need a resolver for it! + // extend TypeDefinedInOtherService @key(fields: "id") { + // id: ID @external + // } + if e.allFieldsAreExternal() { + e.ResolverName = "" + } + + f.Entities = append(f.Entities, e) + } + } + } + + // make sure order remains stable across multiple builds + sort.Slice(f.Entities, func(i, j int) bool { + return f.Entities[i].Name < f.Entities[j].Name + }) +} diff --git a/vendor/github.com/99designs/gqlgen/plugin/federation/federation.gotpl b/vendor/github.com/99designs/gqlgen/plugin/federation/federation.gotpl new file mode 100644 index 000000000..96c25e857 --- /dev/null +++ b/vendor/github.com/99designs/gqlgen/plugin/federation/federation.gotpl @@ -0,0 +1,69 @@ +{{ reserveImport "context" }} +{{ reserveImport "errors" }} +{{ reserveImport "fmt" }} +{{ reserveImport "strings" }} + +{{ reserveImport "github.com/99designs/gqlgen/plugin/federation/fedruntime" }} + +func (ec *executionContext) __resolve__service(ctx context.Context) (fedruntime.Service, error) { + if ec.DisableIntrospection { + return fedruntime.Service{}, errors.New("federated introspection disabled") + } + + var sdl []string + + for _, src := range sources { + if src.BuiltIn { + continue + } + sdl = append(sdl, src.Input) + } + + return fedruntime.Service{ + SDL: strings.Join(sdl, "\n"), + }, nil +} + +{{if .Entities}} +func (ec *executionContext) __resolve_entities(ctx context.Context, representations []map[string]interface{}) ([]fedruntime.Entity, error) { + list := []fedruntime.Entity{} + for _, rep := range representations { + typeName, ok := rep["__typename"].(string) + if !ok { + return nil, errors.New("__typename must be an existing string") + } + switch typeName { + {{ range .Entities }} + {{ if .ResolverName }} + case "{{.Def.Name}}": + {{ range $i, $keyField := .KeyFields -}} + id{{$i}}, err := ec.{{.TypeReference.UnmarshalFunc}}(ctx, rep["{{$keyField.Field.Name}}"]) + if err != nil { + return nil, errors.New(fmt.Sprintf("Field %s undefined in schema.", "{{$keyField.Field.Name}}")) + } + {{end}} + + entity, err := ec.resolvers.Entity().{{.ResolverName | go}}(ctx, + {{ range $i, $_ := .KeyFields -}} id{{$i}}, {{end}}) + if err != nil { + return nil, err + } + + {{ range .Requires }} + {{ range .Fields}} + entity.{{.NameGo}}, err = ec.{{.TypeReference.UnmarshalFunc}}(ctx, rep["{{.Name}}"]) + if err != nil { + return nil, err + } + {{ end }} + {{ end }} + list = append(list, entity) + {{ end }} + {{ end }} + default: + return nil, errors.New("unknown type: "+typeName) + } + } + return list, nil +} +{{end}} diff --git a/vendor/github.com/99designs/gqlgen/plugin/modelgen/models.go b/vendor/github.com/99designs/gqlgen/plugin/modelgen/models.go index bb400f1b7..e0ca18663 100644 --- a/vendor/github.com/99designs/gqlgen/plugin/modelgen/models.go +++ b/vendor/github.com/99designs/gqlgen/plugin/modelgen/models.go @@ -7,11 +7,16 @@ import ( "github.com/99designs/gqlgen/codegen/config" "github.com/99designs/gqlgen/codegen/templates" - "github.com/99designs/gqlgen/internal/code" "github.com/99designs/gqlgen/plugin" - "github.com/vektah/gqlparser/ast" + "github.com/vektah/gqlparser/v2/ast" ) +type BuildMutateHook = func(b *ModelBuild) *ModelBuild + +func defaultBuildMutateHook(b *ModelBuild) *ModelBuild { + return b +} + type ModelBuild struct { PackageName string Interfaces []*Interface @@ -51,10 +56,14 @@ type EnumValue struct { } func New() plugin.Plugin { - return &Plugin{} + return &Plugin{ + MutateHook: defaultBuildMutateHook, + } } -type Plugin struct{} +type Plugin struct { + MutateHook BuildMutateHook +} var _ plugin.ConfigMutator = &Plugin{} @@ -63,31 +72,16 @@ func (m *Plugin) Name() string { } func (m *Plugin) MutateConfig(cfg *config.Config) error { - if err := cfg.Check(); err != nil { - return err - } - - schema, _, err := cfg.LoadSchema() - if err != nil { - return err - } - - cfg.InjectBuiltins(schema) - - binder, err := cfg.NewBinder(schema) - if err != nil { - return err - } + binder := cfg.NewBinder() b := &ModelBuild{ PackageName: cfg.Model.Package, } - for _, schemaType := range schema.Types { + for _, schemaType := range cfg.Schema.Types { if cfg.Models.UserDefined(schemaType.Name) { continue } - switch schemaType.Kind { case ast.Interface, ast.Union: it := &Interface{ @@ -97,25 +91,24 @@ func (m *Plugin) MutateConfig(cfg *config.Config) error { b.Interfaces = append(b.Interfaces, it) case ast.Object, ast.InputObject: - if schemaType == schema.Query || schemaType == schema.Mutation || schemaType == schema.Subscription { + if schemaType == cfg.Schema.Query || schemaType == cfg.Schema.Mutation || schemaType == cfg.Schema.Subscription { continue } it := &Object{ Description: schemaType.Description, Name: schemaType.Name, } - - for _, implementor := range schema.GetImplements(schemaType) { + for _, implementor := range cfg.Schema.GetImplements(schemaType) { it.Implements = append(it.Implements, implementor.Name) } for _, field := range schemaType.Fields { var typ types.Type - fieldDef := schema.Types[field.Type.Name()] + fieldDef := cfg.Schema.Types[field.Type.Name()] if cfg.Models.UserDefined(field.Type.Name()) { - pkg, typeName := code.PkgAndType(cfg.Models[field.Type.Name()].Model[0]) - typ, err = binder.FindType(pkg, typeName) + var err error + typ, err = binder.FindTypeFromName(cfg.Models[field.Type.Name()].Model[0]) if err != nil { return err } @@ -196,7 +189,6 @@ func (m *Plugin) MutateConfig(cfg *config.Config) error { b.Scalars = append(b.Scalars, schemaType.Name) } } - sort.Slice(b.Enums, func(i, j int) bool { return b.Enums[i].Name < b.Enums[j].Name }) sort.Slice(b.Models, func(i, j int) bool { return b.Models[i].Name < b.Models[j].Name }) sort.Slice(b.Interfaces, func(i, j int) bool { return b.Interfaces[i].Name < b.Interfaces[j].Name }) @@ -214,15 +206,20 @@ func (m *Plugin) MutateConfig(cfg *config.Config) error { cfg.Models.Add(it, "github.com/99designs/gqlgen/graphql.String") } - if len(b.Models) == 0 && len(b.Enums) == 0 { + if len(b.Models) == 0 && len(b.Enums) == 0 && len(b.Interfaces) == 0 && len(b.Scalars) == 0 { return nil } + if m.MutateHook != nil { + b = m.MutateHook(b) + } + return templates.Render(templates.Options{ PackageName: cfg.Model.Package, Filename: cfg.Model.Filename, Data: b, GeneratedHeader: true, + Packages: cfg.Packages, }) } diff --git a/vendor/github.com/99designs/gqlgen/plugin/modelgen/models.gotpl b/vendor/github.com/99designs/gqlgen/plugin/modelgen/models.gotpl index 6df200ee0..e58d5b21a 100644 --- a/vendor/github.com/99designs/gqlgen/plugin/modelgen/models.gotpl +++ b/vendor/github.com/99designs/gqlgen/plugin/modelgen/models.gotpl @@ -7,8 +7,8 @@ {{ reserveImport "errors" }} {{ reserveImport "bytes" }} -{{ reserveImport "github.com/vektah/gqlparser" }} -{{ reserveImport "github.com/vektah/gqlparser/ast" }} +{{ reserveImport "github.com/vektah/gqlparser/v2" }} +{{ reserveImport "github.com/vektah/gqlparser/v2/ast" }} {{ reserveImport "github.com/99designs/gqlgen/graphql" }} {{ reserveImport "github.com/99designs/gqlgen/graphql/introspection" }} @@ -36,7 +36,7 @@ {{- end}} {{ range $enum := .Enums }} - {{ with .Description|go }} {{.|prefixLines "// "}} {{end}} + {{ with .Description }} {{.|prefixLines "// "}} {{end}} type {{.Name|go }} string const ( {{- range $value := .Values}} diff --git a/vendor/github.com/99designs/gqlgen/plugin/plugin.go b/vendor/github.com/99designs/gqlgen/plugin/plugin.go index a84bfd327..7de36bd8c 100644 --- a/vendor/github.com/99designs/gqlgen/plugin/plugin.go +++ b/vendor/github.com/99designs/gqlgen/plugin/plugin.go @@ -5,6 +5,7 @@ package plugin import ( "github.com/99designs/gqlgen/codegen" "github.com/99designs/gqlgen/codegen/config" + "github.com/vektah/gqlparser/v2/ast" ) type Plugin interface { @@ -18,3 +19,13 @@ type ConfigMutator interface { type CodeGenerator interface { GenerateCode(cfg *codegen.Data) error } + +// EarlySourceInjector is used to inject things that are required for user schema files to compile. +type EarlySourceInjector interface { + InjectSourceEarly() *ast.Source +} + +// LateSourceInjector is used to inject more sources, after we have loaded the users schema. +type LateSourceInjector interface { + InjectSourceLate(schema *ast.Schema) *ast.Source +} diff --git a/vendor/github.com/99designs/gqlgen/plugin/resolvergen/resolver.go b/vendor/github.com/99designs/gqlgen/plugin/resolvergen/resolver.go index 00a6d5c9d..204801efb 100644 --- a/vendor/github.com/99designs/gqlgen/plugin/resolvergen/resolver.go +++ b/vendor/github.com/99designs/gqlgen/plugin/resolvergen/resolver.go @@ -1,11 +1,14 @@ package resolvergen import ( - "log" "os" + "path/filepath" + "strings" "github.com/99designs/gqlgen/codegen" + "github.com/99designs/gqlgen/codegen/config" "github.com/99designs/gqlgen/codegen/templates" + "github.com/99designs/gqlgen/internal/rewrite" "github.com/99designs/gqlgen/plugin" "github.com/pkg/errors" ) @@ -19,35 +22,186 @@ type Plugin struct{} var _ plugin.CodeGenerator = &Plugin{} func (m *Plugin) Name() string { - return "resovlergen" + return "resolvergen" } + func (m *Plugin) GenerateCode(data *codegen.Data) error { if !data.Config.Resolver.IsDefined() { return nil } + switch data.Config.Resolver.Layout { + case config.LayoutSingleFile: + return m.generateSingleFile(data) + case config.LayoutFollowSchema: + return m.generatePerSchema(data) + } + + return nil +} + +func (m *Plugin) generateSingleFile(data *codegen.Data) error { + file := File{} + + if _, err := os.Stat(data.Config.Resolver.Filename); err == nil { + // file already exists and we dont support updating resolvers with layout = single so just return + return nil + } + + for _, o := range data.Objects { + if o.HasResolvers() { + file.Objects = append(file.Objects, o) + } + for _, f := range o.Fields { + if !f.IsResolver { + continue + } + + resolver := Resolver{o, f, `panic("not implemented")`} + file.Resolvers = append(file.Resolvers, &resolver) + } + } + resolverBuild := &ResolverBuild{ - Data: data, + File: &file, PackageName: data.Config.Resolver.Package, ResolverType: data.Config.Resolver.Type, + HasRoot: true, } - filename := data.Config.Resolver.Filename - if _, err := os.Stat(filename); os.IsNotExist(errors.Cause(err)) { - return templates.Render(templates.Options{ + return templates.Render(templates.Options{ + PackageName: data.Config.Resolver.Package, + FileNotice: `// THIS CODE IS A STARTING POINT ONLY. IT WILL NOT BE UPDATED WITH SCHEMA CHANGES.`, + Filename: data.Config.Resolver.Filename, + Data: resolverBuild, + Packages: data.Config.Packages, + }) +} + +func (m *Plugin) generatePerSchema(data *codegen.Data) error { + rewriter, err := rewrite.New(data.Config.Resolver.Dir()) + if err != nil { + return err + } + + files := map[string]*File{} + + for _, o := range data.Objects { + if o.HasResolvers() { + fn := gqlToResolverName(data.Config.Resolver.Dir(), o.Position.Src.Name, data.Config.Resolver.FilenameTemplate) + if files[fn] == nil { + files[fn] = &File{} + } + + rewriter.MarkStructCopied(templates.LcFirst(o.Name) + templates.UcFirst(data.Config.Resolver.Type)) + rewriter.GetMethodBody(data.Config.Resolver.Type, o.Name) + files[fn].Objects = append(files[fn].Objects, o) + } + for _, f := range o.Fields { + if !f.IsResolver { + continue + } + + structName := templates.LcFirst(o.Name) + templates.UcFirst(data.Config.Resolver.Type) + implementation := strings.TrimSpace(rewriter.GetMethodBody(structName, f.GoFieldName)) + if implementation == "" { + implementation = `panic(fmt.Errorf("not implemented"))` + } + + resolver := Resolver{o, f, implementation} + fn := gqlToResolverName(data.Config.Resolver.Dir(), f.Position.Src.Name, data.Config.Resolver.FilenameTemplate) + if files[fn] == nil { + files[fn] = &File{} + } + + files[fn].Resolvers = append(files[fn].Resolvers, &resolver) + } + } + + for filename, file := range files { + file.imports = rewriter.ExistingImports(filename) + file.RemainingSource = rewriter.RemainingSource(filename) + } + + for filename, file := range files { + resolverBuild := &ResolverBuild{ + File: file, + PackageName: data.Config.Resolver.Package, + ResolverType: data.Config.Resolver.Type, + } + + err := templates.Render(templates.Options{ PackageName: data.Config.Resolver.Package, - Filename: data.Config.Resolver.Filename, - Data: resolverBuild, + FileNotice: ` + // This file will be automatically regenerated based on the schema, any resolver implementations + // will be copied through when generating and any unknown code will be moved to the end.`, + Filename: filename, + Data: resolverBuild, + Packages: data.Config.Packages, }) + if err != nil { + return err + } } - log.Printf("Skipped resolver: %s already exists\n", filename) + if _, err := os.Stat(data.Config.Resolver.Filename); os.IsNotExist(errors.Cause(err)) { + err := templates.Render(templates.Options{ + PackageName: data.Config.Resolver.Package, + FileNotice: ` + // This file will not be regenerated automatically. + // + // It serves as dependency injection for your app, add any dependencies you require here.`, + Template: `type {{.}} struct {}`, + Filename: data.Config.Resolver.Filename, + Data: data.Config.Resolver.Type, + Packages: data.Config.Packages, + }) + if err != nil { + return err + } + } return nil } type ResolverBuild struct { - *codegen.Data - + *File + HasRoot bool PackageName string ResolverType string } + +type File struct { + // These are separated because the type definition of the resolver object may live in a different file from the + //resolver method implementations, for example when extending a type in a different graphql schema file + Objects []*codegen.Object + Resolvers []*Resolver + imports []rewrite.Import + RemainingSource string +} + +func (f *File) Imports() string { + for _, imp := range f.imports { + if imp.Alias == "" { + _, _ = templates.CurrentImports.Reserve(imp.ImportPath) + } else { + _, _ = templates.CurrentImports.Reserve(imp.ImportPath, imp.Alias) + } + } + return "" +} + +type Resolver struct { + Object *codegen.Object + Field *codegen.Field + Implementation string +} + +func gqlToResolverName(base string, gqlname, filenameTmpl string) string { + gqlname = filepath.Base(gqlname) + ext := filepath.Ext(gqlname) + if filenameTmpl == "" { + filenameTmpl = "{name}.resolvers.go" + } + filename := strings.ReplaceAll(filenameTmpl, "{name}", strings.TrimSuffix(gqlname, ext)) + return filepath.Join(base, filename) +} diff --git a/vendor/github.com/99designs/gqlgen/plugin/resolvergen/resolver.gotpl b/vendor/github.com/99designs/gqlgen/plugin/resolvergen/resolver.gotpl index 7d95e6903..543bf136e 100644 --- a/vendor/github.com/99designs/gqlgen/plugin/resolvergen/resolver.gotpl +++ b/vendor/github.com/99designs/gqlgen/plugin/resolvergen/resolver.gotpl @@ -1,5 +1,3 @@ -// THIS CODE IS A STARTING POINT ONLY. IT WILL NOT BE UPDATED WITH SCHEMA CHANGES. - {{ reserveImport "context" }} {{ reserveImport "fmt" }} {{ reserveImport "io" }} @@ -9,32 +7,39 @@ {{ reserveImport "errors" }} {{ reserveImport "bytes" }} -{{ reserveImport "github.com/99designs/gqlgen/handler" }} -{{ reserveImport "github.com/vektah/gqlparser" }} -{{ reserveImport "github.com/vektah/gqlparser/ast" }} +{{ reserveImport "github.com/vektah/gqlparser/v2" }} +{{ reserveImport "github.com/vektah/gqlparser/v2/ast" }} {{ reserveImport "github.com/99designs/gqlgen/graphql" }} {{ reserveImport "github.com/99designs/gqlgen/graphql/introspection" }} -type {{.ResolverType}} struct {} +{{ .Imports }} + +{{ if .HasRoot }} + type {{.ResolverType}} struct {} +{{ end }} + +{{ range $resolver := .Resolvers -}} + func (r *{{lcFirst $resolver.Object.Name}}{{ucFirst $.ResolverType}}) {{$resolver.Field.GoFieldName}}{{ $resolver.Field.ShortResolverDeclaration }} { + {{ $resolver.Implementation }} + } -{{ range $object := .Objects -}} - {{- if $object.HasResolvers -}} - func (r *{{$.ResolverType}}) {{$object.Name}}() {{ $object.ResolverInterface | ref }} { - return &{{lcFirst $object.Name}}Resolver{r} - } - {{ end -}} {{ end }} {{ range $object := .Objects -}} - {{- if $object.HasResolvers -}} - type {{lcFirst $object.Name}}Resolver struct { *Resolver } - - {{ range $field := $object.Fields -}} - {{- if $field.IsResolver -}} - func (r *{{lcFirst $object.Name}}Resolver) {{$field.GoFieldName}}{{ $field.ShortResolverDeclaration }} { - panic("not implemented") - } - {{ end -}} - {{ end -}} - {{ end -}} + // {{$object.Name}} returns {{ $object.ResolverInterface | ref }} implementation. + func (r *{{$.ResolverType}}) {{$object.Name}}() {{ $object.ResolverInterface | ref }} { return &{{lcFirst $object.Name}}{{ucFirst $.ResolverType}}{r} } +{{ end }} + +{{ range $object := .Objects -}} + type {{lcFirst $object.Name}}{{ucFirst $.ResolverType}} struct { *{{$.ResolverType}} } +{{ end }} + +{{ if (ne .RemainingSource "") }} + // !!! WARNING !!! + // The code below was going to be deleted when updating resolvers. It has been copied here so you have + // one last chance to move it out of harms way if you want. There are two reasons this happens: + // - When renaming or deleting a resolver the old code will be put in here. You can safely delete + // it when you're done. + // - You have helper methods in this file. Move them out to keep these resolver files clean. + {{ .RemainingSource }} {{ end }} diff --git a/vendor/github.com/99designs/gqlgen/plugin/servergen/server.go b/vendor/github.com/99designs/gqlgen/plugin/servergen/server.go index 22289c025..029c9ae39 100644 --- a/vendor/github.com/99designs/gqlgen/plugin/servergen/server.go +++ b/vendor/github.com/99designs/gqlgen/plugin/servergen/server.go @@ -34,6 +34,7 @@ func (m *Plugin) GenerateCode(data *codegen.Data) error { PackageName: "main", Filename: m.filename, Data: serverBuild, + Packages: data.Config.Packages, }) } diff --git a/vendor/github.com/99designs/gqlgen/plugin/servergen/server.gotpl b/vendor/github.com/99designs/gqlgen/plugin/servergen/server.gotpl index fca71c53c..a3ae2a877 100644 --- a/vendor/github.com/99designs/gqlgen/plugin/servergen/server.gotpl +++ b/vendor/github.com/99designs/gqlgen/plugin/servergen/server.gotpl @@ -2,7 +2,8 @@ {{ reserveImport "log" }} {{ reserveImport "net/http" }} {{ reserveImport "os" }} -{{ reserveImport "github.com/99designs/gqlgen/handler" }} +{{ reserveImport "github.com/99designs/gqlgen/graphql/playground" }} +{{ reserveImport "github.com/99designs/gqlgen/graphql/handler" }} const defaultPort = "8080" @@ -12,8 +13,10 @@ func main() { port = defaultPort } - http.Handle("/", handler.Playground("GraphQL playground", "/query")) - http.Handle("/query", handler.GraphQL({{ lookupImport .ExecPackageName }}.NewExecutableSchema({{ lookupImport .ExecPackageName}}.Config{Resolvers: &{{ lookupImport .ResolverPackageName}}.Resolver{}}))) + srv := handler.NewDefaultServer({{ lookupImport .ExecPackageName }}.NewExecutableSchema({{ lookupImport .ExecPackageName}}.Config{Resolvers: &{{ lookupImport .ResolverPackageName}}.Resolver{}})) + + http.Handle("/", playground.Handler("GraphQL playground", "/query")) + http.Handle("/query", srv) log.Printf("connect to http://localhost:%s/ for GraphQL playground", port) log.Fatal(http.ListenAndServe(":" + port, nil)) diff --git a/vendor/github.com/99designs/gqlgen/tools.go b/vendor/github.com/99designs/gqlgen/tools.go index 912fc0d63..e63a71a80 100644 --- a/vendor/github.com/99designs/gqlgen/tools.go +++ b/vendor/github.com/99designs/gqlgen/tools.go @@ -2,4 +2,7 @@ package main -import _ "github.com/vektah/dataloaden" +import ( + _ "github.com/matryer/moq" + _ "github.com/vektah/dataloaden" +) diff --git a/vendor/github.com/Yamashou/gqlgenc/.gitignore b/vendor/github.com/Yamashou/gqlgenc/.gitignore new file mode 100644 index 000000000..15b3e741f --- /dev/null +++ b/vendor/github.com/Yamashou/gqlgenc/.gitignore @@ -0,0 +1,7 @@ +/.gqlgenc.yml +/models_gen.go +/**/.graphqlconfig +/schema.graphql +/client.go +/query/ +/.idea/ diff --git a/vendor/github.com/Yamashou/gqlgenc/.golangci.yml b/vendor/github.com/Yamashou/gqlgenc/.golangci.yml new file mode 100644 index 000000000..941fe3034 --- /dev/null +++ b/vendor/github.com/Yamashou/gqlgenc/.golangci.yml @@ -0,0 +1,65 @@ +# See https://github.com/golangci/golangci-lint/blob/master/.golangci.example.yml +run: +linters-settings: + govet: + enable-all: true + disable: + - shadow + unused: + check-exported: true + unparam: + check-exported: true + varcheck: + exported-fields: true + structcheck: + exported-fields: true + nakedret: + max-func-lines: 1 + +linters: + enable-all: true + disable: + - testpackage + - nestif + - godot + - wsl + - lll + - dupl + - funlen + - gochecknoinits + - gochecknoglobals + - godox + - maligned + - gocognit + - gocyclo + - interfacer + - gomnd + - goerr113 + fast: false + +issues: + exclude-rules: + # Test + - path: _test\.go + text: "Using the variable on range scope `tt` in function literal" + linters: + - scopelint + - path: _test\.go + linters: + - unused + - structcheck + - path: introspection/type.go + linters: + - structcheck # These types fits IntrospectionQuery + - path: config/config.go + text: "`Query` is unused" # used in main.go + linters: + - structcheck + - path: graphqljson/graphql.go + text: "`Extensions` is unused" # used in line 48 + linters: + - structcheck + - path: introspection/query.go + text: "`Introspection` is unused" # used in config/config.go + linters: + - varcheck diff --git a/vendor/github.com/Yamashou/gqlgenc/LICENSE b/vendor/github.com/Yamashou/gqlgenc/LICENSE new file mode 100644 index 000000000..e16053d8f --- /dev/null +++ b/vendor/github.com/Yamashou/gqlgenc/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Yamashou + +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. diff --git a/vendor/github.com/Yamashou/gqlgenc/Makefile b/vendor/github.com/Yamashou/gqlgenc/Makefile new file mode 100644 index 000000000..453c30b40 --- /dev/null +++ b/vendor/github.com/Yamashou/gqlgenc/Makefile @@ -0,0 +1,10 @@ +MAKEFLAGS=--no-builtin-rules --no-builtin-variables --always-make + +fmt: + gofumports -local github.com/Yamashou/gqlgenc -w . + +lint: + golangci-lint run + +test: + go test -v ./... diff --git a/vendor/github.com/Yamashou/gqlgenc/README.md b/vendor/github.com/Yamashou/gqlgenc/README.md new file mode 100644 index 000000000..598017976 --- /dev/null +++ b/vendor/github.com/Yamashou/gqlgenc/README.md @@ -0,0 +1,118 @@ +# gqlgenc + +## What is gqlgenc ? + +This is Go library for building GraphQL client with [gqlgen](https://github.com/99designs/gqlgen) + +## Motivation + +Now, if you build GraphQL api client for Go, have choice: + + - [github.com/shurcooL/graphql](https://github.com/shurcooL/graphql) + - [github.com/machinebox/graphql](https://github.com/machinebox/graphql) + +These libraries are very simple and easy to handle. +However, as I work with [gqlgen](https://github.com/99designs/gqlgen) and [graphql-code-generator](https://graphql-code-generator.com/) every day, I find out the beauty of automatic generation. +So I want to automatically generate types. + +## Installation + +```shell script +go get -u github.com/Yamashou/gqlgenc +``` + +## How to use + +### Client Codes Only + +gqlgenc base is gqlgen with [plugins](https://gqlgen.com/reference/plugins/). So the setting is yaml in each format. +gqlgenc can be configured using a .gqlgenc.yml file, + +```yaml + +model: + package: generated + filename: ./models_gen.go # https://github.com/99designs/gqlgen/tree/master/plugin/modelgen +client: + package: generated + filename: ./client.go # Where should any generated client go? +models: + Int: + model: github.com/99designs/gqlgen/graphql.Int64 + Date: + model: github.com/99designs/gqlgen/graphql.Time +endpoint: + url: https://api.annict.com/graphql # Where do you want to send your request? + headers: # If you need header for getting introspection query, set it + Authorization: "Bearer ${ANNICT_KEY}" # support environment variables +query: + - "./query/*.graphql" # Where are all the query files located? +``` + +Execute the following command on same directory for .gqlgenc.yaml + +```shell script +gqlgenc +``` + +### With gqlgen + +Do this when creating a server and client for Go. +You create your own entrypoint for gqlgen. +This use case is very useful for testing your server. + + +```go +package main + +import ( + "fmt" + "os" + + "github.com/Yamashou/gqlgenc/clientgen" + + "github.com/99designs/gqlgen/api" + "github.com/99designs/gqlgen/codegen/config" +) + +func main() { + cfg, err := config.LoadConfigFromDefaultLocations() + if err != nil { + fmt.Fprintln(os.Stderr, "failed to load config", err.Error()) + os.Exit(2) + } + queries := []string{"client.query", "fragemt.query"} + clientPackage := config.PackageConfig{ + Filename: "./client.go", + Package: "gen", + } + + clientPlugin := clientgen.New(queries, clientPackage) + err = api.Generate(cfg, + api.AddPlugin(clientPlugin), + ) + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(3) + } +} +``` + +## Documents + +- [How to configure gqlgen using gqlgen.yml](https://gqlgen.com/config/) +- [How to write plugins for gqlgen](https://gqlgen.com/reference/plugins/) + + +## Comments + +### Japanese Comments +These codes have Japanese comments. Replace with English. + +### Subscription + +This client does not support subscription. If you need a subscription, please create an issue or pull request. + +### Pre-conditions + +[clientgen](https://github.com/Yamashou/gqlgenc/tree/master/clientgen) is created based on [modelgen](https://github.com/99designs/gqlgen/tree/master/plugin/modelgen). So if you don't have a modelgen, it may be a mysterious move. diff --git a/vendor/github.com/Yamashou/gqlgenc/client/client.go b/vendor/github.com/Yamashou/gqlgenc/client/client.go new file mode 100644 index 000000000..94cea8971 --- /dev/null +++ b/vendor/github.com/Yamashou/gqlgenc/client/client.go @@ -0,0 +1,88 @@ +package client + +import ( + "bytes" + "context" + "encoding/json" + "net/http" + + "github.com/Yamashou/gqlgenc/graphqljson" + "golang.org/x/xerrors" +) + +type HTTPRequestOption func(req *http.Request) + +type Client struct { + Client *http.Client + BaseURL string + HTTPRequestOptions []HTTPRequestOption +} + +// Request represents an outgoing GraphQL request +type Request struct { + Query string `json:"query"` + Variables map[string]interface{} `json:"variables,omitempty"` + OperationName string `json:"operationName,omitempty"` +} + +func NewClient(client *http.Client, baseURL string, options ...HTTPRequestOption) *Client { + return &Client{ + Client: client, + BaseURL: baseURL, + HTTPRequestOptions: options, + } +} + +func (c *Client) newRequest(ctx context.Context, query string, vars map[string]interface{}, httpRequestOptions []HTTPRequestOption) (*http.Request, error) { + r := &Request{ + Query: query, + Variables: vars, + OperationName: "", + } + + requestBody, err := json.Marshal(r) + if err != nil { + return nil, xerrors.Errorf("encode: %w", err) + } + + req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.BaseURL, bytes.NewBuffer(requestBody)) + if err != nil { + return nil, xerrors.Errorf("create request struct failed: %w", err) + } + + for _, httpRequestOption := range c.HTTPRequestOptions { + httpRequestOption(req) + } + for _, httpRequestOption := range httpRequestOptions { + httpRequestOption(req) + } + + return req, nil +} + +// Post sends a http POST request to the graphql endpoint with the given query then unpacks +// the response into the given object. +func (c *Client) Post(ctx context.Context, query string, respData interface{}, vars map[string]interface{}, httpRequestOptions ...HTTPRequestOption) error { + req, err := c.newRequest(ctx, query, vars, httpRequestOptions) + if err != nil { + return xerrors.Errorf("don't create request: %w", err) + } + req.Header.Set("Content-Type", "application/json; charset=utf-8") + req.Header.Set("Accept", "application/json; charset=utf-8") + + resp, err := c.Client.Do(req) + if err != nil { + return xerrors.Errorf("request failed: %w", err) + } + defer resp.Body.Close() + + if err := graphqljson.Unmarshal(resp.Body, respData); err != nil { + return err + } + + if resp.StatusCode < 200 || 299 < resp.StatusCode { + return xerrors.Errorf("http status code: %v", resp.StatusCode) + } + + return nil +} diff --git a/vendor/github.com/Yamashou/gqlgenc/clientgen/client.go b/vendor/github.com/Yamashou/gqlgenc/clientgen/client.go new file mode 100644 index 000000000..d037ccb55 --- /dev/null +++ b/vendor/github.com/Yamashou/gqlgenc/clientgen/client.go @@ -0,0 +1,76 @@ +package clientgen + +import ( + "github.com/99designs/gqlgen/codegen/config" + "github.com/99designs/gqlgen/plugin" + "golang.org/x/xerrors" +) + +var _ plugin.ConfigMutator = &Plugin{} + +type Plugin struct { + queryFilePaths []string + Client config.PackageConfig +} + +func New(queryFilePaths []string, client config.PackageConfig) *Plugin { + return &Plugin{ + queryFilePaths: queryFilePaths, + Client: client, + } +} + +func (p *Plugin) Name() string { + return "clientgen" +} + +func (p *Plugin) MutateConfig(cfg *config.Config) error { + querySources, err := LoadQuerySources(p.queryFilePaths) + if err != nil { + return xerrors.Errorf("load query sources failed: %w", err) + } + + // 1. 全体のqueryDocumentを1度にparse + // 1. Parse document from source of query + queryDocument, err := ParseQueryDocuments(cfg.Schema, querySources) + if err != nil { + return xerrors.Errorf(": %w", err) + } + + // 2. OperationごとのqueryDocumentを作成 + // 2. Separate documents for each operation + queryDocuments, err := QueryDocumentsByOperations(cfg.Schema, queryDocument.Operations) + if err != nil { + return xerrors.Errorf("parse query document failed: %w", err) + } + + // 3. テンプレートと情報ソースを元にコード生成 + // 3. Generate code from template and document source + sourceGenerator := NewSourceGenerator(cfg, p.Client) + source := NewSource(cfg.Schema, queryDocument, sourceGenerator) + query, err := source.Query() + if err != nil { + return xerrors.Errorf("generating query object: %w", err) + } + + mutation, err := source.Mutation() + if err != nil { + return xerrors.Errorf("generating mutation object: %w", err) + } + + fragments, err := source.Fragments() + if err != nil { + return xerrors.Errorf("generating fragment failed: %w", err) + } + + operationResponses, err := source.OperationResponses() + if err != nil { + return xerrors.Errorf("generating operation response failed: %w", err) + } + + if err := RenderTemplate(cfg, query, mutation, fragments, source.Operations(queryDocuments), operationResponses, p.Client); err != nil { + return xerrors.Errorf("template failed: %w", err) + } + + return nil +} diff --git a/vendor/github.com/Yamashou/gqlgenc/clientgen/query.go b/vendor/github.com/Yamashou/gqlgenc/clientgen/query.go new file mode 100644 index 000000000..ea99bc5c6 --- /dev/null +++ b/vendor/github.com/Yamashou/gqlgenc/clientgen/query.go @@ -0,0 +1,93 @@ +package clientgen + +import ( + "github.com/vektah/gqlparser/v2/ast" + "github.com/vektah/gqlparser/v2/parser" + "github.com/vektah/gqlparser/v2/validator" + "golang.org/x/xerrors" +) + +func ParseQueryDocuments(schema *ast.Schema, querySources []*ast.Source) (*ast.QueryDocument, error) { + var queryDocument ast.QueryDocument + for _, querySource := range querySources { + query, gqlerr := parser.ParseQuery(querySource) + if gqlerr != nil { + return nil, xerrors.Errorf(": %w", gqlerr) + } + + mergeQueryDocument(&queryDocument, query) + } + + if errs := validator.Validate(schema, &queryDocument); errs != nil { + return nil, xerrors.Errorf(": %w", errs) + } + + return &queryDocument, nil +} + +func mergeQueryDocument(q, other *ast.QueryDocument) { + q.Operations = append(q.Operations, other.Operations...) + q.Fragments = append(q.Fragments, other.Fragments...) +} + +func QueryDocumentsByOperations(schema *ast.Schema, operations ast.OperationList) ([]*ast.QueryDocument, error) { + queryDocuments := make([]*ast.QueryDocument, 0, len(operations)) + for _, operation := range operations { + fragments := fragmentsInOperationDefinition(operation) + + queryDocument := &ast.QueryDocument{ + Operations: ast.OperationList{operation}, + Fragments: fragments, + Position: nil, + } + + if errs := validator.Validate(schema, queryDocument); errs != nil { + return nil, xerrors.Errorf(": %w", errs) + } + + queryDocuments = append(queryDocuments, queryDocument) + } + + return queryDocuments, nil +} + +func fragmentsInOperationDefinition(operation *ast.OperationDefinition) ast.FragmentDefinitionList { + fragments := fragmentsInOperationWalker(operation.SelectionSet) + uniqueFragments := fragmentsUnique(fragments) + + return uniqueFragments +} + +func fragmentsUnique(fragments ast.FragmentDefinitionList) ast.FragmentDefinitionList { + uniqueMap := make(map[string]*ast.FragmentDefinition) + for _, fragment := range fragments { + uniqueMap[fragment.Name] = fragment + } + + uniqueFragments := make(ast.FragmentDefinitionList, 0, len(uniqueMap)) + for _, fragment := range uniqueMap { + uniqueFragments = append(uniqueFragments, fragment) + } + + return uniqueFragments +} + +func fragmentsInOperationWalker(selectionSet ast.SelectionSet) ast.FragmentDefinitionList { + var fragments ast.FragmentDefinitionList + for _, selection := range selectionSet { + var selectionSet ast.SelectionSet + switch selection := selection.(type) { + case *ast.Field: + selectionSet = selection.SelectionSet + case *ast.InlineFragment: + selectionSet = selection.SelectionSet + case *ast.FragmentSpread: + fragments = append(fragments, selection.Definition) + selectionSet = selection.Definition.SelectionSet + } + + fragments = append(fragments, fragmentsInOperationWalker(selectionSet)...) + } + + return fragments +} diff --git a/vendor/github.com/Yamashou/gqlgenc/clientgen/query_source.go b/vendor/github.com/Yamashou/gqlgenc/clientgen/query_source.go new file mode 100644 index 000000000..946b744c0 --- /dev/null +++ b/vendor/github.com/Yamashou/gqlgenc/clientgen/query_source.go @@ -0,0 +1,106 @@ +/* +Copyright (c) 2020 gqlgen authors + +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 clientgen + +import ( + "io/ioutil" + "os" + "path/filepath" + "regexp" + "strings" + + "github.com/99designs/gqlgen/codegen/config" + "github.com/vektah/gqlparser/v2/ast" + "golang.org/x/xerrors" +) + +var path2regex = strings.NewReplacer( + `.`, `\.`, + `*`, `.+`, + `\`, `[\\/]`, + `/`, `[\\/]`, +) + +// LoadQuerySourceなどは、gqlgenがLoadConfigでSchemaを読み込む時の実装をコピーして一部修正している +// **/test/*.graphqlなどに対応している +func LoadQuerySources(queryFileNames []string) ([]*ast.Source, error) { + var noGlobQueryFileNames config.StringList + + var err error + preGlobbing := queryFileNames + for _, f := range preGlobbing { + var matches []string + + // for ** we want to override default globbing patterns and walk all + // subdirectories to match schema files. + if strings.Contains(f, "**") { + pathParts := strings.SplitN(f, "**", 2) + rest := strings.TrimPrefix(strings.TrimPrefix(pathParts[1], `\`), `/`) + // turn the rest of the glob into a regex, anchored only at the end because ** allows + // for any number of dirs in between and walk will let us match against the full path name + globRe := regexp.MustCompile(path2regex.Replace(rest) + `$`) + + if err := filepath.Walk(pathParts[0], func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + + if globRe.MatchString(strings.TrimPrefix(path, pathParts[0])) { + matches = append(matches, path) + } + + return nil + }); err != nil { + return nil, xerrors.Errorf("failed to walk schema at root: %w", pathParts[0]) + } + } else { + matches, err = filepath.Glob(f) + if err != nil { + return nil, xerrors.Errorf("failed to glob schema filename %v: %w", f, err) + } + } + + for _, m := range matches { + if noGlobQueryFileNames.Has(m) { + continue + } + + noGlobQueryFileNames = append(noGlobQueryFileNames, m) + } + } + + querySources := make([]*ast.Source, 0, len(noGlobQueryFileNames)) + for _, filename := range noGlobQueryFileNames { + filename = filepath.ToSlash(filename) + var err error + var schemaRaw []byte + schemaRaw, err = ioutil.ReadFile(filename) + if err != nil { + return nil, xerrors.Errorf("unable to open schema: %w", err) + } + + querySources = append(querySources, &ast.Source{Name: filename, Input: string(schemaRaw)}) + } + + return querySources, nil +} diff --git a/vendor/github.com/Yamashou/gqlgenc/clientgen/source.go b/vendor/github.com/Yamashou/gqlgenc/clientgen/source.go new file mode 100644 index 000000000..534d94416 --- /dev/null +++ b/vendor/github.com/Yamashou/gqlgenc/clientgen/source.go @@ -0,0 +1,193 @@ +package clientgen + +import ( + "bytes" + "fmt" + "go/types" + + "github.com/99designs/gqlgen/codegen/templates" + "github.com/vektah/gqlparser/v2/ast" + "github.com/vektah/gqlparser/v2/formatter" + "golang.org/x/xerrors" +) + +type Source struct { + schema *ast.Schema + queryDocument *ast.QueryDocument + sourceGenerator *SourceGenerator +} + +func NewSource(schema *ast.Schema, queryDocument *ast.QueryDocument, sourceGenerator *SourceGenerator) *Source { + return &Source{ + schema: schema, + queryDocument: queryDocument, + sourceGenerator: sourceGenerator, + } +} + +type Fragment struct { + Name string + Type types.Type +} + +func (s *Source) Fragments() ([]*Fragment, error) { + fragments := make([]*Fragment, 0, len(s.queryDocument.Fragments)) + for _, fragment := range s.queryDocument.Fragments { + responseFields := s.sourceGenerator.NewResponseFields(fragment.SelectionSet) + if s.sourceGenerator.cfg.Models.Exists(fragment.Name) { + return nil, xerrors.New(fmt.Sprintf("%s is duplicated", fragment.Name)) + } + + fragment := &Fragment{ + Name: fragment.Name, + Type: responseFields.StructType(), + } + + fragments = append(fragments, fragment) + } + + for _, fragment := range fragments { + name := fragment.Name + s.sourceGenerator.cfg.Models.Add( + name, + fmt.Sprintf("%s.%s", s.sourceGenerator.client.Pkg(), templates.ToGo(name)), + ) + } + + return fragments, nil +} + +type Operation struct { + Name string + ResponseStructName string + Operation string + Args []*Argument + VariableDefinitions ast.VariableDefinitionList +} + +func NewOperation(operation *ast.OperationDefinition, queryDocument *ast.QueryDocument, args []*Argument) *Operation { + return &Operation{ + Name: operation.Name, + ResponseStructName: getResponseStructName(operation), + Operation: queryString(queryDocument), + Args: args, + VariableDefinitions: operation.VariableDefinitions, + } +} + +func (s *Source) Operations(queryDocuments []*ast.QueryDocument) []*Operation { + operations := make([]*Operation, 0, len(s.queryDocument.Operations)) + + queryDocumentsMap := queryDocumentMapByOperationName(queryDocuments) + operationArgsMap := s.operationArgsMapByOperationName() + for _, operation := range s.queryDocument.Operations { + queryDocument := queryDocumentsMap[operation.Name] + args := operationArgsMap[operation.Name] + operations = append(operations, NewOperation( + operation, + queryDocument, + args, + )) + } + + return operations +} + +func (s *Source) operationArgsMapByOperationName() map[string][]*Argument { + operationArgsMap := make(map[string][]*Argument) + for _, operation := range s.queryDocument.Operations { + operationArgsMap[operation.Name] = s.sourceGenerator.OperationArguments(operation.VariableDefinitions) + } + + return operationArgsMap +} + +func queryDocumentMapByOperationName(queryDocuments []*ast.QueryDocument) map[string]*ast.QueryDocument { + queryDocumentMap := make(map[string]*ast.QueryDocument) + for _, queryDocument := range queryDocuments { + operation := queryDocument.Operations[0] + queryDocumentMap[operation.Name] = queryDocument + } + + return queryDocumentMap +} + +func queryString(queryDocument *ast.QueryDocument) string { + var buf bytes.Buffer + astFormatter := formatter.NewFormatter(&buf) + astFormatter.FormatQueryDocument(queryDocument) + + return buf.String() +} + +type OperationResponse struct { + Name string + Type types.Type +} + +func (s *Source) OperationResponses() ([]*OperationResponse, error) { + operationResponse := make([]*OperationResponse, 0, len(s.queryDocument.Operations)) + for _, operation := range s.queryDocument.Operations { + responseFields := s.sourceGenerator.NewResponseFields(operation.SelectionSet) + name := getResponseStructName(operation) + if s.sourceGenerator.cfg.Models.Exists(name) { + return nil, xerrors.New(fmt.Sprintf("%s is duplicated", name)) + } + operationResponse = append(operationResponse, &OperationResponse{ + Name: name, + Type: responseFields.StructType(), + }) + } + + for _, operationResponse := range operationResponse { + name := operationResponse.Name + s.sourceGenerator.cfg.Models.Add( + name, + fmt.Sprintf("%s.%s", s.sourceGenerator.client.Pkg(), templates.ToGo(name)), + ) + } + + return operationResponse, nil +} + +type Query struct { + Name string + Type types.Type +} + +func (s *Source) Query() (*Query, error) { + fields, err := s.sourceGenerator.NewResponseFieldsByDefinition(s.schema.Query) + if err != nil { + return nil, xerrors.Errorf("generate failed for query struct type : %w", err) + } + + return &Query{ + Name: s.schema.Query.Name, + Type: fields.StructType(), + }, nil +} + +type Mutation struct { + Name string + Type types.Type +} + +func (s *Source) Mutation() (*Mutation, error) { + fields, err := s.sourceGenerator.NewResponseFieldsByDefinition(s.schema.Mutation) + if err != nil { + return nil, xerrors.Errorf("generate failed for mutation struct type : %w", err) + } + + return &Mutation{ + Name: s.schema.Mutation.Name, + Type: fields.StructType(), + }, nil +} + +func getResponseStructName(operation *ast.OperationDefinition) string { + if operation.Operation == ast.Mutation { + return fmt.Sprintf("%sPayload", operation.Name) + } + + return operation.Name +} diff --git a/vendor/github.com/Yamashou/gqlgenc/clientgen/source_generator.go b/vendor/github.com/Yamashou/gqlgenc/clientgen/source_generator.go new file mode 100644 index 000000000..f9be08150 --- /dev/null +++ b/vendor/github.com/Yamashou/gqlgenc/clientgen/source_generator.go @@ -0,0 +1,204 @@ +package clientgen + +import ( + "fmt" + "go/types" + "strings" + + "github.com/99designs/gqlgen/codegen/config" + "github.com/99designs/gqlgen/codegen/templates" + "github.com/vektah/gqlparser/v2/ast" + "golang.org/x/xerrors" +) + +type Argument struct { + Variable string + Type types.Type +} + +type ResponseField struct { + Name string + IsFragmentSpread bool + IsInlineFragment bool + Type types.Type + Tags []string + ResponseFields ResponseFieldList +} + +type ResponseFieldList []*ResponseField + +func (rs ResponseFieldList) StructType() *types.Struct { + vars := make([]*types.Var, 0) + structTags := make([]string, 0) + for _, filed := range rs { + // クエリーのフィールドの子階層がFragmentの場合、このフィールドにそのFragmentの型を追加する + if filed.IsFragmentSpread { + typ := filed.ResponseFields.StructType().Underlying().(*types.Struct) + for j := 0; j < typ.NumFields(); j++ { + vars = append(vars, typ.Field(j)) + structTags = append(structTags, typ.Tag(j)) + } + } else { + vars = append(vars, types.NewVar(0, nil, templates.ToGo(filed.Name), filed.Type)) + structTags = append(structTags, strings.Join(filed.Tags, " ")) + } + } + + return types.NewStruct(vars, structTags) +} + +func (rs ResponseFieldList) IsFragment() bool { + if len(rs) != 1 { + return false + } + + return rs[0].IsInlineFragment || rs[0].IsFragmentSpread +} + +func (rs ResponseFieldList) IsBasicType() bool { + return len(rs) == 0 +} + +func (rs ResponseFieldList) IsStructType() bool { + return len(rs) > 0 && !rs.IsFragment() +} + +type SourceGenerator struct { + cfg *config.Config + binder *config.Binder + client config.PackageConfig +} + +func NewSourceGenerator(cfg *config.Config, client config.PackageConfig) *SourceGenerator { + return &SourceGenerator{ + cfg: cfg, + binder: cfg.NewBinder(), + client: client, + } +} + +func (r *SourceGenerator) NewResponseFields(selectionSet ast.SelectionSet) ResponseFieldList { + responseFields := make(ResponseFieldList, 0, len(selectionSet)) + for _, selection := range selectionSet { + responseFields = append(responseFields, r.NewResponseField(selection)) + } + + return responseFields +} + +func (r *SourceGenerator) NewResponseFieldsByDefinition(definition *ast.Definition) (ResponseFieldList, error) { + fields := make(ResponseFieldList, 0, len(definition.Fields)) + for _, field := range definition.Fields { + if field.Type.Name() == "__Schema" || field.Type.Name() == "__Type" { + continue + } + typ, err := r.binder.FindTypeFromName(r.cfg.Models[field.Type.Name()].Model[0]) + if err != nil { + return nil, xerrors.Errorf("not found type: %w", err) + } + tags := []string{ + fmt.Sprintf(`json:"%s"`, field.Name), + fmt.Sprintf(`graphql:"%s"`, field.Name), + } + + fields = append(fields, &ResponseField{ + Name: field.Name, + Type: r.binder.CopyModifiersFromAst(field.Type, typ), + Tags: tags, + }) + } + + return fields, nil +} + +func (r *SourceGenerator) NewResponseField(selection ast.Selection) *ResponseField { + switch selection := selection.(type) { + case *ast.Field: + fieldsResponseFields := r.NewResponseFields(selection.SelectionSet) + + var baseType types.Type + switch { + case fieldsResponseFields.IsBasicType(): + baseType = r.Type(selection.Definition.Type.Name()) + case fieldsResponseFields.IsFragment(): + // 子フィールドがFragmentの場合はこのFragmentがフィールドの型になる + // if a child field is fragment, this field type became fragment. + baseType = fieldsResponseFields[0].Type + case fieldsResponseFields.IsStructType(): + baseType = fieldsResponseFields.StructType() + default: + // ここにきたらバグ + // here is bug + panic("not match type") + } + + // GraphQLの定義がオプショナルのはtypeのポインタ型が返り、配列の定義場合はポインタのスライスの型になって返ってきます + // return pointer type then optional type or slice pointer then slice type of definition in GraphQL. + typ := r.binder.CopyModifiersFromAst(selection.Definition.Type, baseType) + + tags := []string{ + fmt.Sprintf(`json:"%s"`, selection.Alias), + fmt.Sprintf(`graphql:"%s"`, selection.Alias), + } + + return &ResponseField{ + Name: selection.Alias, + Type: typ, + Tags: tags, + ResponseFields: fieldsResponseFields, + } + + case *ast.FragmentSpread: + // この構造体はテンプレート側で使われることはなく、ast.FieldでFragment判定するために使用する + fieldsResponseFields := r.NewResponseFields(selection.Definition.SelectionSet) + typ := types.NewNamed( + types.NewTypeName(0, r.client.Pkg(), templates.ToGo(selection.Name), nil), + fieldsResponseFields.StructType(), + nil, + ) + + return &ResponseField{ + Name: selection.Name, + Type: typ, + IsFragmentSpread: true, + ResponseFields: fieldsResponseFields, + } + + case *ast.InlineFragment: + // InlineFragmentは子要素をそのままstructとしてもつので、ここで、構造体の型を作成します + fieldsResponseFields := r.NewResponseFields(selection.SelectionSet) + + return &ResponseField{ + Name: selection.TypeCondition, + Type: fieldsResponseFields.StructType(), + IsInlineFragment: true, + Tags: []string{fmt.Sprintf(`graphql:"... on %s"`, selection.TypeCondition)}, + ResponseFields: fieldsResponseFields, + } + } + + panic("unexpected selection type") +} + +func (r *SourceGenerator) OperationArguments(variableDefinitions ast.VariableDefinitionList) []*Argument { + argumentTypes := make([]*Argument, 0, len(variableDefinitions)) + for _, v := range variableDefinitions { + argumentTypes = append(argumentTypes, &Argument{ + Variable: v.Variable, + Type: r.binder.CopyModifiersFromAst(v.Type, r.Type(v.Type.Name())), + }) + } + + return argumentTypes +} + +// Typeの引数に渡すtypeNameは解析した結果からselectionなどから求めた型の名前を渡さなければいけない +func (r *SourceGenerator) Type(typeName string) types.Type { + goType, err := r.binder.FindTypeFromName(r.cfg.Models[typeName].Model[0]) + if err != nil { + // 実装として正しいtypeNameを渡していれば必ず見つかるはずなのでpanic + panic(fmt.Sprintf("%+v", err)) + } + + return goType +} diff --git a/vendor/github.com/Yamashou/gqlgenc/clientgen/template.go b/vendor/github.com/Yamashou/gqlgenc/clientgen/template.go new file mode 100644 index 000000000..3e8fca403 --- /dev/null +++ b/vendor/github.com/Yamashou/gqlgenc/clientgen/template.go @@ -0,0 +1,27 @@ +package clientgen + +import ( + "github.com/99designs/gqlgen/codegen/config" + "github.com/99designs/gqlgen/codegen/templates" + "golang.org/x/xerrors" +) + +func RenderTemplate(cfg *config.Config, query *Query, mutation *Mutation, fragments []*Fragment, operations []*Operation, operationResponses []*OperationResponse, client config.PackageConfig) error { + if err := templates.Render(templates.Options{ + PackageName: client.Package, + Filename: client.Filename, + Data: map[string]interface{}{ + "Query": query, + "Mutation": mutation, + "Fragment": fragments, + "Operation": operations, + "OperationResponse": operationResponses, + }, + Packages: cfg.Packages, + PackageDoc: "// Code generated by github.com/Yamashou/gqlgenc, DO NOT EDIT.\n", + }); err != nil { + return xerrors.Errorf("%s generating failed: %w", client.Filename, err) + } + + return nil +} diff --git a/vendor/github.com/Yamashou/gqlgenc/clientgen/template.gotpl b/vendor/github.com/Yamashou/gqlgenc/clientgen/template.gotpl new file mode 100644 index 000000000..76495ee8a --- /dev/null +++ b/vendor/github.com/Yamashou/gqlgenc/clientgen/template.gotpl @@ -0,0 +1,54 @@ +{{ reserveImport "bytes" }} +{{ reserveImport "context" }} +{{ reserveImport "encoding/json" }} +{{ reserveImport "fmt" }} +{{ reserveImport "io" }} +{{ reserveImport "io/ioutil" }} +{{ reserveImport "net/http" }} +{{ reserveImport "net/url" }} +{{ reserveImport "path" }} +{{ reserveImport "time" }} + +{{ reserveImport "golang.org/x/xerrors" }} + +{{ reserveImport "github.com/Yamashou/gqlgenc/graphqljson" }} +{{ reserveImport "github.com/Yamashou/gqlgenc/client" }} + +type Client struct { + Client *client.Client +} + +func NewClient(cli *http.Client, baseURL string, options ...client.HTTPRequestOption) *Client { + return &Client{Client: client.NewClient(cli, baseURL, options...)} +} + +type {{ .Query.Name | go }} {{ .Query.Type | ref }} + +type {{ .Mutation.Name | go }} {{ .Mutation.Type | ref }} + +{{- range $name, $element := .Fragment }} + type {{ .Name | go }} {{ .Type | ref }} +{{- end }} + +{{- range $name, $element := .OperationResponse }} + type {{ .Name | go }} {{ .Type | ref }} +{{- end }} + +{{- range $model := .Operation}} +const {{ $model.Name|go }}Query = `{{ $model.Operation }}` + +func (c *Client) {{ $model.Name|go }} (ctx context.Context{{- range $arg := .Args }}, {{ $arg.Variable | goPrivate }} {{ $arg.Type | ref }} {{- end }}, httpRequestOptions ...client.HTTPRequestOption) (*{{ $model.ResponseStructName | go }}, error) { + vars := map[string]interface{}{ + {{- range $args := .VariableDefinitions}} + "{{ $args.Variable }}": {{ $args.Variable | goPrivate }}, + {{- end }} + } + + var res {{ $model.ResponseStructName | go }} + if err := c.Client.Post(ctx, {{ $model.Name|go }}Query, &res, vars, httpRequestOptions...); err != nil { + return nil, err + } + + return &res, nil +} +{{- end}} diff --git a/vendor/github.com/Yamashou/gqlgenc/config/config.go b/vendor/github.com/Yamashou/gqlgenc/config/config.go new file mode 100644 index 000000000..620b11ca3 --- /dev/null +++ b/vendor/github.com/Yamashou/gqlgenc/config/config.go @@ -0,0 +1,123 @@ +package config + +import ( + "context" + "io/ioutil" + "net/http" + "os" + "path/filepath" + + "github.com/99designs/gqlgen/codegen/config" + "github.com/Yamashou/gqlgenc/client" + "github.com/Yamashou/gqlgenc/introspection" + "github.com/vektah/gqlparser/v2/ast" + "github.com/vektah/gqlparser/v2/validator" + "golang.org/x/xerrors" + "gopkg.in/yaml.v2" +) + +type Config struct { + Model config.PackageConfig `yaml:"model,omitempty"` + Client config.PackageConfig `yaml:"client,omitempty"` + Models config.TypeMap `yaml:"models,omitempty"` + Endpoint EndPointConfig `yaml:"endpoint"` + Query []string `yaml:"query"` + + // gqlgen config struct + GQLConfig *config.Config `yaml:"-"` +} + +type EndPointConfig struct { + URL string `yaml:"url"` + Headers map[string]string `yaml:"headers,omitempty"` +} + +func findCfg(fileName string) (string, error) { + dir, err := os.Getwd() + if err != nil { + return "", xerrors.Errorf("unable to get working dir to findCfg: %w", err) + } + + cfg := findCfgInDir(dir, fileName) + + if cfg == "" { + return "", os.ErrNotExist + } + + return cfg, nil +} + +func findCfgInDir(dir, fileName string) string { + path := filepath.Join(dir, fileName) + + return path +} + +func LoadConfig(filename string) (*Config, error) { + var cfg Config + file, err := findCfg(filename) + if err != nil { + return nil, xerrors.Errorf("unable to get file path: %w", err) + } + b, err := ioutil.ReadFile(file) + if err != nil { + return nil, xerrors.Errorf("unable to read config: %w", err) + } + + confContent := []byte(os.ExpandEnv(string(b))) + if err := yaml.UnmarshalStrict(confContent, &cfg); err != nil { + return nil, xerrors.Errorf("unable to parse config: %w", err) + } + + cfg.GQLConfig = &config.Config{ + Model: cfg.Model, + Models: cfg.Models, + // TODO: gqlgen must be set exec but client not used + Exec: config.PackageConfig{Filename: "generated.go"}, + Directives: map[string]config.DirectiveConfig{}, + } + + if err := cfg.Client.Check(); err != nil { + return nil, xerrors.Errorf("config.exec: %w", err) + } + + return &cfg, nil +} + +func (c *Config) LoadSchema(ctx context.Context) error { + addHeader := func(req *http.Request) { + for key, value := range c.Endpoint.Headers { + req.Header.Set(key, value) + } + } + gqlclient := client.NewClient(http.DefaultClient, c.Endpoint.URL, addHeader) + schema, err := LoadRemoteSchema(ctx, gqlclient) + if err != nil { + return xerrors.Errorf("load remote schema failed: %w", err) + } + if schema.Query == nil { + schema.Query = &ast.Definition{ + Kind: ast.Object, + Name: "Query", + } + schema.Types["Query"] = schema.Query + } + + c.GQLConfig.Schema = schema + + return nil +} + +func LoadRemoteSchema(ctx context.Context, gqlclient *client.Client) (*ast.Schema, error) { + var res introspection.Query + if err := gqlclient.Post(ctx, introspection.Introspection, &res, nil); err != nil { + return nil, xerrors.Errorf("introspection query failed: %w", err) + } + + schema, err := validator.ValidateSchemaDocument(introspection.ParseIntrospectionQuery(res)) + if err != nil { + return nil, xerrors.Errorf("validation error: %w", err) + } + + return schema, nil +} diff --git a/vendor/github.com/Yamashou/gqlgenc/generator/generater.go b/vendor/github.com/Yamashou/gqlgenc/generator/generater.go new file mode 100644 index 000000000..075b54b12 --- /dev/null +++ b/vendor/github.com/Yamashou/gqlgenc/generator/generater.go @@ -0,0 +1,40 @@ +package generator + +import ( + "context" + + "github.com/99designs/gqlgen/api" + "github.com/99designs/gqlgen/plugin" + "github.com/99designs/gqlgen/plugin/modelgen" + "github.com/Yamashou/gqlgenc/config" + "golang.org/x/xerrors" +) + +func Generate(ctx context.Context, cfg *config.Config, option ...api.Option) error { + var plugins []plugin.Plugin + if cfg.Model.IsDefined() { + plugins = append(plugins, modelgen.New()) + } + for _, o := range option { + o(cfg.GQLConfig, &plugins) + } + + if err := cfg.LoadSchema(ctx); err != nil { + return xerrors.Errorf("failed to load schema: %w\n", err) + } + + if err := cfg.GQLConfig.Init(); err != nil { + return xerrors.Errorf("generating core failed: %w\n", err) + } + + for _, p := range plugins { + if mut, ok := p.(plugin.ConfigMutator); ok { + err := mut.MutateConfig(cfg.GQLConfig) + if err != nil { + return xerrors.Errorf("%s failed: %w\n", p.Name(), err) + } + } + } + + return nil +} diff --git a/vendor/github.com/Yamashou/gqlgenc/go.mod b/vendor/github.com/Yamashou/gqlgenc/go.mod new file mode 100644 index 000000000..7b4208f7d --- /dev/null +++ b/vendor/github.com/Yamashou/gqlgenc/go.mod @@ -0,0 +1,14 @@ +module github.com/Yamashou/gqlgenc + +go 1.14 + +require ( + github.com/99designs/gqlgen v0.12.2 + github.com/agnivade/levenshtein v1.1.0 // indirect + github.com/google/go-cmp v0.5.2 + github.com/pkg/errors v0.9.1 // indirect + github.com/vektah/gqlparser/v2 v2.0.1 + golang.org/x/tools v0.0.0-20200827163409-021d7c6f1ec3 // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 + gopkg.in/yaml.v2 v2.3.0 +) diff --git a/vendor/github.com/Yamashou/gqlgenc/go.sum b/vendor/github.com/Yamashou/gqlgenc/go.sum new file mode 100644 index 000000000..966cfd8d7 --- /dev/null +++ b/vendor/github.com/Yamashou/gqlgenc/go.sum @@ -0,0 +1,122 @@ +github.com/99designs/gqlgen v0.12.2 h1:aOdpsiCycFtCnAv8CAI1exnKrIDHMqtMzQoXeTziY4o= +github.com/99designs/gqlgen v0.12.2/go.mod h1:7zdGo6ry9u1YBp/qlb2uxSU5Mt2jQKLcBETQiKk+Bxo= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/agnivade/levenshtein v1.0.1 h1:3oJU7J3FGFmyhn8KHjmVaZCN5hxTr7GxgRue+sxIXdQ= +github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= +github.com/agnivade/levenshtein v1.0.3 h1:M5ZnqLOoZR8ygVq0FfkXsNOKzMCk0xRiow0R5+5VkQ0= +github.com/agnivade/levenshtein v1.0.3/go.mod h1:4SFRZbbXWLF4MU1T9Qg0pGgH3Pjs+t6ie5efyrwRJXs= +github.com/agnivade/levenshtein v1.1.0 h1:n6qGwyHG61v3ABce1rPVZklEYRT8NFpCMrpZdBUbYGM= +github.com/agnivade/levenshtein v1.1.0/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= +github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= +github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c h1:TUuUh0Xgj97tLMNtWtNvI9mIV6isjEb9lBMNv+77IGM= +github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= +github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g= +github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= +github.com/go-chi/chi v3.3.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/gorilla/context v0.0.0-20160226214623-1ea25387ff6f/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= +github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007 h1:reVOUXwnhsYv/8UqjvhrMOu5CNT9UapHFLbQ2JcXsmg= +github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047 h1:zCoDWFD5nrJJVjbXiDZcVhOBSzKn3o9LgRLLMRNuru8= +github.com/mitchellh/mapstructure v0.0.0-20180203102830-a4e142e9c047/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2 h1:3jA2P6O1F9UOrWVpwrIo17pu01KWvNWg4X946/Y5Zwg= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/shurcooL/vfsgen v0.0.0-20180121065927-ffb13db8def0/go.mod h1:TrYk7fJVaAttu97ZZKrO9UbRa8izdowaMIZcxYMbVaw= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/urfave/cli/v2 v2.1.1 h1:Qt8FeAtxE/vfdrLmR3rxR6JRE0RoVmbXu8+6kZtYU4k= +github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= +github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e h1:+w0Zm/9gaWpEAyDlU1eKOuk5twTjAjuevXqcJJw8hrg= +github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U= +github.com/vektah/gqlparser v1.3.1 h1:8b0IcD3qZKWJQHSzynbDlrtP3IxVydZ2DZepCGofqfU= +github.com/vektah/gqlparser v1.3.1/go.mod h1:bkVf0FX+Stjg/MHnm8mEyubuaArhNEqfQhF+OTiAL74= +github.com/vektah/gqlparser/v2 v2.0.1 h1:xgl5abVnsd4hkN9rk65OJID9bfcLSMuTaTcZj777q1o= +github.com/vektah/gqlparser/v2 v2.0.1/go.mod h1:SyUiHgLATUR8BiYURfTirrTcGpcE+4XkV2se04Px1Ms= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +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-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589 h1:rjUrONFu4kLchcZTfp3/96bR8bW8dIa8uz3cR5n0cgM= +golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200827163409-021d7c6f1ec3 h1:OjYQxZBKJFs+sJbHkvSGIKNMkZXDJQ9JsMpebGhkafI= +golang.org/x/tools v0.0.0-20200827163409-021d7c6f1ec3/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +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 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +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= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= +gopkg.in/yaml.v2 v2.2.4/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= +sourcegraph.com/sourcegraph/appdash v0.0.0-20180110180208-2cc67fd64755/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= +sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67/go.mod h1:L5q+DGLGOQFpo1snNEkLOJT2d1YTW66rWNzatr3He1k= diff --git a/vendor/github.com/Yamashou/gqlgenc/graphqljson/graphql.go b/vendor/github.com/Yamashou/gqlgenc/graphqljson/graphql.go new file mode 100644 index 000000000..223176aa7 --- /dev/null +++ b/vendor/github.com/Yamashou/gqlgenc/graphqljson/graphql.go @@ -0,0 +1,416 @@ +/* +MIT License + +Copyright (c) 2017 Dmitri Shuralyov + +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 jsonutil provides a function for decoding JSON +// into a GraphQL query data structure. +package graphqljson + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "reflect" + "strings" + + "golang.org/x/xerrors" +) + +// Reference: https://blog.gopheracademy.com/advent-2017/custom-json-unmarshaler-for-graphql-client/ + +// RawJSONError is a json formatted error from a GraphQL server. +type RawJSONError struct { + Response +} + +func (r RawJSONError) Error() string { + return fmt.Sprintf("data: %s, error: %s, extensions: %v", r.Data, r.Errors, r.Extensions) +} + +// Response is a GraphQL layer response from a handler. +type Response struct { + Data json.RawMessage + Errors Errors + Extensions map[string]interface{} +} + +func Unmarshal(r io.Reader, data interface{}) error { + resp := Response{} + decoder := json.NewDecoder(r) + if err := decoder.Decode(&resp); err != nil { + var buf bytes.Buffer + if _, e := io.Copy(&buf, decoder.Buffered()); e != nil { + return xerrors.Errorf(": %w", err) + } + + return xerrors.Errorf("%s", buf.String()) + } + + if len(resp.Errors) > 0 { + return xerrors.Errorf("response error: %w", resp.Errors) + } + + if err := UnmarshalData(resp.Data, data); err != nil { + return xerrors.Errorf("response mapping failed: %w", err) + } + + if resp.Errors != nil { + return RawJSONError{resp} + } + + return nil +} + +// UnmarshalGraphQL parses the JSON-encoded GraphQL response data and stores +// the result in the GraphQL query data structure pointed to by v. +// +// The implementation is created on top of the JSON tokenizer available +// in "encoding/json".Decoder. +func UnmarshalData(data json.RawMessage, v interface{}) error { + d := NewDecoder(bytes.NewBuffer(data)) + if err := d.Decode(v); err != nil { + return xerrors.Errorf(": %w", err) + } + + // TODO: この処理が本当に必要かは今後検討 + tok, err := d.jsonDecoder.Token() + switch err { + case io.EOF: + // Expect to get io.EOF. There shouldn't be any more + // tokens left after we've decoded v successfully. + return nil + case nil: + return xerrors.Errorf("invalid token '%v' after top-level value", tok) + } + + return xerrors.Errorf("invalid token '%v' after top-level value", tok) +} + +// decoder is a JSON decoder that performs custom unmarshaling behavior +// for GraphQL query data structures. It's implemented on top of a JSON tokenizer. +type Decoder struct { + jsonDecoder *json.Decoder + + // Stack of what part of input JSON we're in the middle of - objects, arrays. + parseState []json.Delim + + // Stacks of values where to unmarshal. + // The top of each stack is the reflect.Value where to unmarshal next JSON value. + // + // The reason there's more than one stack is because we might be unmarshaling + // a single JSON value into multiple GraphQL fragments or embedded structs, so + // we keep track of them all. + vs [][]reflect.Value +} + +func NewDecoder(r io.Reader) *Decoder { + jsonDecoder := json.NewDecoder(r) + jsonDecoder.UseNumber() + + return &Decoder{ + jsonDecoder: jsonDecoder, + } +} + +// Decode decodes a single JSON value from d.tokenizer into v. +func (d *Decoder) Decode(v interface{}) error { + rv := reflect.ValueOf(v) + if rv.Kind() != reflect.Ptr { + return xerrors.Errorf("cannot decode into non-pointer %T", v) + } + + d.vs = [][]reflect.Value{{rv.Elem()}} + if err := d.decode(); err != nil { + return xerrors.Errorf(": %w", err) + } + + return nil +} + +// decode decodes a single JSON value from d.tokenizer into d.vs. +func (d *Decoder) decode() error { + // The loop invariant is that the top of each d.vs stack + // is where we try to unmarshal the next JSON value we see. + for len(d.vs) > 0 { + tok, err := d.jsonDecoder.Token() + if err == io.EOF { + return xerrors.New("unexpected end of JSON input") + } else if err != nil { + return xerrors.Errorf(": %w", err) + } + + switch { + // Are we inside an object and seeing next key (rather than end of object)? + case d.state() == '{' && tok != json.Delim('}'): + key, ok := tok.(string) + if !ok { + return xerrors.New("unexpected non-key in JSON input") + } + + someFieldExist := false + for i := range d.vs { + v := d.vs[i][len(d.vs[i])-1] + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + var f reflect.Value + if v.Kind() == reflect.Struct { + f = fieldByGraphQLName(v, key) + if f.IsValid() { + someFieldExist = true + } + } + d.vs[i] = append(d.vs[i], f) + } + if !someFieldExist { + return xerrors.Errorf("struct field for %q doesn't exist in any of %v places to unmarshal", key, len(d.vs)) + } + + // We've just consumed the current token, which was the key. + // Read the next token, which should be the value, and let the rest of code process it. + tok, err = d.jsonDecoder.Token() + if err == io.EOF { + return xerrors.New("unexpected end of JSON input") + } else if err != nil { + return xerrors.Errorf(": %w", err) + } + + // Are we inside an array and seeing next value (rather than end of array)? + case d.state() == '[' && tok != json.Delim(']'): + someSliceExist := false + for i := range d.vs { + v := d.vs[i][len(d.vs[i])-1] + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + var f reflect.Value + if v.Kind() == reflect.Slice { + v.Set(reflect.Append(v, reflect.Zero(v.Type().Elem()))) // v = append(v, T). + f = v.Index(v.Len() - 1) + someSliceExist = true + } + d.vs[i] = append(d.vs[i], f) + } + if !someSliceExist { + return xerrors.Errorf("slice doesn't exist in any of %v places to unmarshal", len(d.vs)) + } + } + + switch tok := tok.(type) { + case string, json.Number, bool, nil: + // Value. + + for i := range d.vs { + v := d.vs[i][len(d.vs[i])-1] + if !v.IsValid() { + continue + } + err := unmarshalValue(tok, v) + if err != nil { + return xerrors.Errorf(": %w", err) + } + } + d.popAllVs() + + case json.Delim: + switch tok { + case '{': + // Start of object. + + d.pushState(tok) + + frontier := make([]reflect.Value, len(d.vs)) // Places to look for GraphQL fragments/embedded structs. + for i := range d.vs { + v := d.vs[i][len(d.vs[i])-1] + frontier[i] = v + // TODO: Do this recursively or not? Add a test case if needed. + if v.Kind() == reflect.Ptr && v.IsNil() { + v.Set(reflect.New(v.Type().Elem())) // v = new(T). + } + } + // Find GraphQL fragments/embedded structs recursively, adding to frontier + // as new ones are discovered and exploring them further. + for len(frontier) > 0 { + v := frontier[0] + frontier = frontier[1:] + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + if v.Kind() != reflect.Struct { + continue + } + for i := 0; i < v.NumField(); i++ { + if isGraphQLFragment(v.Type().Field(i)) || v.Type().Field(i).Anonymous { + // Add GraphQL fragment or embedded struct. + d.vs = append(d.vs, []reflect.Value{v.Field(i)}) + frontier = append(frontier, v.Field(i)) + } + } + } + case '[': + // Start of array. + + d.pushState(tok) + + for i := range d.vs { + v := d.vs[i][len(d.vs[i])-1] + // TODO: Confirm this is needed, write a test case. + // if v.Kind() == reflect.Ptr && v.IsNil() { + // v.Set(reflect.New(v.Type().Elem())) // v = new(T). + //} + + // Reset slice to empty (in case it had non-zero initial value). + if v.Kind() == reflect.Ptr { + v = v.Elem() + } + if v.Kind() != reflect.Slice { + continue + } + v.Set(reflect.MakeSlice(v.Type(), 0, 0)) // v = make(T, 0, 0). + } + case '}', ']': + // End of object or array. + d.popAllVs() + d.popState() + default: + return xerrors.New("unexpected delimiter in JSON input") + } + default: + return xerrors.New("unexpected token in JSON input") + } + } + + return nil +} + +// pushState pushes a new parse state s onto the stack. +func (d *Decoder) pushState(s json.Delim) { + d.parseState = append(d.parseState, s) +} + +// popState pops a parse state (already obtained) off the stack. +// The stack must be non-empty. +func (d *Decoder) popState() { + d.parseState = d.parseState[:len(d.parseState)-1] +} + +// state reports the parse state on top of stack, or 0 if empty. +func (d *Decoder) state() json.Delim { + if len(d.parseState) == 0 { + return 0 + } + + return d.parseState[len(d.parseState)-1] +} + +// popAllVs pops from all d.vs stacks, keeping only non-empty ones. +func (d *Decoder) popAllVs() { + var nonEmpty [][]reflect.Value + for i := range d.vs { + d.vs[i] = d.vs[i][:len(d.vs[i])-1] + if len(d.vs[i]) > 0 { + nonEmpty = append(nonEmpty, d.vs[i]) + } + } + d.vs = nonEmpty +} + +// fieldByGraphQLName returns an exported struct field of struct v +// that matches GraphQL name, or invalid reflect.Value if none found. +func fieldByGraphQLName(v reflect.Value, name string) reflect.Value { + for i := 0; i < v.NumField(); i++ { + if v.Type().Field(i).PkgPath != "" { + // Skip unexported field. + continue + } + if hasGraphQLName(v.Type().Field(i), name) { + return v.Field(i) + } + } + + return reflect.Value{} +} + +// hasGraphQLName reports whether struct field f has GraphQL name. +func hasGraphQLName(f reflect.StructField, name string) bool { + value, ok := f.Tag.Lookup("graphql") + if !ok { + // TODO: caseconv package is relatively slow. Optimize it, then consider using it here. + // return caseconv.MixedCapsToLowerCamelCase(f.Name) == name + return strings.EqualFold(f.Name, name) + } + value = strings.TrimSpace(value) // TODO: Parse better. + if strings.HasPrefix(value, "...") { + // GraphQL fragment. It doesn't have a name. + return false + } + if i := strings.Index(value, "("); i != -1 { + value = value[:i] + } + if i := strings.Index(value, ":"); i != -1 { + value = value[:i] + } + + return strings.TrimSpace(value) == name +} + +// isGraphQLFragment reports whether struct field f is a GraphQL fragment. +func isGraphQLFragment(f reflect.StructField) bool { + value, ok := f.Tag.Lookup("graphql") + if !ok { + return false + } + value = strings.TrimSpace(value) // TODO: Parse better. + + return strings.HasPrefix(value, "...") +} + +// unmarshalValue unmarshals JSON value into v. +// v must be addressable and not obtained by the use of unexported +// struct fields, otherwise unmarshalValue will panic. +func unmarshalValue(value json.Token, v reflect.Value) error { + b, err := json.Marshal(value) // TODO: Short-circuit (if profiling says it's worth it). + if err != nil { + return xerrors.Errorf(": %w", err) + } + + return json.Unmarshal(b, v.Addr().Interface()) +} + +// Errors represents the "Errors" array in a response from a GraphQL server. +// If returned via error interface, the slice is expected to contain at least 1 element. +// +// Specification: https://facebook.github.io/graphql/#sec-Errors. +type Errors []struct { + Message string + Locations []struct { + Line int + Column int + } +} + +// Error implements error interface. +func (e Errors) Error() string { + return e[0].Message +} diff --git a/vendor/github.com/Yamashou/gqlgenc/introspection/parse.go b/vendor/github.com/Yamashou/gqlgenc/introspection/parse.go new file mode 100644 index 000000000..8ffb4649d --- /dev/null +++ b/vendor/github.com/Yamashou/gqlgenc/introspection/parse.go @@ -0,0 +1,289 @@ +package introspection + +import ( + "fmt" + + "github.com/vektah/gqlparser/v2/ast" +) + +func ParseIntrospectionQuery(query Query) *ast.SchemaDocument { + var doc ast.SchemaDocument + typeMap := query.Schema.Types.NameMap() + + doc.Schema = append(doc.Schema, parseSchemaDefinition(query, typeMap)) + + for _, typeVale := range typeMap { + doc.Definitions = append(doc.Definitions, parseTypeSystemDefinition(typeVale)) + } + + for _, directiveValue := range query.Schema.Directives { + doc.Directives = append(doc.Directives, parseDirectiveDefinition(directiveValue)) + } + + return &doc +} + +func parseSchemaDefinition(query Query, typeMap map[string]*FullType) *ast.SchemaDefinition { + def := ast.SchemaDefinition{} + + def.OperationTypes = append(def.OperationTypes, + parseOperationTypeDefinitionForQuery(typeMap[*query.Schema.QueryType.Name]), + parseOperationTypeDefinitionForMutation(typeMap[*query.Schema.MutationType.Name]), + ) + + return &def +} + +func parseOperationTypeDefinitionForQuery(fullType *FullType) *ast.OperationTypeDefinition { + var op ast.OperationTypeDefinition + op.Operation = ast.Query + op.Type = *fullType.Name + + return &op +} + +func parseOperationTypeDefinitionForMutation(fullType *FullType) *ast.OperationTypeDefinition { + var op ast.OperationTypeDefinition + op.Operation = ast.Mutation + op.Type = *fullType.Name + + return &op +} + +func parseDirectiveDefinition(directiveValue *DirectiveType) *ast.DirectiveDefinition { + args := make(ast.ArgumentDefinitionList, 0, len(directiveValue.Args)) + for _, arg := range directiveValue.Args { + argumentDefinition := buildInputValue(arg) + args = append(args, argumentDefinition) + } + locations := make([]ast.DirectiveLocation, 0, len(directiveValue.Locations)) + for _, locationValue := range directiveValue.Locations { + locations = append(locations, ast.DirectiveLocation(locationValue)) + } + + return &ast.DirectiveDefinition{ + Description: pointerString(directiveValue.Description), + Name: directiveValue.Name, + Arguments: args, + Locations: locations, + } +} + +func parseObjectFields(typeVale *FullType) ast.FieldList { + fieldList := make(ast.FieldList, 0, len(typeVale.Fields)) + for _, field := range typeVale.Fields { + typ := getType(&field.Type) + args := make(ast.ArgumentDefinitionList, 0, len(field.Args)) + for _, arg := range field.Args { + argumentDefinition := buildInputValue(arg) + args = append(args, argumentDefinition) + } + + fieldDefinition := &ast.FieldDefinition{ + Description: pointerString(field.Description), + Name: field.Name, + Arguments: args, + Type: typ, + } + fieldList = append(fieldList, fieldDefinition) + } + + return fieldList +} + +func parseInputObjectFields(typeVale *FullType) ast.FieldList { + fieldList := make(ast.FieldList, 0, len(typeVale.InputFields)) + for _, field := range typeVale.InputFields { + typ := getType(&field.Type) + fieldDefinition := &ast.FieldDefinition{ + Description: pointerString(field.Description), + Name: field.Name, + Type: typ, + } + fieldList = append(fieldList, fieldDefinition) + } + + return fieldList +} + +func parseObjectTypeDefinition(typeVale *FullType) *ast.Definition { + fieldList := parseObjectFields(typeVale) + interfaces := make([]string, 0, len(typeVale.Interfaces)) + for _, intf := range typeVale.Interfaces { + interfaces = append(interfaces, pointerString(intf.Name)) + } + + enums := make(ast.EnumValueList, 0, len(typeVale.EnumValues)) + for _, enum := range typeVale.EnumValues { + enumValue := &ast.EnumValueDefinition{ + Description: pointerString(enum.Description), + Name: enum.Name, + } + enums = append(enums, enumValue) + } + + return &ast.Definition{ + Kind: ast.Object, + Description: pointerString(typeVale.Description), + Name: pointerString(typeVale.Name), + Interfaces: interfaces, + Fields: fieldList, + EnumValues: enums, + Position: nil, + BuiltIn: true, + } +} + +func parseInterfaceTypeDefinition(typeVale *FullType) *ast.Definition { + fieldList := parseObjectFields(typeVale) + interfaces := make([]string, 0, len(typeVale.Interfaces)) + for _, intf := range typeVale.Interfaces { + interfaces = append(interfaces, pointerString(intf.Name)) + } + + return &ast.Definition{ + Kind: ast.Interface, + Description: pointerString(typeVale.Description), + Name: pointerString(typeVale.Name), + Interfaces: interfaces, + Fields: fieldList, + Position: nil, + BuiltIn: true, + } +} + +func parseInputObjectTypeDefinition(typeVale *FullType) *ast.Definition { + fieldList := parseInputObjectFields(typeVale) + interfaces := make([]string, 0, len(typeVale.Interfaces)) + for _, intf := range typeVale.Interfaces { + interfaces = append(interfaces, pointerString(intf.Name)) + } + + return &ast.Definition{ + Kind: ast.InputObject, + Description: pointerString(typeVale.Description), + Name: pointerString(typeVale.Name), + Interfaces: interfaces, + Fields: fieldList, + Position: nil, + BuiltIn: true, + } +} + +func parseUnionTypeDefinition(typeVale *FullType) *ast.Definition { + unions := make([]string, 0, len(typeVale.PossibleTypes)) + for _, unionValue := range typeVale.PossibleTypes { + unions = append(unions, *unionValue.Name) + } + + return &ast.Definition{ + Kind: ast.Union, + Description: pointerString(typeVale.Description), + Name: pointerString(typeVale.Name), + Types: unions, + Position: nil, + BuiltIn: true, + } +} + +func parseEnumTypeDefinition(typeVale *FullType) *ast.Definition { + enums := make(ast.EnumValueList, 0, len(typeVale.EnumValues)) + for _, enum := range typeVale.EnumValues { + enumValue := &ast.EnumValueDefinition{ + Description: pointerString(enum.Description), + Name: enum.Name, + } + enums = append(enums, enumValue) + } + + return &ast.Definition{ + Kind: ast.Enum, + Description: pointerString(typeVale.Description), + Name: pointerString(typeVale.Name), + EnumValues: enums, + Position: nil, + BuiltIn: true, + } +} + +func parseScalarTypeExtension(typeVale *FullType) *ast.Definition { + return &ast.Definition{ + Kind: ast.Scalar, + Description: pointerString(typeVale.Description), + Name: pointerString(typeVale.Name), + Position: nil, + BuiltIn: true, + } +} + +func parseTypeSystemDefinition(typeVale *FullType) *ast.Definition { + switch typeVale.Kind { + case TypeKindScalar: + return parseScalarTypeExtension(typeVale) + case TypeKindInterface: + return parseInterfaceTypeDefinition(typeVale) + case TypeKindEnum: + return parseEnumTypeDefinition(typeVale) + case TypeKindUnion: + return parseUnionTypeDefinition(typeVale) + case TypeKindObject: + return parseObjectTypeDefinition(typeVale) + case TypeKindInputObject: + return parseInputObjectTypeDefinition(typeVale) + case TypeKindList, TypeKindNonNull: + panic(fmt.Sprintf("not match Kind: %s", typeVale.Kind)) + } + + panic(fmt.Sprintf("not match Kind: %s", typeVale.Kind)) +} + +func pointerString(s *string) string { + if s == nil { + return "" + } + + return *s +} + +func buildInputValue(input *InputValue) *ast.ArgumentDefinition { + typ := getType(&input.Type) + + var defaultValue *ast.Value + if input.DefaultValue != nil { + defaultValue = &ast.Value{ + Raw: pointerString(input.DefaultValue), + Kind: ast.Variable, + } + } + + return &ast.ArgumentDefinition{ + Description: pointerString(input.Description), + Name: input.Name, + DefaultValue: defaultValue, + Type: typ, + } +} + +func getType(typeRef *TypeRef) *ast.Type { + if typeRef.Kind == TypeKindList { + itemRef := typeRef.OfType + if itemRef == nil { + panic("Decorated type deeper than introspection query.") + } + + return ast.ListType(getType(itemRef), nil) + } + + if typeRef.Kind == TypeKindNonNull { + nullableRef := typeRef.OfType + if nullableRef == nil { + panic("Decorated type deeper than introspection query.") + } + nullableType := getType(nullableRef) + nullableType.NonNull = true + + return nullableType + } + + return ast.NamedType(pointerString(typeRef.Name), nil) +} diff --git a/vendor/github.com/Yamashou/gqlgenc/introspection/query.go b/vendor/github.com/Yamashou/gqlgenc/introspection/query.go new file mode 100644 index 000000000..7bd126ac2 --- /dev/null +++ b/vendor/github.com/Yamashou/gqlgenc/introspection/query.go @@ -0,0 +1,93 @@ +package introspection + +const Introspection = `query Query { + __schema { + queryType { name } + mutationType { name } + subscriptionType { name } + types { + ...FullType + } + directives { + name + description + locations + args { + ...InputValue + } + } + } + } + + fragment FullType on __Type { + kind + name + description + fields(includeDeprecated: true) { + name + description + args { + ...InputValue + } + type { + ...TypeRef + } + isDeprecated + deprecationReason + } + inputFields { + ...InputValue + } + interfaces { + ...TypeRef + } + enumValues(includeDeprecated: true) { + name + description + isDeprecated + deprecationReason + } + possibleTypes { + ...TypeRef + } + } + + fragment InputValue on __InputValue { + name + description + type { ...TypeRef } + defaultValue + } + + fragment TypeRef on __Type { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + ofType { + kind + name + } + } + } + } + } + } + } + }` diff --git a/vendor/github.com/Yamashou/gqlgenc/introspection/type.go b/vendor/github.com/Yamashou/gqlgenc/introspection/type.go new file mode 100644 index 000000000..789f72bf7 --- /dev/null +++ b/vendor/github.com/Yamashou/gqlgenc/introspection/type.go @@ -0,0 +1,80 @@ +package introspection + +type TypeKind string + +const ( + TypeKindScalar TypeKind = "SCALAR" + TypeKindObject TypeKind = "OBJECT" + TypeKindInterface TypeKind = "INTERFACE" + TypeKindUnion TypeKind = "UNION" + TypeKindEnum TypeKind = "ENUM" + TypeKindInputObject TypeKind = "INPUT_OBJECT" + TypeKindList TypeKind = "LIST" + TypeKindNonNull TypeKind = "NON_NULL" +) + +type FullTypes []*FullType + +func (fs FullTypes) NameMap() map[string]*FullType { + typeMap := make(map[string]*FullType) + for _, typ := range fs { + typeMap[*typ.Name] = typ + } + + return typeMap +} + +type FullType struct { + Kind TypeKind + Name *string + Description *string + Fields []*FieldValue + InputFields []*InputValue + Interfaces []*TypeRef + EnumValues []*struct { + Name string + Description *string + IsDeprecated bool + DeprecationReason *string + } + PossibleTypes []*TypeRef +} + +type FieldValue struct { + Name string + Description *string + Args []*InputValue + Type TypeRef + IsDeprecated bool + DeprecationReason *string +} + +type InputValue struct { + Name string + Description *string + Type TypeRef + DefaultValue *string +} + +type TypeRef struct { + Kind TypeKind + Name *string + OfType *TypeRef +} + +type Query struct { + Schema struct { + QueryType struct{ Name *string } + MutationType *struct{ Name *string } + SubscriptionType *struct{ Name *string } + Types FullTypes + Directives []*DirectiveType + } `graphql:"__schema"` +} + +type DirectiveType struct { + Name string + Description *string + Locations []string + Args []*InputValue +} diff --git a/vendor/github.com/Yamashou/gqlgenc/main.go b/vendor/github.com/Yamashou/gqlgenc/main.go new file mode 100644 index 000000000..26747b167 --- /dev/null +++ b/vendor/github.com/Yamashou/gqlgenc/main.go @@ -0,0 +1,27 @@ +package main + +import ( + "context" + "fmt" + "os" + + "github.com/99designs/gqlgen/api" + "github.com/Yamashou/gqlgenc/clientgen" + "github.com/Yamashou/gqlgenc/config" + "github.com/Yamashou/gqlgenc/generator" +) + +func main() { + ctx := context.Background() + cfg, err := config.LoadConfig(".gqlgenc.yml") + if err != nil { + fmt.Fprintf(os.Stderr, "%+v", err.Error()) + os.Exit(2) + } + + clientPlugin := clientgen.New(cfg.Query, cfg.Client) + if err := generator.Generate(ctx, cfg, api.AddPlugin(clientPlugin)); err != nil { + fmt.Fprintf(os.Stderr, "%+v", err.Error()) + os.Exit(4) + } +} diff --git a/vendor/github.com/agnivade/levenshtein/.travis.yml b/vendor/github.com/agnivade/levenshtein/.travis.yml index f830ec4ec..8ea828ed9 100644 --- a/vendor/github.com/agnivade/levenshtein/.travis.yml +++ b/vendor/github.com/agnivade/levenshtein/.travis.yml @@ -1,7 +1,23 @@ language: go +# See https://travis-ci.community/t/goos-js-goarch-wasm-go-run-fails-panic-newosproc-not-implemented/1651 +#addons: +# chrome: stable + +before_install: +- export GO111MODULE=on + +#install: +#- go get github.com/agnivade/wasmbrowsertest +#- mv $GOPATH/bin/wasmbrowsertest $GOPATH/bin/go_js_wasm_exec +#- export PATH=$GOPATH/bin:$PATH + go: -- 1.9.x -- 1.10.x - 1.11.x +- 1.12.x +- 1.13.x - tip + +script: +#- GOOS=js GOARCH=wasm go test -v +- go test -v diff --git a/vendor/github.com/agnivade/levenshtein/Makefile b/vendor/github.com/agnivade/levenshtein/Makefile index 4bef27dd1..5f6890d61 100644 --- a/vendor/github.com/agnivade/levenshtein/Makefile +++ b/vendor/github.com/agnivade/levenshtein/Makefile @@ -4,10 +4,12 @@ install: go install lint: - gofmt -l -s -w . && go tool vet -all . && golint + gofmt -l -s -w . && go vet . && golint -set_exit_status=1 . -test: - go test -race -v -coverprofile=coverage.txt -covermode=atomic +test: # The first 2 go gets are to support older Go versions + go get github.com/arbovm/levenshtein + go get github.com/dgryski/trifles/leven + GO111MODULE=on go test -race -v -coverprofile=coverage.txt -covermode=atomic bench: - go test -run=XXX -bench=. -benchmem + go test -run=XXX -bench=. -benchmem -count=5 diff --git a/vendor/github.com/agnivade/levenshtein/README.md b/vendor/github.com/agnivade/levenshtein/README.md index b0fd81df7..6c56689f7 100644 --- a/vendor/github.com/agnivade/levenshtein/README.md +++ b/vendor/github.com/agnivade/levenshtein/README.md @@ -6,6 +6,10 @@ levenshtein [![Build Status](https://travis-ci.org/agnivade/levenshtein.svg?bran The library is fully capable of working with non-ascii strings. But the strings are not normalized. That is left as a user-dependant use case. Please normalize the strings before passing it to the library if you have such a requirement. - https://blog.golang.org/normalization +#### Limitation + +As a performance optimization, the library can handle strings only up to 65536 characters (runes). This is only available on tip, and is not part of a tagged release yet. If you require such an optimization, please use the version at tip. + Install ------- @@ -38,10 +42,10 @@ Benchmarks ``` name time/op -Simple/ASCII-4 537ns ± 2% -Simple/French-4 956ns ± 0% -Simple/Nordic-4 1.95µs ± 1% -Simple/Tibetan-4 1.53µs ± 2% +Simple/ASCII-4 330ns ± 2% +Simple/French-4 617ns ± 2% +Simple/Nordic-4 1.16µs ± 4% +Simple/Tibetan-4 1.05µs ± 1% name alloc/op Simple/ASCII-4 96.0B ± 0% @@ -55,3 +59,22 @@ Simple/French-4 1.00 ± 0% Simple/Nordic-4 1.00 ± 0% Simple/Tibetan-4 1.00 ± 0% ``` + +Comparisons with other libraries +-------------------------------- + +``` +name time/op +Leven/ASCII/agniva-4 353ns ± 1% +Leven/ASCII/arbovm-4 485ns ± 1% +Leven/ASCII/dgryski-4 395ns ± 0% +Leven/French/agniva-4 648ns ± 1% +Leven/French/arbovm-4 791ns ± 0% +Leven/French/dgryski-4 682ns ± 0% +Leven/Nordic/agniva-4 1.28µs ± 1% +Leven/Nordic/arbovm-4 1.52µs ± 1% +Leven/Nordic/dgryski-4 1.32µs ± 1% +Leven/Tibetan/agniva-4 1.12µs ± 1% +Leven/Tibetan/arbovm-4 1.31µs ± 0% +Leven/Tibetan/dgryski-4 1.16µs ± 0% +``` diff --git a/vendor/github.com/agnivade/levenshtein/go.mod b/vendor/github.com/agnivade/levenshtein/go.mod index b2921fb35..4fcfe43e0 100644 --- a/vendor/github.com/agnivade/levenshtein/go.mod +++ b/vendor/github.com/agnivade/levenshtein/go.mod @@ -1 +1,8 @@ module github.com/agnivade/levenshtein + +go 1.13 + +require ( + github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 + github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 +) diff --git a/vendor/github.com/agnivade/levenshtein/go.sum b/vendor/github.com/agnivade/levenshtein/go.sum new file mode 100644 index 000000000..74d92aad1 --- /dev/null +++ b/vendor/github.com/agnivade/levenshtein/go.sum @@ -0,0 +1,4 @@ +github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q= +github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE= +github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g= +github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= diff --git a/vendor/github.com/agnivade/levenshtein/levenshtein.go b/vendor/github.com/agnivade/levenshtein/levenshtein.go index 6b08acade..5aecdb592 100644 --- a/vendor/github.com/agnivade/levenshtein/levenshtein.go +++ b/vendor/github.com/agnivade/levenshtein/levenshtein.go @@ -25,12 +25,10 @@ func ComputeDistance(a, b string) int { return 0 } - // We need to convert to []rune if the strings are non-ascii. + // We need to convert to []rune if the strings are non-ASCII. // This could be avoided by using utf8.RuneCountInString - // and then doing some juggling with rune indices. - // The primary challenge is keeping track of the previous rune. - // With a range loop, its not that easy. And with a for-loop - // we need to keep track of the inter-rune width using utf8.DecodeRuneInString + // and then doing some juggling with rune indices, + // but leads to far more bounds checks. It is a reasonable trade-off. s1 := []rune(a) s2 := []rune(b) @@ -42,21 +40,21 @@ func ComputeDistance(a, b string) int { lenS2 := len(s2) // init the row - x := make([]int, lenS1+1) - for i := 0; i <= lenS1; i++ { - x[i] = i + x := make([]uint16, lenS1+1) + // we start from 1 because index 0 is already 0. + for i := 1; i < len(x); i++ { + x[i] = uint16(i) } + // make a dummy bounds check to prevent the 2 bounds check down below. + // The one inside the loop is particularly costly. + _ = x[lenS1] // fill in the rest for i := 1; i <= lenS2; i++ { - prev := i - var current int - + prev := uint16(i) for j := 1; j <= lenS1; j++ { - - if s2[i-1] == s1[j-1] { - current = x[j-1] // match - } else { + current := x[j-1] // match + if s2[i-1] != s1[j-1] { current = min(min(x[j-1]+1, prev+1), x[j]+1) } x[j-1] = prev @@ -64,10 +62,10 @@ func ComputeDistance(a, b string) int { } x[lenS1] = prev } - return x[lenS1] + return int(x[lenS1]) } -func min(a, b int) int { +func min(a, b uint16) uint16 { if a < b { return a } diff --git a/vendor/github.com/cpuguy83/go-md2man/v2/LICENSE.md b/vendor/github.com/cpuguy83/go-md2man/v2/LICENSE.md new file mode 100644 index 000000000..1cade6cef --- /dev/null +++ b/vendor/github.com/cpuguy83/go-md2man/v2/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Brian Goff + +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. diff --git a/vendor/github.com/cpuguy83/go-md2man/v2/md2man/md2man.go b/vendor/github.com/cpuguy83/go-md2man/v2/md2man/md2man.go new file mode 100644 index 000000000..b48005673 --- /dev/null +++ b/vendor/github.com/cpuguy83/go-md2man/v2/md2man/md2man.go @@ -0,0 +1,14 @@ +package md2man + +import ( + "github.com/russross/blackfriday/v2" +) + +// Render converts a markdown document into a roff formatted document. +func Render(doc []byte) []byte { + renderer := NewRoffRenderer() + + return blackfriday.Run(doc, + []blackfriday.Option{blackfriday.WithRenderer(renderer), + blackfriday.WithExtensions(renderer.GetExtensions())}...) +} diff --git a/vendor/github.com/cpuguy83/go-md2man/v2/md2man/roff.go b/vendor/github.com/cpuguy83/go-md2man/v2/md2man/roff.go new file mode 100644 index 000000000..0668a66cf --- /dev/null +++ b/vendor/github.com/cpuguy83/go-md2man/v2/md2man/roff.go @@ -0,0 +1,345 @@ +package md2man + +import ( + "fmt" + "io" + "os" + "strings" + + "github.com/russross/blackfriday/v2" +) + +// roffRenderer implements the blackfriday.Renderer interface for creating +// roff format (manpages) from markdown text +type roffRenderer struct { + extensions blackfriday.Extensions + listCounters []int + firstHeader bool + defineTerm bool + listDepth int +} + +const ( + titleHeader = ".TH " + topLevelHeader = "\n\n.SH " + secondLevelHdr = "\n.SH " + otherHeader = "\n.SS " + crTag = "\n" + emphTag = "\\fI" + emphCloseTag = "\\fP" + strongTag = "\\fB" + strongCloseTag = "\\fP" + breakTag = "\n.br\n" + paraTag = "\n.PP\n" + hruleTag = "\n.ti 0\n\\l'\\n(.lu'\n" + linkTag = "\n\\[la]" + linkCloseTag = "\\[ra]" + codespanTag = "\\fB\\fC" + codespanCloseTag = "\\fR" + codeTag = "\n.PP\n.RS\n\n.nf\n" + codeCloseTag = "\n.fi\n.RE\n" + quoteTag = "\n.PP\n.RS\n" + quoteCloseTag = "\n.RE\n" + listTag = "\n.RS\n" + listCloseTag = "\n.RE\n" + arglistTag = "\n.TP\n" + tableStart = "\n.TS\nallbox;\n" + tableEnd = ".TE\n" + tableCellStart = "T{\n" + tableCellEnd = "\nT}\n" +) + +// NewRoffRenderer creates a new blackfriday Renderer for generating roff documents +// from markdown +func NewRoffRenderer() *roffRenderer { // nolint: golint + var extensions blackfriday.Extensions + + extensions |= blackfriday.NoIntraEmphasis + extensions |= blackfriday.Tables + extensions |= blackfriday.FencedCode + extensions |= blackfriday.SpaceHeadings + extensions |= blackfriday.Footnotes + extensions |= blackfriday.Titleblock + extensions |= blackfriday.DefinitionLists + return &roffRenderer{ + extensions: extensions, + } +} + +// GetExtensions returns the list of extensions used by this renderer implementation +func (r *roffRenderer) GetExtensions() blackfriday.Extensions { + return r.extensions +} + +// RenderHeader handles outputting the header at document start +func (r *roffRenderer) RenderHeader(w io.Writer, ast *blackfriday.Node) { + // disable hyphenation + out(w, ".nh\n") +} + +// RenderFooter handles outputting the footer at the document end; the roff +// renderer has no footer information +func (r *roffRenderer) RenderFooter(w io.Writer, ast *blackfriday.Node) { +} + +// RenderNode is called for each node in a markdown document; based on the node +// type the equivalent roff output is sent to the writer +func (r *roffRenderer) RenderNode(w io.Writer, node *blackfriday.Node, entering bool) blackfriday.WalkStatus { + + var walkAction = blackfriday.GoToNext + + switch node.Type { + case blackfriday.Text: + r.handleText(w, node, entering) + case blackfriday.Softbreak: + out(w, crTag) + case blackfriday.Hardbreak: + out(w, breakTag) + case blackfriday.Emph: + if entering { + out(w, emphTag) + } else { + out(w, emphCloseTag) + } + case blackfriday.Strong: + if entering { + out(w, strongTag) + } else { + out(w, strongCloseTag) + } + case blackfriday.Link: + if !entering { + out(w, linkTag+string(node.LinkData.Destination)+linkCloseTag) + } + case blackfriday.Image: + // ignore images + walkAction = blackfriday.SkipChildren + case blackfriday.Code: + out(w, codespanTag) + escapeSpecialChars(w, node.Literal) + out(w, codespanCloseTag) + case blackfriday.Document: + break + case blackfriday.Paragraph: + // roff .PP markers break lists + if r.listDepth > 0 { + return blackfriday.GoToNext + } + if entering { + out(w, paraTag) + } else { + out(w, crTag) + } + case blackfriday.BlockQuote: + if entering { + out(w, quoteTag) + } else { + out(w, quoteCloseTag) + } + case blackfriday.Heading: + r.handleHeading(w, node, entering) + case blackfriday.HorizontalRule: + out(w, hruleTag) + case blackfriday.List: + r.handleList(w, node, entering) + case blackfriday.Item: + r.handleItem(w, node, entering) + case blackfriday.CodeBlock: + out(w, codeTag) + escapeSpecialChars(w, node.Literal) + out(w, codeCloseTag) + case blackfriday.Table: + r.handleTable(w, node, entering) + case blackfriday.TableCell: + r.handleTableCell(w, node, entering) + case blackfriday.TableHead: + case blackfriday.TableBody: + case blackfriday.TableRow: + // no action as cell entries do all the nroff formatting + return blackfriday.GoToNext + default: + fmt.Fprintln(os.Stderr, "WARNING: go-md2man does not handle node type "+node.Type.String()) + } + return walkAction +} + +func (r *roffRenderer) handleText(w io.Writer, node *blackfriday.Node, entering bool) { + var ( + start, end string + ) + // handle special roff table cell text encapsulation + if node.Parent.Type == blackfriday.TableCell { + if len(node.Literal) > 30 { + start = tableCellStart + end = tableCellEnd + } else { + // end rows that aren't terminated by "tableCellEnd" with a cr if end of row + if node.Parent.Next == nil && !node.Parent.IsHeader { + end = crTag + } + } + } + out(w, start) + escapeSpecialChars(w, node.Literal) + out(w, end) +} + +func (r *roffRenderer) handleHeading(w io.Writer, node *blackfriday.Node, entering bool) { + if entering { + switch node.Level { + case 1: + if !r.firstHeader { + out(w, titleHeader) + r.firstHeader = true + break + } + out(w, topLevelHeader) + case 2: + out(w, secondLevelHdr) + default: + out(w, otherHeader) + } + } +} + +func (r *roffRenderer) handleList(w io.Writer, node *blackfriday.Node, entering bool) { + openTag := listTag + closeTag := listCloseTag + if node.ListFlags&blackfriday.ListTypeDefinition != 0 { + // tags for definition lists handled within Item node + openTag = "" + closeTag = "" + } + if entering { + r.listDepth++ + if node.ListFlags&blackfriday.ListTypeOrdered != 0 { + r.listCounters = append(r.listCounters, 1) + } + out(w, openTag) + } else { + if node.ListFlags&blackfriday.ListTypeOrdered != 0 { + r.listCounters = r.listCounters[:len(r.listCounters)-1] + } + out(w, closeTag) + r.listDepth-- + } +} + +func (r *roffRenderer) handleItem(w io.Writer, node *blackfriday.Node, entering bool) { + if entering { + if node.ListFlags&blackfriday.ListTypeOrdered != 0 { + out(w, fmt.Sprintf(".IP \"%3d.\" 5\n", r.listCounters[len(r.listCounters)-1])) + r.listCounters[len(r.listCounters)-1]++ + } else if node.ListFlags&blackfriday.ListTypeDefinition != 0 { + // state machine for handling terms and following definitions + // since blackfriday does not distinguish them properly, nor + // does it seperate them into separate lists as it should + if !r.defineTerm { + out(w, arglistTag) + r.defineTerm = true + } else { + r.defineTerm = false + } + } else { + out(w, ".IP \\(bu 2\n") + } + } else { + out(w, "\n") + } +} + +func (r *roffRenderer) handleTable(w io.Writer, node *blackfriday.Node, entering bool) { + if entering { + out(w, tableStart) + //call walker to count cells (and rows?) so format section can be produced + columns := countColumns(node) + out(w, strings.Repeat("l ", columns)+"\n") + out(w, strings.Repeat("l ", columns)+".\n") + } else { + out(w, tableEnd) + } +} + +func (r *roffRenderer) handleTableCell(w io.Writer, node *blackfriday.Node, entering bool) { + var ( + start, end string + ) + if node.IsHeader { + start = codespanTag + end = codespanCloseTag + } + if entering { + if node.Prev != nil && node.Prev.Type == blackfriday.TableCell { + out(w, "\t"+start) + } else { + out(w, start) + } + } else { + // need to carriage return if we are at the end of the header row + if node.IsHeader && node.Next == nil { + end = end + crTag + } + out(w, end) + } +} + +// because roff format requires knowing the column count before outputting any table +// data we need to walk a table tree and count the columns +func countColumns(node *blackfriday.Node) int { + var columns int + + node.Walk(func(node *blackfriday.Node, entering bool) blackfriday.WalkStatus { + switch node.Type { + case blackfriday.TableRow: + if !entering { + return blackfriday.Terminate + } + case blackfriday.TableCell: + if entering { + columns++ + } + default: + } + return blackfriday.GoToNext + }) + return columns +} + +func out(w io.Writer, output string) { + io.WriteString(w, output) // nolint: errcheck +} + +func needsBackslash(c byte) bool { + for _, r := range []byte("-_&\\~") { + if c == r { + return true + } + } + return false +} + +func escapeSpecialChars(w io.Writer, text []byte) { + for i := 0; i < len(text); i++ { + // escape initial apostrophe or period + if len(text) >= 1 && (text[0] == '\'' || text[0] == '.') { + out(w, "\\&") + } + + // directly copy normal characters + org := i + + for i < len(text) && !needsBackslash(text[i]) { + i++ + } + if i > org { + w.Write(text[org:i]) // nolint: errcheck + } + + // escape a character + if i >= len(text) { + break + } + + w.Write([]byte{'\\', text[i]}) // nolint: errcheck + } +} diff --git a/vendor/github.com/gorilla/websocket/.travis.yml b/vendor/github.com/gorilla/websocket/.travis.yml deleted file mode 100644 index a49db51c4..000000000 --- a/vendor/github.com/gorilla/websocket/.travis.yml +++ /dev/null @@ -1,19 +0,0 @@ -language: go -sudo: false - -matrix: - include: - - go: 1.7.x - - go: 1.8.x - - go: 1.9.x - - go: 1.10.x - - go: 1.11.x - - go: tip - allow_failures: - - go: tip - -script: - - go get -t -v ./... - - diff -u <(echo -n) <(gofmt -d .) - - go vet $(go list ./... | grep -v /vendor/) - - go test -v -race ./... diff --git a/vendor/github.com/gorilla/websocket/README.md b/vendor/github.com/gorilla/websocket/README.md index 20e391f86..19aa2e75c 100644 --- a/vendor/github.com/gorilla/websocket/README.md +++ b/vendor/github.com/gorilla/websocket/README.md @@ -1,14 +1,14 @@ # Gorilla WebSocket +[![GoDoc](https://godoc.org/github.com/gorilla/websocket?status.svg)](https://godoc.org/github.com/gorilla/websocket) +[![CircleCI](https://circleci.com/gh/gorilla/websocket.svg?style=svg)](https://circleci.com/gh/gorilla/websocket) + Gorilla WebSocket is a [Go](http://golang.org/) implementation of the [WebSocket](http://www.rfc-editor.org/rfc/rfc6455.txt) protocol. -[![Build Status](https://travis-ci.org/gorilla/websocket.svg?branch=master)](https://travis-ci.org/gorilla/websocket) -[![GoDoc](https://godoc.org/github.com/gorilla/websocket?status.svg)](https://godoc.org/github.com/gorilla/websocket) - ### Documentation -* [API Reference](http://godoc.org/github.com/gorilla/websocket) +* [API Reference](https://pkg.go.dev/github.com/gorilla/websocket?tab=doc) * [Chat example](https://github.com/gorilla/websocket/tree/master/examples/chat) * [Command example](https://github.com/gorilla/websocket/tree/master/examples/command) * [Client and server example](https://github.com/gorilla/websocket/tree/master/examples/echo) @@ -27,7 +27,7 @@ package API is stable. ### Protocol Compliance The Gorilla WebSocket package passes the server tests in the [Autobahn Test -Suite](http://autobahn.ws/testsuite) using the application in the [examples/autobahn +Suite](https://github.com/crossbario/autobahn-testsuite) using the application in the [examples/autobahn subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn). ### Gorilla WebSocket compared with other packages @@ -40,7 +40,7 @@ subdirectory](https://github.com/gorilla/websocket/tree/master/examples/autobahn RFC 6455 Features -Passes Autobahn Test SuiteYesNo +Passes Autobahn Test SuiteYesNo Receive fragmented messageYesNo, see note 1 Send close messageYesNo Send pings and receive pongsYesNo diff --git a/vendor/github.com/gorilla/websocket/client.go b/vendor/github.com/gorilla/websocket/client.go index 2e32fd506..962c06a39 100644 --- a/vendor/github.com/gorilla/websocket/client.go +++ b/vendor/github.com/gorilla/websocket/client.go @@ -70,7 +70,7 @@ type Dialer struct { // HandshakeTimeout specifies the duration for the handshake to complete. HandshakeTimeout time.Duration - // ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer + // ReadBufferSize and WriteBufferSize specify I/O buffer sizes in bytes. If a buffer // size is zero, then a useful default size is used. The I/O buffer sizes // do not limit the size of the messages that can be sent or received. ReadBufferSize, WriteBufferSize int @@ -140,7 +140,7 @@ var nilDialer = *DefaultDialer // Use the response.Header to get the selected subprotocol // (Sec-WebSocket-Protocol) and cookies (Set-Cookie). // -// The context will be used in the request and in the Dialer +// The context will be used in the request and in the Dialer. // // If the WebSocket handshake fails, ErrBadHandshake is returned along with a // non-nil *http.Response so that callers can handle redirects, authentication, diff --git a/vendor/github.com/gorilla/websocket/conn.go b/vendor/github.com/gorilla/websocket/conn.go index d2a21c148..ca46d2f79 100644 --- a/vendor/github.com/gorilla/websocket/conn.go +++ b/vendor/github.com/gorilla/websocket/conn.go @@ -244,8 +244,8 @@ type Conn struct { subprotocol string // Write fields - mu chan bool // used as mutex to protect write to conn - writeBuf []byte // frame is constructed in this buffer. + mu chan struct{} // used as mutex to protect write to conn + writeBuf []byte // frame is constructed in this buffer. writePool BufferPool writeBufSize int writeDeadline time.Time @@ -260,10 +260,12 @@ type Conn struct { newCompressionWriter func(io.WriteCloser, int) io.WriteCloser // Read fields - reader io.ReadCloser // the current reader returned to the application - readErr error - br *bufio.Reader - readRemaining int64 // bytes remaining in current frame. + reader io.ReadCloser // the current reader returned to the application + readErr error + br *bufio.Reader + // bytes remaining in current frame. + // set setReadRemaining to safely update this value and prevent overflow + readRemaining int64 readFinal bool // true the current message has more frames. readLength int64 // Message size. readLimit int64 // Maximum message size. @@ -300,8 +302,8 @@ func newConn(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int, writeBuf = make([]byte, writeBufferSize) } - mu := make(chan bool, 1) - mu <- true + mu := make(chan struct{}, 1) + mu <- struct{}{} c := &Conn{ isServer: isServer, br: br, @@ -320,6 +322,17 @@ func newConn(conn net.Conn, isServer bool, readBufferSize, writeBufferSize int, return c } +// setReadRemaining tracks the number of bytes remaining on the connection. If n +// overflows, an ErrReadLimit is returned. +func (c *Conn) setReadRemaining(n int64) error { + if n < 0 { + return ErrReadLimit + } + + c.readRemaining = n + return nil +} + // Subprotocol returns the negotiated protocol for the connection. func (c *Conn) Subprotocol() string { return c.subprotocol @@ -364,7 +377,7 @@ func (c *Conn) read(n int) ([]byte, error) { func (c *Conn) write(frameType int, deadline time.Time, buf0, buf1 []byte) error { <-c.mu - defer func() { c.mu <- true }() + defer func() { c.mu <- struct{}{} }() c.writeErrMu.Lock() err := c.writeErr @@ -416,7 +429,7 @@ func (c *Conn) WriteControl(messageType int, data []byte, deadline time.Time) er maskBytes(key, 0, buf[6:]) } - d := time.Hour * 1000 + d := 1000 * time.Hour if !deadline.IsZero() { d = deadline.Sub(time.Now()) if d < 0 { @@ -431,7 +444,7 @@ func (c *Conn) WriteControl(messageType int, data []byte, deadline time.Time) er case <-timer.C: return errWriteTimeout } - defer func() { c.mu <- true }() + defer func() { c.mu <- struct{}{} }() c.writeErrMu.Lock() err := c.writeErr @@ -451,7 +464,8 @@ func (c *Conn) WriteControl(messageType int, data []byte, deadline time.Time) er return err } -func (c *Conn) prepWrite(messageType int) error { +// beginMessage prepares a connection and message writer for a new message. +func (c *Conn) beginMessage(mw *messageWriter, messageType int) error { // Close previous writer if not already closed by the application. It's // probably better to return an error in this situation, but we cannot // change this without breaking existing applications. @@ -471,6 +485,10 @@ func (c *Conn) prepWrite(messageType int) error { return err } + mw.c = c + mw.frameType = messageType + mw.pos = maxFrameHeaderSize + if c.writeBuf == nil { wpd, ok := c.writePool.Get().(writePoolData) if ok { @@ -491,16 +509,11 @@ func (c *Conn) prepWrite(messageType int) error { // All message types (TextMessage, BinaryMessage, CloseMessage, PingMessage and // PongMessage) are supported. func (c *Conn) NextWriter(messageType int) (io.WriteCloser, error) { - if err := c.prepWrite(messageType); err != nil { + var mw messageWriter + if err := c.beginMessage(&mw, messageType); err != nil { return nil, err } - - mw := &messageWriter{ - c: c, - frameType: messageType, - pos: maxFrameHeaderSize, - } - c.writer = mw + c.writer = &mw if c.newCompressionWriter != nil && c.enableWriteCompression && isData(messageType) { w := c.newCompressionWriter(c.writer, c.compressionLevel) mw.compress = true @@ -517,10 +530,16 @@ type messageWriter struct { err error } -func (w *messageWriter) fatal(err error) error { +func (w *messageWriter) endMessage(err error) error { if w.err != nil { - w.err = err - w.c.writer = nil + return err + } + c := w.c + w.err = err + c.writer = nil + if c.writePool != nil { + c.writePool.Put(writePoolData{buf: c.writeBuf}) + c.writeBuf = nil } return err } @@ -534,7 +553,7 @@ func (w *messageWriter) flushFrame(final bool, extra []byte) error { // Check for invalid control frames. if isControl(w.frameType) && (!final || length > maxControlFramePayloadSize) { - return w.fatal(errInvalidControlFrame) + return w.endMessage(errInvalidControlFrame) } b0 := byte(w.frameType) @@ -579,7 +598,7 @@ func (w *messageWriter) flushFrame(final bool, extra []byte) error { copy(c.writeBuf[maxFrameHeaderSize-4:], key[:]) maskBytes(key, 0, c.writeBuf[maxFrameHeaderSize:w.pos]) if len(extra) > 0 { - return c.writeFatal(errors.New("websocket: internal error, extra used in client mode")) + return w.endMessage(c.writeFatal(errors.New("websocket: internal error, extra used in client mode"))) } } @@ -600,15 +619,11 @@ func (w *messageWriter) flushFrame(final bool, extra []byte) error { c.isWriting = false if err != nil { - return w.fatal(err) + return w.endMessage(err) } if final { - c.writer = nil - if c.writePool != nil { - c.writePool.Put(writePoolData{buf: c.writeBuf}) - c.writeBuf = nil - } + w.endMessage(errWriteClosed) return nil } @@ -706,11 +721,7 @@ func (w *messageWriter) Close() error { if w.err != nil { return w.err } - if err := w.flushFrame(true, nil); err != nil { - return err - } - w.err = errWriteClosed - return nil + return w.flushFrame(true, nil) } // WritePreparedMessage writes prepared message into connection. @@ -742,10 +753,10 @@ func (c *Conn) WriteMessage(messageType int, data []byte) error { if c.isServer && (c.newCompressionWriter == nil || !c.enableWriteCompression) { // Fast path with no allocations and single frame. - if err := c.prepWrite(messageType); err != nil { + var mw messageWriter + if err := c.beginMessage(&mw, messageType); err != nil { return err } - mw := messageWriter{c: c, frameType: messageType, pos: maxFrameHeaderSize} n := copy(c.writeBuf[mw.pos:], data) mw.pos += n data = data[n:] @@ -792,7 +803,7 @@ func (c *Conn) advanceFrame() (int, error) { final := p[0]&finalBit != 0 frameType := int(p[0] & 0xf) mask := p[1]&maskBit != 0 - c.readRemaining = int64(p[1] & 0x7f) + c.setReadRemaining(int64(p[1] & 0x7f)) c.readDecompress = false if c.newDecompressionReader != nil && (p[0]&rsv1Bit) != 0 { @@ -826,7 +837,17 @@ func (c *Conn) advanceFrame() (int, error) { return noFrame, c.handleProtocolError("unknown opcode " + strconv.Itoa(frameType)) } - // 3. Read and parse frame length. + // 3. Read and parse frame length as per + // https://tools.ietf.org/html/rfc6455#section-5.2 + // + // The length of the "Payload data", in bytes: if 0-125, that is the payload + // length. + // - If 126, the following 2 bytes interpreted as a 16-bit unsigned + // integer are the payload length. + // - If 127, the following 8 bytes interpreted as + // a 64-bit unsigned integer (the most significant bit MUST be 0) are the + // payload length. Multibyte length quantities are expressed in network byte + // order. switch c.readRemaining { case 126: @@ -834,13 +855,19 @@ func (c *Conn) advanceFrame() (int, error) { if err != nil { return noFrame, err } - c.readRemaining = int64(binary.BigEndian.Uint16(p)) + + if err := c.setReadRemaining(int64(binary.BigEndian.Uint16(p))); err != nil { + return noFrame, err + } case 127: p, err := c.read(8) if err != nil { return noFrame, err } - c.readRemaining = int64(binary.BigEndian.Uint64(p)) + + if err := c.setReadRemaining(int64(binary.BigEndian.Uint64(p))); err != nil { + return noFrame, err + } } // 4. Handle frame masking. @@ -863,6 +890,12 @@ func (c *Conn) advanceFrame() (int, error) { if frameType == continuationFrame || frameType == TextMessage || frameType == BinaryMessage { c.readLength += c.readRemaining + // Don't allow readLength to overflow in the presence of a large readRemaining + // counter. + if c.readLength < 0 { + return noFrame, ErrReadLimit + } + if c.readLimit > 0 && c.readLength > c.readLimit { c.WriteControl(CloseMessage, FormatCloseMessage(CloseMessageTooBig, ""), time.Now().Add(writeWait)) return noFrame, ErrReadLimit @@ -876,7 +909,7 @@ func (c *Conn) advanceFrame() (int, error) { var payload []byte if c.readRemaining > 0 { payload, err = c.read(int(c.readRemaining)) - c.readRemaining = 0 + c.setReadRemaining(0) if err != nil { return noFrame, err } @@ -949,6 +982,7 @@ func (c *Conn) NextReader() (messageType int, r io.Reader, err error) { c.readErr = hideTempErr(err) break } + if frameType == TextMessage || frameType == BinaryMessage { c.messageReader = &messageReader{c} c.reader = c.messageReader @@ -989,7 +1023,9 @@ func (r *messageReader) Read(b []byte) (int, error) { if c.isServer { c.readMaskPos = maskBytes(c.readMaskKey, c.readMaskPos, b[:n]) } - c.readRemaining -= int64(n) + rem := c.readRemaining + rem -= int64(n) + c.setReadRemaining(rem) if c.readRemaining > 0 && c.readErr == io.EOF { c.readErr = errUnexpectedEOF } @@ -1041,7 +1077,7 @@ func (c *Conn) SetReadDeadline(t time.Time) error { return c.conn.SetReadDeadline(t) } -// SetReadLimit sets the maximum size for a message read from the peer. If a +// SetReadLimit sets the maximum size in bytes for a message read from the peer. If a // message exceeds the limit, the connection sends a close message to the peer // and returns ErrReadLimit to the application. func (c *Conn) SetReadLimit(limit int64) { diff --git a/vendor/github.com/gorilla/websocket/doc.go b/vendor/github.com/gorilla/websocket/doc.go index dcce1a63c..8db0cef95 100644 --- a/vendor/github.com/gorilla/websocket/doc.go +++ b/vendor/github.com/gorilla/websocket/doc.go @@ -151,6 +151,53 @@ // checking. The application is responsible for checking the Origin header // before calling the Upgrade function. // +// Buffers +// +// Connections buffer network input and output to reduce the number +// of system calls when reading or writing messages. +// +// Write buffers are also used for constructing WebSocket frames. See RFC 6455, +// Section 5 for a discussion of message framing. A WebSocket frame header is +// written to the network each time a write buffer is flushed to the network. +// Decreasing the size of the write buffer can increase the amount of framing +// overhead on the connection. +// +// The buffer sizes in bytes are specified by the ReadBufferSize and +// WriteBufferSize fields in the Dialer and Upgrader. The Dialer uses a default +// size of 4096 when a buffer size field is set to zero. The Upgrader reuses +// buffers created by the HTTP server when a buffer size field is set to zero. +// The HTTP server buffers have a size of 4096 at the time of this writing. +// +// The buffer sizes do not limit the size of a message that can be read or +// written by a connection. +// +// Buffers are held for the lifetime of the connection by default. If the +// Dialer or Upgrader WriteBufferPool field is set, then a connection holds the +// write buffer only when writing a message. +// +// Applications should tune the buffer sizes to balance memory use and +// performance. Increasing the buffer size uses more memory, but can reduce the +// number of system calls to read or write the network. In the case of writing, +// increasing the buffer size can reduce the number of frame headers written to +// the network. +// +// Some guidelines for setting buffer parameters are: +// +// Limit the buffer sizes to the maximum expected message size. Buffers larger +// than the largest message do not provide any benefit. +// +// Depending on the distribution of message sizes, setting the buffer size to +// a value less than the maximum expected message size can greatly reduce memory +// use with a small impact on performance. Here's an example: If 99% of the +// messages are smaller than 256 bytes and the maximum message size is 512 +// bytes, then a buffer size of 256 bytes will result in 1.01 more system calls +// than a buffer size of 512 bytes. The memory savings is 50%. +// +// A write buffer pool is useful when the application has a modest number +// writes over a large number of connections. when buffers are pooled, a larger +// buffer size has a reduced impact on total memory use and has the benefit of +// reducing system calls and frame overhead. +// // Compression EXPERIMENTAL // // Per message compression extensions (RFC 7692) are experimentally supported diff --git a/vendor/github.com/gorilla/websocket/go.mod b/vendor/github.com/gorilla/websocket/go.mod new file mode 100644 index 000000000..1a7afd502 --- /dev/null +++ b/vendor/github.com/gorilla/websocket/go.mod @@ -0,0 +1,3 @@ +module github.com/gorilla/websocket + +go 1.12 diff --git a/vendor/github.com/gorilla/websocket/go.sum b/vendor/github.com/gorilla/websocket/go.sum new file mode 100644 index 000000000..e69de29bb diff --git a/vendor/github.com/gorilla/websocket/join.go b/vendor/github.com/gorilla/websocket/join.go new file mode 100644 index 000000000..c64f8c829 --- /dev/null +++ b/vendor/github.com/gorilla/websocket/join.go @@ -0,0 +1,42 @@ +// Copyright 2019 The Gorilla WebSocket Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package websocket + +import ( + "io" + "strings" +) + +// JoinMessages concatenates received messages to create a single io.Reader. +// The string term is appended to each message. The returned reader does not +// support concurrent calls to the Read method. +func JoinMessages(c *Conn, term string) io.Reader { + return &joinReader{c: c, term: term} +} + +type joinReader struct { + c *Conn + term string + r io.Reader +} + +func (r *joinReader) Read(p []byte) (int, error) { + if r.r == nil { + var err error + _, r.r, err = r.c.NextReader() + if err != nil { + return 0, err + } + if r.term != "" { + r.r = io.MultiReader(r.r, strings.NewReader(r.term)) + } + } + n, err := r.r.Read(p) + if err == io.EOF { + err = nil + r.r = nil + } + return n, err +} diff --git a/vendor/github.com/gorilla/websocket/prepared.go b/vendor/github.com/gorilla/websocket/prepared.go index 74ec565d2..c854225e9 100644 --- a/vendor/github.com/gorilla/websocket/prepared.go +++ b/vendor/github.com/gorilla/websocket/prepared.go @@ -73,8 +73,8 @@ func (pm *PreparedMessage) frame(key prepareKey) (int, []byte, error) { // Prepare a frame using a 'fake' connection. // TODO: Refactor code in conn.go to allow more direct construction of // the frame. - mu := make(chan bool, 1) - mu <- true + mu := make(chan struct{}, 1) + mu <- struct{}{} var nc prepareConn c := &Conn{ conn: &nc, diff --git a/vendor/github.com/gorilla/websocket/proxy.go b/vendor/github.com/gorilla/websocket/proxy.go index bf2478e43..e87a8c9f0 100644 --- a/vendor/github.com/gorilla/websocket/proxy.go +++ b/vendor/github.com/gorilla/websocket/proxy.go @@ -22,18 +22,18 @@ func (fn netDialerFunc) Dial(network, addr string) (net.Conn, error) { func init() { proxy_RegisterDialerType("http", func(proxyURL *url.URL, forwardDialer proxy_Dialer) (proxy_Dialer, error) { - return &httpProxyDialer{proxyURL: proxyURL, fowardDial: forwardDialer.Dial}, nil + return &httpProxyDialer{proxyURL: proxyURL, forwardDial: forwardDialer.Dial}, nil }) } type httpProxyDialer struct { - proxyURL *url.URL - fowardDial func(network, addr string) (net.Conn, error) + proxyURL *url.URL + forwardDial func(network, addr string) (net.Conn, error) } func (hpd *httpProxyDialer) Dial(network string, addr string) (net.Conn, error) { hostPort, _ := hostPortNoPort(hpd.proxyURL) - conn, err := hpd.fowardDial(network, hostPort) + conn, err := hpd.forwardDial(network, hostPort) if err != nil { return nil, err } diff --git a/vendor/github.com/gorilla/websocket/server.go b/vendor/github.com/gorilla/websocket/server.go index a761824b3..887d55891 100644 --- a/vendor/github.com/gorilla/websocket/server.go +++ b/vendor/github.com/gorilla/websocket/server.go @@ -27,7 +27,7 @@ type Upgrader struct { // HandshakeTimeout specifies the duration for the handshake to complete. HandshakeTimeout time.Duration - // ReadBufferSize and WriteBufferSize specify I/O buffer sizes. If a buffer + // ReadBufferSize and WriteBufferSize specify I/O buffer sizes in bytes. If a buffer // size is zero, then buffers allocated by the HTTP server are used. The // I/O buffer sizes do not limit the size of the messages that can be sent // or received. @@ -153,7 +153,7 @@ func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeade challengeKey := r.Header.Get("Sec-Websocket-Key") if challengeKey == "" { - return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: `Sec-WebSocket-Key' header is missing or blank") + return u.returnError(w, r, http.StatusBadRequest, "websocket: not a websocket handshake: 'Sec-WebSocket-Key' header is missing or blank") } subprotocol := u.selectSubprotocol(r, responseHeader) diff --git a/vendor/github.com/gorilla/websocket/util.go b/vendor/github.com/gorilla/websocket/util.go index 354001e1e..7bf2f66c6 100644 --- a/vendor/github.com/gorilla/websocket/util.go +++ b/vendor/github.com/gorilla/websocket/util.go @@ -31,68 +31,113 @@ func generateChallengeKey() (string, error) { return base64.StdEncoding.EncodeToString(p), nil } -// Octet types from RFC 2616. -var octetTypes [256]byte - -const ( - isTokenOctet = 1 << iota - isSpaceOctet -) - -func init() { - // From RFC 2616 - // - // OCTET = - // CHAR = - // CTL = - // CR = - // LF = - // SP = - // HT = - // <"> = - // CRLF = CR LF - // LWS = [CRLF] 1*( SP | HT ) - // TEXT = - // separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <"> - // | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT - // token = 1* - // qdtext = > - - for c := 0; c < 256; c++ { - var t byte - isCtl := c <= 31 || c == 127 - isChar := 0 <= c && c <= 127 - isSeparator := strings.IndexRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) >= 0 - if strings.IndexRune(" \t\r\n", rune(c)) >= 0 { - t |= isSpaceOctet - } - if isChar && !isCtl && !isSeparator { - t |= isTokenOctet - } - octetTypes[c] = t - } +// Token octets per RFC 2616. +var isTokenOctet = [256]bool{ + '!': true, + '#': true, + '$': true, + '%': true, + '&': true, + '\'': true, + '*': true, + '+': true, + '-': true, + '.': true, + '0': true, + '1': true, + '2': true, + '3': true, + '4': true, + '5': true, + '6': true, + '7': true, + '8': true, + '9': true, + 'A': true, + 'B': true, + 'C': true, + 'D': true, + 'E': true, + 'F': true, + 'G': true, + 'H': true, + 'I': true, + 'J': true, + 'K': true, + 'L': true, + 'M': true, + 'N': true, + 'O': true, + 'P': true, + 'Q': true, + 'R': true, + 'S': true, + 'T': true, + 'U': true, + 'W': true, + 'V': true, + 'X': true, + 'Y': true, + 'Z': true, + '^': true, + '_': true, + '`': true, + 'a': true, + 'b': true, + 'c': true, + 'd': true, + 'e': true, + 'f': true, + 'g': true, + 'h': true, + 'i': true, + 'j': true, + 'k': true, + 'l': true, + 'm': true, + 'n': true, + 'o': true, + 'p': true, + 'q': true, + 'r': true, + 's': true, + 't': true, + 'u': true, + 'v': true, + 'w': true, + 'x': true, + 'y': true, + 'z': true, + '|': true, + '~': true, } +// skipSpace returns a slice of the string s with all leading RFC 2616 linear +// whitespace removed. func skipSpace(s string) (rest string) { i := 0 for ; i < len(s); i++ { - if octetTypes[s[i]]&isSpaceOctet == 0 { + if b := s[i]; b != ' ' && b != '\t' { break } } return s[i:] } +// nextToken returns the leading RFC 2616 token of s and the string following +// the token. func nextToken(s string) (token, rest string) { i := 0 for ; i < len(s); i++ { - if octetTypes[s[i]]&isTokenOctet == 0 { + if !isTokenOctet[s[i]] { break } } return s[:i], s[i:] } +// nextTokenOrQuoted returns the leading token or quoted string per RFC 2616 +// and the string following the token or quoted string. func nextTokenOrQuoted(s string) (value string, rest string) { if !strings.HasPrefix(s, "\"") { return nextToken(s) @@ -128,7 +173,8 @@ func nextTokenOrQuoted(s string) (value string, rest string) { return "", "" } -// equalASCIIFold returns true if s is equal to t with ASCII case folding. +// equalASCIIFold returns true if s is equal to t with ASCII case folding as +// defined in RFC 4790. func equalASCIIFold(s, t string) bool { for s != "" && t != "" { sr, size := utf8.DecodeRuneInString(s) diff --git a/vendor/github.com/magiconair/properties/.travis.yml b/vendor/github.com/magiconair/properties/.travis.yml index 3e7c3d2c8..f07376f9c 100644 --- a/vendor/github.com/magiconair/properties/.travis.yml +++ b/vendor/github.com/magiconair/properties/.travis.yml @@ -7,4 +7,6 @@ go: - 1.8.x - 1.9.x - "1.10.x" + - "1.11.x" + - "1.12.x" - tip diff --git a/vendor/github.com/magiconair/properties/CHANGELOG.md b/vendor/github.com/magiconair/properties/CHANGELOG.md index f83adc205..176626a15 100644 --- a/vendor/github.com/magiconair/properties/CHANGELOG.md +++ b/vendor/github.com/magiconair/properties/CHANGELOG.md @@ -1,5 +1,13 @@ ## Changelog +### [1.8.1](https://github.com/magiconair/properties/tree/v1.8.1) - 10 May 2019 + + * [PR #26](https://github.com/magiconair/properties/pull/35): Close body always after request + + This patch ensures that in `LoadURL` the response body is always closed. + + Thanks to [@liubog2008](https://github.com/liubog2008) for the patch. + ### [1.8](https://github.com/magiconair/properties/tree/v1.8) - 15 May 2018 * [PR #26](https://github.com/magiconair/properties/pull/26): Disable expansion during loading diff --git a/vendor/github.com/magiconair/properties/README.md b/vendor/github.com/magiconair/properties/README.md index 2c05f290f..42ed5c37c 100644 --- a/vendor/github.com/magiconair/properties/README.md +++ b/vendor/github.com/magiconair/properties/README.md @@ -1,6 +1,6 @@ [![](https://img.shields.io/github/tag/magiconair/properties.svg?style=flat-square&label=release)](https://github.com/magiconair/properties/releases) [![Travis CI Status](https://img.shields.io/travis/magiconair/properties.svg?branch=master&style=flat-square&label=travis)](https://travis-ci.org/magiconair/properties) -[![Codeship CI Status](https://img.shields.io/codeship/16aaf660-f615-0135-b8f0-7e33b70920c0/master.svg?label=codeship&style=flat-square)](https://app.codeship.com/projects/274177") +[![CircleCI Status](https://img.shields.io/circleci/project/github/magiconair/properties.svg?label=circle+ci&style=flat-square)](https://circleci.com/gh/magiconair/properties) [![License](https://img.shields.io/badge/License-BSD%202--Clause-orange.svg?style=flat-square)](https://raw.githubusercontent.com/magiconair/properties/master/LICENSE) [![GoDoc](http://img.shields.io/badge/godoc-reference-5272B4.svg?style=flat-square)](http://godoc.org/github.com/magiconair/properties) @@ -30,7 +30,7 @@ changed from `panic` to `log.Fatal` but this is configurable and custom error handling functions can be provided. See the package documentation for details. -Read the full documentation on [GoDoc](https://godoc.org/github.com/magiconair/properties) [![GoDoc](https://godoc.org/github.com/magiconair/properties?status.png)](https://godoc.org/github.com/magiconair/properties) +Read the full documentation on [![GoDoc](http://img.shields.io/badge/godoc-reference-5272B4.svg?style=flat-square)](http://godoc.org/github.com/magiconair/properties) ## Getting Started diff --git a/vendor/github.com/magiconair/properties/go.mod b/vendor/github.com/magiconair/properties/go.mod new file mode 100644 index 000000000..02a6f8655 --- /dev/null +++ b/vendor/github.com/magiconair/properties/go.mod @@ -0,0 +1 @@ +module github.com/magiconair/properties diff --git a/vendor/github.com/magiconair/properties/load.go b/vendor/github.com/magiconair/properties/load.go index c8e1b5804..ab9532535 100644 --- a/vendor/github.com/magiconair/properties/load.go +++ b/vendor/github.com/magiconair/properties/load.go @@ -115,6 +115,7 @@ func (l *Loader) LoadURL(url string) (*Properties, error) { if err != nil { return nil, fmt.Errorf("properties: error fetching %q. %s", url, err) } + defer resp.Body.Close() if resp.StatusCode == 404 && l.IgnoreMissing { LogPrintf("properties: %s returned %d. skipping", url, resp.StatusCode) @@ -129,7 +130,6 @@ func (l *Loader) LoadURL(url string) (*Properties, error) { if err != nil { return nil, fmt.Errorf("properties: %s error reading response. %s", url, err) } - defer resp.Body.Close() ct := resp.Header.Get("Content-Type") var enc Encoding diff --git a/vendor/github.com/matryer/moq/.gitignore b/vendor/github.com/matryer/moq/.gitignore new file mode 100644 index 000000000..b15784e0c --- /dev/null +++ b/vendor/github.com/matryer/moq/.gitignore @@ -0,0 +1,25 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof +.vscode diff --git a/vendor/github.com/matryer/moq/.travis.yml b/vendor/github.com/matryer/moq/.travis.yml new file mode 100644 index 000000000..1bcf6df60 --- /dev/null +++ b/vendor/github.com/matryer/moq/.travis.yml @@ -0,0 +1,23 @@ +language: go + +sudo: false + +branches: + only: + - master + +go: + - 1.11.x + - 1.12.x + - 1.13.x + - tip + +before_install: + - go get golang.org/x/lint/golint + +before_script: + - go vet ./... + - golint ./... + +script: + - go test -v ./... diff --git a/vendor/github.com/matryer/moq/LICENSE b/vendor/github.com/matryer/moq/LICENSE new file mode 100644 index 000000000..157d9d25d --- /dev/null +++ b/vendor/github.com/matryer/moq/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016 Mat Ryer and David Hernandez + +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. diff --git a/vendor/github.com/matryer/moq/README.md b/vendor/github.com/matryer/moq/README.md new file mode 100644 index 000000000..5327c44e9 --- /dev/null +++ b/vendor/github.com/matryer/moq/README.md @@ -0,0 +1,110 @@ +![moq logo](moq-logo-small.png) [![Build Status](https://travis-ci.org/matryer/moq.svg?branch=master)](https://travis-ci.org/matryer/moq) [![Go Report Card](https://goreportcard.com/badge/github.com/matryer/moq)](https://goreportcard.com/report/github.com/matryer/moq) + +Interface mocking tool for go generate. + +By [Mat Ryer](https://twitter.com/matryer) and [David Hernandez](https://github.com/dahernan), with ideas lovingly stolen from [Ernesto Jimenez](https://github.com/ernesto-jimenez). + +### What is Moq? + +Moq is a tool that generates a struct from any interface. The struct can be used in test code as a mock of the interface. + +![Preview](preview.png) + +above: Moq generates the code on the right. + +You can read more in the [Meet Moq blog post](http://bit.ly/meetmoq). + +### Installing + +To start using Moq, just run go get: +``` +$ go get github.com/matryer/moq +``` + +### Usage + +``` +moq [flags] destination interface [interface2 [interface3 [...]]] + -out string + output file (default stdout) + -pkg string + package name (default will infer) +Specifying an alias for the mock is also supported with the format 'interface:alias' +Ex: moq -pkg different . MyInterface:MyMock +``` + +In a command line: + +``` +$ moq -out mocks_test.go . MyInterface +``` + +In code (for go generate): + +```go +package my + +//go:generate moq -out myinterface_moq_test.go . MyInterface + +type MyInterface interface { + Method1() error + Method2(i int) +} +``` + +Then run `go generate` for your package. + +### How to use it + +Mocking interfaces is a nice way to write unit tests where you can easily control the behaviour of the mocked object. + +Moq creates a struct that has a function field for each method, which you can declare in your test code. + +In this example, Moq generated the `EmailSenderMock` type: + +```go +func TestCompleteSignup(t *testing.T) { + + var sentTo string + + mockedEmailSender = &EmailSenderMock{ + SendFunc: func(to, subject, body string) error { + sentTo = to + return nil + }, + } + + CompleteSignUp("me@email.com", mockedEmailSender) + + callsToSend := len(mockedEmailSender.SendCalls()) + if callsToSend != 1 { + t.Errorf("Send was called %d times", callsToSend) + } + if sentTo != "me@email.com" { + t.Errorf("unexpected recipient: %s", sentTo) + } + +} + +func CompleteSignUp(to string, sender EmailSender) { + // TODO: this +} +``` + +The mocked structure implements the interface, where each method calls the associated function field. + +## Tips + +* Keep mocked logic inside the test that is using it +* Only mock the fields you need +* It will panic if a nil function gets called +* Name arguments in the interface for a better experience +* Use closured variables inside your test function to capture details about the calls to the methods +* Use `.MethodCalls()` to track the calls +* Use `go:generate` to invoke the `moq` command + +## License + +The Moq project (and all code) is licensed under the [MIT License](LICENSE). + +The Moq logo was created by [Chris Ryer](http://chrisryer.co.uk) and is licensed under the [Creative Commons Attribution 3.0 License](https://creativecommons.org/licenses/by/3.0/). diff --git a/vendor/github.com/matryer/moq/main.go b/vendor/github.com/matryer/moq/main.go new file mode 100644 index 000000000..cd246cc8f --- /dev/null +++ b/vendor/github.com/matryer/moq/main.go @@ -0,0 +1,77 @@ +package main + +import ( + "bytes" + "errors" + "flag" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + + "github.com/matryer/moq/pkg/moq" +) + +type userFlags struct { + outFile string + pkgName string + args []string +} + +func main() { + var flags userFlags + flag.StringVar(&flags.outFile, "out", "", "output file (default stdout)") + flag.StringVar(&flags.pkgName, "pkg", "", "package name (default will infer)") + + flag.Usage = func() { + fmt.Println(`moq [flags] destination interface [interface2 [interface3 [...]]]`) + flag.PrintDefaults() + fmt.Println(`Specifying an alias for the mock is also supported with the format 'interface:alias'`) + fmt.Println(`Ex: moq -pkg different . MyInterface:MyMock`) + } + + flag.Parse() + flags.args = flag.Args() + + if err := run(flags); err != nil { + fmt.Fprintln(os.Stderr, err) + flag.Usage() + os.Exit(1) + } +} + +func run(flags userFlags) error { + if len(flags.args) < 2 { + return errors.New("not enough arguments") + } + + var buf bytes.Buffer + var out io.Writer = os.Stdout + if flags.outFile != "" { + out = &buf + } + + destination := flags.args[0] + args := flags.args[1:] + m, err := moq.New(destination, flags.pkgName) + if err != nil { + return err + } + + if err = m.Mock(out, args...); err != nil { + return err + } + + if flags.outFile == "" { + return nil + } + + // create the file + err = os.MkdirAll(filepath.Dir(flags.outFile), 0755) + if err != nil { + return err + } + + return ioutil.WriteFile(flags.outFile, buf.Bytes(), 0644) +} diff --git a/vendor/github.com/matryer/moq/moq-logo-small.png b/vendor/github.com/matryer/moq/moq-logo-small.png new file mode 100644 index 000000000..7a71c177e Binary files /dev/null and b/vendor/github.com/matryer/moq/moq-logo-small.png differ diff --git a/vendor/github.com/matryer/moq/moq-logo.png b/vendor/github.com/matryer/moq/moq-logo.png new file mode 100644 index 000000000..7eae08f6f Binary files /dev/null and b/vendor/github.com/matryer/moq/moq-logo.png differ diff --git a/vendor/github.com/matryer/moq/pkg/moq/moq.go b/vendor/github.com/matryer/moq/pkg/moq/moq.go new file mode 100644 index 000000000..971c9421e --- /dev/null +++ b/vendor/github.com/matryer/moq/pkg/moq/moq.go @@ -0,0 +1,372 @@ +package moq + +import ( + "bytes" + "errors" + "fmt" + "go/format" + "go/types" + "io" + "os" + "path" + "path/filepath" + "strings" + "text/template" + + "golang.org/x/tools/go/packages" +) + +// This list comes from the golint codebase. Golint will complain about any of +// these being mixed-case, like "Id" instead of "ID". +var golintInitialisms = []string{ + "ACL", + "API", + "ASCII", + "CPU", + "CSS", + "DNS", + "EOF", + "GUID", + "HTML", + "HTTP", + "HTTPS", + "ID", + "IP", + "JSON", + "LHS", + "QPS", + "RAM", + "RHS", + "RPC", + "SLA", + "SMTP", + "SQL", + "SSH", + "TCP", + "TLS", + "TTL", + "UDP", + "UI", + "UID", + "UUID", + "URI", + "URL", + "UTF8", + "VM", + "XML", + "XMPP", + "XSRF", + "XSS", +} + +// Mocker can generate mock structs. +type Mocker struct { + srcPkg *packages.Package + tmpl *template.Template + pkgName string + pkgPath string + + imports map[string]bool +} + +// New makes a new Mocker for the specified package directory. +func New(src, packageName string) (*Mocker, error) { + srcPkg, err := pkgInfoFromPath(src, packages.LoadSyntax) + if err != nil { + return nil, fmt.Errorf("couldn't load source package: %s", err) + } + pkgPath, err := findPkgPath(packageName, srcPkg) + if err != nil { + return nil, fmt.Errorf("couldn't load mock package: %s", err) + } + + tmpl, err := template.New("moq").Funcs(templateFuncs).Parse(moqTemplate) + if err != nil { + return nil, err + } + return &Mocker{ + tmpl: tmpl, + srcPkg: srcPkg, + pkgName: preventZeroStr(packageName, srcPkg.Name), + pkgPath: pkgPath, + imports: make(map[string]bool), + }, nil +} + +func preventZeroStr(val, defaultVal string) string { + if val == "" { + return defaultVal + } + return val +} + +func findPkgPath(pkgInputVal string, srcPkg *packages.Package) (string, error) { + if pkgInputVal == "" { + return srcPkg.PkgPath, nil + } + if pkgInDir(".", pkgInputVal) { + return ".", nil + } + if pkgInDir(srcPkg.PkgPath, pkgInputVal) { + return srcPkg.PkgPath, nil + } + subdirectoryPath := filepath.Join(srcPkg.PkgPath, pkgInputVal) + if pkgInDir(subdirectoryPath, pkgInputVal) { + return subdirectoryPath, nil + } + return "", nil +} + +func pkgInDir(pkgName, dir string) bool { + currentPkg, err := pkgInfoFromPath(dir, packages.LoadFiles) + if err != nil { + return false + } + return currentPkg.Name == pkgName || currentPkg.Name+"_test" == pkgName +} + +// Mock generates a mock for the specified interface name. +func (m *Mocker) Mock(w io.Writer, names ...string) error { + if len(names) == 0 { + return errors.New("must specify one interface") + } + + doc := doc{ + PackageName: m.pkgName, + Imports: moqImports, + } + + mocksMethods := false + + tpkg := m.srcPkg.Types + for _, name := range names { + n, mockName := parseInterfaceName(name) + iface := tpkg.Scope().Lookup(n) + if iface == nil { + return fmt.Errorf("cannot find interface %s", n) + } + if !types.IsInterface(iface.Type()) { + return fmt.Errorf("%s (%s) not an interface", n, iface.Type().String()) + } + iiface := iface.Type().Underlying().(*types.Interface).Complete() + obj := obj{ + InterfaceName: n, + MockName: mockName, + } + for i := 0; i < iiface.NumMethods(); i++ { + mocksMethods = true + meth := iiface.Method(i) + sig := meth.Type().(*types.Signature) + method := &method{ + Name: meth.Name(), + } + obj.Methods = append(obj.Methods, method) + method.Params = m.extractArgs(sig, sig.Params(), "in%d") + method.Returns = m.extractArgs(sig, sig.Results(), "out%d") + } + doc.Objects = append(doc.Objects, obj) + } + + if mocksMethods { + doc.Imports = append(doc.Imports, "sync") + } + + for pkgToImport := range m.imports { + doc.Imports = append(doc.Imports, stripVendorPath(pkgToImport)) + } + + if tpkg.Name() != m.pkgName { + doc.SourcePackagePrefix = tpkg.Name() + "." + doc.Imports = append(doc.Imports, tpkg.Path()) + } + + var buf bytes.Buffer + err := m.tmpl.Execute(&buf, doc) + if err != nil { + return err + } + formatted, err := format.Source(buf.Bytes()) + if err != nil { + return fmt.Errorf("go/format: %s", err) + } + if _, err := w.Write(formatted); err != nil { + return err + } + return nil +} + +func (m *Mocker) packageQualifier(pkg *types.Package) string { + if m.pkgPath != "" && m.pkgPath == pkg.Path() { + return "" + } + path := pkg.Path() + if pkg.Path() == "." { + wd, err := os.Getwd() + if err == nil { + path = stripGopath(wd) + } + } + m.imports[path] = true + return pkg.Name() +} + +func (m *Mocker) extractArgs(sig *types.Signature, list *types.Tuple, nameFormat string) []*param { + var params []*param + listLen := list.Len() + for ii := 0; ii < listLen; ii++ { + p := list.At(ii) + name := p.Name() + if name == "" { + name = fmt.Sprintf(nameFormat, ii+1) + } + typename := types.TypeString(p.Type(), m.packageQualifier) + // check for final variadic argument + variadic := sig.Variadic() && ii == listLen-1 && typename[0:2] == "[]" + param := ¶m{ + Name: name, + Type: typename, + Variadic: variadic, + } + params = append(params, param) + } + return params +} + +func pkgInfoFromPath(src string, mode packages.LoadMode) (*packages.Package, error) { + conf := packages.Config{ + Mode: mode, + Dir: src, + } + pkgs, err := packages.Load(&conf) + if err != nil { + return nil, err + } + if len(pkgs) == 0 { + return nil, errors.New("No packages found") + } + if len(pkgs) > 1 { + return nil, errors.New("More than one package was found") + } + return pkgs[0], nil +} + +func parseInterfaceName(name string) (ifaceName, mockName string) { + parts := strings.SplitN(name, ":", 2) + ifaceName = parts[0] + mockName = ifaceName + "Mock" + if len(parts) == 2 { + mockName = parts[1] + } + return +} + +type doc struct { + PackageName string + SourcePackagePrefix string + Objects []obj + Imports []string +} + +type obj struct { + InterfaceName string + MockName string + Methods []*method +} +type method struct { + Name string + Params []*param + Returns []*param +} + +func (m *method) Arglist() string { + params := make([]string, len(m.Params)) + for i, p := range m.Params { + params[i] = p.String() + } + return strings.Join(params, ", ") +} + +func (m *method) ArgCallList() string { + params := make([]string, len(m.Params)) + for i, p := range m.Params { + params[i] = p.CallName() + } + return strings.Join(params, ", ") +} + +func (m *method) ReturnArglist() string { + params := make([]string, len(m.Returns)) + for i, p := range m.Returns { + params[i] = p.TypeString() + } + if len(m.Returns) > 1 { + return fmt.Sprintf("(%s)", strings.Join(params, ", ")) + } + return strings.Join(params, ", ") +} + +type param struct { + Name string + Type string + Variadic bool +} + +func (p param) String() string { + return fmt.Sprintf("%s %s", p.Name, p.TypeString()) +} + +func (p param) CallName() string { + if p.Variadic { + return p.Name + "..." + } + return p.Name +} + +func (p param) TypeString() string { + if p.Variadic { + return "..." + p.Type[2:] + } + return p.Type +} + +var templateFuncs = template.FuncMap{ + "Exported": func(s string) string { + if s == "" { + return "" + } + for _, initialism := range golintInitialisms { + if strings.ToUpper(s) == initialism { + return initialism + } + } + return strings.ToUpper(s[0:1]) + s[1:] + }, +} + +// stripVendorPath strips the vendor dir prefix from a package path. +// For example we might encounter an absolute path like +// github.com/foo/bar/vendor/github.com/pkg/errors which is resolved +// to github.com/pkg/errors. +func stripVendorPath(p string) string { + parts := strings.Split(p, "/vendor/") + if len(parts) == 1 { + return p + } + return strings.TrimLeft(path.Join(parts[1:]...), "/") +} + +// stripGopath takes the directory to a package and remove the gopath to get the +// canonical package name. +// +// taken from https://github.com/ernesto-jimenez/gogen +// Copyright (c) 2015 Ernesto Jiménez +func stripGopath(p string) string { + for _, gopath := range gopaths() { + p = strings.TrimPrefix(p, path.Join(gopath, "src")+"/") + } + return p +} + +func gopaths() []string { + return strings.Split(os.Getenv("GOPATH"), string(filepath.ListSeparator)) +} diff --git a/vendor/github.com/matryer/moq/pkg/moq/template.go b/vendor/github.com/matryer/moq/pkg/moq/template.go new file mode 100644 index 000000000..bc33de236 --- /dev/null +++ b/vendor/github.com/matryer/moq/pkg/moq/template.go @@ -0,0 +1,107 @@ +package moq + +// moqImports are the imports all moq files get. +var moqImports = []string{} + +// moqTemplate is the template for mocked code. +var moqTemplate = `// Code generated by moq; DO NOT EDIT. +// github.com/matryer/moq + +package {{.PackageName}} +{{- $sourcePackagePrefix := .SourcePackagePrefix}} + +import ( +{{- range .Imports }} + "{{.}}" +{{- end }} +) + +{{ range $i, $obj := .Objects -}} +var ( +{{- range .Methods }} + lock{{$obj.MockName}}{{.Name}} sync.RWMutex +{{- end }} +) + +// Ensure, that {{.MockName}} does implement {{$sourcePackagePrefix}}{{.InterfaceName}}. +// If this is not the case, regenerate this file with moq. +var _ {{$sourcePackagePrefix}}{{.InterfaceName}} = &{{.MockName}}{} + +// {{.MockName}} is a mock implementation of {{$sourcePackagePrefix}}{{.InterfaceName}}. +// +// func TestSomethingThatUses{{.InterfaceName}}(t *testing.T) { +// +// // make and configure a mocked {{$sourcePackagePrefix}}{{.InterfaceName}} +// mocked{{.InterfaceName}} := &{{.MockName}}{ {{ range .Methods }} +// {{.Name}}Func: func({{ .Arglist }}) {{.ReturnArglist}} { +// panic("mock out the {{.Name}} method") +// },{{- end }} +// } +// +// // use mocked{{.InterfaceName}} in code that requires {{$sourcePackagePrefix}}{{.InterfaceName}} +// // and then make assertions. +// +// } +type {{.MockName}} struct { +{{- range .Methods }} + // {{.Name}}Func mocks the {{.Name}} method. + {{.Name}}Func func({{ .Arglist }}) {{.ReturnArglist}} +{{ end }} + // calls tracks calls to the methods. + calls struct { +{{- range .Methods }} + // {{ .Name }} holds details about calls to the {{.Name}} method. + {{ .Name }} []struct { + {{- range .Params }} + // {{ .Name | Exported }} is the {{ .Name }} argument value. + {{ .Name | Exported }} {{ .Type }} + {{- end }} + } +{{- end }} + } +} +{{ range .Methods }} +// {{.Name}} calls {{.Name}}Func. +func (mock *{{$obj.MockName}}) {{.Name}}({{.Arglist}}) {{.ReturnArglist}} { + if mock.{{.Name}}Func == nil { + panic("{{$obj.MockName}}.{{.Name}}Func: method is nil but {{$obj.InterfaceName}}.{{.Name}} was just called") + } + callInfo := struct { + {{- range .Params }} + {{ .Name | Exported }} {{ .Type }} + {{- end }} + }{ + {{- range .Params }} + {{ .Name | Exported }}: {{ .Name }}, + {{- end }} + } + lock{{$obj.MockName}}{{.Name}}.Lock() + mock.calls.{{.Name}} = append(mock.calls.{{.Name}}, callInfo) + lock{{$obj.MockName}}{{.Name}}.Unlock() +{{- if .ReturnArglist }} + return mock.{{.Name}}Func({{.ArgCallList}}) +{{- else }} + mock.{{.Name}}Func({{.ArgCallList}}) +{{- end }} +} + +// {{.Name}}Calls gets all the calls that were made to {{.Name}}. +// Check the length with: +// len(mocked{{$obj.InterfaceName}}.{{.Name}}Calls()) +func (mock *{{$obj.MockName}}) {{.Name}}Calls() []struct { + {{- range .Params }} + {{ .Name | Exported }} {{ .Type }} + {{- end }} + } { + var calls []struct { + {{- range .Params }} + {{ .Name | Exported }} {{ .Type }} + {{- end }} + } + lock{{$obj.MockName}}{{.Name}}.RLock() + calls = mock.calls.{{.Name}} + lock{{$obj.MockName}}{{.Name}}.RUnlock() + return calls +} +{{ end -}} +{{ end -}}` diff --git a/vendor/github.com/matryer/moq/preview.png b/vendor/github.com/matryer/moq/preview.png new file mode 100644 index 000000000..8fa7a6274 Binary files /dev/null and b/vendor/github.com/matryer/moq/preview.png differ diff --git a/vendor/github.com/mitchellh/go-homedir/LICENSE b/vendor/github.com/mitchellh/go-homedir/LICENSE new file mode 100644 index 000000000..f9c841a51 --- /dev/null +++ b/vendor/github.com/mitchellh/go-homedir/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013 Mitchell Hashimoto + +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. diff --git a/vendor/github.com/mitchellh/go-homedir/README.md b/vendor/github.com/mitchellh/go-homedir/README.md new file mode 100644 index 000000000..d70706d5b --- /dev/null +++ b/vendor/github.com/mitchellh/go-homedir/README.md @@ -0,0 +1,14 @@ +# go-homedir + +This is a Go library for detecting the user's home directory without +the use of cgo, so the library can be used in cross-compilation environments. + +Usage is incredibly simple, just call `homedir.Dir()` to get the home directory +for a user, and `homedir.Expand()` to expand the `~` in a path to the home +directory. + +**Why not just use `os/user`?** The built-in `os/user` package requires +cgo on Darwin systems. This means that any Go code that uses that package +cannot cross compile. But 99% of the time the use for `os/user` is just to +retrieve the home directory, which we can do for the current user without +cgo. This library does that, enabling cross-compilation. diff --git a/vendor/github.com/mitchellh/go-homedir/go.mod b/vendor/github.com/mitchellh/go-homedir/go.mod new file mode 100644 index 000000000..7efa09a04 --- /dev/null +++ b/vendor/github.com/mitchellh/go-homedir/go.mod @@ -0,0 +1 @@ +module github.com/mitchellh/go-homedir diff --git a/vendor/github.com/mitchellh/go-homedir/homedir.go b/vendor/github.com/mitchellh/go-homedir/homedir.go new file mode 100644 index 000000000..25378537e --- /dev/null +++ b/vendor/github.com/mitchellh/go-homedir/homedir.go @@ -0,0 +1,167 @@ +package homedir + +import ( + "bytes" + "errors" + "os" + "os/exec" + "path/filepath" + "runtime" + "strconv" + "strings" + "sync" +) + +// DisableCache will disable caching of the home directory. Caching is enabled +// by default. +var DisableCache bool + +var homedirCache string +var cacheLock sync.RWMutex + +// Dir returns the home directory for the executing user. +// +// This uses an OS-specific method for discovering the home directory. +// An error is returned if a home directory cannot be detected. +func Dir() (string, error) { + if !DisableCache { + cacheLock.RLock() + cached := homedirCache + cacheLock.RUnlock() + if cached != "" { + return cached, nil + } + } + + cacheLock.Lock() + defer cacheLock.Unlock() + + var result string + var err error + if runtime.GOOS == "windows" { + result, err = dirWindows() + } else { + // Unix-like system, so just assume Unix + result, err = dirUnix() + } + + if err != nil { + return "", err + } + homedirCache = result + return result, nil +} + +// Expand expands the path to include the home directory if the path +// is prefixed with `~`. If it isn't prefixed with `~`, the path is +// returned as-is. +func Expand(path string) (string, error) { + if len(path) == 0 { + return path, nil + } + + if path[0] != '~' { + return path, nil + } + + if len(path) > 1 && path[1] != '/' && path[1] != '\\' { + return "", errors.New("cannot expand user-specific home dir") + } + + dir, err := Dir() + if err != nil { + return "", err + } + + return filepath.Join(dir, path[1:]), nil +} + +// Reset clears the cache, forcing the next call to Dir to re-detect +// the home directory. This generally never has to be called, but can be +// useful in tests if you're modifying the home directory via the HOME +// env var or something. +func Reset() { + cacheLock.Lock() + defer cacheLock.Unlock() + homedirCache = "" +} + +func dirUnix() (string, error) { + homeEnv := "HOME" + if runtime.GOOS == "plan9" { + // On plan9, env vars are lowercase. + homeEnv = "home" + } + + // First prefer the HOME environmental variable + if home := os.Getenv(homeEnv); home != "" { + return home, nil + } + + var stdout bytes.Buffer + + // If that fails, try OS specific commands + if runtime.GOOS == "darwin" { + cmd := exec.Command("sh", "-c", `dscl -q . -read /Users/"$(whoami)" NFSHomeDirectory | sed 's/^[^ ]*: //'`) + cmd.Stdout = &stdout + if err := cmd.Run(); err == nil { + result := strings.TrimSpace(stdout.String()) + if result != "" { + return result, nil + } + } + } else { + cmd := exec.Command("getent", "passwd", strconv.Itoa(os.Getuid())) + cmd.Stdout = &stdout + if err := cmd.Run(); err != nil { + // If the error is ErrNotFound, we ignore it. Otherwise, return it. + if err != exec.ErrNotFound { + return "", err + } + } else { + if passwd := strings.TrimSpace(stdout.String()); passwd != "" { + // username:password:uid:gid:gecos:home:shell + passwdParts := strings.SplitN(passwd, ":", 7) + if len(passwdParts) > 5 { + return passwdParts[5], nil + } + } + } + } + + // If all else fails, try the shell + stdout.Reset() + cmd := exec.Command("sh", "-c", "cd && pwd") + cmd.Stdout = &stdout + if err := cmd.Run(); err != nil { + return "", err + } + + result := strings.TrimSpace(stdout.String()) + if result == "" { + return "", errors.New("blank output when reading home directory") + } + + return result, nil +} + +func dirWindows() (string, error) { + // First prefer the HOME environmental variable + if home := os.Getenv("HOME"); home != "" { + return home, nil + } + + // Prefer standard environment variable USERPROFILE + if home := os.Getenv("USERPROFILE"); home != "" { + return home, nil + } + + drive := os.Getenv("HOMEDRIVE") + path := os.Getenv("HOMEPATH") + home := drive + path + if drive == "" || path == "" { + return "", errors.New("HOMEDRIVE, HOMEPATH, or USERPROFILE are blank") + } + + return home, nil +} diff --git a/vendor/github.com/modern-go/concurrent/.gitignore b/vendor/github.com/modern-go/concurrent/.gitignore new file mode 100644 index 000000000..3f2bc4741 --- /dev/null +++ b/vendor/github.com/modern-go/concurrent/.gitignore @@ -0,0 +1 @@ +/coverage.txt diff --git a/vendor/github.com/modern-go/concurrent/.travis.yml b/vendor/github.com/modern-go/concurrent/.travis.yml new file mode 100644 index 000000000..449e67cd0 --- /dev/null +++ b/vendor/github.com/modern-go/concurrent/.travis.yml @@ -0,0 +1,14 @@ +language: go + +go: + - 1.8.x + - 1.x + +before_install: + - go get -t -v ./... + +script: + - ./test.sh + +after_success: + - bash <(curl -s https://codecov.io/bash) diff --git a/vendor/github.com/modern-go/concurrent/README.md b/vendor/github.com/modern-go/concurrent/README.md index 91d6adb3f..acab3200a 100644 --- a/vendor/github.com/modern-go/concurrent/README.md +++ b/vendor/github.com/modern-go/concurrent/README.md @@ -1,2 +1,49 @@ # concurrent -concurrency utilities + +[![Sourcegraph](https://sourcegraph.com/github.com/modern-go/concurrent/-/badge.svg)](https://sourcegraph.com/github.com/modern-go/concurrent?badge) +[![GoDoc](http://img.shields.io/badge/go-documentation-blue.svg?style=flat-square)](http://godoc.org/github.com/modern-go/concurrent) +[![Build Status](https://travis-ci.org/modern-go/concurrent.svg?branch=master)](https://travis-ci.org/modern-go/concurrent) +[![codecov](https://codecov.io/gh/modern-go/concurrent/branch/master/graph/badge.svg)](https://codecov.io/gh/modern-go/concurrent) +[![rcard](https://goreportcard.com/badge/github.com/modern-go/concurrent)](https://goreportcard.com/report/github.com/modern-go/concurrent) +[![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://raw.githubusercontent.com/modern-go/concurrent/master/LICENSE) + +* concurrent.Map: backport sync.Map for go below 1.9 +* concurrent.Executor: goroutine with explicit ownership and cancellable + +# concurrent.Map + +because sync.Map is only available in go 1.9, we can use concurrent.Map to make code portable + +```go +m := concurrent.NewMap() +m.Store("hello", "world") +elem, found := m.Load("hello") +// elem will be "world" +// found will be true +``` + +# concurrent.Executor + +```go +executor := concurrent.NewUnboundedExecutor() +executor.Go(func(ctx context.Context) { + everyMillisecond := time.NewTicker(time.Millisecond) + for { + select { + case <-ctx.Done(): + fmt.Println("goroutine exited") + return + case <-everyMillisecond.C: + // do something + } + } +}) +time.Sleep(time.Second) +executor.StopAndWaitForever() +fmt.Println("executor stopped") +``` + +attach goroutine to executor instance, so that we can + +* cancel it by stop the executor with Stop/StopAndWait/StopAndWaitForever +* handle panic by callback: the default behavior will no longer crash your application \ No newline at end of file diff --git a/vendor/github.com/modern-go/concurrent/executor.go b/vendor/github.com/modern-go/concurrent/executor.go index 56e5d22bf..623dba1ac 100644 --- a/vendor/github.com/modern-go/concurrent/executor.go +++ b/vendor/github.com/modern-go/concurrent/executor.go @@ -2,6 +2,13 @@ package concurrent import "context" +// Executor replace go keyword to start a new goroutine +// the goroutine should cancel itself if the context passed in has been cancelled +// the goroutine started by the executor, is owned by the executor +// we can cancel all executors owned by the executor just by stop the executor itself +// however Executor interface does not Stop method, the one starting and owning executor +// should use the concrete type of executor, instead of this interface. type Executor interface { + // Go starts a new goroutine controlled by the context Go(handler func(ctx context.Context)) -} \ No newline at end of file +} diff --git a/vendor/github.com/modern-go/concurrent/go_above_19.go b/vendor/github.com/modern-go/concurrent/go_above_19.go index a9f259347..aeabf8c4f 100644 --- a/vendor/github.com/modern-go/concurrent/go_above_19.go +++ b/vendor/github.com/modern-go/concurrent/go_above_19.go @@ -4,10 +4,12 @@ package concurrent import "sync" +// Map is a wrapper for sync.Map introduced in go1.9 type Map struct { sync.Map } +// NewMap creates a thread safe Map func NewMap() *Map { return &Map{} -} \ No newline at end of file +} diff --git a/vendor/github.com/modern-go/concurrent/go_below_19.go b/vendor/github.com/modern-go/concurrent/go_below_19.go index 3f79f4fe4..b9c8df7f4 100644 --- a/vendor/github.com/modern-go/concurrent/go_below_19.go +++ b/vendor/github.com/modern-go/concurrent/go_below_19.go @@ -4,17 +4,20 @@ package concurrent import "sync" +// Map implements a thread safe map for go version below 1.9 using mutex type Map struct { lock sync.RWMutex data map[interface{}]interface{} } +// NewMap creates a thread safe map func NewMap() *Map { return &Map{ data: make(map[interface{}]interface{}, 32), } } +// Load is same as sync.Map Load func (m *Map) Load(key interface{}) (elem interface{}, found bool) { m.lock.RLock() elem, found = m.data[key] @@ -22,9 +25,9 @@ func (m *Map) Load(key interface{}) (elem interface{}, found bool) { return } +// Load is same as sync.Map Store func (m *Map) Store(key interface{}, elem interface{}) { m.lock.Lock() m.data[key] = elem m.lock.Unlock() } - diff --git a/vendor/github.com/modern-go/concurrent/log.go b/vendor/github.com/modern-go/concurrent/log.go new file mode 100644 index 000000000..9756fcc75 --- /dev/null +++ b/vendor/github.com/modern-go/concurrent/log.go @@ -0,0 +1,13 @@ +package concurrent + +import ( + "os" + "log" + "io/ioutil" +) + +// ErrorLogger is used to print out error, can be set to writer other than stderr +var ErrorLogger = log.New(os.Stderr, "", 0) + +// InfoLogger is used to print informational message, default to off +var InfoLogger = log.New(ioutil.Discard, "", 0) \ No newline at end of file diff --git a/vendor/github.com/modern-go/concurrent/test.sh b/vendor/github.com/modern-go/concurrent/test.sh new file mode 100644 index 000000000..d1e6b2ec5 --- /dev/null +++ b/vendor/github.com/modern-go/concurrent/test.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +set -e +echo "" > coverage.txt + +for d in $(go list ./... | grep -v vendor); do + go test -coverprofile=profile.out -coverpkg=github.com/modern-go/concurrent $d + if [ -f profile.out ]; then + cat profile.out >> coverage.txt + rm profile.out + fi +done diff --git a/vendor/github.com/modern-go/concurrent/unbounded_executor.go b/vendor/github.com/modern-go/concurrent/unbounded_executor.go index 70a1cf0f1..05a77dceb 100644 --- a/vendor/github.com/modern-go/concurrent/unbounded_executor.go +++ b/vendor/github.com/modern-go/concurrent/unbounded_executor.go @@ -4,33 +4,37 @@ import ( "context" "fmt" "runtime" + "runtime/debug" "sync" "time" - "runtime/debug" + "reflect" ) -var LogInfo = func(event string, properties ...interface{}) { +// HandlePanic logs goroutine panic by default +var HandlePanic = func(recovered interface{}, funcName string) { + ErrorLogger.Println(fmt.Sprintf("%s panic: %v", funcName, recovered)) + ErrorLogger.Println(string(debug.Stack())) } -var LogPanic = func(recovered interface{}, properties ...interface{}) interface{} { - fmt.Println(fmt.Sprintf("paniced: %v", recovered)) - debug.PrintStack() - return recovered -} - -const StopSignal = "STOP!" - +// UnboundedExecutor is a executor without limits on counts of alive goroutines +// it tracks the goroutine started by it, and can cancel them when shutdown type UnboundedExecutor struct { ctx context.Context cancel context.CancelFunc activeGoroutinesMutex *sync.Mutex activeGoroutines map[string]int + HandlePanic func(recovered interface{}, funcName string) } // GlobalUnboundedExecutor has the life cycle of the program itself // any goroutine want to be shutdown before main exit can be started from this executor +// GlobalUnboundedExecutor expects the main function to call stop +// it does not magically knows the main function exits var GlobalUnboundedExecutor = NewUnboundedExecutor() +// NewUnboundedExecutor creates a new UnboundedExecutor, +// UnboundedExecutor can not be created by &UnboundedExecutor{} +// HandlePanic can be set with a callback to override global HandlePanic func NewUnboundedExecutor() *UnboundedExecutor { ctx, cancel := context.WithCancel(context.TODO()) return &UnboundedExecutor{ @@ -41,8 +45,13 @@ func NewUnboundedExecutor() *UnboundedExecutor { } } +// Go starts a new goroutine and tracks its lifecycle. +// Panic will be recovered and logged automatically, except for StopSignal func (executor *UnboundedExecutor) Go(handler func(ctx context.Context)) { - _, file, line, _ := runtime.Caller(1) + pc := reflect.ValueOf(handler).Pointer() + f := runtime.FuncForPC(pc) + funcName := f.Name() + file, line := f.FileLine(pc) executor.activeGoroutinesMutex.Lock() defer executor.activeGoroutinesMutex.Unlock() startFrom := fmt.Sprintf("%s:%d", file, line) @@ -50,46 +59,57 @@ func (executor *UnboundedExecutor) Go(handler func(ctx context.Context)) { go func() { defer func() { recovered := recover() - if recovered != nil && recovered != StopSignal { - LogPanic(recovered) + // if you want to quit a goroutine without trigger HandlePanic + // use runtime.Goexit() to quit + if recovered != nil { + if executor.HandlePanic == nil { + HandlePanic(recovered, funcName) + } else { + executor.HandlePanic(recovered, funcName) + } } executor.activeGoroutinesMutex.Lock() - defer executor.activeGoroutinesMutex.Unlock() executor.activeGoroutines[startFrom] -= 1 + executor.activeGoroutinesMutex.Unlock() }() handler(executor.ctx) }() } +// Stop cancel all goroutines started by this executor without wait func (executor *UnboundedExecutor) Stop() { executor.cancel() } +// StopAndWaitForever cancel all goroutines started by this executor and +// wait until all goroutines exited func (executor *UnboundedExecutor) StopAndWaitForever() { executor.StopAndWait(context.Background()) } +// StopAndWait cancel all goroutines started by this executor and wait. +// Wait can be cancelled by the context passed in. func (executor *UnboundedExecutor) StopAndWait(ctx context.Context) { executor.cancel() for { - fiveSeconds := time.NewTimer(time.Millisecond * 100) + oneHundredMilliseconds := time.NewTimer(time.Millisecond * 100) select { - case <-fiveSeconds.C: + case <-oneHundredMilliseconds.C: + if executor.checkNoActiveGoroutines() { + return + } case <-ctx.Done(): return } - if executor.checkGoroutines() { - return - } } } -func (executor *UnboundedExecutor) checkGoroutines() bool { +func (executor *UnboundedExecutor) checkNoActiveGoroutines() bool { executor.activeGoroutinesMutex.Lock() defer executor.activeGoroutinesMutex.Unlock() for startFrom, count := range executor.activeGoroutines { if count > 0 { - LogInfo("event!unbounded_executor.still waiting goroutines to quit", + InfoLogger.Println("UnboundedExecutor is still waiting goroutines to quit", "startFrom", startFrom, "count", count) return false diff --git a/vendor/github.com/modern-go/reflect2/type_map.go b/vendor/github.com/modern-go/reflect2/type_map.go index 6d489112f..3acfb5580 100644 --- a/vendor/github.com/modern-go/reflect2/type_map.go +++ b/vendor/github.com/modern-go/reflect2/type_map.go @@ -4,6 +4,7 @@ import ( "reflect" "runtime" "strings" + "sync" "unsafe" ) @@ -15,10 +16,17 @@ func typelinks1() [][]unsafe.Pointer //go:linkname typelinks2 reflect.typelinks func typelinks2() (sections []unsafe.Pointer, offset [][]int32) -var types = map[string]reflect.Type{} -var packages = map[string]map[string]reflect.Type{} +// initOnce guards initialization of types and packages +var initOnce sync.Once + +var types map[string]reflect.Type +var packages map[string]map[string]reflect.Type + +// discoverTypes initializes types and packages +func discoverTypes() { + types = make(map[string]reflect.Type) + packages = make(map[string]map[string]reflect.Type) -func init() { ver := runtime.Version() if ver == "go1.5" || strings.HasPrefix(ver, "go1.5.") { loadGo15Types() @@ -90,11 +98,13 @@ type emptyInterface struct { // TypeByName return the type by its name, just like Class.forName in java func TypeByName(typeName string) Type { + initOnce.Do(discoverTypes) return Type2(types[typeName]) } // TypeByPackageName return the type by its package and name func TypeByPackageName(pkgPath string, name string) Type { + initOnce.Do(discoverTypes) pkgTypes := packages[pkgPath] if pkgTypes == nil { return nil diff --git a/vendor/github.com/pkg/errors/.travis.yml b/vendor/github.com/pkg/errors/.travis.yml index d4b92663b..9159de03e 100644 --- a/vendor/github.com/pkg/errors/.travis.yml +++ b/vendor/github.com/pkg/errors/.travis.yml @@ -1,15 +1,10 @@ language: go go_import_path: github.com/pkg/errors go: - - 1.4.x - - 1.5.x - - 1.6.x - - 1.7.x - - 1.8.x - - 1.9.x - - 1.10.x - 1.11.x + - 1.12.x + - 1.13.x - tip script: - - go test -v ./... + - make check diff --git a/vendor/github.com/pkg/errors/Makefile b/vendor/github.com/pkg/errors/Makefile new file mode 100644 index 000000000..ce9d7cded --- /dev/null +++ b/vendor/github.com/pkg/errors/Makefile @@ -0,0 +1,44 @@ +PKGS := github.com/pkg/errors +SRCDIRS := $(shell go list -f '{{.Dir}}' $(PKGS)) +GO := go + +check: test vet gofmt misspell unconvert staticcheck ineffassign unparam + +test: + $(GO) test $(PKGS) + +vet: | test + $(GO) vet $(PKGS) + +staticcheck: + $(GO) get honnef.co/go/tools/cmd/staticcheck + staticcheck -checks all $(PKGS) + +misspell: + $(GO) get github.com/client9/misspell/cmd/misspell + misspell \ + -locale GB \ + -error \ + *.md *.go + +unconvert: + $(GO) get github.com/mdempsky/unconvert + unconvert -v $(PKGS) + +ineffassign: + $(GO) get github.com/gordonklaus/ineffassign + find $(SRCDIRS) -name '*.go' | xargs ineffassign + +pedantic: check errcheck + +unparam: + $(GO) get mvdan.cc/unparam + unparam ./... + +errcheck: + $(GO) get github.com/kisielk/errcheck + errcheck $(PKGS) + +gofmt: + @echo Checking code is gofmted + @test -z "$(shell gofmt -s -l -d -e $(SRCDIRS) | tee /dev/stderr)" diff --git a/vendor/github.com/pkg/errors/README.md b/vendor/github.com/pkg/errors/README.md index 6483ba2af..54dfdcb12 100644 --- a/vendor/github.com/pkg/errors/README.md +++ b/vendor/github.com/pkg/errors/README.md @@ -41,11 +41,18 @@ default: [Read the package documentation for more information](https://godoc.org/github.com/pkg/errors). +## Roadmap + +With the upcoming [Go2 error proposals](https://go.googlesource.com/proposal/+/master/design/go2draft.md) this package is moving into maintenance mode. The roadmap for a 1.0 release is as follows: + +- 0.9. Remove pre Go 1.9 and Go 1.10 support, address outstanding pull requests (if possible) +- 1.0. Final release. + ## Contributing -We welcome pull requests, bug fixes and issue reports. With that said, the bar for adding new symbols to this package is intentionally set high. +Because of the Go2 errors changes, this package is not accepting proposals for new functionality. With that said, we welcome pull requests, bug fixes and issue reports. -Before proposing a change, please discuss your change by raising an issue. +Before sending a PR, please discuss your change by raising an issue. ## License diff --git a/vendor/github.com/pkg/errors/errors.go b/vendor/github.com/pkg/errors/errors.go index 7421f326f..161aea258 100644 --- a/vendor/github.com/pkg/errors/errors.go +++ b/vendor/github.com/pkg/errors/errors.go @@ -82,7 +82,7 @@ // // if err, ok := err.(stackTracer); ok { // for _, f := range err.StackTrace() { -// fmt.Printf("%+s:%d", f) +// fmt.Printf("%+s:%d\n", f, f) // } // } // @@ -159,6 +159,9 @@ type withStack struct { func (w *withStack) Cause() error { return w.error } +// Unwrap provides compatibility for Go 1.13 error chains. +func (w *withStack) Unwrap() error { return w.error } + func (w *withStack) Format(s fmt.State, verb rune) { switch verb { case 'v': @@ -241,6 +244,9 @@ type withMessage struct { func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } func (w *withMessage) Cause() error { return w.cause } +// Unwrap provides compatibility for Go 1.13 error chains. +func (w *withMessage) Unwrap() error { return w.cause } + func (w *withMessage) Format(s fmt.State, verb rune) { switch verb { case 'v': diff --git a/vendor/github.com/pkg/errors/go113.go b/vendor/github.com/pkg/errors/go113.go new file mode 100644 index 000000000..be0d10d0c --- /dev/null +++ b/vendor/github.com/pkg/errors/go113.go @@ -0,0 +1,38 @@ +// +build go1.13 + +package errors + +import ( + stderrors "errors" +) + +// Is reports whether any error in err's chain matches target. +// +// The chain consists of err itself followed by the sequence of errors obtained by +// repeatedly calling Unwrap. +// +// An error is considered to match a target if it is equal to that target or if +// it implements a method Is(error) bool such that Is(target) returns true. +func Is(err, target error) bool { return stderrors.Is(err, target) } + +// As finds the first error in err's chain that matches target, and if so, sets +// target to that error value and returns true. +// +// The chain consists of err itself followed by the sequence of errors obtained by +// repeatedly calling Unwrap. +// +// An error matches target if the error's concrete value is assignable to the value +// pointed to by target, or if the error has a method As(interface{}) bool such that +// As(target) returns true. In the latter case, the As method is responsible for +// setting target. +// +// As will panic if target is not a non-nil pointer to either a type that implements +// error, or to any interface type. As returns false if err is nil. +func As(err error, target interface{}) bool { return stderrors.As(err, target) } + +// Unwrap returns the result of calling the Unwrap method on err, if err's +// type contains an Unwrap method returning error. +// Otherwise, Unwrap returns nil. +func Unwrap(err error) error { + return stderrors.Unwrap(err) +} diff --git a/vendor/github.com/pkg/errors/stack.go b/vendor/github.com/pkg/errors/stack.go index 2874a048c..779a8348f 100644 --- a/vendor/github.com/pkg/errors/stack.go +++ b/vendor/github.com/pkg/errors/stack.go @@ -5,10 +5,13 @@ import ( "io" "path" "runtime" + "strconv" "strings" ) // Frame represents a program counter inside a stack frame. +// For historical reasons if Frame is interpreted as a uintptr +// its value represents the program counter + 1. type Frame uintptr // pc returns the program counter for this frame; @@ -37,6 +40,15 @@ func (f Frame) line() int { return line } +// name returns the name of this function, if known. +func (f Frame) name() string { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return "unknown" + } + return fn.Name() +} + // Format formats the frame according to the fmt.Formatter interface. // // %s source file @@ -54,22 +66,16 @@ func (f Frame) Format(s fmt.State, verb rune) { case 's': switch { case s.Flag('+'): - pc := f.pc() - fn := runtime.FuncForPC(pc) - if fn == nil { - io.WriteString(s, "unknown") - } else { - file, _ := fn.FileLine(pc) - fmt.Fprintf(s, "%s\n\t%s", fn.Name(), file) - } + io.WriteString(s, f.name()) + io.WriteString(s, "\n\t") + io.WriteString(s, f.file()) default: io.WriteString(s, path.Base(f.file())) } case 'd': - fmt.Fprintf(s, "%d", f.line()) + io.WriteString(s, strconv.Itoa(f.line())) case 'n': - name := runtime.FuncForPC(f.pc()).Name() - io.WriteString(s, funcname(name)) + io.WriteString(s, funcname(f.name())) case 'v': f.Format(s, 's') io.WriteString(s, ":") @@ -77,6 +83,16 @@ func (f Frame) Format(s fmt.State, verb rune) { } } +// MarshalText formats a stacktrace Frame as a text string. The output is the +// same as that of fmt.Sprintf("%+v", f), but without newlines or tabs. +func (f Frame) MarshalText() ([]byte, error) { + name := f.name() + if name == "unknown" { + return []byte(name), nil + } + return []byte(fmt.Sprintf("%s %s:%d", name, f.file(), f.line())), nil +} + // StackTrace is stack of Frames from innermost (newest) to outermost (oldest). type StackTrace []Frame @@ -94,18 +110,32 @@ func (st StackTrace) Format(s fmt.State, verb rune) { switch { case s.Flag('+'): for _, f := range st { - fmt.Fprintf(s, "\n%+v", f) + io.WriteString(s, "\n") + f.Format(s, verb) } case s.Flag('#'): fmt.Fprintf(s, "%#v", []Frame(st)) default: - fmt.Fprintf(s, "%v", []Frame(st)) + st.formatSlice(s, verb) } case 's': - fmt.Fprintf(s, "%s", []Frame(st)) + st.formatSlice(s, verb) } } +// formatSlice will format this StackTrace into the given buffer as a slice of +// Frame, only valid when called with '%s' or '%v'. +func (st StackTrace) formatSlice(s fmt.State, verb rune) { + io.WriteString(s, "[") + for i, f := range st { + if i > 0 { + io.WriteString(s, " ") + } + f.Format(s, verb) + } + io.WriteString(s, "]") +} + // stack represents a stack of program counters. type stack []uintptr diff --git a/vendor/github.com/rs/zerolog/.gitignore b/vendor/github.com/rs/zerolog/.gitignore new file mode 100644 index 000000000..8ebe58b15 --- /dev/null +++ b/vendor/github.com/rs/zerolog/.gitignore @@ -0,0 +1,25 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test +tmp + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof diff --git a/vendor/github.com/rs/zerolog/.travis.yml b/vendor/github.com/rs/zerolog/.travis.yml new file mode 100644 index 000000000..70b67c963 --- /dev/null +++ b/vendor/github.com/rs/zerolog/.travis.yml @@ -0,0 +1,15 @@ +language: go +go: +- "1.7" +- "1.8" +- "1.9" +- "1.10" +- "1.11" +- "1.12" +- "master" +matrix: + allow_failures: + - go: "master" +script: + - go test -v -race -cpu=1,2,4 -bench . -benchmem ./... + - go test -v -tags binary_log -race -cpu=1,2,4 -bench . -benchmem ./... diff --git a/vendor/github.com/rs/zerolog/CNAME b/vendor/github.com/rs/zerolog/CNAME new file mode 100644 index 000000000..9ce57a6eb --- /dev/null +++ b/vendor/github.com/rs/zerolog/CNAME @@ -0,0 +1 @@ +zerolog.io \ No newline at end of file diff --git a/vendor/github.com/rs/zerolog/LICENSE b/vendor/github.com/rs/zerolog/LICENSE new file mode 100644 index 000000000..677e07f7a --- /dev/null +++ b/vendor/github.com/rs/zerolog/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Olivier Poitrey + +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. diff --git a/vendor/github.com/rs/zerolog/README.md b/vendor/github.com/rs/zerolog/README.md new file mode 100644 index 000000000..bd28c29a5 --- /dev/null +++ b/vendor/github.com/rs/zerolog/README.md @@ -0,0 +1,595 @@ +# Zero Allocation JSON Logger + +[![godoc](http://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/rs/zerolog) [![license](http://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/rs/zerolog/master/LICENSE) [![Build Status](https://travis-ci.org/rs/zerolog.svg?branch=master)](https://travis-ci.org/rs/zerolog) [![Coverage](http://gocover.io/_badge/github.com/rs/zerolog)](http://gocover.io/github.com/rs/zerolog) + +The zerolog package provides a fast and simple logger dedicated to JSON output. + +Zerolog's API is designed to provide both a great developer experience and stunning [performance](#benchmarks). Its unique chaining API allows zerolog to write JSON (or CBOR) log events by avoiding allocations and reflection. + +Uber's [zap](https://godoc.org/go.uber.org/zap) library pioneered this approach. Zerolog is taking this concept to the next level with a simpler to use API and even better performance. + +To keep the code base and the API simple, zerolog focuses on efficient structured logging only. Pretty logging on the console is made possible using the provided (but inefficient) [`zerolog.ConsoleWriter`](#pretty-logging). + +![Pretty Logging Image](pretty.png) + +## Who uses zerolog + +Find out [who uses zerolog](https://github.com/rs/zerolog/wiki/Who-uses-zerolog) and add your company / project to the list. + +## Features + +* Blazing fast +* Low to zero allocation +* Level logging +* Sampling +* Hooks +* Contextual fields +* `context.Context` integration +* `net/http` helpers +* JSON and CBOR encoding formats +* Pretty logging for development + +## Installation + +```bash +go get -u github.com/rs/zerolog/log +``` + +## Getting Started + +### Simple Logging Example + +For simple logging, import the global logger package **github.com/rs/zerolog/log** + +```go +package main + +import ( + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +func main() { + // UNIX Time is faster and smaller than most timestamps + // If you set zerolog.TimeFieldFormat to an empty string, + // logs will write with UNIX time + zerolog.TimeFieldFormat = zerolog.TimeFormatUnix + + log.Print("hello world") +} + +// Output: {"time":1516134303,"level":"debug","message":"hello world"} +``` +> Note: By default log writes to `os.Stderr` +> Note: The default log level for `log.Print` is *debug* + +### Contextual Logging + +**zerolog** allows data to be added to log messages in the form of key:value pairs. The data added to the message adds "context" about the log event that can be critical for debugging as well as myriad other purposes. An example of this is below: + +```go +package main + +import ( + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +func main() { + zerolog.TimeFieldFormat = zerolog.TimeFormatUnix + + log.Debug(). + Str("Scale", "833 cents"). + Float64("Interval", 833.09). + Msg("Fibonacci is everywhere") + + log.Debug(). + Str("Name", "Tom"). + Send() +} + +// Output: {"level":"debug","Scale":"833 cents","Interval":833.09,"time":1562212768,"message":"Fibonacci is everywhere"} +// Output: {"level":"debug","Name":"Tom","time":1562212768} +``` + +> You'll note in the above example that when adding contextual fields, the fields are strongly typed. You can find the full list of supported fields [here](#standard-types) + +### Leveled Logging + +#### Simple Leveled Logging Example + +```go +package main + +import ( + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +func main() { + zerolog.TimeFieldFormat = zerolog.TimeFormatUnix + + log.Info().Msg("hello world") +} + +// Output: {"time":1516134303,"level":"info","message":"hello world"} +``` + +> It is very important to note that when using the **zerolog** chaining API, as shown above (`log.Info().Msg("hello world"`), the chain must have either the `Msg` or `Msgf` method call. If you forget to add either of these, the log will not occur and there is no compile time error to alert you of this. + +**zerolog** allows for logging at the following levels (from highest to lowest): + +* panic (`zerolog.PanicLevel`, 5) +* fatal (`zerolog.FatalLevel`, 4) +* error (`zerolog.ErrorLevel`, 3) +* warn (`zerolog.WarnLevel`, 2) +* info (`zerolog.InfoLevel`, 1) +* debug (`zerolog.DebugLevel`, 0) +* trace (`zerolog.TraceLevel`, -1) + +You can set the Global logging level to any of these options using the `SetGlobalLevel` function in the zerolog package, passing in one of the given constants above, e.g. `zerolog.InfoLevel` would be the "info" level. Whichever level is chosen, all logs with a level greater than or equal to that level will be written. To turn off logging entirely, pass the `zerolog.Disabled` constant. + +#### Setting Global Log Level + +This example uses command-line flags to demonstrate various outputs depending on the chosen log level. + +```go +package main + +import ( + "flag" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +func main() { + zerolog.TimeFieldFormat = zerolog.TimeFormatUnix + debug := flag.Bool("debug", false, "sets log level to debug") + + flag.Parse() + + // Default level for this example is info, unless debug flag is present + zerolog.SetGlobalLevel(zerolog.InfoLevel) + if *debug { + zerolog.SetGlobalLevel(zerolog.DebugLevel) + } + + log.Debug().Msg("This message appears only when log level set to Debug") + log.Info().Msg("This message appears when log level set to Debug or Info") + + if e := log.Debug(); e.Enabled() { + // Compute log output only if enabled. + value := "bar" + e.Str("foo", value).Msg("some debug message") + } +} +``` + +Info Output (no flag) + +```bash +$ ./logLevelExample +{"time":1516387492,"level":"info","message":"This message appears when log level set to Debug or Info"} +``` + +Debug Output (debug flag set) + +```bash +$ ./logLevelExample -debug +{"time":1516387573,"level":"debug","message":"This message appears only when log level set to Debug"} +{"time":1516387573,"level":"info","message":"This message appears when log level set to Debug or Info"} +{"time":1516387573,"level":"debug","foo":"bar","message":"some debug message"} +``` + +#### Logging without Level or Message + +You may choose to log without a specific level by using the `Log` method. You may also write without a message by setting an empty string in the `msg string` parameter of the `Msg` method. Both are demonstrated in the example below. + +```go +package main + +import ( + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +func main() { + zerolog.TimeFieldFormat = zerolog.TimeFormatUnix + + log.Log(). + Str("foo", "bar"). + Msg("") +} + +// Output: {"time":1494567715,"foo":"bar"} +``` + +#### Logging Fatal Messages + +```go +package main + +import ( + "errors" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" +) + +func main() { + err := errors.New("A repo man spends his life getting into tense situations") + service := "myservice" + + zerolog.TimeFieldFormat = zerolog.TimeFormatUnix + + log.Fatal(). + Err(err). + Str("service", service). + Msgf("Cannot start %s", service) +} + +// Output: {"time":1516133263,"level":"fatal","error":"A repo man spends his life getting into tense situations","service":"myservice","message":"Cannot start myservice"} +// exit status 1 +``` + +> NOTE: Using `Msgf` generates one allocation even when the logger is disabled. + +### Create logger instance to manage different outputs + +```go +logger := zerolog.New(os.Stderr).With().Timestamp().Logger() + +logger.Info().Str("foo", "bar").Msg("hello world") + +// Output: {"level":"info","time":1494567715,"message":"hello world","foo":"bar"} +``` + +### Sub-loggers let you chain loggers with additional context + +```go +sublogger := log.With(). + Str("component", "foo"). + Logger() +sublogger.Info().Msg("hello world") + +// Output: {"level":"info","time":1494567715,"message":"hello world","component":"foo"} +``` + +### Pretty logging + +To log a human-friendly, colorized output, use `zerolog.ConsoleWriter`: + +```go +log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) + +log.Info().Str("foo", "bar").Msg("Hello world") + +// Output: 3:04PM INF Hello World foo=bar +``` + +To customize the configuration and formatting: + +```go +output := zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.RFC3339} +output.FormatLevel = func(i interface{}) string { + return strings.ToUpper(fmt.Sprintf("| %-6s|", i)) +} +output.FormatMessage = func(i interface{}) string { + return fmt.Sprintf("***%s****", i) +} +output.FormatFieldName = func(i interface{}) string { + return fmt.Sprintf("%s:", i) +} +output.FormatFieldValue = func(i interface{}) string { + return strings.ToUpper(fmt.Sprintf("%s", i)) +} + +log := zerolog.New(output).With().Timestamp().Logger() + +log.Info().Str("foo", "bar").Msg("Hello World") + +// Output: 2006-01-02T15:04:05Z07:00 | INFO | ***Hello World**** foo:BAR +``` + +### Sub dictionary + +```go +log.Info(). + Str("foo", "bar"). + Dict("dict", zerolog.Dict(). + Str("bar", "baz"). + Int("n", 1), + ).Msg("hello world") + +// Output: {"level":"info","time":1494567715,"foo":"bar","dict":{"bar":"baz","n":1},"message":"hello world"} +``` + +### Customize automatic field names + +```go +zerolog.TimestampFieldName = "t" +zerolog.LevelFieldName = "l" +zerolog.MessageFieldName = "m" + +log.Info().Msg("hello world") + +// Output: {"l":"info","t":1494567715,"m":"hello world"} +``` + +### Add contextual fields to the global logger + +```go +log.Logger = log.With().Str("foo", "bar").Logger() +``` + +### Add file and line number to log + +```go +log.Logger = log.With().Caller().Logger() +log.Info().Msg("hello world") + +// Output: {"level": "info", "message": "hello world", "caller": "/go/src/your_project/some_file:21"} +``` + + +### Thread-safe, lock-free, non-blocking writer + +If your writer might be slow or not thread-safe and you need your log producers to never get slowed down by a slow writer, you can use a `diode.Writer` as follow: + +```go +wr := diode.NewWriter(os.Stdout, 1000, 10*time.Millisecond, func(missed int) { + fmt.Printf("Logger Dropped %d messages", missed) + }) +log := zerolog.New(w) +log.Print("test") +``` + +You will need to install `code.cloudfoundry.org/go-diodes` to use this feature. + +### Log Sampling + +```go +sampled := log.Sample(&zerolog.BasicSampler{N: 10}) +sampled.Info().Msg("will be logged every 10 messages") + +// Output: {"time":1494567715,"level":"info","message":"will be logged every 10 messages"} +``` + +More advanced sampling: + +```go +// Will let 5 debug messages per period of 1 second. +// Over 5 debug message, 1 every 100 debug messages are logged. +// Other levels are not sampled. +sampled := log.Sample(zerolog.LevelSampler{ + DebugSampler: &zerolog.BurstSampler{ + Burst: 5, + Period: 1*time.Second, + NextSampler: &zerolog.BasicSampler{N: 100}, + }, +}) +sampled.Debug().Msg("hello world") + +// Output: {"time":1494567715,"level":"debug","message":"hello world"} +``` + +### Hooks + +```go +type SeverityHook struct{} + +func (h SeverityHook) Run(e *zerolog.Event, level zerolog.Level, msg string) { + if level != zerolog.NoLevel { + e.Str("severity", level.String()) + } +} + +hooked := log.Hook(SeverityHook{}) +hooked.Warn().Msg("") + +// Output: {"level":"warn","severity":"warn"} +``` + +### Pass a sub-logger by context + +```go +ctx := log.With().Str("component", "module").Logger().WithContext(ctx) + +log.Ctx(ctx).Info().Msg("hello world") + +// Output: {"component":"module","level":"info","message":"hello world"} +``` + +### Set as standard logger output + +```go +log := zerolog.New(os.Stdout).With(). + Str("foo", "bar"). + Logger() + +stdlog.SetFlags(0) +stdlog.SetOutput(log) + +stdlog.Print("hello world") + +// Output: {"foo":"bar","message":"hello world"} +``` + +### Integration with `net/http` + +The `github.com/rs/zerolog/hlog` package provides some helpers to integrate zerolog with `http.Handler`. + +In this example we use [alice](https://github.com/justinas/alice) to install logger for better readability. + +```go +log := zerolog.New(os.Stdout).With(). + Timestamp(). + Str("role", "my-service"). + Str("host", host). + Logger() + +c := alice.New() + +// Install the logger handler with default output on the console +c = c.Append(hlog.NewHandler(log)) + +// Install some provided extra handler to set some request's context fields. +// Thanks to those handler, all our logs will come with some pre-populated fields. +c = c.Append(hlog.AccessHandler(func(r *http.Request, status, size int, duration time.Duration) { + hlog.FromRequest(r).Info(). + Str("method", r.Method). + Str("url", r.URL.String()). + Int("status", status). + Int("size", size). + Dur("duration", duration). + Msg("") +})) +c = c.Append(hlog.RemoteAddrHandler("ip")) +c = c.Append(hlog.UserAgentHandler("user_agent")) +c = c.Append(hlog.RefererHandler("referer")) +c = c.Append(hlog.RequestIDHandler("req_id", "Request-Id")) + +// Here is your final handler +h := c.Then(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Get the logger from the request's context. You can safely assume it + // will be always there: if the handler is removed, hlog.FromRequest + // will return a no-op logger. + hlog.FromRequest(r).Info(). + Str("user", "current user"). + Str("status", "ok"). + Msg("Something happened") + + // Output: {"level":"info","time":"2001-02-03T04:05:06Z","role":"my-service","host":"local-hostname","req_id":"b4g0l5t6tfid6dtrapu0","user":"current user","status":"ok","message":"Something happened"} +})) +http.Handle("/", h) + +if err := http.ListenAndServe(":8080", nil); err != nil { + log.Fatal().Err(err).Msg("Startup failed") +} +``` + +## Global Settings + +Some settings can be changed and will by applied to all loggers: + +* `log.Logger`: You can set this value to customize the global logger (the one used by package level methods). +* `zerolog.SetGlobalLevel`: Can raise the minimum level of all loggers. Set this to `zerolog.Disabled` to disable logging altogether (quiet mode). +* `zerolog.DisableSampling`: If argument is `true`, all sampled loggers will stop sampling and issue 100% of their log events. +* `zerolog.TimestampFieldName`: Can be set to customize `Timestamp` field name. +* `zerolog.LevelFieldName`: Can be set to customize level field name. +* `zerolog.MessageFieldName`: Can be set to customize message field name. +* `zerolog.ErrorFieldName`: Can be set to customize `Err` field name. +* `zerolog.TimeFieldFormat`: Can be set to customize `Time` field value formatting. If set with `zerolog.TimeFormatUnix`, `zerolog.TimeFormatUnixMs` or `zerolog.TimeFormatUnixMicro`, times are formated as UNIX timestamp. +* `zerolog.DurationFieldUnit`: Can be set to customize the unit for time.Duration type fields added by `Dur` (default: `time.Millisecond`). +* `zerolog.DurationFieldInteger`: If set to `true`, `Dur` fields are formatted as integers instead of floats (default: `false`). +* `zerolog.ErrorHandler`: Called whenever zerolog fails to write an event on its output. If not set, an error is printed on the stderr. This handler must be thread safe and non-blocking. + +## Field Types + +### Standard Types + +* `Str` +* `Bool` +* `Int`, `Int8`, `Int16`, `Int32`, `Int64` +* `Uint`, `Uint8`, `Uint16`, `Uint32`, `Uint64` +* `Float32`, `Float64` + +### Advanced Fields + +* `Err`: Takes an `error` and render it as a string using the `zerolog.ErrorFieldName` field name. +* `Timestamp`: Insert a timestamp field with `zerolog.TimestampFieldName` field name and formatted using `zerolog.TimeFieldFormat`. +* `Time`: Adds a field with the time formated with the `zerolog.TimeFieldFormat`. +* `Dur`: Adds a field with a `time.Duration`. +* `Dict`: Adds a sub-key/value as a field of the event. +* `Interface`: Uses reflection to marshal the type. + +## Binary Encoding + +In addition to the default JSON encoding, `zerolog` can produce binary logs using [CBOR](http://cbor.io) encoding. The choice of encoding can be decided at compile time using the build tag `binary_log` as follows: + +```bash +go build -tags binary_log . +``` + +To Decode binary encoded log files you can use any CBOR decoder. One has been tested to work +with zerolog library is [CSD](https://github.com/toravir/csd/). + +## Related Projects + +* [grpc-zerolog](https://github.com/cheapRoc/grpc-zerolog): Implementation of `grpclog.LoggerV2` interface using `zerolog` + +## Benchmarks + +See [logbench](http://hackemist.com/logbench/) for more comprehensive and up-to-date benchmarks. + +All operations are allocation free (those numbers *include* JSON encoding): + +```text +BenchmarkLogEmpty-8 100000000 19.1 ns/op 0 B/op 0 allocs/op +BenchmarkDisabled-8 500000000 4.07 ns/op 0 B/op 0 allocs/op +BenchmarkInfo-8 30000000 42.5 ns/op 0 B/op 0 allocs/op +BenchmarkContextFields-8 30000000 44.9 ns/op 0 B/op 0 allocs/op +BenchmarkLogFields-8 10000000 184 ns/op 0 B/op 0 allocs/op +``` + +There are a few Go logging benchmarks and comparisons that include zerolog. + +* [imkira/go-loggers-bench](https://github.com/imkira/go-loggers-bench) +* [uber-common/zap](https://github.com/uber-go/zap#performance) + +Using Uber's zap comparison benchmark: + +Log a message and 10 fields: + +| Library | Time | Bytes Allocated | Objects Allocated | +| :--- | :---: | :---: | :---: | +| zerolog | 767 ns/op | 552 B/op | 6 allocs/op | +| :zap: zap | 848 ns/op | 704 B/op | 2 allocs/op | +| :zap: zap (sugared) | 1363 ns/op | 1610 B/op | 20 allocs/op | +| go-kit | 3614 ns/op | 2895 B/op | 66 allocs/op | +| lion | 5392 ns/op | 5807 B/op | 63 allocs/op | +| logrus | 5661 ns/op | 6092 B/op | 78 allocs/op | +| apex/log | 15332 ns/op | 3832 B/op | 65 allocs/op | +| log15 | 20657 ns/op | 5632 B/op | 93 allocs/op | + +Log a message with a logger that already has 10 fields of context: + +| Library | Time | Bytes Allocated | Objects Allocated | +| :--- | :---: | :---: | :---: | +| zerolog | 52 ns/op | 0 B/op | 0 allocs/op | +| :zap: zap | 283 ns/op | 0 B/op | 0 allocs/op | +| :zap: zap (sugared) | 337 ns/op | 80 B/op | 2 allocs/op | +| lion | 2702 ns/op | 4074 B/op | 38 allocs/op | +| go-kit | 3378 ns/op | 3046 B/op | 52 allocs/op | +| logrus | 4309 ns/op | 4564 B/op | 63 allocs/op | +| apex/log | 13456 ns/op | 2898 B/op | 51 allocs/op | +| log15 | 14179 ns/op | 2642 B/op | 44 allocs/op | + +Log a static string, without any context or `printf`-style templating: + +| Library | Time | Bytes Allocated | Objects Allocated | +| :--- | :---: | :---: | :---: | +| zerolog | 50 ns/op | 0 B/op | 0 allocs/op | +| :zap: zap | 236 ns/op | 0 B/op | 0 allocs/op | +| standard library | 453 ns/op | 80 B/op | 2 allocs/op | +| :zap: zap (sugared) | 337 ns/op | 80 B/op | 2 allocs/op | +| go-kit | 508 ns/op | 656 B/op | 13 allocs/op | +| lion | 771 ns/op | 1224 B/op | 10 allocs/op | +| logrus | 1244 ns/op | 1505 B/op | 27 allocs/op | +| apex/log | 2751 ns/op | 584 B/op | 11 allocs/op | +| log15 | 5181 ns/op | 1592 B/op | 26 allocs/op | + +## Caveats + +Note that zerolog does no de-duplication of fields. Using the same key multiple times creates multiple keys in final JSON: + +```go +logger := zerolog.New(os.Stderr).With().Timestamp().Logger() +logger.Info(). + Timestamp(). + Msg("dup") +// Output: {"level":"info","time":1494567715,"time":1494567715,"message":"dup"} +``` + +In this case, many consumers will take the last value, but this is not guaranteed; check yours if in doubt. diff --git a/vendor/github.com/rs/zerolog/_config.yml b/vendor/github.com/rs/zerolog/_config.yml new file mode 100644 index 000000000..a1e896d7b --- /dev/null +++ b/vendor/github.com/rs/zerolog/_config.yml @@ -0,0 +1 @@ +remote_theme: rs/gh-readme diff --git a/vendor/github.com/rs/zerolog/array.go b/vendor/github.com/rs/zerolog/array.go new file mode 100644 index 000000000..0f7f53eed --- /dev/null +++ b/vendor/github.com/rs/zerolog/array.go @@ -0,0 +1,233 @@ +package zerolog + +import ( + "net" + "sync" + "time" +) + +var arrayPool = &sync.Pool{ + New: func() interface{} { + return &Array{ + buf: make([]byte, 0, 500), + } + }, +} + +// Array is used to prepopulate an array of items +// which can be re-used to add to log messages. +type Array struct { + buf []byte +} + +func putArray(a *Array) { + // Proper usage of a sync.Pool requires each entry to have approximately + // the same memory cost. To obtain this property when the stored type + // contains a variably-sized buffer, we add a hard limit on the maximum buffer + // to place back in the pool. + // + // See https://golang.org/issue/23199 + const maxSize = 1 << 16 // 64KiB + if cap(a.buf) > maxSize { + return + } + arrayPool.Put(a) +} + +// Arr creates an array to be added to an Event or Context. +func Arr() *Array { + a := arrayPool.Get().(*Array) + a.buf = a.buf[:0] + return a +} + +// MarshalZerologArray method here is no-op - since data is +// already in the needed format. +func (*Array) MarshalZerologArray(*Array) { +} + +func (a *Array) write(dst []byte) []byte { + dst = enc.AppendArrayStart(dst) + if len(a.buf) > 0 { + dst = append(append(dst, a.buf...)) + } + dst = enc.AppendArrayEnd(dst) + putArray(a) + return dst +} + +// Object marshals an object that implement the LogObjectMarshaler +// interface and append append it to the array. +func (a *Array) Object(obj LogObjectMarshaler) *Array { + e := Dict() + obj.MarshalZerologObject(e) + e.buf = enc.AppendEndMarker(e.buf) + a.buf = append(enc.AppendArrayDelim(a.buf), e.buf...) + putEvent(e) + return a +} + +// Str append append the val as a string to the array. +func (a *Array) Str(val string) *Array { + a.buf = enc.AppendString(enc.AppendArrayDelim(a.buf), val) + return a +} + +// Bytes append append the val as a string to the array. +func (a *Array) Bytes(val []byte) *Array { + a.buf = enc.AppendBytes(enc.AppendArrayDelim(a.buf), val) + return a +} + +// Hex append append the val as a hex string to the array. +func (a *Array) Hex(val []byte) *Array { + a.buf = enc.AppendHex(enc.AppendArrayDelim(a.buf), val) + return a +} + +// RawJSON adds already encoded JSON to the array. +func (a *Array) RawJSON(val []byte) *Array { + a.buf = appendJSON(enc.AppendArrayDelim(a.buf), val) + return a +} + +// Err serializes and appends the err to the array. +func (a *Array) Err(err error) *Array { + switch m := ErrorMarshalFunc(err).(type) { + case LogObjectMarshaler: + e := newEvent(nil, 0) + e.buf = e.buf[:0] + e.appendObject(m) + a.buf = append(enc.AppendArrayDelim(a.buf), e.buf...) + putEvent(e) + case error: + if m == nil || isNilValue(m) { + a.buf = enc.AppendNil(enc.AppendArrayDelim(a.buf)) + } else { + a.buf = enc.AppendString(enc.AppendArrayDelim(a.buf), m.Error()) + } + case string: + a.buf = enc.AppendString(enc.AppendArrayDelim(a.buf), m) + default: + a.buf = enc.AppendInterface(enc.AppendArrayDelim(a.buf), m) + } + + return a +} + +// Bool append append the val as a bool to the array. +func (a *Array) Bool(b bool) *Array { + a.buf = enc.AppendBool(enc.AppendArrayDelim(a.buf), b) + return a +} + +// Int append append i as a int to the array. +func (a *Array) Int(i int) *Array { + a.buf = enc.AppendInt(enc.AppendArrayDelim(a.buf), i) + return a +} + +// Int8 append append i as a int8 to the array. +func (a *Array) Int8(i int8) *Array { + a.buf = enc.AppendInt8(enc.AppendArrayDelim(a.buf), i) + return a +} + +// Int16 append append i as a int16 to the array. +func (a *Array) Int16(i int16) *Array { + a.buf = enc.AppendInt16(enc.AppendArrayDelim(a.buf), i) + return a +} + +// Int32 append append i as a int32 to the array. +func (a *Array) Int32(i int32) *Array { + a.buf = enc.AppendInt32(enc.AppendArrayDelim(a.buf), i) + return a +} + +// Int64 append append i as a int64 to the array. +func (a *Array) Int64(i int64) *Array { + a.buf = enc.AppendInt64(enc.AppendArrayDelim(a.buf), i) + return a +} + +// Uint append append i as a uint to the array. +func (a *Array) Uint(i uint) *Array { + a.buf = enc.AppendUint(enc.AppendArrayDelim(a.buf), i) + return a +} + +// Uint8 append append i as a uint8 to the array. +func (a *Array) Uint8(i uint8) *Array { + a.buf = enc.AppendUint8(enc.AppendArrayDelim(a.buf), i) + return a +} + +// Uint16 append append i as a uint16 to the array. +func (a *Array) Uint16(i uint16) *Array { + a.buf = enc.AppendUint16(enc.AppendArrayDelim(a.buf), i) + return a +} + +// Uint32 append append i as a uint32 to the array. +func (a *Array) Uint32(i uint32) *Array { + a.buf = enc.AppendUint32(enc.AppendArrayDelim(a.buf), i) + return a +} + +// Uint64 append append i as a uint64 to the array. +func (a *Array) Uint64(i uint64) *Array { + a.buf = enc.AppendUint64(enc.AppendArrayDelim(a.buf), i) + return a +} + +// Float32 append append f as a float32 to the array. +func (a *Array) Float32(f float32) *Array { + a.buf = enc.AppendFloat32(enc.AppendArrayDelim(a.buf), f) + return a +} + +// Float64 append append f as a float64 to the array. +func (a *Array) Float64(f float64) *Array { + a.buf = enc.AppendFloat64(enc.AppendArrayDelim(a.buf), f) + return a +} + +// Time append append t formated as string using zerolog.TimeFieldFormat. +func (a *Array) Time(t time.Time) *Array { + a.buf = enc.AppendTime(enc.AppendArrayDelim(a.buf), t, TimeFieldFormat) + return a +} + +// Dur append append d to the array. +func (a *Array) Dur(d time.Duration) *Array { + a.buf = enc.AppendDuration(enc.AppendArrayDelim(a.buf), d, DurationFieldUnit, DurationFieldInteger) + return a +} + +// Interface append append i marshaled using reflection. +func (a *Array) Interface(i interface{}) *Array { + if obj, ok := i.(LogObjectMarshaler); ok { + return a.Object(obj) + } + a.buf = enc.AppendInterface(enc.AppendArrayDelim(a.buf), i) + return a +} + +// IPAddr adds IPv4 or IPv6 address to the array +func (a *Array) IPAddr(ip net.IP) *Array { + a.buf = enc.AppendIPAddr(enc.AppendArrayDelim(a.buf), ip) + return a +} + +// IPPrefix adds IPv4 or IPv6 Prefix (IP + mask) to the array +func (a *Array) IPPrefix(pfx net.IPNet) *Array { + a.buf = enc.AppendIPPrefix(enc.AppendArrayDelim(a.buf), pfx) + return a +} + +// MACAddr adds a MAC (Ethernet) address to the array +func (a *Array) MACAddr(ha net.HardwareAddr) *Array { + a.buf = enc.AppendMACAddr(enc.AppendArrayDelim(a.buf), ha) + return a +} diff --git a/vendor/github.com/rs/zerolog/console.go b/vendor/github.com/rs/zerolog/console.go new file mode 100644 index 000000000..54f799457 --- /dev/null +++ b/vendor/github.com/rs/zerolog/console.go @@ -0,0 +1,397 @@ +package zerolog + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "os" + "sort" + "strconv" + "strings" + "sync" + "time" +) + +const ( + colorBlack = iota + 30 + colorRed + colorGreen + colorYellow + colorBlue + colorMagenta + colorCyan + colorWhite + + colorBold = 1 + colorDarkGray = 90 +) + +var ( + consoleBufPool = sync.Pool{ + New: func() interface{} { + return bytes.NewBuffer(make([]byte, 0, 100)) + }, + } +) + +const ( + consoleDefaultTimeFormat = time.Kitchen +) + +// Formatter transforms the input into a formatted string. +type Formatter func(interface{}) string + +// ConsoleWriter parses the JSON input and writes it in an +// (optionally) colorized, human-friendly format to Out. +type ConsoleWriter struct { + // Out is the output destination. + Out io.Writer + + // NoColor disables the colorized output. + NoColor bool + + // TimeFormat specifies the format for timestamp in output. + TimeFormat string + + // PartsOrder defines the order of parts in output. + PartsOrder []string + + FormatTimestamp Formatter + FormatLevel Formatter + FormatCaller Formatter + FormatMessage Formatter + FormatFieldName Formatter + FormatFieldValue Formatter + FormatErrFieldName Formatter + FormatErrFieldValue Formatter +} + +// NewConsoleWriter creates and initializes a new ConsoleWriter. +func NewConsoleWriter(options ...func(w *ConsoleWriter)) ConsoleWriter { + w := ConsoleWriter{ + Out: os.Stdout, + TimeFormat: consoleDefaultTimeFormat, + PartsOrder: consoleDefaultPartsOrder(), + } + + for _, opt := range options { + opt(&w) + } + + return w +} + +// Write transforms the JSON input with formatters and appends to w.Out. +func (w ConsoleWriter) Write(p []byte) (n int, err error) { + if w.PartsOrder == nil { + w.PartsOrder = consoleDefaultPartsOrder() + } + + var buf = consoleBufPool.Get().(*bytes.Buffer) + defer func() { + buf.Reset() + consoleBufPool.Put(buf) + }() + + var evt map[string]interface{} + p = decodeIfBinaryToBytes(p) + d := json.NewDecoder(bytes.NewReader(p)) + d.UseNumber() + err = d.Decode(&evt) + if err != nil { + return n, fmt.Errorf("cannot decode event: %s", err) + } + + for _, p := range w.PartsOrder { + w.writePart(buf, evt, p) + } + + w.writeFields(evt, buf) + + err = buf.WriteByte('\n') + if err != nil { + return n, err + } + _, err = buf.WriteTo(w.Out) + return len(p), err +} + +// writeFields appends formatted key-value pairs to buf. +func (w ConsoleWriter) writeFields(evt map[string]interface{}, buf *bytes.Buffer) { + var fields = make([]string, 0, len(evt)) + for field := range evt { + switch field { + case LevelFieldName, TimestampFieldName, MessageFieldName, CallerFieldName: + continue + } + fields = append(fields, field) + } + sort.Strings(fields) + + if len(fields) > 0 { + buf.WriteByte(' ') + } + + // Move the "error" field to the front + ei := sort.Search(len(fields), func(i int) bool { return fields[i] >= ErrorFieldName }) + if ei < len(fields) && fields[ei] == ErrorFieldName { + fields[ei] = "" + fields = append([]string{ErrorFieldName}, fields...) + var xfields = make([]string, 0, len(fields)) + for _, field := range fields { + if field == "" { // Skip empty fields + continue + } + xfields = append(xfields, field) + } + fields = xfields + } + + for i, field := range fields { + var fn Formatter + var fv Formatter + + if field == ErrorFieldName { + if w.FormatErrFieldName == nil { + fn = consoleDefaultFormatErrFieldName(w.NoColor) + } else { + fn = w.FormatErrFieldName + } + + if w.FormatErrFieldValue == nil { + fv = consoleDefaultFormatErrFieldValue(w.NoColor) + } else { + fv = w.FormatErrFieldValue + } + } else { + if w.FormatFieldName == nil { + fn = consoleDefaultFormatFieldName(w.NoColor) + } else { + fn = w.FormatFieldName + } + + if w.FormatFieldValue == nil { + fv = consoleDefaultFormatFieldValue + } else { + fv = w.FormatFieldValue + } + } + + buf.WriteString(fn(field)) + + switch fValue := evt[field].(type) { + case string: + if needsQuote(fValue) { + buf.WriteString(fv(strconv.Quote(fValue))) + } else { + buf.WriteString(fv(fValue)) + } + case json.Number: + buf.WriteString(fv(fValue)) + default: + b, err := json.Marshal(fValue) + if err != nil { + fmt.Fprintf(buf, colorize("[error: %v]", colorRed, w.NoColor), err) + } else { + fmt.Fprint(buf, fv(b)) + } + } + + if i < len(fields)-1 { // Skip space for last field + buf.WriteByte(' ') + } + } +} + +// writePart appends a formatted part to buf. +func (w ConsoleWriter) writePart(buf *bytes.Buffer, evt map[string]interface{}, p string) { + var f Formatter + + switch p { + case LevelFieldName: + if w.FormatLevel == nil { + f = consoleDefaultFormatLevel(w.NoColor) + } else { + f = w.FormatLevel + } + case TimestampFieldName: + if w.FormatTimestamp == nil { + f = consoleDefaultFormatTimestamp(w.TimeFormat, w.NoColor) + } else { + f = w.FormatTimestamp + } + case MessageFieldName: + if w.FormatMessage == nil { + f = consoleDefaultFormatMessage + } else { + f = w.FormatMessage + } + case CallerFieldName: + if w.FormatCaller == nil { + f = consoleDefaultFormatCaller(w.NoColor) + } else { + f = w.FormatCaller + } + default: + if w.FormatFieldValue == nil { + f = consoleDefaultFormatFieldValue + } else { + f = w.FormatFieldValue + } + } + + var s = f(evt[p]) + + if len(s) > 0 { + buf.WriteString(s) + if p != w.PartsOrder[len(w.PartsOrder)-1] { // Skip space for last part + buf.WriteByte(' ') + } + } +} + +// needsQuote returns true when the string s should be quoted in output. +func needsQuote(s string) bool { + for i := range s { + if s[i] < 0x20 || s[i] > 0x7e || s[i] == ' ' || s[i] == '\\' || s[i] == '"' { + return true + } + } + return false +} + +// colorize returns the string s wrapped in ANSI code c, unless disabled is true. +func colorize(s interface{}, c int, disabled bool) string { + if disabled { + return fmt.Sprintf("%s", s) + } + return fmt.Sprintf("\x1b[%dm%v\x1b[0m", c, s) +} + +// ----- DEFAULT FORMATTERS --------------------------------------------------- + +func consoleDefaultPartsOrder() []string { + return []string{ + TimestampFieldName, + LevelFieldName, + CallerFieldName, + MessageFieldName, + } +} + +func consoleDefaultFormatTimestamp(timeFormat string, noColor bool) Formatter { + if timeFormat == "" { + timeFormat = consoleDefaultTimeFormat + } + return func(i interface{}) string { + t := "" + switch tt := i.(type) { + case string: + ts, err := time.Parse(TimeFieldFormat, tt) + if err != nil { + t = tt + } else { + t = ts.Format(timeFormat) + } + case json.Number: + i, err := tt.Int64() + if err != nil { + t = tt.String() + } else { + var sec, nsec int64 = i, 0 + switch TimeFieldFormat { + case TimeFormatUnixMs: + nsec = int64(time.Duration(i) * time.Millisecond) + sec = 0 + case TimeFormatUnixMicro: + nsec = int64(time.Duration(i) * time.Microsecond) + sec = 0 + } + ts := time.Unix(sec, nsec).UTC() + t = ts.Format(timeFormat) + } + } + return colorize(t, colorDarkGray, noColor) + } +} + +func consoleDefaultFormatLevel(noColor bool) Formatter { + return func(i interface{}) string { + var l string + if ll, ok := i.(string); ok { + switch ll { + case "trace": + l = colorize("TRC", colorMagenta, noColor) + case "debug": + l = colorize("DBG", colorYellow, noColor) + case "info": + l = colorize("INF", colorGreen, noColor) + case "warn": + l = colorize("WRN", colorRed, noColor) + case "error": + l = colorize(colorize("ERR", colorRed, noColor), colorBold, noColor) + case "fatal": + l = colorize(colorize("FTL", colorRed, noColor), colorBold, noColor) + case "panic": + l = colorize(colorize("PNC", colorRed, noColor), colorBold, noColor) + default: + l = colorize("???", colorBold, noColor) + } + } else { + if i == nil { + l = colorize("???", colorBold, noColor) + } else { + l = strings.ToUpper(fmt.Sprintf("%s", i))[0:3] + } + } + return l + } +} + +func consoleDefaultFormatCaller(noColor bool) Formatter { + return func(i interface{}) string { + var c string + if cc, ok := i.(string); ok { + c = cc + } + if len(c) > 0 { + cwd, err := os.Getwd() + if err == nil { + c = strings.TrimPrefix(c, cwd) + c = strings.TrimPrefix(c, "/") + } + c = colorize(c, colorBold, noColor) + colorize(" >", colorCyan, noColor) + } + return c + } +} + +func consoleDefaultFormatMessage(i interface{}) string { + if i == nil { + return "" + } + return fmt.Sprintf("%s", i) +} + +func consoleDefaultFormatFieldName(noColor bool) Formatter { + return func(i interface{}) string { + return colorize(fmt.Sprintf("%s=", i), colorCyan, noColor) + } +} + +func consoleDefaultFormatFieldValue(i interface{}) string { + return fmt.Sprintf("%s", i) +} + +func consoleDefaultFormatErrFieldName(noColor bool) Formatter { + return func(i interface{}) string { + return colorize(fmt.Sprintf("%s=", i), colorRed, noColor) + } +} + +func consoleDefaultFormatErrFieldValue(noColor bool) Formatter { + return func(i interface{}) string { + return colorize(fmt.Sprintf("%s", i), colorRed, noColor) + } +} diff --git a/vendor/github.com/rs/zerolog/context.go b/vendor/github.com/rs/zerolog/context.go new file mode 100644 index 000000000..27f0d9a1a --- /dev/null +++ b/vendor/github.com/rs/zerolog/context.go @@ -0,0 +1,427 @@ +package zerolog + +import ( + "io/ioutil" + "math" + "net" + "time" +) + +// Context configures a new sub-logger with contextual fields. +type Context struct { + l Logger +} + +// Logger returns the logger with the context previously set. +func (c Context) Logger() Logger { + return c.l +} + +// Fields is a helper function to use a map to set fields using type assertion. +func (c Context) Fields(fields map[string]interface{}) Context { + c.l.context = appendFields(c.l.context, fields) + return c +} + +// Dict adds the field key with the dict to the logger context. +func (c Context) Dict(key string, dict *Event) Context { + dict.buf = enc.AppendEndMarker(dict.buf) + c.l.context = append(enc.AppendKey(c.l.context, key), dict.buf...) + putEvent(dict) + return c +} + +// Array adds the field key with an array to the event context. +// Use zerolog.Arr() to create the array or pass a type that +// implement the LogArrayMarshaler interface. +func (c Context) Array(key string, arr LogArrayMarshaler) Context { + c.l.context = enc.AppendKey(c.l.context, key) + if arr, ok := arr.(*Array); ok { + c.l.context = arr.write(c.l.context) + return c + } + var a *Array + if aa, ok := arr.(*Array); ok { + a = aa + } else { + a = Arr() + arr.MarshalZerologArray(a) + } + c.l.context = a.write(c.l.context) + return c +} + +// Object marshals an object that implement the LogObjectMarshaler interface. +func (c Context) Object(key string, obj LogObjectMarshaler) Context { + e := newEvent(levelWriterAdapter{ioutil.Discard}, 0) + e.Object(key, obj) + c.l.context = enc.AppendObjectData(c.l.context, e.buf) + putEvent(e) + return c +} + +// EmbedObject marshals and Embeds an object that implement the LogObjectMarshaler interface. +func (c Context) EmbedObject(obj LogObjectMarshaler) Context { + e := newEvent(levelWriterAdapter{ioutil.Discard}, 0) + e.EmbedObject(obj) + c.l.context = enc.AppendObjectData(c.l.context, e.buf) + putEvent(e) + return c +} + +// Str adds the field key with val as a string to the logger context. +func (c Context) Str(key, val string) Context { + c.l.context = enc.AppendString(enc.AppendKey(c.l.context, key), val) + return c +} + +// Strs adds the field key with val as a string to the logger context. +func (c Context) Strs(key string, vals []string) Context { + c.l.context = enc.AppendStrings(enc.AppendKey(c.l.context, key), vals) + return c +} + +// Bytes adds the field key with val as a []byte to the logger context. +func (c Context) Bytes(key string, val []byte) Context { + c.l.context = enc.AppendBytes(enc.AppendKey(c.l.context, key), val) + return c +} + +// Hex adds the field key with val as a hex string to the logger context. +func (c Context) Hex(key string, val []byte) Context { + c.l.context = enc.AppendHex(enc.AppendKey(c.l.context, key), val) + return c +} + +// RawJSON adds already encoded JSON to context. +// +// No sanity check is performed on b; it must not contain carriage returns and +// be valid JSON. +func (c Context) RawJSON(key string, b []byte) Context { + c.l.context = appendJSON(enc.AppendKey(c.l.context, key), b) + return c +} + +// AnErr adds the field key with serialized err to the logger context. +func (c Context) AnErr(key string, err error) Context { + switch m := ErrorMarshalFunc(err).(type) { + case nil: + return c + case LogObjectMarshaler: + return c.Object(key, m) + case error: + if m == nil || isNilValue(m) { + return c + } else { + return c.Str(key, m.Error()) + } + case string: + return c.Str(key, m) + default: + return c.Interface(key, m) + } +} + +// Errs adds the field key with errs as an array of serialized errors to the +// logger context. +func (c Context) Errs(key string, errs []error) Context { + arr := Arr() + for _, err := range errs { + switch m := ErrorMarshalFunc(err).(type) { + case LogObjectMarshaler: + arr = arr.Object(m) + case error: + if m == nil || isNilValue(m) { + arr = arr.Interface(nil) + } else { + arr = arr.Str(m.Error()) + } + case string: + arr = arr.Str(m) + default: + arr = arr.Interface(m) + } + } + + return c.Array(key, arr) +} + +// Err adds the field "error" with serialized err to the logger context. +func (c Context) Err(err error) Context { + return c.AnErr(ErrorFieldName, err) +} + +// Bool adds the field key with val as a bool to the logger context. +func (c Context) Bool(key string, b bool) Context { + c.l.context = enc.AppendBool(enc.AppendKey(c.l.context, key), b) + return c +} + +// Bools adds the field key with val as a []bool to the logger context. +func (c Context) Bools(key string, b []bool) Context { + c.l.context = enc.AppendBools(enc.AppendKey(c.l.context, key), b) + return c +} + +// Int adds the field key with i as a int to the logger context. +func (c Context) Int(key string, i int) Context { + c.l.context = enc.AppendInt(enc.AppendKey(c.l.context, key), i) + return c +} + +// Ints adds the field key with i as a []int to the logger context. +func (c Context) Ints(key string, i []int) Context { + c.l.context = enc.AppendInts(enc.AppendKey(c.l.context, key), i) + return c +} + +// Int8 adds the field key with i as a int8 to the logger context. +func (c Context) Int8(key string, i int8) Context { + c.l.context = enc.AppendInt8(enc.AppendKey(c.l.context, key), i) + return c +} + +// Ints8 adds the field key with i as a []int8 to the logger context. +func (c Context) Ints8(key string, i []int8) Context { + c.l.context = enc.AppendInts8(enc.AppendKey(c.l.context, key), i) + return c +} + +// Int16 adds the field key with i as a int16 to the logger context. +func (c Context) Int16(key string, i int16) Context { + c.l.context = enc.AppendInt16(enc.AppendKey(c.l.context, key), i) + return c +} + +// Ints16 adds the field key with i as a []int16 to the logger context. +func (c Context) Ints16(key string, i []int16) Context { + c.l.context = enc.AppendInts16(enc.AppendKey(c.l.context, key), i) + return c +} + +// Int32 adds the field key with i as a int32 to the logger context. +func (c Context) Int32(key string, i int32) Context { + c.l.context = enc.AppendInt32(enc.AppendKey(c.l.context, key), i) + return c +} + +// Ints32 adds the field key with i as a []int32 to the logger context. +func (c Context) Ints32(key string, i []int32) Context { + c.l.context = enc.AppendInts32(enc.AppendKey(c.l.context, key), i) + return c +} + +// Int64 adds the field key with i as a int64 to the logger context. +func (c Context) Int64(key string, i int64) Context { + c.l.context = enc.AppendInt64(enc.AppendKey(c.l.context, key), i) + return c +} + +// Ints64 adds the field key with i as a []int64 to the logger context. +func (c Context) Ints64(key string, i []int64) Context { + c.l.context = enc.AppendInts64(enc.AppendKey(c.l.context, key), i) + return c +} + +// Uint adds the field key with i as a uint to the logger context. +func (c Context) Uint(key string, i uint) Context { + c.l.context = enc.AppendUint(enc.AppendKey(c.l.context, key), i) + return c +} + +// Uints adds the field key with i as a []uint to the logger context. +func (c Context) Uints(key string, i []uint) Context { + c.l.context = enc.AppendUints(enc.AppendKey(c.l.context, key), i) + return c +} + +// Uint8 adds the field key with i as a uint8 to the logger context. +func (c Context) Uint8(key string, i uint8) Context { + c.l.context = enc.AppendUint8(enc.AppendKey(c.l.context, key), i) + return c +} + +// Uints8 adds the field key with i as a []uint8 to the logger context. +func (c Context) Uints8(key string, i []uint8) Context { + c.l.context = enc.AppendUints8(enc.AppendKey(c.l.context, key), i) + return c +} + +// Uint16 adds the field key with i as a uint16 to the logger context. +func (c Context) Uint16(key string, i uint16) Context { + c.l.context = enc.AppendUint16(enc.AppendKey(c.l.context, key), i) + return c +} + +// Uints16 adds the field key with i as a []uint16 to the logger context. +func (c Context) Uints16(key string, i []uint16) Context { + c.l.context = enc.AppendUints16(enc.AppendKey(c.l.context, key), i) + return c +} + +// Uint32 adds the field key with i as a uint32 to the logger context. +func (c Context) Uint32(key string, i uint32) Context { + c.l.context = enc.AppendUint32(enc.AppendKey(c.l.context, key), i) + return c +} + +// Uints32 adds the field key with i as a []uint32 to the logger context. +func (c Context) Uints32(key string, i []uint32) Context { + c.l.context = enc.AppendUints32(enc.AppendKey(c.l.context, key), i) + return c +} + +// Uint64 adds the field key with i as a uint64 to the logger context. +func (c Context) Uint64(key string, i uint64) Context { + c.l.context = enc.AppendUint64(enc.AppendKey(c.l.context, key), i) + return c +} + +// Uints64 adds the field key with i as a []uint64 to the logger context. +func (c Context) Uints64(key string, i []uint64) Context { + c.l.context = enc.AppendUints64(enc.AppendKey(c.l.context, key), i) + return c +} + +// Float32 adds the field key with f as a float32 to the logger context. +func (c Context) Float32(key string, f float32) Context { + c.l.context = enc.AppendFloat32(enc.AppendKey(c.l.context, key), f) + return c +} + +// Floats32 adds the field key with f as a []float32 to the logger context. +func (c Context) Floats32(key string, f []float32) Context { + c.l.context = enc.AppendFloats32(enc.AppendKey(c.l.context, key), f) + return c +} + +// Float64 adds the field key with f as a float64 to the logger context. +func (c Context) Float64(key string, f float64) Context { + c.l.context = enc.AppendFloat64(enc.AppendKey(c.l.context, key), f) + return c +} + +// Floats64 adds the field key with f as a []float64 to the logger context. +func (c Context) Floats64(key string, f []float64) Context { + c.l.context = enc.AppendFloats64(enc.AppendKey(c.l.context, key), f) + return c +} + +type timestampHook struct{} + +func (ts timestampHook) Run(e *Event, level Level, msg string) { + e.Timestamp() +} + +var th = timestampHook{} + +// Timestamp adds the current local time as UNIX timestamp to the logger context with the "time" key. +// To customize the key name, change zerolog.TimestampFieldName. +// +// NOTE: It won't dedupe the "time" key if the *Context has one already. +func (c Context) Timestamp() Context { + c.l = c.l.Hook(th) + return c +} + +// Time adds the field key with t formated as string using zerolog.TimeFieldFormat. +func (c Context) Time(key string, t time.Time) Context { + c.l.context = enc.AppendTime(enc.AppendKey(c.l.context, key), t, TimeFieldFormat) + return c +} + +// Times adds the field key with t formated as string using zerolog.TimeFieldFormat. +func (c Context) Times(key string, t []time.Time) Context { + c.l.context = enc.AppendTimes(enc.AppendKey(c.l.context, key), t, TimeFieldFormat) + return c +} + +// Dur adds the fields key with d divided by unit and stored as a float. +func (c Context) Dur(key string, d time.Duration) Context { + c.l.context = enc.AppendDuration(enc.AppendKey(c.l.context, key), d, DurationFieldUnit, DurationFieldInteger) + return c +} + +// Durs adds the fields key with d divided by unit and stored as a float. +func (c Context) Durs(key string, d []time.Duration) Context { + c.l.context = enc.AppendDurations(enc.AppendKey(c.l.context, key), d, DurationFieldUnit, DurationFieldInteger) + return c +} + +// Interface adds the field key with obj marshaled using reflection. +func (c Context) Interface(key string, i interface{}) Context { + c.l.context = enc.AppendInterface(enc.AppendKey(c.l.context, key), i) + return c +} + +type callerHook struct { + callerSkipFrameCount int +} + +func newCallerHook(skipFrameCount int) callerHook { + return callerHook{callerSkipFrameCount: skipFrameCount} +} + +func (ch callerHook) Run(e *Event, level Level, msg string) { + switch ch.callerSkipFrameCount { + case useGlobalSkipFrameCount: + // Extra frames to skip (added by hook infra). + e.caller(CallerSkipFrameCount + contextCallerSkipFrameCount) + default: + // Extra frames to skip (added by hook infra). + e.caller(ch.callerSkipFrameCount + contextCallerSkipFrameCount) + } +} + +// useGlobalSkipFrameCount acts as a flag to informat callerHook.Run +// to use the global CallerSkipFrameCount. +const useGlobalSkipFrameCount = math.MinInt32 + +// ch is the default caller hook using the global CallerSkipFrameCount. +var ch = newCallerHook(useGlobalSkipFrameCount) + +// Caller adds the file:line of the caller with the zerolog.CallerFieldName key. +func (c Context) Caller() Context { + c.l = c.l.Hook(ch) + return c +} + +// CallerWithSkipFrameCount adds the file:line of the caller with the zerolog.CallerFieldName key. +// The specified skipFrameCount int will override the global CallerSkipFrameCount for this context's respective logger. +// If set to -1 the global CallerSkipFrameCount will be used. +func (c Context) CallerWithSkipFrameCount(skipFrameCount int) Context { + c.l = c.l.Hook(newCallerHook(skipFrameCount)) + return c +} + +type stackTraceHook struct{} + +func (sh stackTraceHook) Run(e *Event, level Level, msg string) { + e.Stack() +} + +var sh = stackTraceHook{} + +// Stack enables stack trace printing for the error passed to Err(). +func (c Context) Stack() Context { + c.l = c.l.Hook(sh) + return c +} + +// IPAddr adds IPv4 or IPv6 Address to the context +func (c Context) IPAddr(key string, ip net.IP) Context { + c.l.context = enc.AppendIPAddr(enc.AppendKey(c.l.context, key), ip) + return c +} + +// IPPrefix adds IPv4 or IPv6 Prefix (address and mask) to the context +func (c Context) IPPrefix(key string, pfx net.IPNet) Context { + c.l.context = enc.AppendIPPrefix(enc.AppendKey(c.l.context, key), pfx) + return c +} + +// MACAddr adds MAC address to the context +func (c Context) MACAddr(key string, ha net.HardwareAddr) Context { + c.l.context = enc.AppendMACAddr(enc.AppendKey(c.l.context, key), ha) + return c +} diff --git a/vendor/github.com/rs/zerolog/ctx.go b/vendor/github.com/rs/zerolog/ctx.go new file mode 100644 index 000000000..ce18a32cb --- /dev/null +++ b/vendor/github.com/rs/zerolog/ctx.go @@ -0,0 +1,48 @@ +package zerolog + +import ( + "context" +) + +var disabledLogger *Logger + +func init() { + SetGlobalLevel(TraceLevel) + l := Nop() + disabledLogger = &l +} + +type ctxKey struct{} + +// WithContext returns a copy of ctx with l associated. If an instance of Logger +// is already in the context, the context is not updated. +// +// For instance, to add a field to an existing logger in the context, use this +// notation: +// +// ctx := r.Context() +// l := zerolog.Ctx(ctx) +// l.UpdateContext(func(c Context) Context { +// return c.Str("bar", "baz") +// }) +func (l *Logger) WithContext(ctx context.Context) context.Context { + if lp, ok := ctx.Value(ctxKey{}).(*Logger); ok { + if lp == l { + // Do not store same logger. + return ctx + } + } else if l.level == Disabled { + // Do not store disabled logger. + return ctx + } + return context.WithValue(ctx, ctxKey{}, l) +} + +// Ctx returns the Logger associated with the ctx. If no logger +// is associated, a disabled logger is returned. +func Ctx(ctx context.Context) *Logger { + if l, ok := ctx.Value(ctxKey{}).(*Logger); ok { + return l + } + return disabledLogger +} diff --git a/vendor/github.com/rs/zerolog/encoder.go b/vendor/github.com/rs/zerolog/encoder.go new file mode 100644 index 000000000..09b24e80c --- /dev/null +++ b/vendor/github.com/rs/zerolog/encoder.go @@ -0,0 +1,56 @@ +package zerolog + +import ( + "net" + "time" +) + +type encoder interface { + AppendArrayDelim(dst []byte) []byte + AppendArrayEnd(dst []byte) []byte + AppendArrayStart(dst []byte) []byte + AppendBeginMarker(dst []byte) []byte + AppendBool(dst []byte, val bool) []byte + AppendBools(dst []byte, vals []bool) []byte + AppendBytes(dst, s []byte) []byte + AppendDuration(dst []byte, d time.Duration, unit time.Duration, useInt bool) []byte + AppendDurations(dst []byte, vals []time.Duration, unit time.Duration, useInt bool) []byte + AppendEndMarker(dst []byte) []byte + AppendFloat32(dst []byte, val float32) []byte + AppendFloat64(dst []byte, val float64) []byte + AppendFloats32(dst []byte, vals []float32) []byte + AppendFloats64(dst []byte, vals []float64) []byte + AppendHex(dst, s []byte) []byte + AppendIPAddr(dst []byte, ip net.IP) []byte + AppendIPPrefix(dst []byte, pfx net.IPNet) []byte + AppendInt(dst []byte, val int) []byte + AppendInt16(dst []byte, val int16) []byte + AppendInt32(dst []byte, val int32) []byte + AppendInt64(dst []byte, val int64) []byte + AppendInt8(dst []byte, val int8) []byte + AppendInterface(dst []byte, i interface{}) []byte + AppendInts(dst []byte, vals []int) []byte + AppendInts16(dst []byte, vals []int16) []byte + AppendInts32(dst []byte, vals []int32) []byte + AppendInts64(dst []byte, vals []int64) []byte + AppendInts8(dst []byte, vals []int8) []byte + AppendKey(dst []byte, key string) []byte + AppendLineBreak(dst []byte) []byte + AppendMACAddr(dst []byte, ha net.HardwareAddr) []byte + AppendNil(dst []byte) []byte + AppendObjectData(dst []byte, o []byte) []byte + AppendString(dst []byte, s string) []byte + AppendStrings(dst []byte, vals []string) []byte + AppendTime(dst []byte, t time.Time, format string) []byte + AppendTimes(dst []byte, vals []time.Time, format string) []byte + AppendUint(dst []byte, val uint) []byte + AppendUint16(dst []byte, val uint16) []byte + AppendUint32(dst []byte, val uint32) []byte + AppendUint64(dst []byte, val uint64) []byte + AppendUint8(dst []byte, val uint8) []byte + AppendUints(dst []byte, vals []uint) []byte + AppendUints16(dst []byte, vals []uint16) []byte + AppendUints32(dst []byte, vals []uint32) []byte + AppendUints64(dst []byte, vals []uint64) []byte + AppendUints8(dst []byte, vals []uint8) []byte +} diff --git a/vendor/github.com/rs/zerolog/encoder_cbor.go b/vendor/github.com/rs/zerolog/encoder_cbor.go new file mode 100644 index 000000000..f8d3fe9e7 --- /dev/null +++ b/vendor/github.com/rs/zerolog/encoder_cbor.go @@ -0,0 +1,35 @@ +// +build binary_log + +package zerolog + +// This file contains bindings to do binary encoding. + +import ( + "github.com/rs/zerolog/internal/cbor" +) + +var ( + _ encoder = (*cbor.Encoder)(nil) + + enc = cbor.Encoder{} +) + +func appendJSON(dst []byte, j []byte) []byte { + return cbor.AppendEmbeddedJSON(dst, j) +} + +// decodeIfBinaryToString - converts a binary formatted log msg to a +// JSON formatted String Log message. +func decodeIfBinaryToString(in []byte) string { + return cbor.DecodeIfBinaryToString(in) +} + +func decodeObjectToStr(in []byte) string { + return cbor.DecodeObjectToStr(in) +} + +// decodeIfBinaryToBytes - converts a binary formatted log msg to a +// JSON formatted Bytes Log message. +func decodeIfBinaryToBytes(in []byte) []byte { + return cbor.DecodeIfBinaryToBytes(in) +} diff --git a/vendor/github.com/rs/zerolog/encoder_json.go b/vendor/github.com/rs/zerolog/encoder_json.go new file mode 100644 index 000000000..fe580f5f6 --- /dev/null +++ b/vendor/github.com/rs/zerolog/encoder_json.go @@ -0,0 +1,32 @@ +// +build !binary_log + +package zerolog + +// encoder_json.go file contains bindings to generate +// JSON encoded byte stream. + +import ( + "github.com/rs/zerolog/internal/json" +) + +var ( + _ encoder = (*json.Encoder)(nil) + + enc = json.Encoder{} +) + +func appendJSON(dst []byte, j []byte) []byte { + return append(dst, j...) +} + +func decodeIfBinaryToString(in []byte) string { + return string(in) +} + +func decodeObjectToStr(in []byte) string { + return string(in) +} + +func decodeIfBinaryToBytes(in []byte) []byte { + return in +} diff --git a/vendor/github.com/rs/zerolog/event.go b/vendor/github.com/rs/zerolog/event.go new file mode 100644 index 000000000..224799c8b --- /dev/null +++ b/vendor/github.com/rs/zerolog/event.go @@ -0,0 +1,721 @@ +package zerolog + +import ( + "fmt" + "net" + "os" + "runtime" + "sync" + "time" +) + +var eventPool = &sync.Pool{ + New: func() interface{} { + return &Event{ + buf: make([]byte, 0, 500), + } + }, +} + +// Event represents a log event. It is instanced by one of the level method of +// Logger and finalized by the Msg or Msgf method. +type Event struct { + buf []byte + w LevelWriter + level Level + done func(msg string) + stack bool // enable error stack trace + ch []Hook // hooks from context +} + +func putEvent(e *Event) { + // Proper usage of a sync.Pool requires each entry to have approximately + // the same memory cost. To obtain this property when the stored type + // contains a variably-sized buffer, we add a hard limit on the maximum buffer + // to place back in the pool. + // + // See https://golang.org/issue/23199 + const maxSize = 1 << 16 // 64KiB + if cap(e.buf) > maxSize { + return + } + eventPool.Put(e) +} + +// LogObjectMarshaler provides a strongly-typed and encoding-agnostic interface +// to be implemented by types used with Event/Context's Object methods. +type LogObjectMarshaler interface { + MarshalZerologObject(e *Event) +} + +// LogArrayMarshaler provides a strongly-typed and encoding-agnostic interface +// to be implemented by types used with Event/Context's Array methods. +type LogArrayMarshaler interface { + MarshalZerologArray(a *Array) +} + +func newEvent(w LevelWriter, level Level) *Event { + e := eventPool.Get().(*Event) + e.buf = e.buf[:0] + e.ch = nil + e.buf = enc.AppendBeginMarker(e.buf) + e.w = w + e.level = level + return e +} + +func (e *Event) write() (err error) { + if e == nil { + return nil + } + if e.level != Disabled { + e.buf = enc.AppendEndMarker(e.buf) + e.buf = enc.AppendLineBreak(e.buf) + if e.w != nil { + _, err = e.w.WriteLevel(e.level, e.buf) + } + } + putEvent(e) + return +} + +// Enabled return false if the *Event is going to be filtered out by +// log level or sampling. +func (e *Event) Enabled() bool { + return e != nil && e.level != Disabled +} + +// Discard disables the event so Msg(f) won't print it. +func (e *Event) Discard() *Event { + if e == nil { + return e + } + e.level = Disabled + return nil +} + +// Msg sends the *Event with msg added as the message field if not empty. +// +// NOTICE: once this method is called, the *Event should be disposed. +// Calling Msg twice can have unexpected result. +func (e *Event) Msg(msg string) { + if e == nil { + return + } + e.msg(msg) +} + +// Send is equivalent to calling Msg(""). +// +// NOTICE: once this method is called, the *Event should be disposed. +func (e *Event) Send() { + if e == nil { + return + } + e.msg("") +} + +// Msgf sends the event with formatted msg added as the message field if not empty. +// +// NOTICE: once this method is called, the *Event should be disposed. +// Calling Msgf twice can have unexpected result. +func (e *Event) Msgf(format string, v ...interface{}) { + if e == nil { + return + } + e.msg(fmt.Sprintf(format, v...)) +} + +func (e *Event) msg(msg string) { + for _, hook := range e.ch { + hook.Run(e, e.level, msg) + } + if msg != "" { + e.buf = enc.AppendString(enc.AppendKey(e.buf, MessageFieldName), msg) + } + if e.done != nil { + defer e.done(msg) + } + if err := e.write(); err != nil { + if ErrorHandler != nil { + ErrorHandler(err) + } else { + fmt.Fprintf(os.Stderr, "zerolog: could not write event: %v\n", err) + } + } +} + +// Fields is a helper function to use a map to set fields using type assertion. +func (e *Event) Fields(fields map[string]interface{}) *Event { + if e == nil { + return e + } + e.buf = appendFields(e.buf, fields) + return e +} + +// Dict adds the field key with a dict to the event context. +// Use zerolog.Dict() to create the dictionary. +func (e *Event) Dict(key string, dict *Event) *Event { + if e == nil { + return e + } + dict.buf = enc.AppendEndMarker(dict.buf) + e.buf = append(enc.AppendKey(e.buf, key), dict.buf...) + putEvent(dict) + return e +} + +// Dict creates an Event to be used with the *Event.Dict method. +// Call usual field methods like Str, Int etc to add fields to this +// event and give it as argument the *Event.Dict method. +func Dict() *Event { + return newEvent(nil, 0) +} + +// Array adds the field key with an array to the event context. +// Use zerolog.Arr() to create the array or pass a type that +// implement the LogArrayMarshaler interface. +func (e *Event) Array(key string, arr LogArrayMarshaler) *Event { + if e == nil { + return e + } + e.buf = enc.AppendKey(e.buf, key) + var a *Array + if aa, ok := arr.(*Array); ok { + a = aa + } else { + a = Arr() + arr.MarshalZerologArray(a) + } + e.buf = a.write(e.buf) + return e +} + +func (e *Event) appendObject(obj LogObjectMarshaler) { + e.buf = enc.AppendBeginMarker(e.buf) + obj.MarshalZerologObject(e) + e.buf = enc.AppendEndMarker(e.buf) +} + +// Object marshals an object that implement the LogObjectMarshaler interface. +func (e *Event) Object(key string, obj LogObjectMarshaler) *Event { + if e == nil { + return e + } + e.buf = enc.AppendKey(e.buf, key) + e.appendObject(obj) + return e +} + +// EmbedObject marshals an object that implement the LogObjectMarshaler interface. +func (e *Event) EmbedObject(obj LogObjectMarshaler) *Event { + if e == nil { + return e + } + obj.MarshalZerologObject(e) + return e +} + +// Str adds the field key with val as a string to the *Event context. +func (e *Event) Str(key, val string) *Event { + if e == nil { + return e + } + e.buf = enc.AppendString(enc.AppendKey(e.buf, key), val) + return e +} + +// Strs adds the field key with vals as a []string to the *Event context. +func (e *Event) Strs(key string, vals []string) *Event { + if e == nil { + return e + } + e.buf = enc.AppendStrings(enc.AppendKey(e.buf, key), vals) + return e +} + +// Bytes adds the field key with val as a string to the *Event context. +// +// Runes outside of normal ASCII ranges will be hex-encoded in the resulting +// JSON. +func (e *Event) Bytes(key string, val []byte) *Event { + if e == nil { + return e + } + e.buf = enc.AppendBytes(enc.AppendKey(e.buf, key), val) + return e +} + +// Hex adds the field key with val as a hex string to the *Event context. +func (e *Event) Hex(key string, val []byte) *Event { + if e == nil { + return e + } + e.buf = enc.AppendHex(enc.AppendKey(e.buf, key), val) + return e +} + +// RawJSON adds already encoded JSON to the log line under key. +// +// No sanity check is performed on b; it must not contain carriage returns and +// be valid JSON. +func (e *Event) RawJSON(key string, b []byte) *Event { + if e == nil { + return e + } + e.buf = appendJSON(enc.AppendKey(e.buf, key), b) + return e +} + +// AnErr adds the field key with serialized err to the *Event context. +// If err is nil, no field is added. +func (e *Event) AnErr(key string, err error) *Event { + if e == nil { + return e + } + switch m := ErrorMarshalFunc(err).(type) { + case nil: + return e + case LogObjectMarshaler: + return e.Object(key, m) + case error: + if m == nil || isNilValue(m) { + return e + } else { + return e.Str(key, m.Error()) + } + case string: + return e.Str(key, m) + default: + return e.Interface(key, m) + } +} + +// Errs adds the field key with errs as an array of serialized errors to the +// *Event context. +func (e *Event) Errs(key string, errs []error) *Event { + if e == nil { + return e + } + arr := Arr() + for _, err := range errs { + switch m := ErrorMarshalFunc(err).(type) { + case LogObjectMarshaler: + arr = arr.Object(m) + case error: + arr = arr.Err(m) + case string: + arr = arr.Str(m) + default: + arr = arr.Interface(m) + } + } + + return e.Array(key, arr) +} + +// Err adds the field "error" with serialized err to the *Event context. +// If err is nil, no field is added. +// To customize the key name, change zerolog.ErrorFieldName. +// +// To customize the key name, change zerolog.ErrorFieldName. +// +// If Stack() has been called before and zerolog.ErrorStackMarshaler is defined, +// the err is passed to ErrorStackMarshaler and the result is appended to the +// zerolog.ErrorStackFieldName. +func (e *Event) Err(err error) *Event { + if e == nil { + return e + } + if e.stack && ErrorStackMarshaler != nil { + switch m := ErrorStackMarshaler(err).(type) { + case nil: + case LogObjectMarshaler: + e.Object(ErrorStackFieldName, m) + case error: + if m != nil && !isNilValue(m) { + e.Str(ErrorStackFieldName, m.Error()) + } + case string: + e.Str(ErrorStackFieldName, m) + default: + e.Interface(ErrorStackFieldName, m) + } + } + return e.AnErr(ErrorFieldName, err) +} + +// Stack enables stack trace printing for the error passed to Err(). +// +// ErrorStackMarshaler must be set for this method to do something. +func (e *Event) Stack() *Event { + if e != nil { + e.stack = true + } + return e +} + +// Bool adds the field key with val as a bool to the *Event context. +func (e *Event) Bool(key string, b bool) *Event { + if e == nil { + return e + } + e.buf = enc.AppendBool(enc.AppendKey(e.buf, key), b) + return e +} + +// Bools adds the field key with val as a []bool to the *Event context. +func (e *Event) Bools(key string, b []bool) *Event { + if e == nil { + return e + } + e.buf = enc.AppendBools(enc.AppendKey(e.buf, key), b) + return e +} + +// Int adds the field key with i as a int to the *Event context. +func (e *Event) Int(key string, i int) *Event { + if e == nil { + return e + } + e.buf = enc.AppendInt(enc.AppendKey(e.buf, key), i) + return e +} + +// Ints adds the field key with i as a []int to the *Event context. +func (e *Event) Ints(key string, i []int) *Event { + if e == nil { + return e + } + e.buf = enc.AppendInts(enc.AppendKey(e.buf, key), i) + return e +} + +// Int8 adds the field key with i as a int8 to the *Event context. +func (e *Event) Int8(key string, i int8) *Event { + if e == nil { + return e + } + e.buf = enc.AppendInt8(enc.AppendKey(e.buf, key), i) + return e +} + +// Ints8 adds the field key with i as a []int8 to the *Event context. +func (e *Event) Ints8(key string, i []int8) *Event { + if e == nil { + return e + } + e.buf = enc.AppendInts8(enc.AppendKey(e.buf, key), i) + return e +} + +// Int16 adds the field key with i as a int16 to the *Event context. +func (e *Event) Int16(key string, i int16) *Event { + if e == nil { + return e + } + e.buf = enc.AppendInt16(enc.AppendKey(e.buf, key), i) + return e +} + +// Ints16 adds the field key with i as a []int16 to the *Event context. +func (e *Event) Ints16(key string, i []int16) *Event { + if e == nil { + return e + } + e.buf = enc.AppendInts16(enc.AppendKey(e.buf, key), i) + return e +} + +// Int32 adds the field key with i as a int32 to the *Event context. +func (e *Event) Int32(key string, i int32) *Event { + if e == nil { + return e + } + e.buf = enc.AppendInt32(enc.AppendKey(e.buf, key), i) + return e +} + +// Ints32 adds the field key with i as a []int32 to the *Event context. +func (e *Event) Ints32(key string, i []int32) *Event { + if e == nil { + return e + } + e.buf = enc.AppendInts32(enc.AppendKey(e.buf, key), i) + return e +} + +// Int64 adds the field key with i as a int64 to the *Event context. +func (e *Event) Int64(key string, i int64) *Event { + if e == nil { + return e + } + e.buf = enc.AppendInt64(enc.AppendKey(e.buf, key), i) + return e +} + +// Ints64 adds the field key with i as a []int64 to the *Event context. +func (e *Event) Ints64(key string, i []int64) *Event { + if e == nil { + return e + } + e.buf = enc.AppendInts64(enc.AppendKey(e.buf, key), i) + return e +} + +// Uint adds the field key with i as a uint to the *Event context. +func (e *Event) Uint(key string, i uint) *Event { + if e == nil { + return e + } + e.buf = enc.AppendUint(enc.AppendKey(e.buf, key), i) + return e +} + +// Uints adds the field key with i as a []int to the *Event context. +func (e *Event) Uints(key string, i []uint) *Event { + if e == nil { + return e + } + e.buf = enc.AppendUints(enc.AppendKey(e.buf, key), i) + return e +} + +// Uint8 adds the field key with i as a uint8 to the *Event context. +func (e *Event) Uint8(key string, i uint8) *Event { + if e == nil { + return e + } + e.buf = enc.AppendUint8(enc.AppendKey(e.buf, key), i) + return e +} + +// Uints8 adds the field key with i as a []int8 to the *Event context. +func (e *Event) Uints8(key string, i []uint8) *Event { + if e == nil { + return e + } + e.buf = enc.AppendUints8(enc.AppendKey(e.buf, key), i) + return e +} + +// Uint16 adds the field key with i as a uint16 to the *Event context. +func (e *Event) Uint16(key string, i uint16) *Event { + if e == nil { + return e + } + e.buf = enc.AppendUint16(enc.AppendKey(e.buf, key), i) + return e +} + +// Uints16 adds the field key with i as a []int16 to the *Event context. +func (e *Event) Uints16(key string, i []uint16) *Event { + if e == nil { + return e + } + e.buf = enc.AppendUints16(enc.AppendKey(e.buf, key), i) + return e +} + +// Uint32 adds the field key with i as a uint32 to the *Event context. +func (e *Event) Uint32(key string, i uint32) *Event { + if e == nil { + return e + } + e.buf = enc.AppendUint32(enc.AppendKey(e.buf, key), i) + return e +} + +// Uints32 adds the field key with i as a []int32 to the *Event context. +func (e *Event) Uints32(key string, i []uint32) *Event { + if e == nil { + return e + } + e.buf = enc.AppendUints32(enc.AppendKey(e.buf, key), i) + return e +} + +// Uint64 adds the field key with i as a uint64 to the *Event context. +func (e *Event) Uint64(key string, i uint64) *Event { + if e == nil { + return e + } + e.buf = enc.AppendUint64(enc.AppendKey(e.buf, key), i) + return e +} + +// Uints64 adds the field key with i as a []int64 to the *Event context. +func (e *Event) Uints64(key string, i []uint64) *Event { + if e == nil { + return e + } + e.buf = enc.AppendUints64(enc.AppendKey(e.buf, key), i) + return e +} + +// Float32 adds the field key with f as a float32 to the *Event context. +func (e *Event) Float32(key string, f float32) *Event { + if e == nil { + return e + } + e.buf = enc.AppendFloat32(enc.AppendKey(e.buf, key), f) + return e +} + +// Floats32 adds the field key with f as a []float32 to the *Event context. +func (e *Event) Floats32(key string, f []float32) *Event { + if e == nil { + return e + } + e.buf = enc.AppendFloats32(enc.AppendKey(e.buf, key), f) + return e +} + +// Float64 adds the field key with f as a float64 to the *Event context. +func (e *Event) Float64(key string, f float64) *Event { + if e == nil { + return e + } + e.buf = enc.AppendFloat64(enc.AppendKey(e.buf, key), f) + return e +} + +// Floats64 adds the field key with f as a []float64 to the *Event context. +func (e *Event) Floats64(key string, f []float64) *Event { + if e == nil { + return e + } + e.buf = enc.AppendFloats64(enc.AppendKey(e.buf, key), f) + return e +} + +// Timestamp adds the current local time as UNIX timestamp to the *Event context with the "time" key. +// To customize the key name, change zerolog.TimestampFieldName. +// +// NOTE: It won't dedupe the "time" key if the *Event (or *Context) has one +// already. +func (e *Event) Timestamp() *Event { + if e == nil { + return e + } + e.buf = enc.AppendTime(enc.AppendKey(e.buf, TimestampFieldName), TimestampFunc(), TimeFieldFormat) + return e +} + +// Time adds the field key with t formated as string using zerolog.TimeFieldFormat. +func (e *Event) Time(key string, t time.Time) *Event { + if e == nil { + return e + } + e.buf = enc.AppendTime(enc.AppendKey(e.buf, key), t, TimeFieldFormat) + return e +} + +// Times adds the field key with t formated as string using zerolog.TimeFieldFormat. +func (e *Event) Times(key string, t []time.Time) *Event { + if e == nil { + return e + } + e.buf = enc.AppendTimes(enc.AppendKey(e.buf, key), t, TimeFieldFormat) + return e +} + +// Dur adds the field key with duration d stored as zerolog.DurationFieldUnit. +// If zerolog.DurationFieldInteger is true, durations are rendered as integer +// instead of float. +func (e *Event) Dur(key string, d time.Duration) *Event { + if e == nil { + return e + } + e.buf = enc.AppendDuration(enc.AppendKey(e.buf, key), d, DurationFieldUnit, DurationFieldInteger) + return e +} + +// Durs adds the field key with duration d stored as zerolog.DurationFieldUnit. +// If zerolog.DurationFieldInteger is true, durations are rendered as integer +// instead of float. +func (e *Event) Durs(key string, d []time.Duration) *Event { + if e == nil { + return e + } + e.buf = enc.AppendDurations(enc.AppendKey(e.buf, key), d, DurationFieldUnit, DurationFieldInteger) + return e +} + +// TimeDiff adds the field key with positive duration between time t and start. +// If time t is not greater than start, duration will be 0. +// Duration format follows the same principle as Dur(). +func (e *Event) TimeDiff(key string, t time.Time, start time.Time) *Event { + if e == nil { + return e + } + var d time.Duration + if t.After(start) { + d = t.Sub(start) + } + e.buf = enc.AppendDuration(enc.AppendKey(e.buf, key), d, DurationFieldUnit, DurationFieldInteger) + return e +} + +// Interface adds the field key with i marshaled using reflection. +func (e *Event) Interface(key string, i interface{}) *Event { + if e == nil { + return e + } + if obj, ok := i.(LogObjectMarshaler); ok { + return e.Object(key, obj) + } + e.buf = enc.AppendInterface(enc.AppendKey(e.buf, key), i) + return e +} + +// Caller adds the file:line of the caller with the zerolog.CallerFieldName key. +// The argument skip is the number of stack frames to ascend +// Skip If not passed, use the global variable CallerSkipFrameCount +func (e *Event) Caller(skip ...int) *Event { + sk := CallerSkipFrameCount + if len(skip) > 0 { + sk = skip[0] + CallerSkipFrameCount + } + return e.caller(sk) +} + +func (e *Event) caller(skip int) *Event { + if e == nil { + return e + } + _, file, line, ok := runtime.Caller(skip) + if !ok { + return e + } + e.buf = enc.AppendString(enc.AppendKey(e.buf, CallerFieldName), CallerMarshalFunc(file, line)) + return e +} + +// IPAddr adds IPv4 or IPv6 Address to the event +func (e *Event) IPAddr(key string, ip net.IP) *Event { + if e == nil { + return e + } + e.buf = enc.AppendIPAddr(enc.AppendKey(e.buf, key), ip) + return e +} + +// IPPrefix adds IPv4 or IPv6 Prefix (address and mask) to the event +func (e *Event) IPPrefix(key string, pfx net.IPNet) *Event { + if e == nil { + return e + } + e.buf = enc.AppendIPPrefix(enc.AppendKey(e.buf, key), pfx) + return e +} + +// MACAddr adds MAC address to the event +func (e *Event) MACAddr(key string, ha net.HardwareAddr) *Event { + if e == nil { + return e + } + e.buf = enc.AppendMACAddr(enc.AppendKey(e.buf, key), ha) + return e +} diff --git a/vendor/github.com/rs/zerolog/fields.go b/vendor/github.com/rs/zerolog/fields.go new file mode 100644 index 000000000..cf3c3e918 --- /dev/null +++ b/vendor/github.com/rs/zerolog/fields.go @@ -0,0 +1,253 @@ +package zerolog + +import ( + "net" + "sort" + "time" + "unsafe" +) + +func isNilValue(i interface{}) bool { + return (*[2]uintptr)(unsafe.Pointer(&i))[1] == 0 +} + +func appendFields(dst []byte, fields map[string]interface{}) []byte { + keys := make([]string, 0, len(fields)) + for key := range fields { + keys = append(keys, key) + } + sort.Strings(keys) + for _, key := range keys { + dst = enc.AppendKey(dst, key) + val := fields[key] + if val, ok := val.(LogObjectMarshaler); ok { + e := newEvent(nil, 0) + e.buf = e.buf[:0] + e.appendObject(val) + dst = append(dst, e.buf...) + putEvent(e) + continue + } + switch val := val.(type) { + case string: + dst = enc.AppendString(dst, val) + case []byte: + dst = enc.AppendBytes(dst, val) + case error: + switch m := ErrorMarshalFunc(val).(type) { + case LogObjectMarshaler: + e := newEvent(nil, 0) + e.buf = e.buf[:0] + e.appendObject(m) + dst = append(dst, e.buf...) + putEvent(e) + case error: + if m == nil || isNilValue(m) { + dst = enc.AppendNil(dst) + } else { + dst = enc.AppendString(dst, m.Error()) + } + case string: + dst = enc.AppendString(dst, m) + default: + dst = enc.AppendInterface(dst, m) + } + case []error: + dst = enc.AppendArrayStart(dst) + for i, err := range val { + switch m := ErrorMarshalFunc(err).(type) { + case LogObjectMarshaler: + e := newEvent(nil, 0) + e.buf = e.buf[:0] + e.appendObject(m) + dst = append(dst, e.buf...) + putEvent(e) + case error: + if m == nil || isNilValue(m) { + dst = enc.AppendNil(dst) + } else { + dst = enc.AppendString(dst, m.Error()) + } + case string: + dst = enc.AppendString(dst, m) + default: + dst = enc.AppendInterface(dst, m) + } + + if i < (len(val) - 1) { + enc.AppendArrayDelim(dst) + } + } + dst = enc.AppendArrayEnd(dst) + case bool: + dst = enc.AppendBool(dst, val) + case int: + dst = enc.AppendInt(dst, val) + case int8: + dst = enc.AppendInt8(dst, val) + case int16: + dst = enc.AppendInt16(dst, val) + case int32: + dst = enc.AppendInt32(dst, val) + case int64: + dst = enc.AppendInt64(dst, val) + case uint: + dst = enc.AppendUint(dst, val) + case uint8: + dst = enc.AppendUint8(dst, val) + case uint16: + dst = enc.AppendUint16(dst, val) + case uint32: + dst = enc.AppendUint32(dst, val) + case uint64: + dst = enc.AppendUint64(dst, val) + case float32: + dst = enc.AppendFloat32(dst, val) + case float64: + dst = enc.AppendFloat64(dst, val) + case time.Time: + dst = enc.AppendTime(dst, val, TimeFieldFormat) + case time.Duration: + dst = enc.AppendDuration(dst, val, DurationFieldUnit, DurationFieldInteger) + case *string: + if val != nil { + dst = enc.AppendString(dst, *val) + } else { + dst = enc.AppendNil(dst) + } + case *bool: + if val != nil { + dst = enc.AppendBool(dst, *val) + } else { + dst = enc.AppendNil(dst) + } + case *int: + if val != nil { + dst = enc.AppendInt(dst, *val) + } else { + dst = enc.AppendNil(dst) + } + case *int8: + if val != nil { + dst = enc.AppendInt8(dst, *val) + } else { + dst = enc.AppendNil(dst) + } + case *int16: + if val != nil { + dst = enc.AppendInt16(dst, *val) + } else { + dst = enc.AppendNil(dst) + } + case *int32: + if val != nil { + dst = enc.AppendInt32(dst, *val) + } else { + dst = enc.AppendNil(dst) + } + case *int64: + if val != nil { + dst = enc.AppendInt64(dst, *val) + } else { + dst = enc.AppendNil(dst) + } + case *uint: + if val != nil { + dst = enc.AppendUint(dst, *val) + } else { + dst = enc.AppendNil(dst) + } + case *uint8: + if val != nil { + dst = enc.AppendUint8(dst, *val) + } else { + dst = enc.AppendNil(dst) + } + case *uint16: + if val != nil { + dst = enc.AppendUint16(dst, *val) + } else { + dst = enc.AppendNil(dst) + } + case *uint32: + if val != nil { + dst = enc.AppendUint32(dst, *val) + } else { + dst = enc.AppendNil(dst) + } + case *uint64: + if val != nil { + dst = enc.AppendUint64(dst, *val) + } else { + dst = enc.AppendNil(dst) + } + case *float32: + if val != nil { + dst = enc.AppendFloat32(dst, *val) + } else { + dst = enc.AppendNil(dst) + } + case *float64: + if val != nil { + dst = enc.AppendFloat64(dst, *val) + } else { + dst = enc.AppendNil(dst) + } + case *time.Time: + if val != nil { + dst = enc.AppendTime(dst, *val, TimeFieldFormat) + } else { + dst = enc.AppendNil(dst) + } + case *time.Duration: + if val != nil { + dst = enc.AppendDuration(dst, *val, DurationFieldUnit, DurationFieldInteger) + } else { + dst = enc.AppendNil(dst) + } + case []string: + dst = enc.AppendStrings(dst, val) + case []bool: + dst = enc.AppendBools(dst, val) + case []int: + dst = enc.AppendInts(dst, val) + case []int8: + dst = enc.AppendInts8(dst, val) + case []int16: + dst = enc.AppendInts16(dst, val) + case []int32: + dst = enc.AppendInts32(dst, val) + case []int64: + dst = enc.AppendInts64(dst, val) + case []uint: + dst = enc.AppendUints(dst, val) + // case []uint8: + // dst = enc.AppendUints8(dst, val) + case []uint16: + dst = enc.AppendUints16(dst, val) + case []uint32: + dst = enc.AppendUints32(dst, val) + case []uint64: + dst = enc.AppendUints64(dst, val) + case []float32: + dst = enc.AppendFloats32(dst, val) + case []float64: + dst = enc.AppendFloats64(dst, val) + case []time.Time: + dst = enc.AppendTimes(dst, val, TimeFieldFormat) + case []time.Duration: + dst = enc.AppendDurations(dst, val, DurationFieldUnit, DurationFieldInteger) + case nil: + dst = enc.AppendNil(dst) + case net.IP: + dst = enc.AppendIPAddr(dst, val) + case net.IPNet: + dst = enc.AppendIPPrefix(dst, val) + case net.HardwareAddr: + dst = enc.AppendMACAddr(dst, val) + default: + dst = enc.AppendInterface(dst, val) + } + } + return dst +} diff --git a/vendor/github.com/rs/zerolog/globals.go b/vendor/github.com/rs/zerolog/globals.go new file mode 100644 index 000000000..421429a52 --- /dev/null +++ b/vendor/github.com/rs/zerolog/globals.go @@ -0,0 +1,114 @@ +package zerolog + +import ( + "strconv" + "sync/atomic" + "time" +) + +const ( + // TimeFormatUnix defines a time format that makes time fields to be + // serialized as Unix timestamp integers. + TimeFormatUnix = "" + + // TimeFormatUnixMs defines a time format that makes time fields to be + // serialized as Unix timestamp integers in milliseconds. + TimeFormatUnixMs = "UNIXMS" + + // TimeFormatUnixMicro defines a time format that makes time fields to be + // serialized as Unix timestamp integers in microseconds. + TimeFormatUnixMicro = "UNIXMICRO" +) + +var ( + // TimestampFieldName is the field name used for the timestamp field. + TimestampFieldName = "time" + + // LevelFieldName is the field name used for the level field. + LevelFieldName = "level" + + // LevelFieldMarshalFunc allows customization of global level field marshaling + LevelFieldMarshalFunc = func(l Level) string { + return l.String() + } + + // MessageFieldName is the field name used for the message field. + MessageFieldName = "message" + + // ErrorFieldName is the field name used for error fields. + ErrorFieldName = "error" + + // CallerFieldName is the field name used for caller field. + CallerFieldName = "caller" + + // CallerSkipFrameCount is the number of stack frames to skip to find the caller. + CallerSkipFrameCount = 2 + + // CallerMarshalFunc allows customization of global caller marshaling + CallerMarshalFunc = func(file string, line int) string { + return file + ":" + strconv.Itoa(line) + } + + // ErrorStackFieldName is the field name used for error stacks. + ErrorStackFieldName = "stack" + + // ErrorStackMarshaler extract the stack from err if any. + ErrorStackMarshaler func(err error) interface{} + + // ErrorMarshalFunc allows customization of global error marshaling + ErrorMarshalFunc = func(err error) interface{} { + return err + } + + // TimeFieldFormat defines the time format of the Time field type. If set to + // TimeFormatUnix, TimeFormatUnixMs or TimeFormatUnixMicro, the time is formatted as an UNIX + // timestamp as integer. + TimeFieldFormat = time.RFC3339 + + // TimestampFunc defines the function called to generate a timestamp. + TimestampFunc = time.Now + + // DurationFieldUnit defines the unit for time.Duration type fields added + // using the Dur method. + DurationFieldUnit = time.Millisecond + + // DurationFieldInteger renders Dur fields as integer instead of float if + // set to true. + DurationFieldInteger = false + + // ErrorHandler is called whenever zerolog fails to write an event on its + // output. If not set, an error is printed on the stderr. This handler must + // be thread safe and non-blocking. + ErrorHandler func(err error) +) + +var ( + gLevel = new(int32) + disableSampling = new(int32) +) + +// SetGlobalLevel sets the global override for log level. If this +// values is raised, all Loggers will use at least this value. +// +// To globally disable logs, set GlobalLevel to Disabled. +func SetGlobalLevel(l Level) { + atomic.StoreInt32(gLevel, int32(l)) +} + +// GlobalLevel returns the current global log level +func GlobalLevel() Level { + return Level(atomic.LoadInt32(gLevel)) +} + +// DisableSampling will disable sampling in all Loggers if true. +func DisableSampling(v bool) { + var i int32 + if v { + i = 1 + } + atomic.StoreInt32(disableSampling, i) +} + +func samplingDisabled() bool { + return atomic.LoadInt32(disableSampling) == 1 +} diff --git a/vendor/github.com/rs/zerolog/go.mod b/vendor/github.com/rs/zerolog/go.mod new file mode 100644 index 000000000..8c42ba88f --- /dev/null +++ b/vendor/github.com/rs/zerolog/go.mod @@ -0,0 +1,9 @@ +module github.com/rs/zerolog + +require ( + github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e + github.com/pkg/errors v0.8.1 + github.com/rs/xid v1.2.1 + github.com/zenazn/goji v0.9.0 + golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74 +) diff --git a/vendor/github.com/rs/zerolog/go.sum b/vendor/github.com/rs/zerolog/go.sum new file mode 100644 index 000000000..b14fd2b66 --- /dev/null +++ b/vendor/github.com/rs/zerolog/go.sum @@ -0,0 +1,16 @@ +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/zenazn/goji v0.9.0 h1:RSQQAbXGArQ0dIDEq+PI6WqN6if+5KHu6x2Cx/GXLTQ= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74 h1:4cFkmztxtMslUX2SctSl+blCyXfpzhGOy9LhKAqSMA4= +golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/vendor/github.com/rs/zerolog/go112.go b/vendor/github.com/rs/zerolog/go112.go new file mode 100644 index 000000000..e7b5a1bdc --- /dev/null +++ b/vendor/github.com/rs/zerolog/go112.go @@ -0,0 +1,7 @@ +// +build go1.12 + +package zerolog + +// Since go 1.12, some auto generated init functions are hidden from +// runtime.Caller. +const contextCallerSkipFrameCount = 2 diff --git a/vendor/github.com/rs/zerolog/hook.go b/vendor/github.com/rs/zerolog/hook.go new file mode 100644 index 000000000..ec6effc1a --- /dev/null +++ b/vendor/github.com/rs/zerolog/hook.go @@ -0,0 +1,64 @@ +package zerolog + +// Hook defines an interface to a log hook. +type Hook interface { + // Run runs the hook with the event. + Run(e *Event, level Level, message string) +} + +// HookFunc is an adaptor to allow the use of an ordinary function +// as a Hook. +type HookFunc func(e *Event, level Level, message string) + +// Run implements the Hook interface. +func (h HookFunc) Run(e *Event, level Level, message string) { + h(e, level, message) +} + +// LevelHook applies a different hook for each level. +type LevelHook struct { + NoLevelHook, TraceHook, DebugHook, InfoHook, WarnHook, ErrorHook, FatalHook, PanicHook Hook +} + +// Run implements the Hook interface. +func (h LevelHook) Run(e *Event, level Level, message string) { + switch level { + case TraceLevel: + if h.TraceHook != nil { + h.TraceHook.Run(e, level, message) + } + case DebugLevel: + if h.DebugHook != nil { + h.DebugHook.Run(e, level, message) + } + case InfoLevel: + if h.InfoHook != nil { + h.InfoHook.Run(e, level, message) + } + case WarnLevel: + if h.WarnHook != nil { + h.WarnHook.Run(e, level, message) + } + case ErrorLevel: + if h.ErrorHook != nil { + h.ErrorHook.Run(e, level, message) + } + case FatalLevel: + if h.FatalHook != nil { + h.FatalHook.Run(e, level, message) + } + case PanicLevel: + if h.PanicHook != nil { + h.PanicHook.Run(e, level, message) + } + case NoLevel: + if h.NoLevelHook != nil { + h.NoLevelHook.Run(e, level, message) + } + } +} + +// NewLevelHook returns a new LevelHook. +func NewLevelHook() LevelHook { + return LevelHook{} +} diff --git a/vendor/github.com/rs/zerolog/internal/cbor/README.md b/vendor/github.com/rs/zerolog/internal/cbor/README.md new file mode 100644 index 000000000..92c2e8c7f --- /dev/null +++ b/vendor/github.com/rs/zerolog/internal/cbor/README.md @@ -0,0 +1,56 @@ +## Reference: + CBOR Encoding is described in [RFC7049](https://tools.ietf.org/html/rfc7049) + +## Comparison of JSON vs CBOR + +Two main areas of reduction are: + +1. CPU usage to write a log msg +2. Size (in bytes) of log messages. + + +CPU Usage savings are below: +``` +name JSON time/op CBOR time/op delta +Info-32 15.3ns ± 1% 11.7ns ± 3% -23.78% (p=0.000 n=9+10) +ContextFields-32 16.2ns ± 2% 12.3ns ± 3% -23.97% (p=0.000 n=9+9) +ContextAppend-32 6.70ns ± 0% 6.20ns ± 0% -7.44% (p=0.000 n=9+9) +LogFields-32 66.4ns ± 0% 24.6ns ± 2% -62.89% (p=0.000 n=10+9) +LogArrayObject-32 911ns ±11% 768ns ± 6% -15.64% (p=0.000 n=10+10) +LogFieldType/Floats-32 70.3ns ± 2% 29.5ns ± 1% -57.98% (p=0.000 n=10+10) +LogFieldType/Err-32 14.0ns ± 3% 12.1ns ± 8% -13.20% (p=0.000 n=8+10) +LogFieldType/Dur-32 17.2ns ± 2% 13.1ns ± 1% -24.27% (p=0.000 n=10+9) +LogFieldType/Object-32 54.3ns ±11% 52.3ns ± 7% ~ (p=0.239 n=10+10) +LogFieldType/Ints-32 20.3ns ± 2% 15.1ns ± 2% -25.50% (p=0.000 n=9+10) +LogFieldType/Interfaces-32 642ns ±11% 621ns ± 9% ~ (p=0.118 n=10+10) +LogFieldType/Interface(Objects)-32 635ns ±13% 632ns ± 9% ~ (p=0.592 n=10+10) +LogFieldType/Times-32 294ns ± 0% 27ns ± 1% -90.71% (p=0.000 n=10+9) +LogFieldType/Durs-32 121ns ± 0% 33ns ± 2% -72.44% (p=0.000 n=9+9) +LogFieldType/Interface(Object)-32 56.6ns ± 8% 52.3ns ± 8% -7.54% (p=0.007 n=10+10) +LogFieldType/Errs-32 17.8ns ± 3% 16.1ns ± 2% -9.71% (p=0.000 n=10+9) +LogFieldType/Time-32 40.5ns ± 1% 12.7ns ± 6% -68.66% (p=0.000 n=8+9) +LogFieldType/Bool-32 12.0ns ± 5% 10.2ns ± 2% -15.18% (p=0.000 n=10+8) +LogFieldType/Bools-32 17.2ns ± 2% 12.6ns ± 4% -26.63% (p=0.000 n=10+10) +LogFieldType/Int-32 12.3ns ± 2% 11.2ns ± 4% -9.27% (p=0.000 n=9+10) +LogFieldType/Float-32 16.7ns ± 1% 12.6ns ± 2% -24.42% (p=0.000 n=7+9) +LogFieldType/Str-32 12.7ns ± 7% 11.3ns ± 7% -10.88% (p=0.000 n=10+9) +LogFieldType/Strs-32 20.3ns ± 3% 18.2ns ± 3% -10.25% (p=0.000 n=9+10) +LogFieldType/Interface-32 183ns ±12% 175ns ± 9% ~ (p=0.078 n=10+10) +``` + +Log message size savings is greatly dependent on the number and type of fields in the log message. +Assuming this log message (with an Integer, timestamp and string, in addition to level). + +`{"level":"error","Fault":41650,"time":"2018-04-01T15:18:19-07:00","message":"Some Message"}` + +Two measurements were done for the log file sizes - one without any compression, second +using [compress/zlib](https://golang.org/pkg/compress/zlib/). + +Results for 10,000 log messages: + +| Log Format | Plain File Size (in KB) | Compressed File Size (in KB) | +| :--- | :---: | :---: | +| JSON | 920 | 28 | +| CBOR | 550 | 28 | + +The example used to calculate the above data is available in [Examples](examples). diff --git a/vendor/github.com/rs/zerolog/internal/cbor/base.go b/vendor/github.com/rs/zerolog/internal/cbor/base.go new file mode 100644 index 000000000..58cd0822b --- /dev/null +++ b/vendor/github.com/rs/zerolog/internal/cbor/base.go @@ -0,0 +1,11 @@ +package cbor + +type Encoder struct{} + +// AppendKey adds a key (string) to the binary encoded log message +func (e Encoder) AppendKey(dst []byte, key string) []byte { + if len(dst) < 1 { + dst = e.AppendBeginMarker(dst) + } + return e.AppendString(dst, key) +} \ No newline at end of file diff --git a/vendor/github.com/rs/zerolog/internal/cbor/cbor.go b/vendor/github.com/rs/zerolog/internal/cbor/cbor.go new file mode 100644 index 000000000..969f59159 --- /dev/null +++ b/vendor/github.com/rs/zerolog/internal/cbor/cbor.go @@ -0,0 +1,100 @@ +// Package cbor provides primitives for storing different data +// in the CBOR (binary) format. CBOR is defined in RFC7049. +package cbor + +import "time" + +const ( + majorOffset = 5 + additionalMax = 23 + + // Non Values. + additionalTypeBoolFalse byte = 20 + additionalTypeBoolTrue byte = 21 + additionalTypeNull byte = 22 + + // Integer (+ve and -ve) Sub-types. + additionalTypeIntUint8 byte = 24 + additionalTypeIntUint16 byte = 25 + additionalTypeIntUint32 byte = 26 + additionalTypeIntUint64 byte = 27 + + // Float Sub-types. + additionalTypeFloat16 byte = 25 + additionalTypeFloat32 byte = 26 + additionalTypeFloat64 byte = 27 + additionalTypeBreak byte = 31 + + // Tag Sub-types. + additionalTypeTimestamp byte = 01 + + // Extended Tags - from https://www.iana.org/assignments/cbor-tags/cbor-tags.xhtml + additionalTypeTagNetworkAddr uint16 = 260 + additionalTypeTagNetworkPrefix uint16 = 261 + additionalTypeEmbeddedJSON uint16 = 262 + additionalTypeTagHexString uint16 = 263 + + // Unspecified number of elements. + additionalTypeInfiniteCount byte = 31 +) +const ( + majorTypeUnsignedInt byte = iota << majorOffset // Major type 0 + majorTypeNegativeInt // Major type 1 + majorTypeByteString // Major type 2 + majorTypeUtf8String // Major type 3 + majorTypeArray // Major type 4 + majorTypeMap // Major type 5 + majorTypeTags // Major type 6 + majorTypeSimpleAndFloat // Major type 7 +) + +const ( + maskOutAdditionalType byte = (7 << majorOffset) + maskOutMajorType byte = 31 +) + +const ( + float32Nan = "\xfa\x7f\xc0\x00\x00" + float32PosInfinity = "\xfa\x7f\x80\x00\x00" + float32NegInfinity = "\xfa\xff\x80\x00\x00" + float64Nan = "\xfb\x7f\xf8\x00\x00\x00\x00\x00\x00" + float64PosInfinity = "\xfb\x7f\xf0\x00\x00\x00\x00\x00\x00" + float64NegInfinity = "\xfb\xff\xf0\x00\x00\x00\x00\x00\x00" +) + +// IntegerTimeFieldFormat indicates the format of timestamp decoded +// from an integer (time in seconds). +var IntegerTimeFieldFormat = time.RFC3339 + +// NanoTimeFieldFormat indicates the format of timestamp decoded +// from a float value (time in seconds and nano seconds). +var NanoTimeFieldFormat = time.RFC3339Nano + +func appendCborTypePrefix(dst []byte, major byte, number uint64) []byte { + byteCount := 8 + var minor byte + switch { + case number < 256: + byteCount = 1 + minor = additionalTypeIntUint8 + + case number < 65536: + byteCount = 2 + minor = additionalTypeIntUint16 + + case number < 4294967296: + byteCount = 4 + minor = additionalTypeIntUint32 + + default: + byteCount = 8 + minor = additionalTypeIntUint64 + + } + dst = append(dst, byte(major|minor)) + byteCount-- + for ; byteCount >= 0; byteCount-- { + dst = append(dst, byte(number>>(uint(byteCount)*8))) + } + return dst +} diff --git a/vendor/github.com/rs/zerolog/internal/cbor/decode_stream.go b/vendor/github.com/rs/zerolog/internal/cbor/decode_stream.go new file mode 100644 index 000000000..e3cf3b7db --- /dev/null +++ b/vendor/github.com/rs/zerolog/internal/cbor/decode_stream.go @@ -0,0 +1,614 @@ +package cbor + +// This file contains code to decode a stream of CBOR Data into JSON. + +import ( + "bufio" + "bytes" + "fmt" + "io" + "math" + "net" + "runtime" + "strconv" + "strings" + "time" + "unicode/utf8" +) + +var decodeTimeZone *time.Location + +const hexTable = "0123456789abcdef" + +const isFloat32 = 4 +const isFloat64 = 8 + +func readNBytes(src *bufio.Reader, n int) []byte { + ret := make([]byte, n) + for i := 0; i < n; i++ { + ch, e := src.ReadByte() + if e != nil { + panic(fmt.Errorf("Tried to Read %d Bytes.. But hit end of file", n)) + } + ret[i] = ch + } + return ret +} + +func readByte(src *bufio.Reader) byte { + b, e := src.ReadByte() + if e != nil { + panic(fmt.Errorf("Tried to Read 1 Byte.. But hit end of file")) + } + return b +} + +func decodeIntAdditonalType(src *bufio.Reader, minor byte) int64 { + val := int64(0) + if minor <= 23 { + val = int64(minor) + } else { + bytesToRead := 0 + switch minor { + case additionalTypeIntUint8: + bytesToRead = 1 + case additionalTypeIntUint16: + bytesToRead = 2 + case additionalTypeIntUint32: + bytesToRead = 4 + case additionalTypeIntUint64: + bytesToRead = 8 + default: + panic(fmt.Errorf("Invalid Additional Type: %d in decodeInteger (expected <28)", minor)) + } + pb := readNBytes(src, bytesToRead) + for i := 0; i < bytesToRead; i++ { + val = val * 256 + val += int64(pb[i]) + } + } + return val +} + +func decodeInteger(src *bufio.Reader) int64 { + pb := readByte(src) + major := pb & maskOutAdditionalType + minor := pb & maskOutMajorType + if major != majorTypeUnsignedInt && major != majorTypeNegativeInt { + panic(fmt.Errorf("Major type is: %d in decodeInteger!! (expected 0 or 1)", major)) + } + val := decodeIntAdditonalType(src, minor) + if major == 0 { + return val + } + return (-1 - val) +} + +func decodeFloat(src *bufio.Reader) (float64, int) { + pb := readByte(src) + major := pb & maskOutAdditionalType + minor := pb & maskOutMajorType + if major != majorTypeSimpleAndFloat { + panic(fmt.Errorf("Incorrect Major type is: %d in decodeFloat", major)) + } + + switch minor { + case additionalTypeFloat16: + panic(fmt.Errorf("float16 is not suppported in decodeFloat")) + + case additionalTypeFloat32: + pb := readNBytes(src, 4) + switch string(pb) { + case float32Nan: + return math.NaN(), isFloat32 + case float32PosInfinity: + return math.Inf(0), isFloat32 + case float32NegInfinity: + return math.Inf(-1), isFloat32 + } + n := uint32(0) + for i := 0; i < 4; i++ { + n = n * 256 + n += uint32(pb[i]) + } + val := math.Float32frombits(n) + return float64(val), isFloat32 + case additionalTypeFloat64: + pb := readNBytes(src, 8) + switch string(pb) { + case float64Nan: + return math.NaN(), isFloat64 + case float64PosInfinity: + return math.Inf(0), isFloat64 + case float64NegInfinity: + return math.Inf(-1), isFloat64 + } + n := uint64(0) + for i := 0; i < 8; i++ { + n = n * 256 + n += uint64(pb[i]) + } + val := math.Float64frombits(n) + return val, isFloat64 + } + panic(fmt.Errorf("Invalid Additional Type: %d in decodeFloat", minor)) +} + +func decodeStringComplex(dst []byte, s string, pos uint) []byte { + i := int(pos) + start := 0 + + for i < len(s) { + b := s[i] + if b >= utf8.RuneSelf { + r, size := utf8.DecodeRuneInString(s[i:]) + if r == utf8.RuneError && size == 1 { + // In case of error, first append previous simple characters to + // the byte slice if any and append a replacement character code + // in place of the invalid sequence. + if start < i { + dst = append(dst, s[start:i]...) + } + dst = append(dst, `\ufffd`...) + i += size + start = i + continue + } + i += size + continue + } + if b >= 0x20 && b <= 0x7e && b != '\\' && b != '"' { + i++ + continue + } + // We encountered a character that needs to be encoded. + // Let's append the previous simple characters to the byte slice + // and switch our operation to read and encode the remainder + // characters byte-by-byte. + if start < i { + dst = append(dst, s[start:i]...) + } + switch b { + case '"', '\\': + dst = append(dst, '\\', b) + case '\b': + dst = append(dst, '\\', 'b') + case '\f': + dst = append(dst, '\\', 'f') + case '\n': + dst = append(dst, '\\', 'n') + case '\r': + dst = append(dst, '\\', 'r') + case '\t': + dst = append(dst, '\\', 't') + default: + dst = append(dst, '\\', 'u', '0', '0', hexTable[b>>4], hexTable[b&0xF]) + } + i++ + start = i + } + if start < len(s) { + dst = append(dst, s[start:]...) + } + return dst +} + +func decodeString(src *bufio.Reader, noQuotes bool) []byte { + pb := readByte(src) + major := pb & maskOutAdditionalType + minor := pb & maskOutMajorType + if major != majorTypeByteString { + panic(fmt.Errorf("Major type is: %d in decodeString", major)) + } + result := []byte{} + if !noQuotes { + result = append(result, '"') + } + length := decodeIntAdditonalType(src, minor) + len := int(length) + pbs := readNBytes(src, len) + result = append(result, pbs...) + if noQuotes { + return result + } + return append(result, '"') +} + +func decodeUTF8String(src *bufio.Reader) []byte { + pb := readByte(src) + major := pb & maskOutAdditionalType + minor := pb & maskOutMajorType + if major != majorTypeUtf8String { + panic(fmt.Errorf("Major type is: %d in decodeUTF8String", major)) + } + result := []byte{'"'} + length := decodeIntAdditonalType(src, minor) + len := int(length) + pbs := readNBytes(src, len) + + for i := 0; i < len; i++ { + // Check if the character needs encoding. Control characters, slashes, + // and the double quote need json encoding. Bytes above the ascii + // boundary needs utf8 encoding. + if pbs[i] < 0x20 || pbs[i] > 0x7e || pbs[i] == '\\' || pbs[i] == '"' { + // We encountered a character that needs to be encoded. Switch + // to complex version of the algorithm. + dst := []byte{'"'} + dst = decodeStringComplex(dst, string(pbs), uint(i)) + return append(dst, '"') + } + } + // The string has no need for encoding an therefore is directly + // appended to the byte slice. + result = append(result, pbs...) + return append(result, '"') +} + +func array2Json(src *bufio.Reader, dst io.Writer) { + dst.Write([]byte{'['}) + pb := readByte(src) + major := pb & maskOutAdditionalType + minor := pb & maskOutMajorType + if major != majorTypeArray { + panic(fmt.Errorf("Major type is: %d in array2Json", major)) + } + len := 0 + unSpecifiedCount := false + if minor == additionalTypeInfiniteCount { + unSpecifiedCount = true + } else { + length := decodeIntAdditonalType(src, minor) + len = int(length) + } + for i := 0; unSpecifiedCount || i < len; i++ { + if unSpecifiedCount { + pb, e := src.Peek(1) + if e != nil { + panic(e) + } + if pb[0] == byte(majorTypeSimpleAndFloat|additionalTypeBreak) { + readByte(src) + break + } + } + cbor2JsonOneObject(src, dst) + if unSpecifiedCount { + pb, e := src.Peek(1) + if e != nil { + panic(e) + } + if pb[0] == byte(majorTypeSimpleAndFloat|additionalTypeBreak) { + readByte(src) + break + } + dst.Write([]byte{','}) + } else if i+1 < len { + dst.Write([]byte{','}) + } + } + dst.Write([]byte{']'}) +} + +func map2Json(src *bufio.Reader, dst io.Writer) { + pb := readByte(src) + major := pb & maskOutAdditionalType + minor := pb & maskOutMajorType + if major != majorTypeMap { + panic(fmt.Errorf("Major type is: %d in map2Json", major)) + } + len := 0 + unSpecifiedCount := false + if minor == additionalTypeInfiniteCount { + unSpecifiedCount = true + } else { + length := decodeIntAdditonalType(src, minor) + len = int(length) + } + dst.Write([]byte{'{'}) + for i := 0; unSpecifiedCount || i < len; i++ { + if unSpecifiedCount { + pb, e := src.Peek(1) + if e != nil { + panic(e) + } + if pb[0] == byte(majorTypeSimpleAndFloat|additionalTypeBreak) { + readByte(src) + break + } + } + cbor2JsonOneObject(src, dst) + if i%2 == 0 { + // Even position values are keys. + dst.Write([]byte{':'}) + } else { + if unSpecifiedCount { + pb, e := src.Peek(1) + if e != nil { + panic(e) + } + if pb[0] == byte(majorTypeSimpleAndFloat|additionalTypeBreak) { + readByte(src) + break + } + dst.Write([]byte{','}) + } else if i+1 < len { + dst.Write([]byte{','}) + } + } + } + dst.Write([]byte{'}'}) +} + +func decodeTagData(src *bufio.Reader) []byte { + pb := readByte(src) + major := pb & maskOutAdditionalType + minor := pb & maskOutMajorType + if major != majorTypeTags { + panic(fmt.Errorf("Major type is: %d in decodeTagData", major)) + } + switch minor { + case additionalTypeTimestamp: + return decodeTimeStamp(src) + + // Tag value is larger than 256 (so uint16). + case additionalTypeIntUint16: + val := decodeIntAdditonalType(src, minor) + + switch uint16(val) { + case additionalTypeEmbeddedJSON: + pb := readByte(src) + dataMajor := pb & maskOutAdditionalType + if dataMajor != majorTypeByteString { + panic(fmt.Errorf("Unsupported embedded Type: %d in decodeEmbeddedJSON", dataMajor)) + } + src.UnreadByte() + return decodeString(src, true) + + case additionalTypeTagNetworkAddr: + octets := decodeString(src, true) + ss := []byte{'"'} + switch len(octets) { + case 6: // MAC address. + ha := net.HardwareAddr(octets) + ss = append(append(ss, ha.String()...), '"') + case 4: // IPv4 address. + fallthrough + case 16: // IPv6 address. + ip := net.IP(octets) + ss = append(append(ss, ip.String()...), '"') + default: + panic(fmt.Errorf("Unexpected Network Address length: %d (expected 4,6,16)", len(octets))) + } + return ss + + case additionalTypeTagNetworkPrefix: + pb := readByte(src) + if pb != byte(majorTypeMap|0x1) { + panic(fmt.Errorf("IP Prefix is NOT of MAP of 1 elements as expected")) + } + octets := decodeString(src, true) + val := decodeInteger(src) + ip := net.IP(octets) + var mask net.IPMask + pfxLen := int(val) + if len(octets) == 4 { + mask = net.CIDRMask(pfxLen, 32) + } else { + mask = net.CIDRMask(pfxLen, 128) + } + ipPfx := net.IPNet{IP: ip, Mask: mask} + ss := []byte{'"'} + ss = append(append(ss, ipPfx.String()...), '"') + return ss + + case additionalTypeTagHexString: + octets := decodeString(src, true) + ss := []byte{'"'} + for _, v := range octets { + ss = append(ss, hexTable[v>>4], hexTable[v&0x0f]) + } + return append(ss, '"') + + default: + panic(fmt.Errorf("Unsupported Additional Tag Type: %d in decodeTagData", val)) + } + } + panic(fmt.Errorf("Unsupported Additional Type: %d in decodeTagData", minor)) +} + +func decodeTimeStamp(src *bufio.Reader) []byte { + pb := readByte(src) + src.UnreadByte() + tsMajor := pb & maskOutAdditionalType + if tsMajor == majorTypeUnsignedInt || tsMajor == majorTypeNegativeInt { + n := decodeInteger(src) + t := time.Unix(n, 0) + if decodeTimeZone != nil { + t = t.In(decodeTimeZone) + } else { + t = t.In(time.UTC) + } + tsb := []byte{} + tsb = append(tsb, '"') + tsb = t.AppendFormat(tsb, IntegerTimeFieldFormat) + tsb = append(tsb, '"') + return tsb + } else if tsMajor == majorTypeSimpleAndFloat { + n, _ := decodeFloat(src) + secs := int64(n) + n -= float64(secs) + n *= float64(1e9) + t := time.Unix(secs, int64(n)) + if decodeTimeZone != nil { + t = t.In(decodeTimeZone) + } else { + t = t.In(time.UTC) + } + tsb := []byte{} + tsb = append(tsb, '"') + tsb = t.AppendFormat(tsb, NanoTimeFieldFormat) + tsb = append(tsb, '"') + return tsb + } + panic(fmt.Errorf("TS format is neigther int nor float: %d", tsMajor)) +} + +func decodeSimpleFloat(src *bufio.Reader) []byte { + pb := readByte(src) + major := pb & maskOutAdditionalType + minor := pb & maskOutMajorType + if major != majorTypeSimpleAndFloat { + panic(fmt.Errorf("Major type is: %d in decodeSimpleFloat", major)) + } + switch minor { + case additionalTypeBoolTrue: + return []byte("true") + case additionalTypeBoolFalse: + return []byte("false") + case additionalTypeNull: + return []byte("null") + case additionalTypeFloat16: + fallthrough + case additionalTypeFloat32: + fallthrough + case additionalTypeFloat64: + src.UnreadByte() + v, bc := decodeFloat(src) + ba := []byte{} + switch { + case math.IsNaN(v): + return []byte("\"NaN\"") + case math.IsInf(v, 1): + return []byte("\"+Inf\"") + case math.IsInf(v, -1): + return []byte("\"-Inf\"") + } + if bc == isFloat32 { + ba = strconv.AppendFloat(ba, v, 'f', -1, 32) + } else if bc == isFloat64 { + ba = strconv.AppendFloat(ba, v, 'f', -1, 64) + } else { + panic(fmt.Errorf("Invalid Float precision from decodeFloat: %d", bc)) + } + return ba + default: + panic(fmt.Errorf("Invalid Additional Type: %d in decodeSimpleFloat", minor)) + } +} + +func cbor2JsonOneObject(src *bufio.Reader, dst io.Writer) { + pb, e := src.Peek(1) + if e != nil { + panic(e) + } + major := (pb[0] & maskOutAdditionalType) + + switch major { + case majorTypeUnsignedInt: + fallthrough + case majorTypeNegativeInt: + n := decodeInteger(src) + dst.Write([]byte(strconv.Itoa(int(n)))) + + case majorTypeByteString: + s := decodeString(src, false) + dst.Write(s) + + case majorTypeUtf8String: + s := decodeUTF8String(src) + dst.Write(s) + + case majorTypeArray: + array2Json(src, dst) + + case majorTypeMap: + map2Json(src, dst) + + case majorTypeTags: + s := decodeTagData(src) + dst.Write(s) + + case majorTypeSimpleAndFloat: + s := decodeSimpleFloat(src) + dst.Write(s) + } +} + +func moreBytesToRead(src *bufio.Reader) bool { + _, e := src.ReadByte() + if e == nil { + src.UnreadByte() + return true + } + return false +} + +// Cbor2JsonManyObjects decodes all the CBOR Objects read from src +// reader. It keeps on decoding until reader returns EOF (error when reading). +// Decoded string is written to the dst. At the end of every CBOR Object +// newline is written to the output stream. +// +// Returns error (if any) that was encountered during decode. +// The child functions will generate a panic when error is encountered and +// this function will recover non-runtime Errors and return the reason as error. +func Cbor2JsonManyObjects(src io.Reader, dst io.Writer) (err error) { + defer func() { + if r := recover(); r != nil { + if _, ok := r.(runtime.Error); ok { + panic(r) + } + err = r.(error) + } + }() + bufRdr := bufio.NewReader(src) + for moreBytesToRead(bufRdr) { + cbor2JsonOneObject(bufRdr, dst) + dst.Write([]byte("\n")) + } + return nil +} + +// Detect if the bytes to be printed is Binary or not. +func binaryFmt(p []byte) bool { + if len(p) > 0 && p[0] > 0x7F { + return true + } + return false +} + +func getReader(str string) *bufio.Reader { + return bufio.NewReader(strings.NewReader(str)) +} + +// DecodeIfBinaryToString converts a binary formatted log msg to a +// JSON formatted String Log message - suitable for printing to Console/Syslog. +func DecodeIfBinaryToString(in []byte) string { + if binaryFmt(in) { + var b bytes.Buffer + Cbor2JsonManyObjects(strings.NewReader(string(in)), &b) + return b.String() + } + return string(in) +} + +// DecodeObjectToStr checks if the input is a binary format, if so, +// it will decode a single Object and return the decoded string. +func DecodeObjectToStr(in []byte) string { + if binaryFmt(in) { + var b bytes.Buffer + cbor2JsonOneObject(getReader(string(in)), &b) + return b.String() + } + return string(in) +} + +// DecodeIfBinaryToBytes checks if the input is a binary format, if so, +// it will decode all Objects and return the decoded string as byte array. +func DecodeIfBinaryToBytes(in []byte) []byte { + if binaryFmt(in) { + var b bytes.Buffer + Cbor2JsonManyObjects(bytes.NewReader(in), &b) + return b.Bytes() + } + return in +} diff --git a/vendor/github.com/rs/zerolog/internal/cbor/string.go b/vendor/github.com/rs/zerolog/internal/cbor/string.go new file mode 100644 index 000000000..ff42afab4 --- /dev/null +++ b/vendor/github.com/rs/zerolog/internal/cbor/string.go @@ -0,0 +1,68 @@ +package cbor + +// AppendStrings encodes and adds an array of strings to the dst byte array. +func (e Encoder) AppendStrings(dst []byte, vals []string) []byte { + major := majorTypeArray + l := len(vals) + if l <= additionalMax { + lb := byte(l) + dst = append(dst, byte(major|lb)) + } else { + dst = appendCborTypePrefix(dst, major, uint64(l)) + } + for _, v := range vals { + dst = e.AppendString(dst, v) + } + return dst +} + +// AppendString encodes and adds a string to the dst byte array. +func (Encoder) AppendString(dst []byte, s string) []byte { + major := majorTypeUtf8String + + l := len(s) + if l <= additionalMax { + lb := byte(l) + dst = append(dst, byte(major|lb)) + } else { + dst = appendCborTypePrefix(dst, majorTypeUtf8String, uint64(l)) + } + return append(dst, s...) +} + +// AppendBytes encodes and adds an array of bytes to the dst byte array. +func (Encoder) AppendBytes(dst, s []byte) []byte { + major := majorTypeByteString + + l := len(s) + if l <= additionalMax { + lb := byte(l) + dst = append(dst, byte(major|lb)) + } else { + dst = appendCborTypePrefix(dst, major, uint64(l)) + } + return append(dst, s...) +} + +// AppendEmbeddedJSON adds a tag and embeds input JSON as such. +func AppendEmbeddedJSON(dst, s []byte) []byte { + major := majorTypeTags + minor := additionalTypeEmbeddedJSON + + // Append the TAG to indicate this is Embedded JSON. + dst = append(dst, byte(major|additionalTypeIntUint16)) + dst = append(dst, byte(minor>>8)) + dst = append(dst, byte(minor&0xff)) + + // Append the JSON Object as Byte String. + major = majorTypeByteString + + l := len(s) + if l <= additionalMax { + lb := byte(l) + dst = append(dst, byte(major|lb)) + } else { + dst = appendCborTypePrefix(dst, major, uint64(l)) + } + return append(dst, s...) +} diff --git a/vendor/github.com/rs/zerolog/internal/cbor/time.go b/vendor/github.com/rs/zerolog/internal/cbor/time.go new file mode 100644 index 000000000..12f6a1ddd --- /dev/null +++ b/vendor/github.com/rs/zerolog/internal/cbor/time.go @@ -0,0 +1,93 @@ +package cbor + +import ( + "time" +) + +func appendIntegerTimestamp(dst []byte, t time.Time) []byte { + major := majorTypeTags + minor := additionalTypeTimestamp + dst = append(dst, byte(major|minor)) + secs := t.Unix() + var val uint64 + if secs < 0 { + major = majorTypeNegativeInt + val = uint64(-secs - 1) + } else { + major = majorTypeUnsignedInt + val = uint64(secs) + } + dst = appendCborTypePrefix(dst, major, uint64(val)) + return dst +} + +func (e Encoder) appendFloatTimestamp(dst []byte, t time.Time) []byte { + major := majorTypeTags + minor := additionalTypeTimestamp + dst = append(dst, byte(major|minor)) + secs := t.Unix() + nanos := t.Nanosecond() + var val float64 + val = float64(secs)*1.0 + float64(nanos)*1E-9 + return e.AppendFloat64(dst, val) +} + +// AppendTime encodes and adds a timestamp to the dst byte array. +func (e Encoder) AppendTime(dst []byte, t time.Time, unused string) []byte { + utc := t.UTC() + if utc.Nanosecond() == 0 { + return appendIntegerTimestamp(dst, utc) + } + return e.appendFloatTimestamp(dst, utc) +} + +// AppendTimes encodes and adds an array of timestamps to the dst byte array. +func (e Encoder) AppendTimes(dst []byte, vals []time.Time, unused string) []byte { + major := majorTypeArray + l := len(vals) + if l == 0 { + return e.AppendArrayEnd(e.AppendArrayStart(dst)) + } + if l <= additionalMax { + lb := byte(l) + dst = append(dst, byte(major|lb)) + } else { + dst = appendCborTypePrefix(dst, major, uint64(l)) + } + + for _, t := range vals { + dst = e.AppendTime(dst, t, unused) + } + return dst +} + +// AppendDuration encodes and adds a duration to the dst byte array. +// useInt field indicates whether to store the duration as seconds (integer) or +// as seconds+nanoseconds (float). +func (e Encoder) AppendDuration(dst []byte, d time.Duration, unit time.Duration, useInt bool) []byte { + if useInt { + return e.AppendInt64(dst, int64(d/unit)) + } + return e.AppendFloat64(dst, float64(d)/float64(unit)) +} + +// AppendDurations encodes and adds an array of durations to the dst byte array. +// useInt field indicates whether to store the duration as seconds (integer) or +// as seconds+nanoseconds (float). +func (e Encoder) AppendDurations(dst []byte, vals []time.Duration, unit time.Duration, useInt bool) []byte { + major := majorTypeArray + l := len(vals) + if l == 0 { + return e.AppendArrayEnd(e.AppendArrayStart(dst)) + } + if l <= additionalMax { + lb := byte(l) + dst = append(dst, byte(major|lb)) + } else { + dst = appendCborTypePrefix(dst, major, uint64(l)) + } + for _, d := range vals { + dst = e.AppendDuration(dst, d, unit, useInt) + } + return dst +} diff --git a/vendor/github.com/rs/zerolog/internal/cbor/types.go b/vendor/github.com/rs/zerolog/internal/cbor/types.go new file mode 100644 index 000000000..3d76ea08e --- /dev/null +++ b/vendor/github.com/rs/zerolog/internal/cbor/types.go @@ -0,0 +1,478 @@ +package cbor + +import ( + "encoding/json" + "fmt" + "math" + "net" +) + +// AppendNil inserts a 'Nil' object into the dst byte array. +func (Encoder) AppendNil(dst []byte) []byte { + return append(dst, byte(majorTypeSimpleAndFloat|additionalTypeNull)) +} + +// AppendBeginMarker inserts a map start into the dst byte array. +func (Encoder) AppendBeginMarker(dst []byte) []byte { + return append(dst, byte(majorTypeMap|additionalTypeInfiniteCount)) +} + +// AppendEndMarker inserts a map end into the dst byte array. +func (Encoder) AppendEndMarker(dst []byte) []byte { + return append(dst, byte(majorTypeSimpleAndFloat|additionalTypeBreak)) +} + +// AppendObjectData takes an object in form of a byte array and appends to dst. +func (Encoder) AppendObjectData(dst []byte, o []byte) []byte { + // BeginMarker is present in the dst, which + // should not be copied when appending to existing data. + return append(dst, o[1:]...) +} + +// AppendArrayStart adds markers to indicate the start of an array. +func (Encoder) AppendArrayStart(dst []byte) []byte { + return append(dst, byte(majorTypeArray|additionalTypeInfiniteCount)) +} + +// AppendArrayEnd adds markers to indicate the end of an array. +func (Encoder) AppendArrayEnd(dst []byte) []byte { + return append(dst, byte(majorTypeSimpleAndFloat|additionalTypeBreak)) +} + +// AppendArrayDelim adds markers to indicate end of a particular array element. +func (Encoder) AppendArrayDelim(dst []byte) []byte { + //No delimiters needed in cbor + return dst +} + +// AppendLineBreak is a noop that keep API compat with json encoder. +func (Encoder) AppendLineBreak(dst []byte) []byte { + // No line breaks needed in binary format. + return dst +} + +// AppendBool encodes and inserts a boolean value into the dst byte array. +func (Encoder) AppendBool(dst []byte, val bool) []byte { + b := additionalTypeBoolFalse + if val { + b = additionalTypeBoolTrue + } + return append(dst, byte(majorTypeSimpleAndFloat|b)) +} + +// AppendBools encodes and inserts an array of boolean values into the dst byte array. +func (e Encoder) AppendBools(dst []byte, vals []bool) []byte { + major := majorTypeArray + l := len(vals) + if l == 0 { + return e.AppendArrayEnd(e.AppendArrayStart(dst)) + } + if l <= additionalMax { + lb := byte(l) + dst = append(dst, byte(major|lb)) + } else { + dst = appendCborTypePrefix(dst, major, uint64(l)) + } + for _, v := range vals { + dst = e.AppendBool(dst, v) + } + return dst +} + +// AppendInt encodes and inserts an integer value into the dst byte array. +func (Encoder) AppendInt(dst []byte, val int) []byte { + major := majorTypeUnsignedInt + contentVal := val + if val < 0 { + major = majorTypeNegativeInt + contentVal = -val - 1 + } + if contentVal <= additionalMax { + lb := byte(contentVal) + dst = append(dst, byte(major|lb)) + } else { + dst = appendCborTypePrefix(dst, major, uint64(contentVal)) + } + return dst +} + +// AppendInts encodes and inserts an array of integer values into the dst byte array. +func (e Encoder) AppendInts(dst []byte, vals []int) []byte { + major := majorTypeArray + l := len(vals) + if l == 0 { + return e.AppendArrayEnd(e.AppendArrayStart(dst)) + } + if l <= additionalMax { + lb := byte(l) + dst = append(dst, byte(major|lb)) + } else { + dst = appendCborTypePrefix(dst, major, uint64(l)) + } + for _, v := range vals { + dst = e.AppendInt(dst, v) + } + return dst +} + +// AppendInt8 encodes and inserts an int8 value into the dst byte array. +func (e Encoder) AppendInt8(dst []byte, val int8) []byte { + return e.AppendInt(dst, int(val)) +} + +// AppendInts8 encodes and inserts an array of integer values into the dst byte array. +func (e Encoder) AppendInts8(dst []byte, vals []int8) []byte { + major := majorTypeArray + l := len(vals) + if l == 0 { + return e.AppendArrayEnd(e.AppendArrayStart(dst)) + } + if l <= additionalMax { + lb := byte(l) + dst = append(dst, byte(major|lb)) + } else { + dst = appendCborTypePrefix(dst, major, uint64(l)) + } + for _, v := range vals { + dst = e.AppendInt(dst, int(v)) + } + return dst +} + +// AppendInt16 encodes and inserts a int16 value into the dst byte array. +func (e Encoder) AppendInt16(dst []byte, val int16) []byte { + return e.AppendInt(dst, int(val)) +} + +// AppendInts16 encodes and inserts an array of int16 values into the dst byte array. +func (e Encoder) AppendInts16(dst []byte, vals []int16) []byte { + major := majorTypeArray + l := len(vals) + if l == 0 { + return e.AppendArrayEnd(e.AppendArrayStart(dst)) + } + if l <= additionalMax { + lb := byte(l) + dst = append(dst, byte(major|lb)) + } else { + dst = appendCborTypePrefix(dst, major, uint64(l)) + } + for _, v := range vals { + dst = e.AppendInt(dst, int(v)) + } + return dst +} + +// AppendInt32 encodes and inserts a int32 value into the dst byte array. +func (e Encoder) AppendInt32(dst []byte, val int32) []byte { + return e.AppendInt(dst, int(val)) +} + +// AppendInts32 encodes and inserts an array of int32 values into the dst byte array. +func (e Encoder) AppendInts32(dst []byte, vals []int32) []byte { + major := majorTypeArray + l := len(vals) + if l == 0 { + return e.AppendArrayEnd(e.AppendArrayStart(dst)) + } + if l <= additionalMax { + lb := byte(l) + dst = append(dst, byte(major|lb)) + } else { + dst = appendCborTypePrefix(dst, major, uint64(l)) + } + for _, v := range vals { + dst = e.AppendInt(dst, int(v)) + } + return dst +} + +// AppendInt64 encodes and inserts a int64 value into the dst byte array. +func (Encoder) AppendInt64(dst []byte, val int64) []byte { + major := majorTypeUnsignedInt + contentVal := val + if val < 0 { + major = majorTypeNegativeInt + contentVal = -val - 1 + } + if contentVal <= additionalMax { + lb := byte(contentVal) + dst = append(dst, byte(major|lb)) + } else { + dst = appendCborTypePrefix(dst, major, uint64(contentVal)) + } + return dst +} + +// AppendInts64 encodes and inserts an array of int64 values into the dst byte array. +func (e Encoder) AppendInts64(dst []byte, vals []int64) []byte { + major := majorTypeArray + l := len(vals) + if l == 0 { + return e.AppendArrayEnd(e.AppendArrayStart(dst)) + } + if l <= additionalMax { + lb := byte(l) + dst = append(dst, byte(major|lb)) + } else { + dst = appendCborTypePrefix(dst, major, uint64(l)) + } + for _, v := range vals { + dst = e.AppendInt64(dst, v) + } + return dst +} + +// AppendUint encodes and inserts an unsigned integer value into the dst byte array. +func (e Encoder) AppendUint(dst []byte, val uint) []byte { + return e.AppendInt64(dst, int64(val)) +} + +// AppendUints encodes and inserts an array of unsigned integer values into the dst byte array. +func (e Encoder) AppendUints(dst []byte, vals []uint) []byte { + major := majorTypeArray + l := len(vals) + if l == 0 { + return e.AppendArrayEnd(e.AppendArrayStart(dst)) + } + if l <= additionalMax { + lb := byte(l) + dst = append(dst, byte(major|lb)) + } else { + dst = appendCborTypePrefix(dst, major, uint64(l)) + } + for _, v := range vals { + dst = e.AppendUint(dst, v) + } + return dst +} + +// AppendUint8 encodes and inserts a unsigned int8 value into the dst byte array. +func (e Encoder) AppendUint8(dst []byte, val uint8) []byte { + return e.AppendUint(dst, uint(val)) +} + +// AppendUints8 encodes and inserts an array of uint8 values into the dst byte array. +func (e Encoder) AppendUints8(dst []byte, vals []uint8) []byte { + major := majorTypeArray + l := len(vals) + if l == 0 { + return e.AppendArrayEnd(e.AppendArrayStart(dst)) + } + if l <= additionalMax { + lb := byte(l) + dst = append(dst, byte(major|lb)) + } else { + dst = appendCborTypePrefix(dst, major, uint64(l)) + } + for _, v := range vals { + dst = e.AppendUint8(dst, v) + } + return dst +} + +// AppendUint16 encodes and inserts a uint16 value into the dst byte array. +func (e Encoder) AppendUint16(dst []byte, val uint16) []byte { + return e.AppendUint(dst, uint(val)) +} + +// AppendUints16 encodes and inserts an array of uint16 values into the dst byte array. +func (e Encoder) AppendUints16(dst []byte, vals []uint16) []byte { + major := majorTypeArray + l := len(vals) + if l == 0 { + return e.AppendArrayEnd(e.AppendArrayStart(dst)) + } + if l <= additionalMax { + lb := byte(l) + dst = append(dst, byte(major|lb)) + } else { + dst = appendCborTypePrefix(dst, major, uint64(l)) + } + for _, v := range vals { + dst = e.AppendUint16(dst, v) + } + return dst +} + +// AppendUint32 encodes and inserts a uint32 value into the dst byte array. +func (e Encoder) AppendUint32(dst []byte, val uint32) []byte { + return e.AppendUint(dst, uint(val)) +} + +// AppendUints32 encodes and inserts an array of uint32 values into the dst byte array. +func (e Encoder) AppendUints32(dst []byte, vals []uint32) []byte { + major := majorTypeArray + l := len(vals) + if l == 0 { + return e.AppendArrayEnd(e.AppendArrayStart(dst)) + } + if l <= additionalMax { + lb := byte(l) + dst = append(dst, byte(major|lb)) + } else { + dst = appendCborTypePrefix(dst, major, uint64(l)) + } + for _, v := range vals { + dst = e.AppendUint32(dst, v) + } + return dst +} + +// AppendUint64 encodes and inserts a uint64 value into the dst byte array. +func (Encoder) AppendUint64(dst []byte, val uint64) []byte { + major := majorTypeUnsignedInt + contentVal := val + if contentVal <= additionalMax { + lb := byte(contentVal) + dst = append(dst, byte(major|lb)) + } else { + dst = appendCborTypePrefix(dst, major, uint64(contentVal)) + } + return dst +} + +// AppendUints64 encodes and inserts an array of uint64 values into the dst byte array. +func (e Encoder) AppendUints64(dst []byte, vals []uint64) []byte { + major := majorTypeArray + l := len(vals) + if l == 0 { + return e.AppendArrayEnd(e.AppendArrayStart(dst)) + } + if l <= additionalMax { + lb := byte(l) + dst = append(dst, byte(major|lb)) + } else { + dst = appendCborTypePrefix(dst, major, uint64(l)) + } + for _, v := range vals { + dst = e.AppendUint64(dst, v) + } + return dst +} + +// AppendFloat32 encodes and inserts a single precision float value into the dst byte array. +func (Encoder) AppendFloat32(dst []byte, val float32) []byte { + switch { + case math.IsNaN(float64(val)): + return append(dst, "\xfa\x7f\xc0\x00\x00"...) + case math.IsInf(float64(val), 1): + return append(dst, "\xfa\x7f\x80\x00\x00"...) + case math.IsInf(float64(val), -1): + return append(dst, "\xfa\xff\x80\x00\x00"...) + } + major := majorTypeSimpleAndFloat + subType := additionalTypeFloat32 + n := math.Float32bits(val) + var buf [4]byte + for i := uint(0); i < 4; i++ { + buf[i] = byte(n >> ((3 - i) * 8)) + } + return append(append(dst, byte(major|subType)), buf[0], buf[1], buf[2], buf[3]) +} + +// AppendFloats32 encodes and inserts an array of single precision float value into the dst byte array. +func (e Encoder) AppendFloats32(dst []byte, vals []float32) []byte { + major := majorTypeArray + l := len(vals) + if l == 0 { + return e.AppendArrayEnd(e.AppendArrayStart(dst)) + } + if l <= additionalMax { + lb := byte(l) + dst = append(dst, byte(major|lb)) + } else { + dst = appendCborTypePrefix(dst, major, uint64(l)) + } + for _, v := range vals { + dst = e.AppendFloat32(dst, v) + } + return dst +} + +// AppendFloat64 encodes and inserts a double precision float value into the dst byte array. +func (Encoder) AppendFloat64(dst []byte, val float64) []byte { + switch { + case math.IsNaN(val): + return append(dst, "\xfb\x7f\xf8\x00\x00\x00\x00\x00\x00"...) + case math.IsInf(val, 1): + return append(dst, "\xfb\x7f\xf0\x00\x00\x00\x00\x00\x00"...) + case math.IsInf(val, -1): + return append(dst, "\xfb\xff\xf0\x00\x00\x00\x00\x00\x00"...) + } + major := majorTypeSimpleAndFloat + subType := additionalTypeFloat64 + n := math.Float64bits(val) + dst = append(dst, byte(major|subType)) + for i := uint(1); i <= 8; i++ { + b := byte(n >> ((8 - i) * 8)) + dst = append(dst, b) + } + return dst +} + +// AppendFloats64 encodes and inserts an array of double precision float values into the dst byte array. +func (e Encoder) AppendFloats64(dst []byte, vals []float64) []byte { + major := majorTypeArray + l := len(vals) + if l == 0 { + return e.AppendArrayEnd(e.AppendArrayStart(dst)) + } + if l <= additionalMax { + lb := byte(l) + dst = append(dst, byte(major|lb)) + } else { + dst = appendCborTypePrefix(dst, major, uint64(l)) + } + for _, v := range vals { + dst = e.AppendFloat64(dst, v) + } + return dst +} + +// AppendInterface takes an arbitrary object and converts it to JSON and embeds it dst. +func (e Encoder) AppendInterface(dst []byte, i interface{}) []byte { + marshaled, err := json.Marshal(i) + if err != nil { + return e.AppendString(dst, fmt.Sprintf("marshaling error: %v", err)) + } + return AppendEmbeddedJSON(dst, marshaled) +} + +// AppendIPAddr encodes and inserts an IP Address (IPv4 or IPv6). +func (e Encoder) AppendIPAddr(dst []byte, ip net.IP) []byte { + dst = append(dst, byte(majorTypeTags|additionalTypeIntUint16)) + dst = append(dst, byte(additionalTypeTagNetworkAddr>>8)) + dst = append(dst, byte(additionalTypeTagNetworkAddr&0xff)) + return e.AppendBytes(dst, ip) +} + +// AppendIPPrefix encodes and inserts an IP Address Prefix (Address + Mask Length). +func (e Encoder) AppendIPPrefix(dst []byte, pfx net.IPNet) []byte { + dst = append(dst, byte(majorTypeTags|additionalTypeIntUint16)) + dst = append(dst, byte(additionalTypeTagNetworkPrefix>>8)) + dst = append(dst, byte(additionalTypeTagNetworkPrefix&0xff)) + + // Prefix is a tuple (aka MAP of 1 pair of elements) - + // first element is prefix, second is mask length. + dst = append(dst, byte(majorTypeMap|0x1)) + dst = e.AppendBytes(dst, pfx.IP) + maskLen, _ := pfx.Mask.Size() + return e.AppendUint8(dst, uint8(maskLen)) +} + +// AppendMACAddr encodes and inserts an Hardware (MAC) address. +func (e Encoder) AppendMACAddr(dst []byte, ha net.HardwareAddr) []byte { + dst = append(dst, byte(majorTypeTags|additionalTypeIntUint16)) + dst = append(dst, byte(additionalTypeTagNetworkAddr>>8)) + dst = append(dst, byte(additionalTypeTagNetworkAddr&0xff)) + return e.AppendBytes(dst, ha) +} + +// AppendHex adds a TAG and inserts a hex bytes as a string. +func (e Encoder) AppendHex(dst []byte, val []byte) []byte { + dst = append(dst, byte(majorTypeTags|additionalTypeIntUint16)) + dst = append(dst, byte(additionalTypeTagHexString>>8)) + dst = append(dst, byte(additionalTypeTagHexString&0xff)) + return e.AppendBytes(dst, val) +} diff --git a/vendor/github.com/rs/zerolog/internal/json/base.go b/vendor/github.com/rs/zerolog/internal/json/base.go new file mode 100644 index 000000000..d6f8839e3 --- /dev/null +++ b/vendor/github.com/rs/zerolog/internal/json/base.go @@ -0,0 +1,12 @@ +package json + +type Encoder struct{} + +// AppendKey appends a new key to the output JSON. +func (e Encoder) AppendKey(dst []byte, key string) []byte { + if len(dst) > 1 && dst[len(dst)-1] != '{' { + dst = append(dst, ',') + } + dst = e.AppendString(dst, key) + return append(dst, ':') +} \ No newline at end of file diff --git a/vendor/github.com/rs/zerolog/internal/json/bytes.go b/vendor/github.com/rs/zerolog/internal/json/bytes.go new file mode 100644 index 000000000..de64120d1 --- /dev/null +++ b/vendor/github.com/rs/zerolog/internal/json/bytes.go @@ -0,0 +1,85 @@ +package json + +import "unicode/utf8" + +// AppendBytes is a mirror of appendString with []byte arg +func (Encoder) AppendBytes(dst, s []byte) []byte { + dst = append(dst, '"') + for i := 0; i < len(s); i++ { + if !noEscapeTable[s[i]] { + dst = appendBytesComplex(dst, s, i) + return append(dst, '"') + } + } + dst = append(dst, s...) + return append(dst, '"') +} + +// AppendHex encodes the input bytes to a hex string and appends +// the encoded string to the input byte slice. +// +// The operation loops though each byte and encodes it as hex using +// the hex lookup table. +func (Encoder) AppendHex(dst, s []byte) []byte { + dst = append(dst, '"') + for _, v := range s { + dst = append(dst, hex[v>>4], hex[v&0x0f]) + } + return append(dst, '"') +} + +// appendBytesComplex is a mirror of the appendStringComplex +// with []byte arg +func appendBytesComplex(dst, s []byte, i int) []byte { + start := 0 + for i < len(s) { + b := s[i] + if b >= utf8.RuneSelf { + r, size := utf8.DecodeRune(s[i:]) + if r == utf8.RuneError && size == 1 { + if start < i { + dst = append(dst, s[start:i]...) + } + dst = append(dst, `\ufffd`...) + i += size + start = i + continue + } + i += size + continue + } + if noEscapeTable[b] { + i++ + continue + } + // We encountered a character that needs to be encoded. + // Let's append the previous simple characters to the byte slice + // and switch our operation to read and encode the remainder + // characters byte-by-byte. + if start < i { + dst = append(dst, s[start:i]...) + } + switch b { + case '"', '\\': + dst = append(dst, '\\', b) + case '\b': + dst = append(dst, '\\', 'b') + case '\f': + dst = append(dst, '\\', 'f') + case '\n': + dst = append(dst, '\\', 'n') + case '\r': + dst = append(dst, '\\', 'r') + case '\t': + dst = append(dst, '\\', 't') + default: + dst = append(dst, '\\', 'u', '0', '0', hex[b>>4], hex[b&0xF]) + } + i++ + start = i + } + if start < len(s) { + dst = append(dst, s[start:]...) + } + return dst +} diff --git a/vendor/github.com/rs/zerolog/internal/json/string.go b/vendor/github.com/rs/zerolog/internal/json/string.go new file mode 100644 index 000000000..815906ff7 --- /dev/null +++ b/vendor/github.com/rs/zerolog/internal/json/string.go @@ -0,0 +1,121 @@ +package json + +import "unicode/utf8" + +const hex = "0123456789abcdef" + +var noEscapeTable = [256]bool{} + +func init() { + for i := 0; i <= 0x7e; i++ { + noEscapeTable[i] = i >= 0x20 && i != '\\' && i != '"' + } +} + +// AppendStrings encodes the input strings to json and +// appends the encoded string list to the input byte slice. +func (e Encoder) AppendStrings(dst []byte, vals []string) []byte { + if len(vals) == 0 { + return append(dst, '[', ']') + } + dst = append(dst, '[') + dst = e.AppendString(dst, vals[0]) + if len(vals) > 1 { + for _, val := range vals[1:] { + dst = e.AppendString(append(dst, ','), val) + } + } + dst = append(dst, ']') + return dst +} + +// AppendString encodes the input string to json and appends +// the encoded string to the input byte slice. +// +// The operation loops though each byte in the string looking +// for characters that need json or utf8 encoding. If the string +// does not need encoding, then the string is appended in it's +// entirety to the byte slice. +// If we encounter a byte that does need encoding, switch up +// the operation and perform a byte-by-byte read-encode-append. +func (Encoder) AppendString(dst []byte, s string) []byte { + // Start with a double quote. + dst = append(dst, '"') + // Loop through each character in the string. + for i := 0; i < len(s); i++ { + // Check if the character needs encoding. Control characters, slashes, + // and the double quote need json encoding. Bytes above the ascii + // boundary needs utf8 encoding. + if !noEscapeTable[s[i]] { + // We encountered a character that needs to be encoded. Switch + // to complex version of the algorithm. + dst = appendStringComplex(dst, s, i) + return append(dst, '"') + } + } + // The string has no need for encoding an therefore is directly + // appended to the byte slice. + dst = append(dst, s...) + // End with a double quote + return append(dst, '"') +} + +// appendStringComplex is used by appendString to take over an in +// progress JSON string encoding that encountered a character that needs +// to be encoded. +func appendStringComplex(dst []byte, s string, i int) []byte { + start := 0 + for i < len(s) { + b := s[i] + if b >= utf8.RuneSelf { + r, size := utf8.DecodeRuneInString(s[i:]) + if r == utf8.RuneError && size == 1 { + // In case of error, first append previous simple characters to + // the byte slice if any and append a remplacement character code + // in place of the invalid sequence. + if start < i { + dst = append(dst, s[start:i]...) + } + dst = append(dst, `\ufffd`...) + i += size + start = i + continue + } + i += size + continue + } + if noEscapeTable[b] { + i++ + continue + } + // We encountered a character that needs to be encoded. + // Let's append the previous simple characters to the byte slice + // and switch our operation to read and encode the remainder + // characters byte-by-byte. + if start < i { + dst = append(dst, s[start:i]...) + } + switch b { + case '"', '\\': + dst = append(dst, '\\', b) + case '\b': + dst = append(dst, '\\', 'b') + case '\f': + dst = append(dst, '\\', 'f') + case '\n': + dst = append(dst, '\\', 'n') + case '\r': + dst = append(dst, '\\', 'r') + case '\t': + dst = append(dst, '\\', 't') + default: + dst = append(dst, '\\', 'u', '0', '0', hex[b>>4], hex[b&0xF]) + } + i++ + start = i + } + if start < len(s) { + dst = append(dst, s[start:]...) + } + return dst +} diff --git a/vendor/github.com/rs/zerolog/internal/json/time.go b/vendor/github.com/rs/zerolog/internal/json/time.go new file mode 100644 index 000000000..5aff6be33 --- /dev/null +++ b/vendor/github.com/rs/zerolog/internal/json/time.go @@ -0,0 +1,106 @@ +package json + +import ( + "strconv" + "time" +) + +const ( + // Import from zerolog/global.go + timeFormatUnix = "" + timeFormatUnixMs = "UNIXMS" + timeFormatUnixMicro = "UNIXMICRO" +) + +// AppendTime formats the input time with the given format +// and appends the encoded string to the input byte slice. +func (e Encoder) AppendTime(dst []byte, t time.Time, format string) []byte { + switch format { + case timeFormatUnix: + return e.AppendInt64(dst, t.Unix()) + case timeFormatUnixMs: + return e.AppendInt64(dst, t.UnixNano()/1000000) + case timeFormatUnixMicro: + return e.AppendInt64(dst, t.UnixNano()/1000) + } + return append(t.AppendFormat(append(dst, '"'), format), '"') +} + +// AppendTimes converts the input times with the given format +// and appends the encoded string list to the input byte slice. +func (Encoder) AppendTimes(dst []byte, vals []time.Time, format string) []byte { + switch format { + case timeFormatUnix: + return appendUnixTimes(dst, vals) + case timeFormatUnixMs: + return appendUnixMsTimes(dst, vals) + } + if len(vals) == 0 { + return append(dst, '[', ']') + } + dst = append(dst, '[') + dst = append(vals[0].AppendFormat(append(dst, '"'), format), '"') + if len(vals) > 1 { + for _, t := range vals[1:] { + dst = append(t.AppendFormat(append(dst, ',', '"'), format), '"') + } + } + dst = append(dst, ']') + return dst +} + +func appendUnixTimes(dst []byte, vals []time.Time) []byte { + if len(vals) == 0 { + return append(dst, '[', ']') + } + dst = append(dst, '[') + dst = strconv.AppendInt(dst, vals[0].Unix(), 10) + if len(vals) > 1 { + for _, t := range vals[1:] { + dst = strconv.AppendInt(append(dst, ','), t.Unix(), 10) + } + } + dst = append(dst, ']') + return dst +} + +func appendUnixMsTimes(dst []byte, vals []time.Time) []byte { + if len(vals) == 0 { + return append(dst, '[', ']') + } + dst = append(dst, '[') + dst = strconv.AppendInt(dst, vals[0].UnixNano()/1000000, 10) + if len(vals) > 1 { + for _, t := range vals[1:] { + dst = strconv.AppendInt(append(dst, ','), t.UnixNano()/1000000, 10) + } + } + dst = append(dst, ']') + return dst +} + +// AppendDuration formats the input duration with the given unit & format +// and appends the encoded string to the input byte slice. +func (e Encoder) AppendDuration(dst []byte, d time.Duration, unit time.Duration, useInt bool) []byte { + if useInt { + return strconv.AppendInt(dst, int64(d/unit), 10) + } + return e.AppendFloat64(dst, float64(d)/float64(unit)) +} + +// AppendDurations formats the input durations with the given unit & format +// and appends the encoded string list to the input byte slice. +func (e Encoder) AppendDurations(dst []byte, vals []time.Duration, unit time.Duration, useInt bool) []byte { + if len(vals) == 0 { + return append(dst, '[', ']') + } + dst = append(dst, '[') + dst = e.AppendDuration(dst, vals[0], unit, useInt) + if len(vals) > 1 { + for _, d := range vals[1:] { + dst = e.AppendDuration(append(dst, ','), d, unit, useInt) + } + } + dst = append(dst, ']') + return dst +} diff --git a/vendor/github.com/rs/zerolog/internal/json/types.go b/vendor/github.com/rs/zerolog/internal/json/types.go new file mode 100644 index 000000000..bc8bc0957 --- /dev/null +++ b/vendor/github.com/rs/zerolog/internal/json/types.go @@ -0,0 +1,407 @@ +package json + +import ( + "encoding/json" + "fmt" + "math" + "net" + "strconv" +) + +// AppendNil inserts a 'Nil' object into the dst byte array. +func (Encoder) AppendNil(dst []byte) []byte { + return append(dst, "null"...) +} + +// AppendBeginMarker inserts a map start into the dst byte array. +func (Encoder) AppendBeginMarker(dst []byte) []byte { + return append(dst, '{') +} + +// AppendEndMarker inserts a map end into the dst byte array. +func (Encoder) AppendEndMarker(dst []byte) []byte { + return append(dst, '}') +} + +// AppendLineBreak appends a line break. +func (Encoder) AppendLineBreak(dst []byte) []byte { + return append(dst, '\n') +} + +// AppendArrayStart adds markers to indicate the start of an array. +func (Encoder) AppendArrayStart(dst []byte) []byte { + return append(dst, '[') +} + +// AppendArrayEnd adds markers to indicate the end of an array. +func (Encoder) AppendArrayEnd(dst []byte) []byte { + return append(dst, ']') +} + +// AppendArrayDelim adds markers to indicate end of a particular array element. +func (Encoder) AppendArrayDelim(dst []byte) []byte { + if len(dst) > 0 { + return append(dst, ',') + } + return dst +} + +// AppendBool converts the input bool to a string and +// appends the encoded string to the input byte slice. +func (Encoder) AppendBool(dst []byte, val bool) []byte { + return strconv.AppendBool(dst, val) +} + +// AppendBools encodes the input bools to json and +// appends the encoded string list to the input byte slice. +func (Encoder) AppendBools(dst []byte, vals []bool) []byte { + if len(vals) == 0 { + return append(dst, '[', ']') + } + dst = append(dst, '[') + dst = strconv.AppendBool(dst, vals[0]) + if len(vals) > 1 { + for _, val := range vals[1:] { + dst = strconv.AppendBool(append(dst, ','), val) + } + } + dst = append(dst, ']') + return dst +} + +// AppendInt converts the input int to a string and +// appends the encoded string to the input byte slice. +func (Encoder) AppendInt(dst []byte, val int) []byte { + return strconv.AppendInt(dst, int64(val), 10) +} + +// AppendInts encodes the input ints to json and +// appends the encoded string list to the input byte slice. +func (Encoder) AppendInts(dst []byte, vals []int) []byte { + if len(vals) == 0 { + return append(dst, '[', ']') + } + dst = append(dst, '[') + dst = strconv.AppendInt(dst, int64(vals[0]), 10) + if len(vals) > 1 { + for _, val := range vals[1:] { + dst = strconv.AppendInt(append(dst, ','), int64(val), 10) + } + } + dst = append(dst, ']') + return dst +} + +// AppendInt8 converts the input []int8 to a string and +// appends the encoded string to the input byte slice. +func (Encoder) AppendInt8(dst []byte, val int8) []byte { + return strconv.AppendInt(dst, int64(val), 10) +} + +// AppendInts8 encodes the input int8s to json and +// appends the encoded string list to the input byte slice. +func (Encoder) AppendInts8(dst []byte, vals []int8) []byte { + if len(vals) == 0 { + return append(dst, '[', ']') + } + dst = append(dst, '[') + dst = strconv.AppendInt(dst, int64(vals[0]), 10) + if len(vals) > 1 { + for _, val := range vals[1:] { + dst = strconv.AppendInt(append(dst, ','), int64(val), 10) + } + } + dst = append(dst, ']') + return dst +} + +// AppendInt16 converts the input int16 to a string and +// appends the encoded string to the input byte slice. +func (Encoder) AppendInt16(dst []byte, val int16) []byte { + return strconv.AppendInt(dst, int64(val), 10) +} + +// AppendInts16 encodes the input int16s to json and +// appends the encoded string list to the input byte slice. +func (Encoder) AppendInts16(dst []byte, vals []int16) []byte { + if len(vals) == 0 { + return append(dst, '[', ']') + } + dst = append(dst, '[') + dst = strconv.AppendInt(dst, int64(vals[0]), 10) + if len(vals) > 1 { + for _, val := range vals[1:] { + dst = strconv.AppendInt(append(dst, ','), int64(val), 10) + } + } + dst = append(dst, ']') + return dst +} + +// AppendInt32 converts the input int32 to a string and +// appends the encoded string to the input byte slice. +func (Encoder) AppendInt32(dst []byte, val int32) []byte { + return strconv.AppendInt(dst, int64(val), 10) +} + +// AppendInts32 encodes the input int32s to json and +// appends the encoded string list to the input byte slice. +func (Encoder) AppendInts32(dst []byte, vals []int32) []byte { + if len(vals) == 0 { + return append(dst, '[', ']') + } + dst = append(dst, '[') + dst = strconv.AppendInt(dst, int64(vals[0]), 10) + if len(vals) > 1 { + for _, val := range vals[1:] { + dst = strconv.AppendInt(append(dst, ','), int64(val), 10) + } + } + dst = append(dst, ']') + return dst +} + +// AppendInt64 converts the input int64 to a string and +// appends the encoded string to the input byte slice. +func (Encoder) AppendInt64(dst []byte, val int64) []byte { + return strconv.AppendInt(dst, val, 10) +} + +// AppendInts64 encodes the input int64s to json and +// appends the encoded string list to the input byte slice. +func (Encoder) AppendInts64(dst []byte, vals []int64) []byte { + if len(vals) == 0 { + return append(dst, '[', ']') + } + dst = append(dst, '[') + dst = strconv.AppendInt(dst, vals[0], 10) + if len(vals) > 1 { + for _, val := range vals[1:] { + dst = strconv.AppendInt(append(dst, ','), val, 10) + } + } + dst = append(dst, ']') + return dst +} + +// AppendUint converts the input uint to a string and +// appends the encoded string to the input byte slice. +func (Encoder) AppendUint(dst []byte, val uint) []byte { + return strconv.AppendUint(dst, uint64(val), 10) +} + +// AppendUints encodes the input uints to json and +// appends the encoded string list to the input byte slice. +func (Encoder) AppendUints(dst []byte, vals []uint) []byte { + if len(vals) == 0 { + return append(dst, '[', ']') + } + dst = append(dst, '[') + dst = strconv.AppendUint(dst, uint64(vals[0]), 10) + if len(vals) > 1 { + for _, val := range vals[1:] { + dst = strconv.AppendUint(append(dst, ','), uint64(val), 10) + } + } + dst = append(dst, ']') + return dst +} + +// AppendUint8 converts the input uint8 to a string and +// appends the encoded string to the input byte slice. +func (Encoder) AppendUint8(dst []byte, val uint8) []byte { + return strconv.AppendUint(dst, uint64(val), 10) +} + +// AppendUints8 encodes the input uint8s to json and +// appends the encoded string list to the input byte slice. +func (Encoder) AppendUints8(dst []byte, vals []uint8) []byte { + if len(vals) == 0 { + return append(dst, '[', ']') + } + dst = append(dst, '[') + dst = strconv.AppendUint(dst, uint64(vals[0]), 10) + if len(vals) > 1 { + for _, val := range vals[1:] { + dst = strconv.AppendUint(append(dst, ','), uint64(val), 10) + } + } + dst = append(dst, ']') + return dst +} + +// AppendUint16 converts the input uint16 to a string and +// appends the encoded string to the input byte slice. +func (Encoder) AppendUint16(dst []byte, val uint16) []byte { + return strconv.AppendUint(dst, uint64(val), 10) +} + +// AppendUints16 encodes the input uint16s to json and +// appends the encoded string list to the input byte slice. +func (Encoder) AppendUints16(dst []byte, vals []uint16) []byte { + if len(vals) == 0 { + return append(dst, '[', ']') + } + dst = append(dst, '[') + dst = strconv.AppendUint(dst, uint64(vals[0]), 10) + if len(vals) > 1 { + for _, val := range vals[1:] { + dst = strconv.AppendUint(append(dst, ','), uint64(val), 10) + } + } + dst = append(dst, ']') + return dst +} + +// AppendUint32 converts the input uint32 to a string and +// appends the encoded string to the input byte slice. +func (Encoder) AppendUint32(dst []byte, val uint32) []byte { + return strconv.AppendUint(dst, uint64(val), 10) +} + +// AppendUints32 encodes the input uint32s to json and +// appends the encoded string list to the input byte slice. +func (Encoder) AppendUints32(dst []byte, vals []uint32) []byte { + if len(vals) == 0 { + return append(dst, '[', ']') + } + dst = append(dst, '[') + dst = strconv.AppendUint(dst, uint64(vals[0]), 10) + if len(vals) > 1 { + for _, val := range vals[1:] { + dst = strconv.AppendUint(append(dst, ','), uint64(val), 10) + } + } + dst = append(dst, ']') + return dst +} + +// AppendUint64 converts the input uint64 to a string and +// appends the encoded string to the input byte slice. +func (Encoder) AppendUint64(dst []byte, val uint64) []byte { + return strconv.AppendUint(dst, uint64(val), 10) +} + +// AppendUints64 encodes the input uint64s to json and +// appends the encoded string list to the input byte slice. +func (Encoder) AppendUints64(dst []byte, vals []uint64) []byte { + if len(vals) == 0 { + return append(dst, '[', ']') + } + dst = append(dst, '[') + dst = strconv.AppendUint(dst, vals[0], 10) + if len(vals) > 1 { + for _, val := range vals[1:] { + dst = strconv.AppendUint(append(dst, ','), val, 10) + } + } + dst = append(dst, ']') + return dst +} + +func appendFloat(dst []byte, val float64, bitSize int) []byte { + // JSON does not permit NaN or Infinity. A typical JSON encoder would fail + // with an error, but a logging library wants the data to get thru so we + // make a tradeoff and store those types as string. + switch { + case math.IsNaN(val): + return append(dst, `"NaN"`...) + case math.IsInf(val, 1): + return append(dst, `"+Inf"`...) + case math.IsInf(val, -1): + return append(dst, `"-Inf"`...) + } + return strconv.AppendFloat(dst, val, 'f', -1, bitSize) +} + +// AppendFloat32 converts the input float32 to a string and +// appends the encoded string to the input byte slice. +func (Encoder) AppendFloat32(dst []byte, val float32) []byte { + return appendFloat(dst, float64(val), 32) +} + +// AppendFloats32 encodes the input float32s to json and +// appends the encoded string list to the input byte slice. +func (Encoder) AppendFloats32(dst []byte, vals []float32) []byte { + if len(vals) == 0 { + return append(dst, '[', ']') + } + dst = append(dst, '[') + dst = appendFloat(dst, float64(vals[0]), 32) + if len(vals) > 1 { + for _, val := range vals[1:] { + dst = appendFloat(append(dst, ','), float64(val), 32) + } + } + dst = append(dst, ']') + return dst +} + +// AppendFloat64 converts the input float64 to a string and +// appends the encoded string to the input byte slice. +func (Encoder) AppendFloat64(dst []byte, val float64) []byte { + return appendFloat(dst, val, 64) +} + +// AppendFloats64 encodes the input float64s to json and +// appends the encoded string list to the input byte slice. +func (Encoder) AppendFloats64(dst []byte, vals []float64) []byte { + if len(vals) == 0 { + return append(dst, '[', ']') + } + dst = append(dst, '[') + dst = appendFloat(dst, vals[0], 32) + if len(vals) > 1 { + for _, val := range vals[1:] { + dst = appendFloat(append(dst, ','), val, 64) + } + } + dst = append(dst, ']') + return dst +} + +// AppendInterface marshals the input interface to a string and +// appends the encoded string to the input byte slice. +func (e Encoder) AppendInterface(dst []byte, i interface{}) []byte { + marshaled, err := json.Marshal(i) + if err != nil { + return e.AppendString(dst, fmt.Sprintf("marshaling error: %v", err)) + } + return append(dst, marshaled...) +} + +// AppendObjectData takes in an object that is already in a byte array +// and adds it to the dst. +func (Encoder) AppendObjectData(dst []byte, o []byte) []byte { + // Three conditions apply here: + // 1. new content starts with '{' - which should be dropped OR + // 2. new content starts with '{' - which should be replaced with ',' + // to separate with existing content OR + // 3. existing content has already other fields + if o[0] == '{' { + if len(dst) == 0 { + o = o[1:] + } else { + o[0] = ',' + } + } else if len(dst) > 1 { + dst = append(dst, ',') + } + return append(dst, o...) +} + +// AppendIPAddr adds IPv4 or IPv6 address to dst. +func (e Encoder) AppendIPAddr(dst []byte, ip net.IP) []byte { + return e.AppendString(dst, ip.String()) +} + +// AppendIPPrefix adds IPv4 or IPv6 Prefix (address & mask) to dst. +func (e Encoder) AppendIPPrefix(dst []byte, pfx net.IPNet) []byte { + return e.AppendString(dst, pfx.String()) + +} + +// AppendMACAddr adds MAC address to dst. +func (e Encoder) AppendMACAddr(dst []byte, ha net.HardwareAddr) []byte { + return e.AppendString(dst, ha.String()) +} diff --git a/vendor/github.com/rs/zerolog/log.go b/vendor/github.com/rs/zerolog/log.go new file mode 100644 index 000000000..b1e7ac136 --- /dev/null +++ b/vendor/github.com/rs/zerolog/log.go @@ -0,0 +1,433 @@ +// Package zerolog provides a lightweight logging library dedicated to JSON logging. +// +// A global Logger can be use for simple logging: +// +// import "github.com/rs/zerolog/log" +// +// log.Info().Msg("hello world") +// // Output: {"time":1494567715,"level":"info","message":"hello world"} +// +// NOTE: To import the global logger, import the "log" subpackage "github.com/rs/zerolog/log". +// +// Fields can be added to log messages: +// +// log.Info().Str("foo", "bar").Msg("hello world") +// // Output: {"time":1494567715,"level":"info","message":"hello world","foo":"bar"} +// +// Create logger instance to manage different outputs: +// +// logger := zerolog.New(os.Stderr).With().Timestamp().Logger() +// logger.Info(). +// Str("foo", "bar"). +// Msg("hello world") +// // Output: {"time":1494567715,"level":"info","message":"hello world","foo":"bar"} +// +// Sub-loggers let you chain loggers with additional context: +// +// sublogger := log.With().Str("component": "foo").Logger() +// sublogger.Info().Msg("hello world") +// // Output: {"time":1494567715,"level":"info","message":"hello world","component":"foo"} +// +// Level logging +// +// zerolog.SetGlobalLevel(zerolog.InfoLevel) +// +// log.Debug().Msg("filtered out message") +// log.Info().Msg("routed message") +// +// if e := log.Debug(); e.Enabled() { +// // Compute log output only if enabled. +// value := compute() +// e.Str("foo": value).Msg("some debug message") +// } +// // Output: {"level":"info","time":1494567715,"routed message"} +// +// Customize automatic field names: +// +// log.TimestampFieldName = "t" +// log.LevelFieldName = "p" +// log.MessageFieldName = "m" +// +// log.Info().Msg("hello world") +// // Output: {"t":1494567715,"p":"info","m":"hello world"} +// +// Log with no level and message: +// +// log.Log().Str("foo","bar").Msg("") +// // Output: {"time":1494567715,"foo":"bar"} +// +// Add contextual fields to global Logger: +// +// log.Logger = log.With().Str("foo", "bar").Logger() +// +// Sample logs: +// +// sampled := log.Sample(&zerolog.BasicSampler{N: 10}) +// sampled.Info().Msg("will be logged every 10 messages") +// +// Log with contextual hooks: +// +// // Create the hook: +// type SeverityHook struct{} +// +// func (h SeverityHook) Run(e *zerolog.Event, level zerolog.Level, msg string) { +// if level != zerolog.NoLevel { +// e.Str("severity", level.String()) +// } +// } +// +// // And use it: +// var h SeverityHook +// log := zerolog.New(os.Stdout).Hook(h) +// log.Warn().Msg("") +// // Output: {"level":"warn","severity":"warn"} +// +// +// Caveats +// +// There is no fields deduplication out-of-the-box. +// Using the same key multiple times creates new key in final JSON each time. +// +// logger := zerolog.New(os.Stderr).With().Timestamp().Logger() +// logger.Info(). +// Timestamp(). +// Msg("dup") +// // Output: {"level":"info","time":1494567715,"time":1494567715,"message":"dup"} +// +// In this case, many consumers will take the last value, +// but this is not guaranteed; check yours if in doubt. +package zerolog + +import ( + "fmt" + "io" + "io/ioutil" + "os" + "strconv" +) + +// Level defines log levels. +type Level int8 + +const ( + // DebugLevel defines debug log level. + DebugLevel Level = iota + // InfoLevel defines info log level. + InfoLevel + // WarnLevel defines warn log level. + WarnLevel + // ErrorLevel defines error log level. + ErrorLevel + // FatalLevel defines fatal log level. + FatalLevel + // PanicLevel defines panic log level. + PanicLevel + // NoLevel defines an absent log level. + NoLevel + // Disabled disables the logger. + Disabled + + // TraceLevel defines trace log level. + TraceLevel Level = -1 +) + +func (l Level) String() string { + switch l { + case TraceLevel: + return "trace" + case DebugLevel: + return "debug" + case InfoLevel: + return "info" + case WarnLevel: + return "warn" + case ErrorLevel: + return "error" + case FatalLevel: + return "fatal" + case PanicLevel: + return "panic" + case NoLevel: + return "" + } + return "" +} + +// ParseLevel converts a level string into a zerolog Level value. +// returns an error if the input string does not match known values. +func ParseLevel(levelStr string) (Level, error) { + switch levelStr { + case LevelFieldMarshalFunc(TraceLevel): + return TraceLevel, nil + case LevelFieldMarshalFunc(DebugLevel): + return DebugLevel, nil + case LevelFieldMarshalFunc(InfoLevel): + return InfoLevel, nil + case LevelFieldMarshalFunc(WarnLevel): + return WarnLevel, nil + case LevelFieldMarshalFunc(ErrorLevel): + return ErrorLevel, nil + case LevelFieldMarshalFunc(FatalLevel): + return FatalLevel, nil + case LevelFieldMarshalFunc(PanicLevel): + return PanicLevel, nil + case LevelFieldMarshalFunc(NoLevel): + return NoLevel, nil + } + return NoLevel, fmt.Errorf("Unknown Level String: '%s', defaulting to NoLevel", levelStr) +} + +// A Logger represents an active logging object that generates lines +// of JSON output to an io.Writer. Each logging operation makes a single +// call to the Writer's Write method. There is no guarantee on access +// serialization to the Writer. If your Writer is not thread safe, +// you may consider a sync wrapper. +type Logger struct { + w LevelWriter + level Level + sampler Sampler + context []byte + hooks []Hook +} + +// New creates a root logger with given output writer. If the output writer implements +// the LevelWriter interface, the WriteLevel method will be called instead of the Write +// one. +// +// Each logging operation makes a single call to the Writer's Write method. There is no +// guarantee on access serialization to the Writer. If your Writer is not thread safe, +// you may consider using sync wrapper. +func New(w io.Writer) Logger { + if w == nil { + w = ioutil.Discard + } + lw, ok := w.(LevelWriter) + if !ok { + lw = levelWriterAdapter{w} + } + return Logger{w: lw, level: TraceLevel} +} + +// Nop returns a disabled logger for which all operation are no-op. +func Nop() Logger { + return New(nil).Level(Disabled) +} + +// Output duplicates the current logger and sets w as its output. +func (l Logger) Output(w io.Writer) Logger { + l2 := New(w) + l2.level = l.level + l2.sampler = l.sampler + if len(l.hooks) > 0 { + l2.hooks = append(l2.hooks, l.hooks...) + } + if l.context != nil { + l2.context = make([]byte, len(l.context), cap(l.context)) + copy(l2.context, l.context) + } + return l2 +} + +// With creates a child logger with the field added to its context. +func (l Logger) With() Context { + context := l.context + l.context = make([]byte, 0, 500) + if context != nil { + l.context = append(l.context, context...) + } + return Context{l} +} + +// UpdateContext updates the internal logger's context. +// +// Use this method with caution. If unsure, prefer the With method. +func (l *Logger) UpdateContext(update func(c Context) Context) { + if l == disabledLogger { + return + } + if cap(l.context) == 0 { + l.context = make([]byte, 0, 500) + } + c := update(Context{*l}) + l.context = c.l.context +} + +// Level creates a child logger with the minimum accepted level set to level. +func (l Logger) Level(lvl Level) Logger { + l.level = lvl + return l +} + +// GetLevel returns the current Level of l. +func (l Logger) GetLevel() Level { + return l.level +} + +// Sample returns a logger with the s sampler. +func (l Logger) Sample(s Sampler) Logger { + l.sampler = s + return l +} + +// Hook returns a logger with the h Hook. +func (l Logger) Hook(h Hook) Logger { + l.hooks = append(l.hooks, h) + return l +} + +// Trace starts a new message with trace level. +// +// You must call Msg on the returned event in order to send the event. +func (l *Logger) Trace() *Event { + return l.newEvent(TraceLevel, nil) +} + +// Debug starts a new message with debug level. +// +// You must call Msg on the returned event in order to send the event. +func (l *Logger) Debug() *Event { + return l.newEvent(DebugLevel, nil) +} + +// Info starts a new message with info level. +// +// You must call Msg on the returned event in order to send the event. +func (l *Logger) Info() *Event { + return l.newEvent(InfoLevel, nil) +} + +// Warn starts a new message with warn level. +// +// You must call Msg on the returned event in order to send the event. +func (l *Logger) Warn() *Event { + return l.newEvent(WarnLevel, nil) +} + +// Error starts a new message with error level. +// +// You must call Msg on the returned event in order to send the event. +func (l *Logger) Error() *Event { + return l.newEvent(ErrorLevel, nil) +} + +// Err starts a new message with error level with err as a field if not nil or +// with info level if err is nil. +// +// You must call Msg on the returned event in order to send the event. +func (l *Logger) Err(err error) *Event { + if err != nil { + return l.Error().Err(err) + } + + return l.Info() +} + +// Fatal starts a new message with fatal level. The os.Exit(1) function +// is called by the Msg method, which terminates the program immediately. +// +// You must call Msg on the returned event in order to send the event. +func (l *Logger) Fatal() *Event { + return l.newEvent(FatalLevel, func(msg string) { os.Exit(1) }) +} + +// Panic starts a new message with panic level. The panic() function +// is called by the Msg method, which stops the ordinary flow of a goroutine. +// +// You must call Msg on the returned event in order to send the event. +func (l *Logger) Panic() *Event { + return l.newEvent(PanicLevel, func(msg string) { panic(msg) }) +} + +// WithLevel starts a new message with level. Unlike Fatal and Panic +// methods, WithLevel does not terminate the program or stop the ordinary +// flow of a gourotine when used with their respective levels. +// +// You must call Msg on the returned event in order to send the event. +func (l *Logger) WithLevel(level Level) *Event { + switch level { + case TraceLevel: + return l.Trace() + case DebugLevel: + return l.Debug() + case InfoLevel: + return l.Info() + case WarnLevel: + return l.Warn() + case ErrorLevel: + return l.Error() + case FatalLevel: + return l.newEvent(FatalLevel, nil) + case PanicLevel: + return l.newEvent(PanicLevel, nil) + case NoLevel: + return l.Log() + case Disabled: + return nil + default: + panic("zerolog: WithLevel(): invalid level: " + strconv.Itoa(int(level))) + } +} + +// Log starts a new message with no level. Setting GlobalLevel to Disabled +// will still disable events produced by this method. +// +// You must call Msg on the returned event in order to send the event. +func (l *Logger) Log() *Event { + return l.newEvent(NoLevel, nil) +} + +// Print sends a log event using debug level and no extra field. +// Arguments are handled in the manner of fmt.Print. +func (l *Logger) Print(v ...interface{}) { + if e := l.Debug(); e.Enabled() { + e.Msg(fmt.Sprint(v...)) + } +} + +// Printf sends a log event using debug level and no extra field. +// Arguments are handled in the manner of fmt.Printf. +func (l *Logger) Printf(format string, v ...interface{}) { + if e := l.Debug(); e.Enabled() { + e.Msg(fmt.Sprintf(format, v...)) + } +} + +// Write implements the io.Writer interface. This is useful to set as a writer +// for the standard library log. +func (l Logger) Write(p []byte) (n int, err error) { + n = len(p) + if n > 0 && p[n-1] == '\n' { + // Trim CR added by stdlog. + p = p[0 : n-1] + } + l.Log().Msg(string(p)) + return +} + +func (l *Logger) newEvent(level Level, done func(string)) *Event { + enabled := l.should(level) + if !enabled { + return nil + } + e := newEvent(l.w, level) + e.done = done + e.ch = l.hooks + if level != NoLevel { + e.Str(LevelFieldName, LevelFieldMarshalFunc(level)) + } + if l.context != nil && len(l.context) > 0 { + e.buf = enc.AppendObjectData(e.buf, l.context) + } + return e +} + +// should returns true if the log event should be logged. +func (l *Logger) should(lvl Level) bool { + if lvl < l.level || lvl < GlobalLevel() { + return false + } + if l.sampler != nil && !samplingDisabled() { + return l.sampler.Sample(lvl) + } + return true +} diff --git a/vendor/github.com/rs/zerolog/log/log.go b/vendor/github.com/rs/zerolog/log/log.go new file mode 100644 index 000000000..b96f1c144 --- /dev/null +++ b/vendor/github.com/rs/zerolog/log/log.go @@ -0,0 +1,130 @@ +// Package log provides a global logger for zerolog. +package log + +import ( + "context" + "io" + "os" + + "github.com/rs/zerolog" +) + +// Logger is the global logger. +var Logger = zerolog.New(os.Stderr).With().Timestamp().Logger() + +// Output duplicates the global logger and sets w as its output. +func Output(w io.Writer) zerolog.Logger { + return Logger.Output(w) +} + +// With creates a child logger with the field added to its context. +func With() zerolog.Context { + return Logger.With() +} + +// Level creates a child logger with the minimum accepted level set to level. +func Level(level zerolog.Level) zerolog.Logger { + return Logger.Level(level) +} + +// Sample returns a logger with the s sampler. +func Sample(s zerolog.Sampler) zerolog.Logger { + return Logger.Sample(s) +} + +// Hook returns a logger with the h Hook. +func Hook(h zerolog.Hook) zerolog.Logger { + return Logger.Hook(h) +} + +// Err starts a new message with error level with err as a field if not nil or +// with info level if err is nil. +// +// You must call Msg on the returned event in order to send the event. +func Err(err error) *zerolog.Event { + return Logger.Err(err) +} + +// Trace starts a new message with trace level. +// +// You must call Msg on the returned event in order to send the event. +func Trace() *zerolog.Event { + return Logger.Trace() +} + +// Debug starts a new message with debug level. +// +// You must call Msg on the returned event in order to send the event. +func Debug() *zerolog.Event { + return Logger.Debug() +} + +// Info starts a new message with info level. +// +// You must call Msg on the returned event in order to send the event. +func Info() *zerolog.Event { + return Logger.Info() +} + +// Warn starts a new message with warn level. +// +// You must call Msg on the returned event in order to send the event. +func Warn() *zerolog.Event { + return Logger.Warn() +} + +// Error starts a new message with error level. +// +// You must call Msg on the returned event in order to send the event. +func Error() *zerolog.Event { + return Logger.Error() +} + +// Fatal starts a new message with fatal level. The os.Exit(1) function +// is called by the Msg method. +// +// You must call Msg on the returned event in order to send the event. +func Fatal() *zerolog.Event { + return Logger.Fatal() +} + +// Panic starts a new message with panic level. The message is also sent +// to the panic function. +// +// You must call Msg on the returned event in order to send the event. +func Panic() *zerolog.Event { + return Logger.Panic() +} + +// WithLevel starts a new message with level. +// +// You must call Msg on the returned event in order to send the event. +func WithLevel(level zerolog.Level) *zerolog.Event { + return Logger.WithLevel(level) +} + +// Log starts a new message with no level. Setting zerolog.GlobalLevel to +// zerolog.Disabled will still disable events produced by this method. +// +// You must call Msg on the returned event in order to send the event. +func Log() *zerolog.Event { + return Logger.Log() +} + +// Print sends a log event using debug level and no extra field. +// Arguments are handled in the manner of fmt.Print. +func Print(v ...interface{}) { + Logger.Print(v...) +} + +// Printf sends a log event using debug level and no extra field. +// Arguments are handled in the manner of fmt.Printf. +func Printf(format string, v ...interface{}) { + Logger.Printf(format, v...) +} + +// Ctx returns the Logger associated with the ctx. If no logger +// is associated, a disabled logger is returned. +func Ctx(ctx context.Context) *zerolog.Logger { + return zerolog.Ctx(ctx) +} diff --git a/vendor/github.com/rs/zerolog/not_go112.go b/vendor/github.com/rs/zerolog/not_go112.go new file mode 100644 index 000000000..4c43c9e76 --- /dev/null +++ b/vendor/github.com/rs/zerolog/not_go112.go @@ -0,0 +1,5 @@ +// +build !go1.12 + +package zerolog + +const contextCallerSkipFrameCount = 3 diff --git a/vendor/github.com/rs/zerolog/pretty.png b/vendor/github.com/rs/zerolog/pretty.png new file mode 100644 index 000000000..34e43085f Binary files /dev/null and b/vendor/github.com/rs/zerolog/pretty.png differ diff --git a/vendor/github.com/rs/zerolog/sampler.go b/vendor/github.com/rs/zerolog/sampler.go new file mode 100644 index 000000000..a99629eb0 --- /dev/null +++ b/vendor/github.com/rs/zerolog/sampler.go @@ -0,0 +1,134 @@ +package zerolog + +import ( + "math/rand" + "sync/atomic" + "time" +) + +var ( + // Often samples log every ~ 10 events. + Often = RandomSampler(10) + // Sometimes samples log every ~ 100 events. + Sometimes = RandomSampler(100) + // Rarely samples log every ~ 1000 events. + Rarely = RandomSampler(1000) +) + +// Sampler defines an interface to a log sampler. +type Sampler interface { + // Sample returns true if the event should be part of the sample, false if + // the event should be dropped. + Sample(lvl Level) bool +} + +// RandomSampler use a PRNG to randomly sample an event out of N events, +// regardless of their level. +type RandomSampler uint32 + +// Sample implements the Sampler interface. +func (s RandomSampler) Sample(lvl Level) bool { + if s <= 0 { + return false + } + if rand.Intn(int(s)) != 0 { + return false + } + return true +} + +// BasicSampler is a sampler that will send every Nth events, regardless of +// there level. +type BasicSampler struct { + N uint32 + counter uint32 +} + +// Sample implements the Sampler interface. +func (s *BasicSampler) Sample(lvl Level) bool { + n := s.N + if n == 1 { + return true + } + c := atomic.AddUint32(&s.counter, 1) + return c%n == 1 +} + +// BurstSampler lets Burst events pass per Period then pass the decision to +// NextSampler. If Sampler is not set, all subsequent events are rejected. +type BurstSampler struct { + // Burst is the maximum number of event per period allowed before calling + // NextSampler. + Burst uint32 + // Period defines the burst period. If 0, NextSampler is always called. + Period time.Duration + // NextSampler is the sampler used after the burst is reached. If nil, + // events are always rejected after the burst. + NextSampler Sampler + + counter uint32 + resetAt int64 +} + +// Sample implements the Sampler interface. +func (s *BurstSampler) Sample(lvl Level) bool { + if s.Burst > 0 && s.Period > 0 { + if s.inc() <= s.Burst { + return true + } + } + if s.NextSampler == nil { + return false + } + return s.NextSampler.Sample(lvl) +} + +func (s *BurstSampler) inc() uint32 { + now := time.Now().UnixNano() + resetAt := atomic.LoadInt64(&s.resetAt) + var c uint32 + if now > resetAt { + c = 1 + atomic.StoreUint32(&s.counter, c) + newResetAt := now + s.Period.Nanoseconds() + reset := atomic.CompareAndSwapInt64(&s.resetAt, resetAt, newResetAt) + if !reset { + // Lost the race with another goroutine trying to reset. + c = atomic.AddUint32(&s.counter, 1) + } + } else { + c = atomic.AddUint32(&s.counter, 1) + } + return c +} + +// LevelSampler applies a different sampler for each level. +type LevelSampler struct { + TraceSampler, DebugSampler, InfoSampler, WarnSampler, ErrorSampler Sampler +} + +func (s LevelSampler) Sample(lvl Level) bool { + switch lvl { + case TraceLevel: + if s.TraceSampler != nil { + return s.TraceSampler.Sample(lvl) + } + case DebugLevel: + if s.DebugSampler != nil { + return s.DebugSampler.Sample(lvl) + } + case InfoLevel: + if s.InfoSampler != nil { + return s.InfoSampler.Sample(lvl) + } + case WarnLevel: + if s.WarnSampler != nil { + return s.WarnSampler.Sample(lvl) + } + case ErrorLevel: + if s.ErrorSampler != nil { + return s.ErrorSampler.Sample(lvl) + } + } + return true +} diff --git a/vendor/github.com/rs/zerolog/syslog.go b/vendor/github.com/rs/zerolog/syslog.go new file mode 100644 index 000000000..ef3b2c83b --- /dev/null +++ b/vendor/github.com/rs/zerolog/syslog.go @@ -0,0 +1,58 @@ +// +build !windows +// +build !binary_log + +package zerolog + +import ( + "io" +) + +// SyslogWriter is an interface matching a syslog.Writer struct. +type SyslogWriter interface { + io.Writer + Debug(m string) error + Info(m string) error + Warning(m string) error + Err(m string) error + Emerg(m string) error + Crit(m string) error +} + +type syslogWriter struct { + w SyslogWriter +} + +// SyslogLevelWriter wraps a SyslogWriter and call the right syslog level +// method matching the zerolog level. +func SyslogLevelWriter(w SyslogWriter) LevelWriter { + return syslogWriter{w} +} + +func (sw syslogWriter) Write(p []byte) (n int, err error) { + return sw.w.Write(p) +} + +// WriteLevel implements LevelWriter interface. +func (sw syslogWriter) WriteLevel(level Level, p []byte) (n int, err error) { + switch level { + case TraceLevel: + case DebugLevel: + err = sw.w.Debug(string(p)) + case InfoLevel: + err = sw.w.Info(string(p)) + case WarnLevel: + err = sw.w.Warning(string(p)) + case ErrorLevel: + err = sw.w.Err(string(p)) + case FatalLevel: + err = sw.w.Emerg(string(p)) + case PanicLevel: + err = sw.w.Crit(string(p)) + case NoLevel: + err = sw.w.Info(string(p)) + default: + panic("invalid level") + } + n = len(p) + return +} diff --git a/vendor/github.com/rs/zerolog/writer.go b/vendor/github.com/rs/zerolog/writer.go new file mode 100644 index 000000000..a58d71776 --- /dev/null +++ b/vendor/github.com/rs/zerolog/writer.go @@ -0,0 +1,100 @@ +package zerolog + +import ( + "io" + "sync" +) + +// LevelWriter defines as interface a writer may implement in order +// to receive level information with payload. +type LevelWriter interface { + io.Writer + WriteLevel(level Level, p []byte) (n int, err error) +} + +type levelWriterAdapter struct { + io.Writer +} + +func (lw levelWriterAdapter) WriteLevel(l Level, p []byte) (n int, err error) { + return lw.Write(p) +} + +type syncWriter struct { + mu sync.Mutex + lw LevelWriter +} + +// SyncWriter wraps w so that each call to Write is synchronized with a mutex. +// This syncer can be the call to writer's Write method is not thread safe. +// Note that os.File Write operation is using write() syscall which is supposed +// to be thread-safe on POSIX systems. So there is no need to use this with +// os.File on such systems as zerolog guaranties to issue a single Write call +// per log event. +func SyncWriter(w io.Writer) io.Writer { + if lw, ok := w.(LevelWriter); ok { + return &syncWriter{lw: lw} + } + return &syncWriter{lw: levelWriterAdapter{w}} +} + +// Write implements the io.Writer interface. +func (s *syncWriter) Write(p []byte) (n int, err error) { + s.mu.Lock() + defer s.mu.Unlock() + return s.lw.Write(p) +} + +// WriteLevel implements the LevelWriter interface. +func (s *syncWriter) WriteLevel(l Level, p []byte) (n int, err error) { + s.mu.Lock() + defer s.mu.Unlock() + return s.lw.WriteLevel(l, p) +} + +type multiLevelWriter struct { + writers []LevelWriter +} + +func (t multiLevelWriter) Write(p []byte) (n int, err error) { + for _, w := range t.writers { + n, err = w.Write(p) + if err != nil { + return + } + if n != len(p) { + err = io.ErrShortWrite + return + } + } + return len(p), nil +} + +func (t multiLevelWriter) WriteLevel(l Level, p []byte) (n int, err error) { + for _, w := range t.writers { + n, err = w.WriteLevel(l, p) + if err != nil { + return + } + if n != len(p) { + err = io.ErrShortWrite + return + } + } + return len(p), nil +} + +// MultiLevelWriter creates a writer that duplicates its writes to all the +// provided writers, similar to the Unix tee(1) command. If some writers +// implement LevelWriter, their WriteLevel method will be used instead of Write. +func MultiLevelWriter(writers ...io.Writer) LevelWriter { + lwriters := make([]LevelWriter, 0, len(writers)) + for _, w := range writers { + if lw, ok := w.(LevelWriter); ok { + lwriters = append(lwriters, lw) + } else { + lwriters = append(lwriters, levelWriterAdapter{w}) + } + } + return multiLevelWriter{lwriters} +} diff --git a/vendor/github.com/russross/blackfriday/v2/.gitignore b/vendor/github.com/russross/blackfriday/v2/.gitignore new file mode 100644 index 000000000..75623dccc --- /dev/null +++ b/vendor/github.com/russross/blackfriday/v2/.gitignore @@ -0,0 +1,8 @@ +*.out +*.swp +*.8 +*.6 +_obj +_test* +markdown +tags diff --git a/vendor/github.com/russross/blackfriday/v2/.travis.yml b/vendor/github.com/russross/blackfriday/v2/.travis.yml new file mode 100644 index 000000000..b0b525a5a --- /dev/null +++ b/vendor/github.com/russross/blackfriday/v2/.travis.yml @@ -0,0 +1,17 @@ +sudo: false +language: go +go: + - "1.10.x" + - "1.11.x" + - tip +matrix: + fast_finish: true + allow_failures: + - go: tip +install: + - # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step). +script: + - go get -t -v ./... + - diff -u <(echo -n) <(gofmt -d -s .) + - go tool vet . + - go test -v ./... diff --git a/vendor/github.com/russross/blackfriday/v2/LICENSE.txt b/vendor/github.com/russross/blackfriday/v2/LICENSE.txt new file mode 100644 index 000000000..2885af360 --- /dev/null +++ b/vendor/github.com/russross/blackfriday/v2/LICENSE.txt @@ -0,0 +1,29 @@ +Blackfriday is distributed under the Simplified BSD License: + +> Copyright © 2011 Russ Ross +> All rights reserved. +> +> Redistribution and use in source and binary forms, with or without +> modification, are permitted provided that the following conditions +> are met: +> +> 1. Redistributions of source code must retain the above copyright +> notice, this list of conditions and the following disclaimer. +> +> 2. Redistributions in binary form must reproduce the above +> copyright notice, this list of conditions and the following +> disclaimer in the documentation and/or other materials provided with +> the distribution. +> +> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +> "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +> LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +> FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +> COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +> INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +> BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +> LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +> CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +> LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +> ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +> POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/russross/blackfriday/v2/README.md b/vendor/github.com/russross/blackfriday/v2/README.md new file mode 100644 index 000000000..d5a8649bd --- /dev/null +++ b/vendor/github.com/russross/blackfriday/v2/README.md @@ -0,0 +1,291 @@ +Blackfriday [![Build Status](https://travis-ci.org/russross/blackfriday.svg?branch=master)](https://travis-ci.org/russross/blackfriday) +=========== + +Blackfriday is a [Markdown][1] processor implemented in [Go][2]. It +is paranoid about its input (so you can safely feed it user-supplied +data), it is fast, it supports common extensions (tables, smart +punctuation substitutions, etc.), and it is safe for all utf-8 +(unicode) input. + +HTML output is currently supported, along with Smartypants +extensions. + +It started as a translation from C of [Sundown][3]. + + +Installation +------------ + +Blackfriday is compatible with any modern Go release. With Go 1.7 and git +installed: + + go get gopkg.in/russross/blackfriday.v2 + +will download, compile, and install the package into your `$GOPATH` +directory hierarchy. Alternatively, you can achieve the same if you +import it into a project: + + import "gopkg.in/russross/blackfriday.v2" + +and `go get` without parameters. + + +Versions +-------- + +Currently maintained and recommended version of Blackfriday is `v2`. It's being +developed on its own branch: https://github.com/russross/blackfriday/tree/v2 and the +documentation is available at +https://godoc.org/gopkg.in/russross/blackfriday.v2. + +It is `go get`-able via via [gopkg.in][6] at `gopkg.in/russross/blackfriday.v2`, +but we highly recommend using package management tool like [dep][7] or +[Glide][8] and make use of semantic versioning. With package management you +should import `github.com/russross/blackfriday` and specify that you're using +version 2.0.0. + +Version 2 offers a number of improvements over v1: + +* Cleaned up API +* A separate call to [`Parse`][4], which produces an abstract syntax tree for + the document +* Latest bug fixes +* Flexibility to easily add your own rendering extensions + +Potential drawbacks: + +* Our benchmarks show v2 to be slightly slower than v1. Currently in the + ballpark of around 15%. +* API breakage. If you can't afford modifying your code to adhere to the new API + and don't care too much about the new features, v2 is probably not for you. +* Several bug fixes are trailing behind and still need to be forward-ported to + v2. See issue [#348](https://github.com/russross/blackfriday/issues/348) for + tracking. + +Usage +----- + +For the most sensible markdown processing, it is as simple as getting your input +into a byte slice and calling: + +```go +output := blackfriday.Run(input) +``` + +Your input will be parsed and the output rendered with a set of most popular +extensions enabled. If you want the most basic feature set, corresponding with +the bare Markdown specification, use: + +```go +output := blackfriday.Run(input, blackfriday.WithNoExtensions()) +``` + +### Sanitize untrusted content + +Blackfriday itself does nothing to protect against malicious content. If you are +dealing with user-supplied markdown, we recommend running Blackfriday's output +through HTML sanitizer such as [Bluemonday][5]. + +Here's an example of simple usage of Blackfriday together with Bluemonday: + +```go +import ( + "github.com/microcosm-cc/bluemonday" + "github.com/russross/blackfriday" +) + +// ... +unsafe := blackfriday.Run(input) +html := bluemonday.UGCPolicy().SanitizeBytes(unsafe) +``` + +### Custom options + +If you want to customize the set of options, use `blackfriday.WithExtensions`, +`blackfriday.WithRenderer` and `blackfriday.WithRefOverride`. + +You can also check out `blackfriday-tool` for a more complete example +of how to use it. Download and install it using: + + go get github.com/russross/blackfriday-tool + +This is a simple command-line tool that allows you to process a +markdown file using a standalone program. You can also browse the +source directly on github if you are just looking for some example +code: + +* + +Note that if you have not already done so, installing +`blackfriday-tool` will be sufficient to download and install +blackfriday in addition to the tool itself. The tool binary will be +installed in `$GOPATH/bin`. This is a statically-linked binary that +can be copied to wherever you need it without worrying about +dependencies and library versions. + + +Features +-------- + +All features of Sundown are supported, including: + +* **Compatibility**. The Markdown v1.0.3 test suite passes with + the `--tidy` option. Without `--tidy`, the differences are + mostly in whitespace and entity escaping, where blackfriday is + more consistent and cleaner. + +* **Common extensions**, including table support, fenced code + blocks, autolinks, strikethroughs, non-strict emphasis, etc. + +* **Safety**. Blackfriday is paranoid when parsing, making it safe + to feed untrusted user input without fear of bad things + happening. The test suite stress tests this and there are no + known inputs that make it crash. If you find one, please let me + know and send me the input that does it. + + NOTE: "safety" in this context means *runtime safety only*. In order to + protect yourself against JavaScript injection in untrusted content, see + [this example](https://github.com/russross/blackfriday#sanitize-untrusted-content). + +* **Fast processing**. It is fast enough to render on-demand in + most web applications without having to cache the output. + +* **Thread safety**. You can run multiple parsers in different + goroutines without ill effect. There is no dependence on global + shared state. + +* **Minimal dependencies**. Blackfriday only depends on standard + library packages in Go. The source code is pretty + self-contained, so it is easy to add to any project, including + Google App Engine projects. + +* **Standards compliant**. Output successfully validates using the + W3C validation tool for HTML 4.01 and XHTML 1.0 Transitional. + + +Extensions +---------- + +In addition to the standard markdown syntax, this package +implements the following extensions: + +* **Intra-word emphasis supression**. The `_` character is + commonly used inside words when discussing code, so having + markdown interpret it as an emphasis command is usually the + wrong thing. Blackfriday lets you treat all emphasis markers as + normal characters when they occur inside a word. + +* **Tables**. Tables can be created by drawing them in the input + using a simple syntax: + + ``` + Name | Age + --------|------ + Bob | 27 + Alice | 23 + ``` + +* **Fenced code blocks**. In addition to the normal 4-space + indentation to mark code blocks, you can explicitly mark them + and supply a language (to make syntax highlighting simple). Just + mark it like this: + + ```go + func getTrue() bool { + return true + } + ``` + + You can use 3 or more backticks to mark the beginning of the + block, and the same number to mark the end of the block. + +* **Definition lists**. A simple definition list is made of a single-line + term followed by a colon and the definition for that term. + + Cat + : Fluffy animal everyone likes + + Internet + : Vector of transmission for pictures of cats + + Terms must be separated from the previous definition by a blank line. + +* **Footnotes**. A marker in the text that will become a superscript number; + a footnote definition that will be placed in a list of footnotes at the + end of the document. A footnote looks like this: + + This is a footnote.[^1] + + [^1]: the footnote text. + +* **Autolinking**. Blackfriday can find URLs that have not been + explicitly marked as links and turn them into links. + +* **Strikethrough**. Use two tildes (`~~`) to mark text that + should be crossed out. + +* **Hard line breaks**. With this extension enabled newlines in the input + translate into line breaks in the output. This extension is off by default. + +* **Smart quotes**. Smartypants-style punctuation substitution is + supported, turning normal double- and single-quote marks into + curly quotes, etc. + +* **LaTeX-style dash parsing** is an additional option, where `--` + is translated into `–`, and `---` is translated into + `—`. This differs from most smartypants processors, which + turn a single hyphen into an ndash and a double hyphen into an + mdash. + +* **Smart fractions**, where anything that looks like a fraction + is translated into suitable HTML (instead of just a few special + cases like most smartypant processors). For example, `4/5` + becomes `45`, which renders as + 45. + + +Other renderers +--------------- + +Blackfriday is structured to allow alternative rendering engines. Here +are a few of note: + +* [github_flavored_markdown](https://godoc.org/github.com/shurcooL/github_flavored_markdown): + provides a GitHub Flavored Markdown renderer with fenced code block + highlighting, clickable heading anchor links. + + It's not customizable, and its goal is to produce HTML output + equivalent to the [GitHub Markdown API endpoint](https://developer.github.com/v3/markdown/#render-a-markdown-document-in-raw-mode), + except the rendering is performed locally. + +* [markdownfmt](https://github.com/shurcooL/markdownfmt): like gofmt, + but for markdown. + +* [LaTeX output](https://github.com/Ambrevar/Blackfriday-LaTeX): + renders output as LaTeX. + +* [Blackfriday-Confluence](https://github.com/kentaro-m/blackfriday-confluence): provides a [Confluence Wiki Markup](https://confluence.atlassian.com/doc/confluence-wiki-markup-251003035.html) renderer. + + +Todo +---- + +* More unit testing +* Improve unicode support. It does not understand all unicode + rules (about what constitutes a letter, a punctuation symbol, + etc.), so it may fail to detect word boundaries correctly in + some instances. It is safe on all utf-8 input. + + +License +------- + +[Blackfriday is distributed under the Simplified BSD License](LICENSE.txt) + + + [1]: https://daringfireball.net/projects/markdown/ "Markdown" + [2]: https://golang.org/ "Go Language" + [3]: https://github.com/vmg/sundown "Sundown" + [4]: https://godoc.org/gopkg.in/russross/blackfriday.v2#Parse "Parse func" + [5]: https://github.com/microcosm-cc/bluemonday "Bluemonday" + [6]: https://labix.org/gopkg.in "gopkg.in" diff --git a/vendor/github.com/russross/blackfriday/v2/block.go b/vendor/github.com/russross/blackfriday/v2/block.go new file mode 100644 index 000000000..b8607474e --- /dev/null +++ b/vendor/github.com/russross/blackfriday/v2/block.go @@ -0,0 +1,1590 @@ +// +// Blackfriday Markdown Processor +// Available at http://github.com/russross/blackfriday +// +// Copyright © 2011 Russ Ross . +// Distributed under the Simplified BSD License. +// See README.md for details. +// + +// +// Functions to parse block-level elements. +// + +package blackfriday + +import ( + "bytes" + "html" + "regexp" + "strings" + + "github.com/shurcooL/sanitized_anchor_name" +) + +const ( + charEntity = "&(?:#x[a-f0-9]{1,8}|#[0-9]{1,8}|[a-z][a-z0-9]{1,31});" + escapable = "[!\"#$%&'()*+,./:;<=>?@[\\\\\\]^_`{|}~-]" +) + +var ( + reBackslashOrAmp = regexp.MustCompile("[\\&]") + reEntityOrEscapedChar = regexp.MustCompile("(?i)\\\\" + escapable + "|" + charEntity) +) + +// Parse block-level data. +// Note: this function and many that it calls assume that +// the input buffer ends with a newline. +func (p *Markdown) block(data []byte) { + // this is called recursively: enforce a maximum depth + if p.nesting >= p.maxNesting { + return + } + p.nesting++ + + // parse out one block-level construct at a time + for len(data) > 0 { + // prefixed heading: + // + // # Heading 1 + // ## Heading 2 + // ... + // ###### Heading 6 + if p.isPrefixHeading(data) { + data = data[p.prefixHeading(data):] + continue + } + + // block of preformatted HTML: + // + //
    + // ... + //
    + if data[0] == '<' { + if i := p.html(data, true); i > 0 { + data = data[i:] + continue + } + } + + // title block + // + // % stuff + // % more stuff + // % even more stuff + if p.extensions&Titleblock != 0 { + if data[0] == '%' { + if i := p.titleBlock(data, true); i > 0 { + data = data[i:] + continue + } + } + } + + // blank lines. note: returns the # of bytes to skip + if i := p.isEmpty(data); i > 0 { + data = data[i:] + continue + } + + // indented code block: + // + // func max(a, b int) int { + // if a > b { + // return a + // } + // return b + // } + if p.codePrefix(data) > 0 { + data = data[p.code(data):] + continue + } + + // fenced code block: + // + // ``` go + // func fact(n int) int { + // if n <= 1 { + // return n + // } + // return n * fact(n-1) + // } + // ``` + if p.extensions&FencedCode != 0 { + if i := p.fencedCodeBlock(data, true); i > 0 { + data = data[i:] + continue + } + } + + // horizontal rule: + // + // ------ + // or + // ****** + // or + // ______ + if p.isHRule(data) { + p.addBlock(HorizontalRule, nil) + var i int + for i = 0; i < len(data) && data[i] != '\n'; i++ { + } + data = data[i:] + continue + } + + // block quote: + // + // > A big quote I found somewhere + // > on the web + if p.quotePrefix(data) > 0 { + data = data[p.quote(data):] + continue + } + + // table: + // + // Name | Age | Phone + // ------|-----|--------- + // Bob | 31 | 555-1234 + // Alice | 27 | 555-4321 + if p.extensions&Tables != 0 { + if i := p.table(data); i > 0 { + data = data[i:] + continue + } + } + + // an itemized/unordered list: + // + // * Item 1 + // * Item 2 + // + // also works with + or - + if p.uliPrefix(data) > 0 { + data = data[p.list(data, 0):] + continue + } + + // a numbered/ordered list: + // + // 1. Item 1 + // 2. Item 2 + if p.oliPrefix(data) > 0 { + data = data[p.list(data, ListTypeOrdered):] + continue + } + + // definition lists: + // + // Term 1 + // : Definition a + // : Definition b + // + // Term 2 + // : Definition c + if p.extensions&DefinitionLists != 0 { + if p.dliPrefix(data) > 0 { + data = data[p.list(data, ListTypeDefinition):] + continue + } + } + + // anything else must look like a normal paragraph + // note: this finds underlined headings, too + data = data[p.paragraph(data):] + } + + p.nesting-- +} + +func (p *Markdown) addBlock(typ NodeType, content []byte) *Node { + p.closeUnmatchedBlocks() + container := p.addChild(typ, 0) + container.content = content + return container +} + +func (p *Markdown) isPrefixHeading(data []byte) bool { + if data[0] != '#' { + return false + } + + if p.extensions&SpaceHeadings != 0 { + level := 0 + for level < 6 && level < len(data) && data[level] == '#' { + level++ + } + if level == len(data) || data[level] != ' ' { + return false + } + } + return true +} + +func (p *Markdown) prefixHeading(data []byte) int { + level := 0 + for level < 6 && level < len(data) && data[level] == '#' { + level++ + } + i := skipChar(data, level, ' ') + end := skipUntilChar(data, i, '\n') + skip := end + id := "" + if p.extensions&HeadingIDs != 0 { + j, k := 0, 0 + // find start/end of heading id + for j = i; j < end-1 && (data[j] != '{' || data[j+1] != '#'); j++ { + } + for k = j + 1; k < end && data[k] != '}'; k++ { + } + // extract heading id iff found + if j < end && k < end { + id = string(data[j+2 : k]) + end = j + skip = k + 1 + for end > 0 && data[end-1] == ' ' { + end-- + } + } + } + for end > 0 && data[end-1] == '#' { + if isBackslashEscaped(data, end-1) { + break + } + end-- + } + for end > 0 && data[end-1] == ' ' { + end-- + } + if end > i { + if id == "" && p.extensions&AutoHeadingIDs != 0 { + id = sanitized_anchor_name.Create(string(data[i:end])) + } + block := p.addBlock(Heading, data[i:end]) + block.HeadingID = id + block.Level = level + } + return skip +} + +func (p *Markdown) isUnderlinedHeading(data []byte) int { + // test of level 1 heading + if data[0] == '=' { + i := skipChar(data, 1, '=') + i = skipChar(data, i, ' ') + if i < len(data) && data[i] == '\n' { + return 1 + } + return 0 + } + + // test of level 2 heading + if data[0] == '-' { + i := skipChar(data, 1, '-') + i = skipChar(data, i, ' ') + if i < len(data) && data[i] == '\n' { + return 2 + } + return 0 + } + + return 0 +} + +func (p *Markdown) titleBlock(data []byte, doRender bool) int { + if data[0] != '%' { + return 0 + } + splitData := bytes.Split(data, []byte("\n")) + var i int + for idx, b := range splitData { + if !bytes.HasPrefix(b, []byte("%")) { + i = idx // - 1 + break + } + } + + data = bytes.Join(splitData[0:i], []byte("\n")) + consumed := len(data) + data = bytes.TrimPrefix(data, []byte("% ")) + data = bytes.Replace(data, []byte("\n% "), []byte("\n"), -1) + block := p.addBlock(Heading, data) + block.Level = 1 + block.IsTitleblock = true + + return consumed +} + +func (p *Markdown) html(data []byte, doRender bool) int { + var i, j int + + // identify the opening tag + if data[0] != '<' { + return 0 + } + curtag, tagfound := p.htmlFindTag(data[1:]) + + // handle special cases + if !tagfound { + // check for an HTML comment + if size := p.htmlComment(data, doRender); size > 0 { + return size + } + + // check for an
    tag + if size := p.htmlHr(data, doRender); size > 0 { + return size + } + + // no special case recognized + return 0 + } + + // look for an unindented matching closing tag + // followed by a blank line + found := false + /* + closetag := []byte("\n") + j = len(curtag) + 1 + for !found { + // scan for a closing tag at the beginning of a line + if skip := bytes.Index(data[j:], closetag); skip >= 0 { + j += skip + len(closetag) + } else { + break + } + + // see if it is the only thing on the line + if skip := p.isEmpty(data[j:]); skip > 0 { + // see if it is followed by a blank line/eof + j += skip + if j >= len(data) { + found = true + i = j + } else { + if skip := p.isEmpty(data[j:]); skip > 0 { + j += skip + found = true + i = j + } + } + } + } + */ + + // if not found, try a second pass looking for indented match + // but not if tag is "ins" or "del" (following original Markdown.pl) + if !found && curtag != "ins" && curtag != "del" { + i = 1 + for i < len(data) { + i++ + for i < len(data) && !(data[i-1] == '<' && data[i] == '/') { + i++ + } + + if i+2+len(curtag) >= len(data) { + break + } + + j = p.htmlFindEnd(curtag, data[i-1:]) + + if j > 0 { + i += j - 1 + found = true + break + } + } + } + + if !found { + return 0 + } + + // the end of the block has been found + if doRender { + // trim newlines + end := i + for end > 0 && data[end-1] == '\n' { + end-- + } + finalizeHTMLBlock(p.addBlock(HTMLBlock, data[:end])) + } + + return i +} + +func finalizeHTMLBlock(block *Node) { + block.Literal = block.content + block.content = nil +} + +// HTML comment, lax form +func (p *Markdown) htmlComment(data []byte, doRender bool) int { + i := p.inlineHTMLComment(data) + // needs to end with a blank line + if j := p.isEmpty(data[i:]); j > 0 { + size := i + j + if doRender { + // trim trailing newlines + end := size + for end > 0 && data[end-1] == '\n' { + end-- + } + block := p.addBlock(HTMLBlock, data[:end]) + finalizeHTMLBlock(block) + } + return size + } + return 0 +} + +// HR, which is the only self-closing block tag considered +func (p *Markdown) htmlHr(data []byte, doRender bool) int { + if len(data) < 4 { + return 0 + } + if data[0] != '<' || (data[1] != 'h' && data[1] != 'H') || (data[2] != 'r' && data[2] != 'R') { + return 0 + } + if data[3] != ' ' && data[3] != '/' && data[3] != '>' { + // not an
    tag after all; at least not a valid one + return 0 + } + i := 3 + for i < len(data) && data[i] != '>' && data[i] != '\n' { + i++ + } + if i < len(data) && data[i] == '>' { + i++ + if j := p.isEmpty(data[i:]); j > 0 { + size := i + j + if doRender { + // trim newlines + end := size + for end > 0 && data[end-1] == '\n' { + end-- + } + finalizeHTMLBlock(p.addBlock(HTMLBlock, data[:end])) + } + return size + } + } + return 0 +} + +func (p *Markdown) htmlFindTag(data []byte) (string, bool) { + i := 0 + for i < len(data) && isalnum(data[i]) { + i++ + } + key := string(data[:i]) + if _, ok := blockTags[key]; ok { + return key, true + } + return "", false +} + +func (p *Markdown) htmlFindEnd(tag string, data []byte) int { + // assume data[0] == '<' && data[1] == '/' already tested + if tag == "hr" { + return 2 + } + // check if tag is a match + closetag := []byte("") + if !bytes.HasPrefix(data, closetag) { + return 0 + } + i := len(closetag) + + // check that the rest of the line is blank + skip := 0 + if skip = p.isEmpty(data[i:]); skip == 0 { + return 0 + } + i += skip + skip = 0 + + if i >= len(data) { + return i + } + + if p.extensions&LaxHTMLBlocks != 0 { + return i + } + if skip = p.isEmpty(data[i:]); skip == 0 { + // following line must be blank + return 0 + } + + return i + skip +} + +func (*Markdown) isEmpty(data []byte) int { + // it is okay to call isEmpty on an empty buffer + if len(data) == 0 { + return 0 + } + + var i int + for i = 0; i < len(data) && data[i] != '\n'; i++ { + if data[i] != ' ' && data[i] != '\t' { + return 0 + } + } + if i < len(data) && data[i] == '\n' { + i++ + } + return i +} + +func (*Markdown) isHRule(data []byte) bool { + i := 0 + + // skip up to three spaces + for i < 3 && data[i] == ' ' { + i++ + } + + // look at the hrule char + if data[i] != '*' && data[i] != '-' && data[i] != '_' { + return false + } + c := data[i] + + // the whole line must be the char or whitespace + n := 0 + for i < len(data) && data[i] != '\n' { + switch { + case data[i] == c: + n++ + case data[i] != ' ': + return false + } + i++ + } + + return n >= 3 +} + +// isFenceLine checks if there's a fence line (e.g., ``` or ``` go) at the beginning of data, +// and returns the end index if so, or 0 otherwise. It also returns the marker found. +// If info is not nil, it gets set to the syntax specified in the fence line. +func isFenceLine(data []byte, info *string, oldmarker string) (end int, marker string) { + i, size := 0, 0 + + // skip up to three spaces + for i < len(data) && i < 3 && data[i] == ' ' { + i++ + } + + // check for the marker characters: ~ or ` + if i >= len(data) { + return 0, "" + } + if data[i] != '~' && data[i] != '`' { + return 0, "" + } + + c := data[i] + + // the whole line must be the same char or whitespace + for i < len(data) && data[i] == c { + size++ + i++ + } + + // the marker char must occur at least 3 times + if size < 3 { + return 0, "" + } + marker = string(data[i-size : i]) + + // if this is the end marker, it must match the beginning marker + if oldmarker != "" && marker != oldmarker { + return 0, "" + } + + // TODO(shurcooL): It's probably a good idea to simplify the 2 code paths here + // into one, always get the info string, and discard it if the caller doesn't care. + if info != nil { + infoLength := 0 + i = skipChar(data, i, ' ') + + if i >= len(data) { + if i == len(data) { + return i, marker + } + return 0, "" + } + + infoStart := i + + if data[i] == '{' { + i++ + infoStart++ + + for i < len(data) && data[i] != '}' && data[i] != '\n' { + infoLength++ + i++ + } + + if i >= len(data) || data[i] != '}' { + return 0, "" + } + + // strip all whitespace at the beginning and the end + // of the {} block + for infoLength > 0 && isspace(data[infoStart]) { + infoStart++ + infoLength-- + } + + for infoLength > 0 && isspace(data[infoStart+infoLength-1]) { + infoLength-- + } + i++ + i = skipChar(data, i, ' ') + } else { + for i < len(data) && !isverticalspace(data[i]) { + infoLength++ + i++ + } + } + + *info = strings.TrimSpace(string(data[infoStart : infoStart+infoLength])) + } + + if i == len(data) { + return i, marker + } + if i > len(data) || data[i] != '\n' { + return 0, "" + } + return i + 1, marker // Take newline into account. +} + +// fencedCodeBlock returns the end index if data contains a fenced code block at the beginning, +// or 0 otherwise. It writes to out if doRender is true, otherwise it has no side effects. +// If doRender is true, a final newline is mandatory to recognize the fenced code block. +func (p *Markdown) fencedCodeBlock(data []byte, doRender bool) int { + var info string + beg, marker := isFenceLine(data, &info, "") + if beg == 0 || beg >= len(data) { + return 0 + } + + var work bytes.Buffer + work.Write([]byte(info)) + work.WriteByte('\n') + + for { + // safe to assume beg < len(data) + + // check for the end of the code block + fenceEnd, _ := isFenceLine(data[beg:], nil, marker) + if fenceEnd != 0 { + beg += fenceEnd + break + } + + // copy the current line + end := skipUntilChar(data, beg, '\n') + 1 + + // did we reach the end of the buffer without a closing marker? + if end >= len(data) { + return 0 + } + + // verbatim copy to the working buffer + if doRender { + work.Write(data[beg:end]) + } + beg = end + } + + if doRender { + block := p.addBlock(CodeBlock, work.Bytes()) // TODO: get rid of temp buffer + block.IsFenced = true + finalizeCodeBlock(block) + } + + return beg +} + +func unescapeChar(str []byte) []byte { + if str[0] == '\\' { + return []byte{str[1]} + } + return []byte(html.UnescapeString(string(str))) +} + +func unescapeString(str []byte) []byte { + if reBackslashOrAmp.Match(str) { + return reEntityOrEscapedChar.ReplaceAllFunc(str, unescapeChar) + } + return str +} + +func finalizeCodeBlock(block *Node) { + if block.IsFenced { + newlinePos := bytes.IndexByte(block.content, '\n') + firstLine := block.content[:newlinePos] + rest := block.content[newlinePos+1:] + block.Info = unescapeString(bytes.Trim(firstLine, "\n")) + block.Literal = rest + } else { + block.Literal = block.content + } + block.content = nil +} + +func (p *Markdown) table(data []byte) int { + table := p.addBlock(Table, nil) + i, columns := p.tableHeader(data) + if i == 0 { + p.tip = table.Parent + table.Unlink() + return 0 + } + + p.addBlock(TableBody, nil) + + for i < len(data) { + pipes, rowStart := 0, i + for ; i < len(data) && data[i] != '\n'; i++ { + if data[i] == '|' { + pipes++ + } + } + + if pipes == 0 { + i = rowStart + break + } + + // include the newline in data sent to tableRow + if i < len(data) && data[i] == '\n' { + i++ + } + p.tableRow(data[rowStart:i], columns, false) + } + + return i +} + +// check if the specified position is preceded by an odd number of backslashes +func isBackslashEscaped(data []byte, i int) bool { + backslashes := 0 + for i-backslashes-1 >= 0 && data[i-backslashes-1] == '\\' { + backslashes++ + } + return backslashes&1 == 1 +} + +func (p *Markdown) tableHeader(data []byte) (size int, columns []CellAlignFlags) { + i := 0 + colCount := 1 + for i = 0; i < len(data) && data[i] != '\n'; i++ { + if data[i] == '|' && !isBackslashEscaped(data, i) { + colCount++ + } + } + + // doesn't look like a table header + if colCount == 1 { + return + } + + // include the newline in the data sent to tableRow + j := i + if j < len(data) && data[j] == '\n' { + j++ + } + header := data[:j] + + // column count ignores pipes at beginning or end of line + if data[0] == '|' { + colCount-- + } + if i > 2 && data[i-1] == '|' && !isBackslashEscaped(data, i-1) { + colCount-- + } + + columns = make([]CellAlignFlags, colCount) + + // move on to the header underline + i++ + if i >= len(data) { + return + } + + if data[i] == '|' && !isBackslashEscaped(data, i) { + i++ + } + i = skipChar(data, i, ' ') + + // each column header is of form: / *:?-+:? *|/ with # dashes + # colons >= 3 + // and trailing | optional on last column + col := 0 + for i < len(data) && data[i] != '\n' { + dashes := 0 + + if data[i] == ':' { + i++ + columns[col] |= TableAlignmentLeft + dashes++ + } + for i < len(data) && data[i] == '-' { + i++ + dashes++ + } + if i < len(data) && data[i] == ':' { + i++ + columns[col] |= TableAlignmentRight + dashes++ + } + for i < len(data) && data[i] == ' ' { + i++ + } + if i == len(data) { + return + } + // end of column test is messy + switch { + case dashes < 3: + // not a valid column + return + + case data[i] == '|' && !isBackslashEscaped(data, i): + // marker found, now skip past trailing whitespace + col++ + i++ + for i < len(data) && data[i] == ' ' { + i++ + } + + // trailing junk found after last column + if col >= colCount && i < len(data) && data[i] != '\n' { + return + } + + case (data[i] != '|' || isBackslashEscaped(data, i)) && col+1 < colCount: + // something else found where marker was required + return + + case data[i] == '\n': + // marker is optional for the last column + col++ + + default: + // trailing junk found after last column + return + } + } + if col != colCount { + return + } + + p.addBlock(TableHead, nil) + p.tableRow(header, columns, true) + size = i + if size < len(data) && data[size] == '\n' { + size++ + } + return +} + +func (p *Markdown) tableRow(data []byte, columns []CellAlignFlags, header bool) { + p.addBlock(TableRow, nil) + i, col := 0, 0 + + if data[i] == '|' && !isBackslashEscaped(data, i) { + i++ + } + + for col = 0; col < len(columns) && i < len(data); col++ { + for i < len(data) && data[i] == ' ' { + i++ + } + + cellStart := i + + for i < len(data) && (data[i] != '|' || isBackslashEscaped(data, i)) && data[i] != '\n' { + i++ + } + + cellEnd := i + + // skip the end-of-cell marker, possibly taking us past end of buffer + i++ + + for cellEnd > cellStart && cellEnd-1 < len(data) && data[cellEnd-1] == ' ' { + cellEnd-- + } + + cell := p.addBlock(TableCell, data[cellStart:cellEnd]) + cell.IsHeader = header + cell.Align = columns[col] + } + + // pad it out with empty columns to get the right number + for ; col < len(columns); col++ { + cell := p.addBlock(TableCell, nil) + cell.IsHeader = header + cell.Align = columns[col] + } + + // silently ignore rows with too many cells +} + +// returns blockquote prefix length +func (p *Markdown) quotePrefix(data []byte) int { + i := 0 + for i < 3 && i < len(data) && data[i] == ' ' { + i++ + } + if i < len(data) && data[i] == '>' { + if i+1 < len(data) && data[i+1] == ' ' { + return i + 2 + } + return i + 1 + } + return 0 +} + +// blockquote ends with at least one blank line +// followed by something without a blockquote prefix +func (p *Markdown) terminateBlockquote(data []byte, beg, end int) bool { + if p.isEmpty(data[beg:]) <= 0 { + return false + } + if end >= len(data) { + return true + } + return p.quotePrefix(data[end:]) == 0 && p.isEmpty(data[end:]) == 0 +} + +// parse a blockquote fragment +func (p *Markdown) quote(data []byte) int { + block := p.addBlock(BlockQuote, nil) + var raw bytes.Buffer + beg, end := 0, 0 + for beg < len(data) { + end = beg + // Step over whole lines, collecting them. While doing that, check for + // fenced code and if one's found, incorporate it altogether, + // irregardless of any contents inside it + for end < len(data) && data[end] != '\n' { + if p.extensions&FencedCode != 0 { + if i := p.fencedCodeBlock(data[end:], false); i > 0 { + // -1 to compensate for the extra end++ after the loop: + end += i - 1 + break + } + } + end++ + } + if end < len(data) && data[end] == '\n' { + end++ + } + if pre := p.quotePrefix(data[beg:]); pre > 0 { + // skip the prefix + beg += pre + } else if p.terminateBlockquote(data, beg, end) { + break + } + // this line is part of the blockquote + raw.Write(data[beg:end]) + beg = end + } + p.block(raw.Bytes()) + p.finalize(block) + return end +} + +// returns prefix length for block code +func (p *Markdown) codePrefix(data []byte) int { + if len(data) >= 1 && data[0] == '\t' { + return 1 + } + if len(data) >= 4 && data[0] == ' ' && data[1] == ' ' && data[2] == ' ' && data[3] == ' ' { + return 4 + } + return 0 +} + +func (p *Markdown) code(data []byte) int { + var work bytes.Buffer + + i := 0 + for i < len(data) { + beg := i + for i < len(data) && data[i] != '\n' { + i++ + } + if i < len(data) && data[i] == '\n' { + i++ + } + + blankline := p.isEmpty(data[beg:i]) > 0 + if pre := p.codePrefix(data[beg:i]); pre > 0 { + beg += pre + } else if !blankline { + // non-empty, non-prefixed line breaks the pre + i = beg + break + } + + // verbatim copy to the working buffer + if blankline { + work.WriteByte('\n') + } else { + work.Write(data[beg:i]) + } + } + + // trim all the \n off the end of work + workbytes := work.Bytes() + eol := len(workbytes) + for eol > 0 && workbytes[eol-1] == '\n' { + eol-- + } + if eol != len(workbytes) { + work.Truncate(eol) + } + + work.WriteByte('\n') + + block := p.addBlock(CodeBlock, work.Bytes()) // TODO: get rid of temp buffer + block.IsFenced = false + finalizeCodeBlock(block) + + return i +} + +// returns unordered list item prefix +func (p *Markdown) uliPrefix(data []byte) int { + i := 0 + // start with up to 3 spaces + for i < len(data) && i < 3 && data[i] == ' ' { + i++ + } + if i >= len(data)-1 { + return 0 + } + // need one of {'*', '+', '-'} followed by a space or a tab + if (data[i] != '*' && data[i] != '+' && data[i] != '-') || + (data[i+1] != ' ' && data[i+1] != '\t') { + return 0 + } + return i + 2 +} + +// returns ordered list item prefix +func (p *Markdown) oliPrefix(data []byte) int { + i := 0 + + // start with up to 3 spaces + for i < 3 && i < len(data) && data[i] == ' ' { + i++ + } + + // count the digits + start := i + for i < len(data) && data[i] >= '0' && data[i] <= '9' { + i++ + } + if start == i || i >= len(data)-1 { + return 0 + } + + // we need >= 1 digits followed by a dot and a space or a tab + if data[i] != '.' || !(data[i+1] == ' ' || data[i+1] == '\t') { + return 0 + } + return i + 2 +} + +// returns definition list item prefix +func (p *Markdown) dliPrefix(data []byte) int { + if len(data) < 2 { + return 0 + } + i := 0 + // need a ':' followed by a space or a tab + if data[i] != ':' || !(data[i+1] == ' ' || data[i+1] == '\t') { + return 0 + } + for i < len(data) && data[i] == ' ' { + i++ + } + return i + 2 +} + +// parse ordered or unordered list block +func (p *Markdown) list(data []byte, flags ListType) int { + i := 0 + flags |= ListItemBeginningOfList + block := p.addBlock(List, nil) + block.ListFlags = flags + block.Tight = true + + for i < len(data) { + skip := p.listItem(data[i:], &flags) + if flags&ListItemContainsBlock != 0 { + block.ListData.Tight = false + } + i += skip + if skip == 0 || flags&ListItemEndOfList != 0 { + break + } + flags &= ^ListItemBeginningOfList + } + + above := block.Parent + finalizeList(block) + p.tip = above + return i +} + +// Returns true if the list item is not the same type as its parent list +func (p *Markdown) listTypeChanged(data []byte, flags *ListType) bool { + if p.dliPrefix(data) > 0 && *flags&ListTypeDefinition == 0 { + return true + } else if p.oliPrefix(data) > 0 && *flags&ListTypeOrdered == 0 { + return true + } else if p.uliPrefix(data) > 0 && (*flags&ListTypeOrdered != 0 || *flags&ListTypeDefinition != 0) { + return true + } + return false +} + +// Returns true if block ends with a blank line, descending if needed +// into lists and sublists. +func endsWithBlankLine(block *Node) bool { + // TODO: figure this out. Always false now. + for block != nil { + //if block.lastLineBlank { + //return true + //} + t := block.Type + if t == List || t == Item { + block = block.LastChild + } else { + break + } + } + return false +} + +func finalizeList(block *Node) { + block.open = false + item := block.FirstChild + for item != nil { + // check for non-final list item ending with blank line: + if endsWithBlankLine(item) && item.Next != nil { + block.ListData.Tight = false + break + } + // recurse into children of list item, to see if there are spaces + // between any of them: + subItem := item.FirstChild + for subItem != nil { + if endsWithBlankLine(subItem) && (item.Next != nil || subItem.Next != nil) { + block.ListData.Tight = false + break + } + subItem = subItem.Next + } + item = item.Next + } +} + +// Parse a single list item. +// Assumes initial prefix is already removed if this is a sublist. +func (p *Markdown) listItem(data []byte, flags *ListType) int { + // keep track of the indentation of the first line + itemIndent := 0 + if data[0] == '\t' { + itemIndent += 4 + } else { + for itemIndent < 3 && data[itemIndent] == ' ' { + itemIndent++ + } + } + + var bulletChar byte = '*' + i := p.uliPrefix(data) + if i == 0 { + i = p.oliPrefix(data) + } else { + bulletChar = data[i-2] + } + if i == 0 { + i = p.dliPrefix(data) + // reset definition term flag + if i > 0 { + *flags &= ^ListTypeTerm + } + } + if i == 0 { + // if in definition list, set term flag and continue + if *flags&ListTypeDefinition != 0 { + *flags |= ListTypeTerm + } else { + return 0 + } + } + + // skip leading whitespace on first line + for i < len(data) && data[i] == ' ' { + i++ + } + + // find the end of the line + line := i + for i > 0 && i < len(data) && data[i-1] != '\n' { + i++ + } + + // get working buffer + var raw bytes.Buffer + + // put the first line into the working buffer + raw.Write(data[line:i]) + line = i + + // process the following lines + containsBlankLine := false + sublist := 0 + codeBlockMarker := "" + +gatherlines: + for line < len(data) { + i++ + + // find the end of this line + for i < len(data) && data[i-1] != '\n' { + i++ + } + + // if it is an empty line, guess that it is part of this item + // and move on to the next line + if p.isEmpty(data[line:i]) > 0 { + containsBlankLine = true + line = i + continue + } + + // calculate the indentation + indent := 0 + indentIndex := 0 + if data[line] == '\t' { + indentIndex++ + indent += 4 + } else { + for indent < 4 && line+indent < i && data[line+indent] == ' ' { + indent++ + indentIndex++ + } + } + + chunk := data[line+indentIndex : i] + + if p.extensions&FencedCode != 0 { + // determine if in or out of codeblock + // if in codeblock, ignore normal list processing + _, marker := isFenceLine(chunk, nil, codeBlockMarker) + if marker != "" { + if codeBlockMarker == "" { + // start of codeblock + codeBlockMarker = marker + } else { + // end of codeblock. + codeBlockMarker = "" + } + } + // we are in a codeblock, write line, and continue + if codeBlockMarker != "" || marker != "" { + raw.Write(data[line+indentIndex : i]) + line = i + continue gatherlines + } + } + + // evaluate how this line fits in + switch { + // is this a nested list item? + case (p.uliPrefix(chunk) > 0 && !p.isHRule(chunk)) || + p.oliPrefix(chunk) > 0 || + p.dliPrefix(chunk) > 0: + + // to be a nested list, it must be indented more + // if not, it is either a different kind of list + // or the next item in the same list + if indent <= itemIndent { + if p.listTypeChanged(chunk, flags) { + *flags |= ListItemEndOfList + } else if containsBlankLine { + *flags |= ListItemContainsBlock + } + + break gatherlines + } + + if containsBlankLine { + *flags |= ListItemContainsBlock + } + + // is this the first item in the nested list? + if sublist == 0 { + sublist = raw.Len() + } + + // is this a nested prefix heading? + case p.isPrefixHeading(chunk): + // if the heading is not indented, it is not nested in the list + // and thus ends the list + if containsBlankLine && indent < 4 { + *flags |= ListItemEndOfList + break gatherlines + } + *flags |= ListItemContainsBlock + + // anything following an empty line is only part + // of this item if it is indented 4 spaces + // (regardless of the indentation of the beginning of the item) + case containsBlankLine && indent < 4: + if *flags&ListTypeDefinition != 0 && i < len(data)-1 { + // is the next item still a part of this list? + next := i + for next < len(data) && data[next] != '\n' { + next++ + } + for next < len(data)-1 && data[next] == '\n' { + next++ + } + if i < len(data)-1 && data[i] != ':' && data[next] != ':' { + *flags |= ListItemEndOfList + } + } else { + *flags |= ListItemEndOfList + } + break gatherlines + + // a blank line means this should be parsed as a block + case containsBlankLine: + raw.WriteByte('\n') + *flags |= ListItemContainsBlock + } + + // if this line was preceded by one or more blanks, + // re-introduce the blank into the buffer + if containsBlankLine { + containsBlankLine = false + raw.WriteByte('\n') + } + + // add the line into the working buffer without prefix + raw.Write(data[line+indentIndex : i]) + + line = i + } + + rawBytes := raw.Bytes() + + block := p.addBlock(Item, nil) + block.ListFlags = *flags + block.Tight = false + block.BulletChar = bulletChar + block.Delimiter = '.' // Only '.' is possible in Markdown, but ')' will also be possible in CommonMark + + // render the contents of the list item + if *flags&ListItemContainsBlock != 0 && *flags&ListTypeTerm == 0 { + // intermediate render of block item, except for definition term + if sublist > 0 { + p.block(rawBytes[:sublist]) + p.block(rawBytes[sublist:]) + } else { + p.block(rawBytes) + } + } else { + // intermediate render of inline item + if sublist > 0 { + child := p.addChild(Paragraph, 0) + child.content = rawBytes[:sublist] + p.block(rawBytes[sublist:]) + } else { + child := p.addChild(Paragraph, 0) + child.content = rawBytes + } + } + return line +} + +// render a single paragraph that has already been parsed out +func (p *Markdown) renderParagraph(data []byte) { + if len(data) == 0 { + return + } + + // trim leading spaces + beg := 0 + for data[beg] == ' ' { + beg++ + } + + end := len(data) + // trim trailing newline + if data[len(data)-1] == '\n' { + end-- + } + + // trim trailing spaces + for end > beg && data[end-1] == ' ' { + end-- + } + + p.addBlock(Paragraph, data[beg:end]) +} + +func (p *Markdown) paragraph(data []byte) int { + // prev: index of 1st char of previous line + // line: index of 1st char of current line + // i: index of cursor/end of current line + var prev, line, i int + tabSize := TabSizeDefault + if p.extensions&TabSizeEight != 0 { + tabSize = TabSizeDouble + } + // keep going until we find something to mark the end of the paragraph + for i < len(data) { + // mark the beginning of the current line + prev = line + current := data[i:] + line = i + + // did we find a reference or a footnote? If so, end a paragraph + // preceding it and report that we have consumed up to the end of that + // reference: + if refEnd := isReference(p, current, tabSize); refEnd > 0 { + p.renderParagraph(data[:i]) + return i + refEnd + } + + // did we find a blank line marking the end of the paragraph? + if n := p.isEmpty(current); n > 0 { + // did this blank line followed by a definition list item? + if p.extensions&DefinitionLists != 0 { + if i < len(data)-1 && data[i+1] == ':' { + return p.list(data[prev:], ListTypeDefinition) + } + } + + p.renderParagraph(data[:i]) + return i + n + } + + // an underline under some text marks a heading, so our paragraph ended on prev line + if i > 0 { + if level := p.isUnderlinedHeading(current); level > 0 { + // render the paragraph + p.renderParagraph(data[:prev]) + + // ignore leading and trailing whitespace + eol := i - 1 + for prev < eol && data[prev] == ' ' { + prev++ + } + for eol > prev && data[eol-1] == ' ' { + eol-- + } + + id := "" + if p.extensions&AutoHeadingIDs != 0 { + id = sanitized_anchor_name.Create(string(data[prev:eol])) + } + + block := p.addBlock(Heading, data[prev:eol]) + block.Level = level + block.HeadingID = id + + // find the end of the underline + for i < len(data) && data[i] != '\n' { + i++ + } + return i + } + } + + // if the next line starts a block of HTML, then the paragraph ends here + if p.extensions&LaxHTMLBlocks != 0 { + if data[i] == '<' && p.html(current, false) > 0 { + // rewind to before the HTML block + p.renderParagraph(data[:i]) + return i + } + } + + // if there's a prefixed heading or a horizontal rule after this, paragraph is over + if p.isPrefixHeading(current) || p.isHRule(current) { + p.renderParagraph(data[:i]) + return i + } + + // if there's a fenced code block, paragraph is over + if p.extensions&FencedCode != 0 { + if p.fencedCodeBlock(current, false) > 0 { + p.renderParagraph(data[:i]) + return i + } + } + + // if there's a definition list item, prev line is a definition term + if p.extensions&DefinitionLists != 0 { + if p.dliPrefix(current) != 0 { + ret := p.list(data[prev:], ListTypeDefinition) + return ret + } + } + + // if there's a list after this, paragraph is over + if p.extensions&NoEmptyLineBeforeBlock != 0 { + if p.uliPrefix(current) != 0 || + p.oliPrefix(current) != 0 || + p.quotePrefix(current) != 0 || + p.codePrefix(current) != 0 { + p.renderParagraph(data[:i]) + return i + } + } + + // otherwise, scan to the beginning of the next line + nl := bytes.IndexByte(data[i:], '\n') + if nl >= 0 { + i += nl + 1 + } else { + i += len(data[i:]) + } + } + + p.renderParagraph(data[:i]) + return i +} + +func skipChar(data []byte, start int, char byte) int { + i := start + for i < len(data) && data[i] == char { + i++ + } + return i +} + +func skipUntilChar(text []byte, start int, char byte) int { + i := start + for i < len(text) && text[i] != char { + i++ + } + return i +} diff --git a/vendor/github.com/russross/blackfriday/v2/doc.go b/vendor/github.com/russross/blackfriday/v2/doc.go new file mode 100644 index 000000000..5b3fa9876 --- /dev/null +++ b/vendor/github.com/russross/blackfriday/v2/doc.go @@ -0,0 +1,18 @@ +// Package blackfriday is a markdown processor. +// +// It translates plain text with simple formatting rules into an AST, which can +// then be further processed to HTML (provided by Blackfriday itself) or other +// formats (provided by the community). +// +// The simplest way to invoke Blackfriday is to call the Run function. It will +// take a text input and produce a text output in HTML (or other format). +// +// A slightly more sophisticated way to use Blackfriday is to create a Markdown +// processor and to call Parse, which returns a syntax tree for the input +// document. You can leverage Blackfriday's parsing for content extraction from +// markdown documents. You can assign a custom renderer and set various options +// to the Markdown processor. +// +// If you're interested in calling Blackfriday from command line, see +// https://github.com/russross/blackfriday-tool. +package blackfriday diff --git a/vendor/github.com/russross/blackfriday/v2/esc.go b/vendor/github.com/russross/blackfriday/v2/esc.go new file mode 100644 index 000000000..6385f27cb --- /dev/null +++ b/vendor/github.com/russross/blackfriday/v2/esc.go @@ -0,0 +1,34 @@ +package blackfriday + +import ( + "html" + "io" +) + +var htmlEscaper = [256][]byte{ + '&': []byte("&"), + '<': []byte("<"), + '>': []byte(">"), + '"': []byte("""), +} + +func escapeHTML(w io.Writer, s []byte) { + var start, end int + for end < len(s) { + escSeq := htmlEscaper[s[end]] + if escSeq != nil { + w.Write(s[start:end]) + w.Write(escSeq) + start = end + 1 + } + end++ + } + if start < len(s) && end <= len(s) { + w.Write(s[start:end]) + } +} + +func escLink(w io.Writer, text []byte) { + unesc := html.UnescapeString(string(text)) + escapeHTML(w, []byte(unesc)) +} diff --git a/vendor/github.com/russross/blackfriday/v2/go.mod b/vendor/github.com/russross/blackfriday/v2/go.mod new file mode 100644 index 000000000..620b74e0a --- /dev/null +++ b/vendor/github.com/russross/blackfriday/v2/go.mod @@ -0,0 +1 @@ +module github.com/russross/blackfriday/v2 diff --git a/vendor/github.com/russross/blackfriday/v2/html.go b/vendor/github.com/russross/blackfriday/v2/html.go new file mode 100644 index 000000000..284c87184 --- /dev/null +++ b/vendor/github.com/russross/blackfriday/v2/html.go @@ -0,0 +1,949 @@ +// +// Blackfriday Markdown Processor +// Available at http://github.com/russross/blackfriday +// +// Copyright © 2011 Russ Ross . +// Distributed under the Simplified BSD License. +// See README.md for details. +// + +// +// +// HTML rendering backend +// +// + +package blackfriday + +import ( + "bytes" + "fmt" + "io" + "regexp" + "strings" +) + +// HTMLFlags control optional behavior of HTML renderer. +type HTMLFlags int + +// HTML renderer configuration options. +const ( + HTMLFlagsNone HTMLFlags = 0 + SkipHTML HTMLFlags = 1 << iota // Skip preformatted HTML blocks + SkipImages // Skip embedded images + SkipLinks // Skip all links + Safelink // Only link to trusted protocols + NofollowLinks // Only link with rel="nofollow" + NoreferrerLinks // Only link with rel="noreferrer" + NoopenerLinks // Only link with rel="noopener" + HrefTargetBlank // Add a blank target + CompletePage // Generate a complete HTML page + UseXHTML // Generate XHTML output instead of HTML + FootnoteReturnLinks // Generate a link at the end of a footnote to return to the source + Smartypants // Enable smart punctuation substitutions + SmartypantsFractions // Enable smart fractions (with Smartypants) + SmartypantsDashes // Enable smart dashes (with Smartypants) + SmartypantsLatexDashes // Enable LaTeX-style dashes (with Smartypants) + SmartypantsAngledQuotes // Enable angled double quotes (with Smartypants) for double quotes rendering + SmartypantsQuotesNBSP // Enable « French guillemets » (with Smartypants) + TOC // Generate a table of contents +) + +var ( + htmlTagRe = regexp.MustCompile("(?i)^" + htmlTag) +) + +const ( + htmlTag = "(?:" + openTag + "|" + closeTag + "|" + htmlComment + "|" + + processingInstruction + "|" + declaration + "|" + cdata + ")" + closeTag = "]" + openTag = "<" + tagName + attribute + "*" + "\\s*/?>" + attribute = "(?:" + "\\s+" + attributeName + attributeValueSpec + "?)" + attributeValue = "(?:" + unquotedValue + "|" + singleQuotedValue + "|" + doubleQuotedValue + ")" + attributeValueSpec = "(?:" + "\\s*=" + "\\s*" + attributeValue + ")" + attributeName = "[a-zA-Z_:][a-zA-Z0-9:._-]*" + cdata = "" + declaration = "]*>" + doubleQuotedValue = "\"[^\"]*\"" + htmlComment = "|" + processingInstruction = "[<][?].*?[?][>]" + singleQuotedValue = "'[^']*'" + tagName = "[A-Za-z][A-Za-z0-9-]*" + unquotedValue = "[^\"'=<>`\\x00-\\x20]+" +) + +// HTMLRendererParameters is a collection of supplementary parameters tweaking +// the behavior of various parts of HTML renderer. +type HTMLRendererParameters struct { + // Prepend this text to each relative URL. + AbsolutePrefix string + // Add this text to each footnote anchor, to ensure uniqueness. + FootnoteAnchorPrefix string + // Show this text inside the tag for a footnote return link, if the + // HTML_FOOTNOTE_RETURN_LINKS flag is enabled. If blank, the string + // [return] is used. + FootnoteReturnLinkContents string + // If set, add this text to the front of each Heading ID, to ensure + // uniqueness. + HeadingIDPrefix string + // If set, add this text to the back of each Heading ID, to ensure uniqueness. + HeadingIDSuffix string + // Increase heading levels: if the offset is 1,

    becomes

    etc. + // Negative offset is also valid. + // Resulting levels are clipped between 1 and 6. + HeadingLevelOffset int + + Title string // Document title (used if CompletePage is set) + CSS string // Optional CSS file URL (used if CompletePage is set) + Icon string // Optional icon file URL (used if CompletePage is set) + + Flags HTMLFlags // Flags allow customizing this renderer's behavior +} + +// HTMLRenderer is a type that implements the Renderer interface for HTML output. +// +// Do not create this directly, instead use the NewHTMLRenderer function. +type HTMLRenderer struct { + HTMLRendererParameters + + closeTag string // how to end singleton tags: either " />" or ">" + + // Track heading IDs to prevent ID collision in a single generation. + headingIDs map[string]int + + lastOutputLen int + disableTags int + + sr *SPRenderer +} + +const ( + xhtmlClose = " />" + htmlClose = ">" +) + +// NewHTMLRenderer creates and configures an HTMLRenderer object, which +// satisfies the Renderer interface. +func NewHTMLRenderer(params HTMLRendererParameters) *HTMLRenderer { + // configure the rendering engine + closeTag := htmlClose + if params.Flags&UseXHTML != 0 { + closeTag = xhtmlClose + } + + if params.FootnoteReturnLinkContents == "" { + params.FootnoteReturnLinkContents = `[return]` + } + + return &HTMLRenderer{ + HTMLRendererParameters: params, + + closeTag: closeTag, + headingIDs: make(map[string]int), + + sr: NewSmartypantsRenderer(params.Flags), + } +} + +func isHTMLTag(tag []byte, tagname string) bool { + found, _ := findHTMLTagPos(tag, tagname) + return found +} + +// Look for a character, but ignore it when it's in any kind of quotes, it +// might be JavaScript +func skipUntilCharIgnoreQuotes(html []byte, start int, char byte) int { + inSingleQuote := false + inDoubleQuote := false + inGraveQuote := false + i := start + for i < len(html) { + switch { + case html[i] == char && !inSingleQuote && !inDoubleQuote && !inGraveQuote: + return i + case html[i] == '\'': + inSingleQuote = !inSingleQuote + case html[i] == '"': + inDoubleQuote = !inDoubleQuote + case html[i] == '`': + inGraveQuote = !inGraveQuote + } + i++ + } + return start +} + +func findHTMLTagPos(tag []byte, tagname string) (bool, int) { + i := 0 + if i < len(tag) && tag[0] != '<' { + return false, -1 + } + i++ + i = skipSpace(tag, i) + + if i < len(tag) && tag[i] == '/' { + i++ + } + + i = skipSpace(tag, i) + j := 0 + for ; i < len(tag); i, j = i+1, j+1 { + if j >= len(tagname) { + break + } + + if strings.ToLower(string(tag[i]))[0] != tagname[j] { + return false, -1 + } + } + + if i == len(tag) { + return false, -1 + } + + rightAngle := skipUntilCharIgnoreQuotes(tag, i, '>') + if rightAngle >= i { + return true, rightAngle + } + + return false, -1 +} + +func skipSpace(tag []byte, i int) int { + for i < len(tag) && isspace(tag[i]) { + i++ + } + return i +} + +func isRelativeLink(link []byte) (yes bool) { + // a tag begin with '#' + if link[0] == '#' { + return true + } + + // link begin with '/' but not '//', the second maybe a protocol relative link + if len(link) >= 2 && link[0] == '/' && link[1] != '/' { + return true + } + + // only the root '/' + if len(link) == 1 && link[0] == '/' { + return true + } + + // current directory : begin with "./" + if bytes.HasPrefix(link, []byte("./")) { + return true + } + + // parent directory : begin with "../" + if bytes.HasPrefix(link, []byte("../")) { + return true + } + + return false +} + +func (r *HTMLRenderer) ensureUniqueHeadingID(id string) string { + for count, found := r.headingIDs[id]; found; count, found = r.headingIDs[id] { + tmp := fmt.Sprintf("%s-%d", id, count+1) + + if _, tmpFound := r.headingIDs[tmp]; !tmpFound { + r.headingIDs[id] = count + 1 + id = tmp + } else { + id = id + "-1" + } + } + + if _, found := r.headingIDs[id]; !found { + r.headingIDs[id] = 0 + } + + return id +} + +func (r *HTMLRenderer) addAbsPrefix(link []byte) []byte { + if r.AbsolutePrefix != "" && isRelativeLink(link) && link[0] != '.' { + newDest := r.AbsolutePrefix + if link[0] != '/' { + newDest += "/" + } + newDest += string(link) + return []byte(newDest) + } + return link +} + +func appendLinkAttrs(attrs []string, flags HTMLFlags, link []byte) []string { + if isRelativeLink(link) { + return attrs + } + val := []string{} + if flags&NofollowLinks != 0 { + val = append(val, "nofollow") + } + if flags&NoreferrerLinks != 0 { + val = append(val, "noreferrer") + } + if flags&NoopenerLinks != 0 { + val = append(val, "noopener") + } + if flags&HrefTargetBlank != 0 { + attrs = append(attrs, "target=\"_blank\"") + } + if len(val) == 0 { + return attrs + } + attr := fmt.Sprintf("rel=%q", strings.Join(val, " ")) + return append(attrs, attr) +} + +func isMailto(link []byte) bool { + return bytes.HasPrefix(link, []byte("mailto:")) +} + +func needSkipLink(flags HTMLFlags, dest []byte) bool { + if flags&SkipLinks != 0 { + return true + } + return flags&Safelink != 0 && !isSafeLink(dest) && !isMailto(dest) +} + +func isSmartypantable(node *Node) bool { + pt := node.Parent.Type + return pt != Link && pt != CodeBlock && pt != Code +} + +func appendLanguageAttr(attrs []string, info []byte) []string { + if len(info) == 0 { + return attrs + } + endOfLang := bytes.IndexAny(info, "\t ") + if endOfLang < 0 { + endOfLang = len(info) + } + return append(attrs, fmt.Sprintf("class=\"language-%s\"", info[:endOfLang])) +} + +func (r *HTMLRenderer) tag(w io.Writer, name []byte, attrs []string) { + w.Write(name) + if len(attrs) > 0 { + w.Write(spaceBytes) + w.Write([]byte(strings.Join(attrs, " "))) + } + w.Write(gtBytes) + r.lastOutputLen = 1 +} + +func footnoteRef(prefix string, node *Node) []byte { + urlFrag := prefix + string(slugify(node.Destination)) + anchor := fmt.Sprintf(`%d`, urlFrag, node.NoteID) + return []byte(fmt.Sprintf(`%s`, urlFrag, anchor)) +} + +func footnoteItem(prefix string, slug []byte) []byte { + return []byte(fmt.Sprintf(`
  • `, prefix, slug)) +} + +func footnoteReturnLink(prefix, returnLink string, slug []byte) []byte { + const format = ` %s` + return []byte(fmt.Sprintf(format, prefix, slug, returnLink)) +} + +func itemOpenCR(node *Node) bool { + if node.Prev == nil { + return false + } + ld := node.Parent.ListData + return !ld.Tight && ld.ListFlags&ListTypeDefinition == 0 +} + +func skipParagraphTags(node *Node) bool { + grandparent := node.Parent.Parent + if grandparent == nil || grandparent.Type != List { + return false + } + tightOrTerm := grandparent.Tight || node.Parent.ListFlags&ListTypeTerm != 0 + return grandparent.Type == List && tightOrTerm +} + +func cellAlignment(align CellAlignFlags) string { + switch align { + case TableAlignmentLeft: + return "left" + case TableAlignmentRight: + return "right" + case TableAlignmentCenter: + return "center" + default: + return "" + } +} + +func (r *HTMLRenderer) out(w io.Writer, text []byte) { + if r.disableTags > 0 { + w.Write(htmlTagRe.ReplaceAll(text, []byte{})) + } else { + w.Write(text) + } + r.lastOutputLen = len(text) +} + +func (r *HTMLRenderer) cr(w io.Writer) { + if r.lastOutputLen > 0 { + r.out(w, nlBytes) + } +} + +var ( + nlBytes = []byte{'\n'} + gtBytes = []byte{'>'} + spaceBytes = []byte{' '} +) + +var ( + brTag = []byte("
    ") + brXHTMLTag = []byte("
    ") + emTag = []byte("") + emCloseTag = []byte("") + strongTag = []byte("") + strongCloseTag = []byte("") + delTag = []byte("") + delCloseTag = []byte("") + ttTag = []byte("") + ttCloseTag = []byte("") + aTag = []byte("") + preTag = []byte("
    ")
    +	preCloseTag        = []byte("
    ") + codeTag = []byte("") + codeCloseTag = []byte("") + pTag = []byte("

    ") + pCloseTag = []byte("

    ") + blockquoteTag = []byte("
    ") + blockquoteCloseTag = []byte("
    ") + hrTag = []byte("
    ") + hrXHTMLTag = []byte("
    ") + ulTag = []byte("
      ") + ulCloseTag = []byte("
    ") + olTag = []byte("
      ") + olCloseTag = []byte("
    ") + dlTag = []byte("
    ") + dlCloseTag = []byte("
    ") + liTag = []byte("
  • ") + liCloseTag = []byte("
  • ") + ddTag = []byte("
    ") + ddCloseTag = []byte("
    ") + dtTag = []byte("
    ") + dtCloseTag = []byte("
    ") + tableTag = []byte("") + tableCloseTag = []byte("
    ") + tdTag = []byte("") + thTag = []byte("") + theadTag = []byte("") + theadCloseTag = []byte("") + tbodyTag = []byte("") + tbodyCloseTag = []byte("") + trTag = []byte("") + trCloseTag = []byte("") + h1Tag = []byte("") + h2Tag = []byte("") + h3Tag = []byte("") + h4Tag = []byte("") + h5Tag = []byte("") + h6Tag = []byte("") + + footnotesDivBytes = []byte("\n
    \n\n") + footnotesCloseDivBytes = []byte("\n
    \n") +) + +func headingTagsFromLevel(level int) ([]byte, []byte) { + if level <= 1 { + return h1Tag, h1CloseTag + } + switch level { + case 2: + return h2Tag, h2CloseTag + case 3: + return h3Tag, h3CloseTag + case 4: + return h4Tag, h4CloseTag + case 5: + return h5Tag, h5CloseTag + } + return h6Tag, h6CloseTag +} + +func (r *HTMLRenderer) outHRTag(w io.Writer) { + if r.Flags&UseXHTML == 0 { + r.out(w, hrTag) + } else { + r.out(w, hrXHTMLTag) + } +} + +// RenderNode is a default renderer of a single node of a syntax tree. For +// block nodes it will be called twice: first time with entering=true, second +// time with entering=false, so that it could know when it's working on an open +// tag and when on close. It writes the result to w. +// +// The return value is a way to tell the calling walker to adjust its walk +// pattern: e.g. it can terminate the traversal by returning Terminate. Or it +// can ask the walker to skip a subtree of this node by returning SkipChildren. +// The typical behavior is to return GoToNext, which asks for the usual +// traversal to the next node. +func (r *HTMLRenderer) RenderNode(w io.Writer, node *Node, entering bool) WalkStatus { + attrs := []string{} + switch node.Type { + case Text: + if r.Flags&Smartypants != 0 { + var tmp bytes.Buffer + escapeHTML(&tmp, node.Literal) + r.sr.Process(w, tmp.Bytes()) + } else { + if node.Parent.Type == Link { + escLink(w, node.Literal) + } else { + escapeHTML(w, node.Literal) + } + } + case Softbreak: + r.cr(w) + // TODO: make it configurable via out(renderer.softbreak) + case Hardbreak: + if r.Flags&UseXHTML == 0 { + r.out(w, brTag) + } else { + r.out(w, brXHTMLTag) + } + r.cr(w) + case Emph: + if entering { + r.out(w, emTag) + } else { + r.out(w, emCloseTag) + } + case Strong: + if entering { + r.out(w, strongTag) + } else { + r.out(w, strongCloseTag) + } + case Del: + if entering { + r.out(w, delTag) + } else { + r.out(w, delCloseTag) + } + case HTMLSpan: + if r.Flags&SkipHTML != 0 { + break + } + r.out(w, node.Literal) + case Link: + // mark it but don't link it if it is not a safe link: no smartypants + dest := node.LinkData.Destination + if needSkipLink(r.Flags, dest) { + if entering { + r.out(w, ttTag) + } else { + r.out(w, ttCloseTag) + } + } else { + if entering { + dest = r.addAbsPrefix(dest) + var hrefBuf bytes.Buffer + hrefBuf.WriteString("href=\"") + escLink(&hrefBuf, dest) + hrefBuf.WriteByte('"') + attrs = append(attrs, hrefBuf.String()) + if node.NoteID != 0 { + r.out(w, footnoteRef(r.FootnoteAnchorPrefix, node)) + break + } + attrs = appendLinkAttrs(attrs, r.Flags, dest) + if len(node.LinkData.Title) > 0 { + var titleBuff bytes.Buffer + titleBuff.WriteString("title=\"") + escapeHTML(&titleBuff, node.LinkData.Title) + titleBuff.WriteByte('"') + attrs = append(attrs, titleBuff.String()) + } + r.tag(w, aTag, attrs) + } else { + if node.NoteID != 0 { + break + } + r.out(w, aCloseTag) + } + } + case Image: + if r.Flags&SkipImages != 0 { + return SkipChildren + } + if entering { + dest := node.LinkData.Destination + dest = r.addAbsPrefix(dest) + if r.disableTags == 0 { + //if options.safe && potentiallyUnsafe(dest) { + //out(w, ``)
+				//} else {
+				r.out(w, []byte(`<img src=`)) + } + } + case Code: + r.out(w, codeTag) + escapeHTML(w, node.Literal) + r.out(w, codeCloseTag) + case Document: + break + case Paragraph: + if skipParagraphTags(node) { + break + } + if entering { + // TODO: untangle this clusterfuck about when the newlines need + // to be added and when not. + if node.Prev != nil { + switch node.Prev.Type { + case HTMLBlock, List, Paragraph, Heading, CodeBlock, BlockQuote, HorizontalRule: + r.cr(w) + } + } + if node.Parent.Type == BlockQuote && node.Prev == nil { + r.cr(w) + } + r.out(w, pTag) + } else { + r.out(w, pCloseTag) + if !(node.Parent.Type == Item && node.Next == nil) { + r.cr(w) + } + } + case BlockQuote: + if entering { + r.cr(w) + r.out(w, blockquoteTag) + } else { + r.out(w, blockquoteCloseTag) + r.cr(w) + } + case HTMLBlock: + if r.Flags&SkipHTML != 0 { + break + } + r.cr(w) + r.out(w, node.Literal) + r.cr(w) + case Heading: + headingLevel := r.HTMLRendererParameters.HeadingLevelOffset + node.Level + openTag, closeTag := headingTagsFromLevel(headingLevel) + if entering { + if node.IsTitleblock { + attrs = append(attrs, `class="title"`) + } + if node.HeadingID != "" { + id := r.ensureUniqueHeadingID(node.HeadingID) + if r.HeadingIDPrefix != "" { + id = r.HeadingIDPrefix + id + } + if r.HeadingIDSuffix != "" { + id = id + r.HeadingIDSuffix + } + attrs = append(attrs, fmt.Sprintf(`id="%s"`, id)) + } + r.cr(w) + r.tag(w, openTag, attrs) + } else { + r.out(w, closeTag) + if !(node.Parent.Type == Item && node.Next == nil) { + r.cr(w) + } + } + case HorizontalRule: + r.cr(w) + r.outHRTag(w) + r.cr(w) + case List: + openTag := ulTag + closeTag := ulCloseTag + if node.ListFlags&ListTypeOrdered != 0 { + openTag = olTag + closeTag = olCloseTag + } + if node.ListFlags&ListTypeDefinition != 0 { + openTag = dlTag + closeTag = dlCloseTag + } + if entering { + if node.IsFootnotesList { + r.out(w, footnotesDivBytes) + r.outHRTag(w) + r.cr(w) + } + r.cr(w) + if node.Parent.Type == Item && node.Parent.Parent.Tight { + r.cr(w) + } + r.tag(w, openTag[:len(openTag)-1], attrs) + r.cr(w) + } else { + r.out(w, closeTag) + //cr(w) + //if node.parent.Type != Item { + // cr(w) + //} + if node.Parent.Type == Item && node.Next != nil { + r.cr(w) + } + if node.Parent.Type == Document || node.Parent.Type == BlockQuote { + r.cr(w) + } + if node.IsFootnotesList { + r.out(w, footnotesCloseDivBytes) + } + } + case Item: + openTag := liTag + closeTag := liCloseTag + if node.ListFlags&ListTypeDefinition != 0 { + openTag = ddTag + closeTag = ddCloseTag + } + if node.ListFlags&ListTypeTerm != 0 { + openTag = dtTag + closeTag = dtCloseTag + } + if entering { + if itemOpenCR(node) { + r.cr(w) + } + if node.ListData.RefLink != nil { + slug := slugify(node.ListData.RefLink) + r.out(w, footnoteItem(r.FootnoteAnchorPrefix, slug)) + break + } + r.out(w, openTag) + } else { + if node.ListData.RefLink != nil { + slug := slugify(node.ListData.RefLink) + if r.Flags&FootnoteReturnLinks != 0 { + r.out(w, footnoteReturnLink(r.FootnoteAnchorPrefix, r.FootnoteReturnLinkContents, slug)) + } + } + r.out(w, closeTag) + r.cr(w) + } + case CodeBlock: + attrs = appendLanguageAttr(attrs, node.Info) + r.cr(w) + r.out(w, preTag) + r.tag(w, codeTag[:len(codeTag)-1], attrs) + escapeHTML(w, node.Literal) + r.out(w, codeCloseTag) + r.out(w, preCloseTag) + if node.Parent.Type != Item { + r.cr(w) + } + case Table: + if entering { + r.cr(w) + r.out(w, tableTag) + } else { + r.out(w, tableCloseTag) + r.cr(w) + } + case TableCell: + openTag := tdTag + closeTag := tdCloseTag + if node.IsHeader { + openTag = thTag + closeTag = thCloseTag + } + if entering { + align := cellAlignment(node.Align) + if align != "" { + attrs = append(attrs, fmt.Sprintf(`align="%s"`, align)) + } + if node.Prev == nil { + r.cr(w) + } + r.tag(w, openTag, attrs) + } else { + r.out(w, closeTag) + r.cr(w) + } + case TableHead: + if entering { + r.cr(w) + r.out(w, theadTag) + } else { + r.out(w, theadCloseTag) + r.cr(w) + } + case TableBody: + if entering { + r.cr(w) + r.out(w, tbodyTag) + // XXX: this is to adhere to a rather silly test. Should fix test. + if node.FirstChild == nil { + r.cr(w) + } + } else { + r.out(w, tbodyCloseTag) + r.cr(w) + } + case TableRow: + if entering { + r.cr(w) + r.out(w, trTag) + } else { + r.out(w, trCloseTag) + r.cr(w) + } + default: + panic("Unknown node type " + node.Type.String()) + } + return GoToNext +} + +// RenderHeader writes HTML document preamble and TOC if requested. +func (r *HTMLRenderer) RenderHeader(w io.Writer, ast *Node) { + r.writeDocumentHeader(w) + if r.Flags&TOC != 0 { + r.writeTOC(w, ast) + } +} + +// RenderFooter writes HTML document footer. +func (r *HTMLRenderer) RenderFooter(w io.Writer, ast *Node) { + if r.Flags&CompletePage == 0 { + return + } + io.WriteString(w, "\n\n\n") +} + +func (r *HTMLRenderer) writeDocumentHeader(w io.Writer) { + if r.Flags&CompletePage == 0 { + return + } + ending := "" + if r.Flags&UseXHTML != 0 { + io.WriteString(w, "\n") + io.WriteString(w, "\n") + ending = " /" + } else { + io.WriteString(w, "\n") + io.WriteString(w, "\n") + } + io.WriteString(w, "\n") + io.WriteString(w, " ") + if r.Flags&Smartypants != 0 { + r.sr.Process(w, []byte(r.Title)) + } else { + escapeHTML(w, []byte(r.Title)) + } + io.WriteString(w, "\n") + io.WriteString(w, " \n") + io.WriteString(w, " \n") + if r.CSS != "" { + io.WriteString(w, " \n") + } + if r.Icon != "" { + io.WriteString(w, " \n") + } + io.WriteString(w, "\n") + io.WriteString(w, "\n\n") +} + +func (r *HTMLRenderer) writeTOC(w io.Writer, ast *Node) { + buf := bytes.Buffer{} + + inHeading := false + tocLevel := 0 + headingCount := 0 + + ast.Walk(func(node *Node, entering bool) WalkStatus { + if node.Type == Heading && !node.HeadingData.IsTitleblock { + inHeading = entering + if entering { + node.HeadingID = fmt.Sprintf("toc_%d", headingCount) + if node.Level == tocLevel { + buf.WriteString("\n\n
  • ") + } else if node.Level < tocLevel { + for node.Level < tocLevel { + tocLevel-- + buf.WriteString("
  • \n") + } + buf.WriteString("\n\n
  • ") + } else { + for node.Level > tocLevel { + tocLevel++ + buf.WriteString("\n") + } + + if buf.Len() > 0 { + io.WriteString(w, "\n") + } + r.lastOutputLen = buf.Len() +} diff --git a/vendor/github.com/russross/blackfriday/v2/inline.go b/vendor/github.com/russross/blackfriday/v2/inline.go new file mode 100644 index 000000000..4ed290792 --- /dev/null +++ b/vendor/github.com/russross/blackfriday/v2/inline.go @@ -0,0 +1,1228 @@ +// +// Blackfriday Markdown Processor +// Available at http://github.com/russross/blackfriday +// +// Copyright © 2011 Russ Ross . +// Distributed under the Simplified BSD License. +// See README.md for details. +// + +// +// Functions to parse inline elements. +// + +package blackfriday + +import ( + "bytes" + "regexp" + "strconv" +) + +var ( + urlRe = `((https?|ftp):\/\/|\/)[-A-Za-z0-9+&@#\/%?=~_|!:,.;\(\)]+` + anchorRe = regexp.MustCompile(`^(]+")?\s?>` + urlRe + `<\/a>)`) + + // https://www.w3.org/TR/html5/syntax.html#character-references + // highest unicode code point in 17 planes (2^20): 1,114,112d = + // 7 dec digits or 6 hex digits + // named entity references can be 2-31 characters with stuff like < + // at one end and ∳ at the other. There + // are also sometimes numbers at the end, although this isn't inherent + // in the specification; there are never numbers anywhere else in + // current character references, though; see ¾ and ▒, etc. + // https://www.w3.org/TR/html5/syntax.html#named-character-references + // + // entity := "&" (named group | number ref) ";" + // named group := [a-zA-Z]{2,31}[0-9]{0,2} + // number ref := "#" (dec ref | hex ref) + // dec ref := [0-9]{1,7} + // hex ref := ("x" | "X") [0-9a-fA-F]{1,6} + htmlEntityRe = regexp.MustCompile(`&([a-zA-Z]{2,31}[0-9]{0,2}|#([0-9]{1,7}|[xX][0-9a-fA-F]{1,6}));`) +) + +// Functions to parse text within a block +// Each function returns the number of chars taken care of +// data is the complete block being rendered +// offset is the number of valid chars before the current cursor + +func (p *Markdown) inline(currBlock *Node, data []byte) { + // handlers might call us recursively: enforce a maximum depth + if p.nesting >= p.maxNesting || len(data) == 0 { + return + } + p.nesting++ + beg, end := 0, 0 + for end < len(data) { + handler := p.inlineCallback[data[end]] + if handler != nil { + if consumed, node := handler(p, data, end); consumed == 0 { + // No action from the callback. + end++ + } else { + // Copy inactive chars into the output. + currBlock.AppendChild(text(data[beg:end])) + if node != nil { + currBlock.AppendChild(node) + } + // Skip past whatever the callback used. + beg = end + consumed + end = beg + } + } else { + end++ + } + } + if beg < len(data) { + if data[end-1] == '\n' { + end-- + } + currBlock.AppendChild(text(data[beg:end])) + } + p.nesting-- +} + +// single and double emphasis parsing +func emphasis(p *Markdown, data []byte, offset int) (int, *Node) { + data = data[offset:] + c := data[0] + + if len(data) > 2 && data[1] != c { + // whitespace cannot follow an opening emphasis; + // strikethrough only takes two characters '~~' + if c == '~' || isspace(data[1]) { + return 0, nil + } + ret, node := helperEmphasis(p, data[1:], c) + if ret == 0 { + return 0, nil + } + + return ret + 1, node + } + + if len(data) > 3 && data[1] == c && data[2] != c { + if isspace(data[2]) { + return 0, nil + } + ret, node := helperDoubleEmphasis(p, data[2:], c) + if ret == 0 { + return 0, nil + } + + return ret + 2, node + } + + if len(data) > 4 && data[1] == c && data[2] == c && data[3] != c { + if c == '~' || isspace(data[3]) { + return 0, nil + } + ret, node := helperTripleEmphasis(p, data, 3, c) + if ret == 0 { + return 0, nil + } + + return ret + 3, node + } + + return 0, nil +} + +func codeSpan(p *Markdown, data []byte, offset int) (int, *Node) { + data = data[offset:] + + nb := 0 + + // count the number of backticks in the delimiter + for nb < len(data) && data[nb] == '`' { + nb++ + } + + // find the next delimiter + i, end := 0, 0 + for end = nb; end < len(data) && i < nb; end++ { + if data[end] == '`' { + i++ + } else { + i = 0 + } + } + + // no matching delimiter? + if i < nb && end >= len(data) { + return 0, nil + } + + // trim outside whitespace + fBegin := nb + for fBegin < end && data[fBegin] == ' ' { + fBegin++ + } + + fEnd := end - nb + for fEnd > fBegin && data[fEnd-1] == ' ' { + fEnd-- + } + + // render the code span + if fBegin != fEnd { + code := NewNode(Code) + code.Literal = data[fBegin:fEnd] + return end, code + } + + return end, nil +} + +// newline preceded by two spaces becomes
    +func maybeLineBreak(p *Markdown, data []byte, offset int) (int, *Node) { + origOffset := offset + for offset < len(data) && data[offset] == ' ' { + offset++ + } + + if offset < len(data) && data[offset] == '\n' { + if offset-origOffset >= 2 { + return offset - origOffset + 1, NewNode(Hardbreak) + } + return offset - origOffset, nil + } + return 0, nil +} + +// newline without two spaces works when HardLineBreak is enabled +func lineBreak(p *Markdown, data []byte, offset int) (int, *Node) { + if p.extensions&HardLineBreak != 0 { + return 1, NewNode(Hardbreak) + } + return 0, nil +} + +type linkType int + +const ( + linkNormal linkType = iota + linkImg + linkDeferredFootnote + linkInlineFootnote +) + +func isReferenceStyleLink(data []byte, pos int, t linkType) bool { + if t == linkDeferredFootnote { + return false + } + return pos < len(data)-1 && data[pos] == '[' && data[pos+1] != '^' +} + +func maybeImage(p *Markdown, data []byte, offset int) (int, *Node) { + if offset < len(data)-1 && data[offset+1] == '[' { + return link(p, data, offset) + } + return 0, nil +} + +func maybeInlineFootnote(p *Markdown, data []byte, offset int) (int, *Node) { + if offset < len(data)-1 && data[offset+1] == '[' { + return link(p, data, offset) + } + return 0, nil +} + +// '[': parse a link or an image or a footnote +func link(p *Markdown, data []byte, offset int) (int, *Node) { + // no links allowed inside regular links, footnote, and deferred footnotes + if p.insideLink && (offset > 0 && data[offset-1] == '[' || len(data)-1 > offset && data[offset+1] == '^') { + return 0, nil + } + + var t linkType + switch { + // special case: ![^text] == deferred footnote (that follows something with + // an exclamation point) + case p.extensions&Footnotes != 0 && len(data)-1 > offset && data[offset+1] == '^': + t = linkDeferredFootnote + // ![alt] == image + case offset >= 0 && data[offset] == '!': + t = linkImg + offset++ + // ^[text] == inline footnote + // [^refId] == deferred footnote + case p.extensions&Footnotes != 0: + if offset >= 0 && data[offset] == '^' { + t = linkInlineFootnote + offset++ + } else if len(data)-1 > offset && data[offset+1] == '^' { + t = linkDeferredFootnote + } + // [text] == regular link + default: + t = linkNormal + } + + data = data[offset:] + + var ( + i = 1 + noteID int + title, link, altContent []byte + textHasNl = false + ) + + if t == linkDeferredFootnote { + i++ + } + + // look for the matching closing bracket + for level := 1; level > 0 && i < len(data); i++ { + switch { + case data[i] == '\n': + textHasNl = true + + case data[i-1] == '\\': + continue + + case data[i] == '[': + level++ + + case data[i] == ']': + level-- + if level <= 0 { + i-- // compensate for extra i++ in for loop + } + } + } + + if i >= len(data) { + return 0, nil + } + + txtE := i + i++ + var footnoteNode *Node + + // skip any amount of whitespace or newline + // (this is much more lax than original markdown syntax) + for i < len(data) && isspace(data[i]) { + i++ + } + + // inline style link + switch { + case i < len(data) && data[i] == '(': + // skip initial whitespace + i++ + + for i < len(data) && isspace(data[i]) { + i++ + } + + linkB := i + + // look for link end: ' " ) + findlinkend: + for i < len(data) { + switch { + case data[i] == '\\': + i += 2 + + case data[i] == ')' || data[i] == '\'' || data[i] == '"': + break findlinkend + + default: + i++ + } + } + + if i >= len(data) { + return 0, nil + } + linkE := i + + // look for title end if present + titleB, titleE := 0, 0 + if data[i] == '\'' || data[i] == '"' { + i++ + titleB = i + + findtitleend: + for i < len(data) { + switch { + case data[i] == '\\': + i += 2 + + case data[i] == ')': + break findtitleend + + default: + i++ + } + } + + if i >= len(data) { + return 0, nil + } + + // skip whitespace after title + titleE = i - 1 + for titleE > titleB && isspace(data[titleE]) { + titleE-- + } + + // check for closing quote presence + if data[titleE] != '\'' && data[titleE] != '"' { + titleB, titleE = 0, 0 + linkE = i + } + } + + // remove whitespace at the end of the link + for linkE > linkB && isspace(data[linkE-1]) { + linkE-- + } + + // remove optional angle brackets around the link + if data[linkB] == '<' { + linkB++ + } + if data[linkE-1] == '>' { + linkE-- + } + + // build escaped link and title + if linkE > linkB { + link = data[linkB:linkE] + } + + if titleE > titleB { + title = data[titleB:titleE] + } + + i++ + + // reference style link + case isReferenceStyleLink(data, i, t): + var id []byte + altContentConsidered := false + + // look for the id + i++ + linkB := i + for i < len(data) && data[i] != ']' { + i++ + } + if i >= len(data) { + return 0, nil + } + linkE := i + + // find the reference + if linkB == linkE { + if textHasNl { + var b bytes.Buffer + + for j := 1; j < txtE; j++ { + switch { + case data[j] != '\n': + b.WriteByte(data[j]) + case data[j-1] != ' ': + b.WriteByte(' ') + } + } + + id = b.Bytes() + } else { + id = data[1:txtE] + altContentConsidered = true + } + } else { + id = data[linkB:linkE] + } + + // find the reference with matching id + lr, ok := p.getRef(string(id)) + if !ok { + return 0, nil + } + + // keep link and title from reference + link = lr.link + title = lr.title + if altContentConsidered { + altContent = lr.text + } + i++ + + // shortcut reference style link or reference or inline footnote + default: + var id []byte + + // craft the id + if textHasNl { + var b bytes.Buffer + + for j := 1; j < txtE; j++ { + switch { + case data[j] != '\n': + b.WriteByte(data[j]) + case data[j-1] != ' ': + b.WriteByte(' ') + } + } + + id = b.Bytes() + } else { + if t == linkDeferredFootnote { + id = data[2:txtE] // get rid of the ^ + } else { + id = data[1:txtE] + } + } + + footnoteNode = NewNode(Item) + if t == linkInlineFootnote { + // create a new reference + noteID = len(p.notes) + 1 + + var fragment []byte + if len(id) > 0 { + if len(id) < 16 { + fragment = make([]byte, len(id)) + } else { + fragment = make([]byte, 16) + } + copy(fragment, slugify(id)) + } else { + fragment = append([]byte("footnote-"), []byte(strconv.Itoa(noteID))...) + } + + ref := &reference{ + noteID: noteID, + hasBlock: false, + link: fragment, + title: id, + footnote: footnoteNode, + } + + p.notes = append(p.notes, ref) + + link = ref.link + title = ref.title + } else { + // find the reference with matching id + lr, ok := p.getRef(string(id)) + if !ok { + return 0, nil + } + + if t == linkDeferredFootnote { + lr.noteID = len(p.notes) + 1 + lr.footnote = footnoteNode + p.notes = append(p.notes, lr) + } + + // keep link and title from reference + link = lr.link + // if inline footnote, title == footnote contents + title = lr.title + noteID = lr.noteID + } + + // rewind the whitespace + i = txtE + 1 + } + + var uLink []byte + if t == linkNormal || t == linkImg { + if len(link) > 0 { + var uLinkBuf bytes.Buffer + unescapeText(&uLinkBuf, link) + uLink = uLinkBuf.Bytes() + } + + // links need something to click on and somewhere to go + if len(uLink) == 0 || (t == linkNormal && txtE <= 1) { + return 0, nil + } + } + + // call the relevant rendering function + var linkNode *Node + switch t { + case linkNormal: + linkNode = NewNode(Link) + linkNode.Destination = normalizeURI(uLink) + linkNode.Title = title + if len(altContent) > 0 { + linkNode.AppendChild(text(altContent)) + } else { + // links cannot contain other links, so turn off link parsing + // temporarily and recurse + insideLink := p.insideLink + p.insideLink = true + p.inline(linkNode, data[1:txtE]) + p.insideLink = insideLink + } + + case linkImg: + linkNode = NewNode(Image) + linkNode.Destination = uLink + linkNode.Title = title + linkNode.AppendChild(text(data[1:txtE])) + i++ + + case linkInlineFootnote, linkDeferredFootnote: + linkNode = NewNode(Link) + linkNode.Destination = link + linkNode.Title = title + linkNode.NoteID = noteID + linkNode.Footnote = footnoteNode + if t == linkInlineFootnote { + i++ + } + + default: + return 0, nil + } + + return i, linkNode +} + +func (p *Markdown) inlineHTMLComment(data []byte) int { + if len(data) < 5 { + return 0 + } + if data[0] != '<' || data[1] != '!' || data[2] != '-' || data[3] != '-' { + return 0 + } + i := 5 + // scan for an end-of-comment marker, across lines if necessary + for i < len(data) && !(data[i-2] == '-' && data[i-1] == '-' && data[i] == '>') { + i++ + } + // no end-of-comment marker + if i >= len(data) { + return 0 + } + return i + 1 +} + +func stripMailto(link []byte) []byte { + if bytes.HasPrefix(link, []byte("mailto://")) { + return link[9:] + } else if bytes.HasPrefix(link, []byte("mailto:")) { + return link[7:] + } else { + return link + } +} + +// autolinkType specifies a kind of autolink that gets detected. +type autolinkType int + +// These are the possible flag values for the autolink renderer. +const ( + notAutolink autolinkType = iota + normalAutolink + emailAutolink +) + +// '<' when tags or autolinks are allowed +func leftAngle(p *Markdown, data []byte, offset int) (int, *Node) { + data = data[offset:] + altype, end := tagLength(data) + if size := p.inlineHTMLComment(data); size > 0 { + end = size + } + if end > 2 { + if altype != notAutolink { + var uLink bytes.Buffer + unescapeText(&uLink, data[1:end+1-2]) + if uLink.Len() > 0 { + link := uLink.Bytes() + node := NewNode(Link) + node.Destination = link + if altype == emailAutolink { + node.Destination = append([]byte("mailto:"), link...) + } + node.AppendChild(text(stripMailto(link))) + return end, node + } + } else { + htmlTag := NewNode(HTMLSpan) + htmlTag.Literal = data[:end] + return end, htmlTag + } + } + + return end, nil +} + +// '\\' backslash escape +var escapeChars = []byte("\\`*_{}[]()#+-.!:|&<>~") + +func escape(p *Markdown, data []byte, offset int) (int, *Node) { + data = data[offset:] + + if len(data) > 1 { + if p.extensions&BackslashLineBreak != 0 && data[1] == '\n' { + return 2, NewNode(Hardbreak) + } + if bytes.IndexByte(escapeChars, data[1]) < 0 { + return 0, nil + } + + return 2, text(data[1:2]) + } + + return 2, nil +} + +func unescapeText(ob *bytes.Buffer, src []byte) { + i := 0 + for i < len(src) { + org := i + for i < len(src) && src[i] != '\\' { + i++ + } + + if i > org { + ob.Write(src[org:i]) + } + + if i+1 >= len(src) { + break + } + + ob.WriteByte(src[i+1]) + i += 2 + } +} + +// '&' escaped when it doesn't belong to an entity +// valid entities are assumed to be anything matching &#?[A-Za-z0-9]+; +func entity(p *Markdown, data []byte, offset int) (int, *Node) { + data = data[offset:] + + end := 1 + + if end < len(data) && data[end] == '#' { + end++ + } + + for end < len(data) && isalnum(data[end]) { + end++ + } + + if end < len(data) && data[end] == ';' { + end++ // real entity + } else { + return 0, nil // lone '&' + } + + ent := data[:end] + // undo & escaping or it will be converted to &amp; by another + // escaper in the renderer + if bytes.Equal(ent, []byte("&")) { + ent = []byte{'&'} + } + + return end, text(ent) +} + +func linkEndsWithEntity(data []byte, linkEnd int) bool { + entityRanges := htmlEntityRe.FindAllIndex(data[:linkEnd], -1) + return entityRanges != nil && entityRanges[len(entityRanges)-1][1] == linkEnd +} + +// hasPrefixCaseInsensitive is a custom implementation of +// strings.HasPrefix(strings.ToLower(s), prefix) +// we rolled our own because ToLower pulls in a huge machinery of lowercasing +// anything from Unicode and that's very slow. Since this func will only be +// used on ASCII protocol prefixes, we can take shortcuts. +func hasPrefixCaseInsensitive(s, prefix []byte) bool { + if len(s) < len(prefix) { + return false + } + delta := byte('a' - 'A') + for i, b := range prefix { + if b != s[i] && b != s[i]+delta { + return false + } + } + return true +} + +var protocolPrefixes = [][]byte{ + []byte("http://"), + []byte("https://"), + []byte("ftp://"), + []byte("file://"), + []byte("mailto:"), +} + +const shortestPrefix = 6 // len("ftp://"), the shortest of the above + +func maybeAutoLink(p *Markdown, data []byte, offset int) (int, *Node) { + // quick check to rule out most false hits + if p.insideLink || len(data) < offset+shortestPrefix { + return 0, nil + } + for _, prefix := range protocolPrefixes { + endOfHead := offset + 8 // 8 is the len() of the longest prefix + if endOfHead > len(data) { + endOfHead = len(data) + } + if hasPrefixCaseInsensitive(data[offset:endOfHead], prefix) { + return autoLink(p, data, offset) + } + } + return 0, nil +} + +func autoLink(p *Markdown, data []byte, offset int) (int, *Node) { + // Now a more expensive check to see if we're not inside an anchor element + anchorStart := offset + offsetFromAnchor := 0 + for anchorStart > 0 && data[anchorStart] != '<' { + anchorStart-- + offsetFromAnchor++ + } + + anchorStr := anchorRe.Find(data[anchorStart:]) + if anchorStr != nil { + anchorClose := NewNode(HTMLSpan) + anchorClose.Literal = anchorStr[offsetFromAnchor:] + return len(anchorStr) - offsetFromAnchor, anchorClose + } + + // scan backward for a word boundary + rewind := 0 + for offset-rewind > 0 && rewind <= 7 && isletter(data[offset-rewind-1]) { + rewind++ + } + if rewind > 6 { // longest supported protocol is "mailto" which has 6 letters + return 0, nil + } + + origData := data + data = data[offset-rewind:] + + if !isSafeLink(data) { + return 0, nil + } + + linkEnd := 0 + for linkEnd < len(data) && !isEndOfLink(data[linkEnd]) { + linkEnd++ + } + + // Skip punctuation at the end of the link + if (data[linkEnd-1] == '.' || data[linkEnd-1] == ',') && data[linkEnd-2] != '\\' { + linkEnd-- + } + + // But don't skip semicolon if it's a part of escaped entity: + if data[linkEnd-1] == ';' && data[linkEnd-2] != '\\' && !linkEndsWithEntity(data, linkEnd) { + linkEnd-- + } + + // See if the link finishes with a punctuation sign that can be closed. + var copen byte + switch data[linkEnd-1] { + case '"': + copen = '"' + case '\'': + copen = '\'' + case ')': + copen = '(' + case ']': + copen = '[' + case '}': + copen = '{' + default: + copen = 0 + } + + if copen != 0 { + bufEnd := offset - rewind + linkEnd - 2 + + openDelim := 1 + + /* Try to close the final punctuation sign in this same line; + * if we managed to close it outside of the URL, that means that it's + * not part of the URL. If it closes inside the URL, that means it + * is part of the URL. + * + * Examples: + * + * foo http://www.pokemon.com/Pikachu_(Electric) bar + * => http://www.pokemon.com/Pikachu_(Electric) + * + * foo (http://www.pokemon.com/Pikachu_(Electric)) bar + * => http://www.pokemon.com/Pikachu_(Electric) + * + * foo http://www.pokemon.com/Pikachu_(Electric)) bar + * => http://www.pokemon.com/Pikachu_(Electric)) + * + * (foo http://www.pokemon.com/Pikachu_(Electric)) bar + * => foo http://www.pokemon.com/Pikachu_(Electric) + */ + + for bufEnd >= 0 && origData[bufEnd] != '\n' && openDelim != 0 { + if origData[bufEnd] == data[linkEnd-1] { + openDelim++ + } + + if origData[bufEnd] == copen { + openDelim-- + } + + bufEnd-- + } + + if openDelim == 0 { + linkEnd-- + } + } + + var uLink bytes.Buffer + unescapeText(&uLink, data[:linkEnd]) + + if uLink.Len() > 0 { + node := NewNode(Link) + node.Destination = uLink.Bytes() + node.AppendChild(text(uLink.Bytes())) + return linkEnd, node + } + + return linkEnd, nil +} + +func isEndOfLink(char byte) bool { + return isspace(char) || char == '<' +} + +var validUris = [][]byte{[]byte("http://"), []byte("https://"), []byte("ftp://"), []byte("mailto://")} +var validPaths = [][]byte{[]byte("/"), []byte("./"), []byte("../")} + +func isSafeLink(link []byte) bool { + for _, path := range validPaths { + if len(link) >= len(path) && bytes.Equal(link[:len(path)], path) { + if len(link) == len(path) { + return true + } else if isalnum(link[len(path)]) { + return true + } + } + } + + for _, prefix := range validUris { + // TODO: handle unicode here + // case-insensitive prefix test + if len(link) > len(prefix) && bytes.Equal(bytes.ToLower(link[:len(prefix)]), prefix) && isalnum(link[len(prefix)]) { + return true + } + } + + return false +} + +// return the length of the given tag, or 0 is it's not valid +func tagLength(data []byte) (autolink autolinkType, end int) { + var i, j int + + // a valid tag can't be shorter than 3 chars + if len(data) < 3 { + return notAutolink, 0 + } + + // begins with a '<' optionally followed by '/', followed by letter or number + if data[0] != '<' { + return notAutolink, 0 + } + if data[1] == '/' { + i = 2 + } else { + i = 1 + } + + if !isalnum(data[i]) { + return notAutolink, 0 + } + + // scheme test + autolink = notAutolink + + // try to find the beginning of an URI + for i < len(data) && (isalnum(data[i]) || data[i] == '.' || data[i] == '+' || data[i] == '-') { + i++ + } + + if i > 1 && i < len(data) && data[i] == '@' { + if j = isMailtoAutoLink(data[i:]); j != 0 { + return emailAutolink, i + j + } + } + + if i > 2 && i < len(data) && data[i] == ':' { + autolink = normalAutolink + i++ + } + + // complete autolink test: no whitespace or ' or " + switch { + case i >= len(data): + autolink = notAutolink + case autolink != notAutolink: + j = i + + for i < len(data) { + if data[i] == '\\' { + i += 2 + } else if data[i] == '>' || data[i] == '\'' || data[i] == '"' || isspace(data[i]) { + break + } else { + i++ + } + + } + + if i >= len(data) { + return autolink, 0 + } + if i > j && data[i] == '>' { + return autolink, i + 1 + } + + // one of the forbidden chars has been found + autolink = notAutolink + } + i += bytes.IndexByte(data[i:], '>') + if i < 0 { + return autolink, 0 + } + return autolink, i + 1 +} + +// look for the address part of a mail autolink and '>' +// this is less strict than the original markdown e-mail address matching +func isMailtoAutoLink(data []byte) int { + nb := 0 + + // address is assumed to be: [-@._a-zA-Z0-9]+ with exactly one '@' + for i := 0; i < len(data); i++ { + if isalnum(data[i]) { + continue + } + + switch data[i] { + case '@': + nb++ + + case '-', '.', '_': + break + + case '>': + if nb == 1 { + return i + 1 + } + return 0 + default: + return 0 + } + } + + return 0 +} + +// look for the next emph char, skipping other constructs +func helperFindEmphChar(data []byte, c byte) int { + i := 0 + + for i < len(data) { + for i < len(data) && data[i] != c && data[i] != '`' && data[i] != '[' { + i++ + } + if i >= len(data) { + return 0 + } + // do not count escaped chars + if i != 0 && data[i-1] == '\\' { + i++ + continue + } + if data[i] == c { + return i + } + + if data[i] == '`' { + // skip a code span + tmpI := 0 + i++ + for i < len(data) && data[i] != '`' { + if tmpI == 0 && data[i] == c { + tmpI = i + } + i++ + } + if i >= len(data) { + return tmpI + } + i++ + } else if data[i] == '[' { + // skip a link + tmpI := 0 + i++ + for i < len(data) && data[i] != ']' { + if tmpI == 0 && data[i] == c { + tmpI = i + } + i++ + } + i++ + for i < len(data) && (data[i] == ' ' || data[i] == '\n') { + i++ + } + if i >= len(data) { + return tmpI + } + if data[i] != '[' && data[i] != '(' { // not a link + if tmpI > 0 { + return tmpI + } + continue + } + cc := data[i] + i++ + for i < len(data) && data[i] != cc { + if tmpI == 0 && data[i] == c { + return i + } + i++ + } + if i >= len(data) { + return tmpI + } + i++ + } + } + return 0 +} + +func helperEmphasis(p *Markdown, data []byte, c byte) (int, *Node) { + i := 0 + + // skip one symbol if coming from emph3 + if len(data) > 1 && data[0] == c && data[1] == c { + i = 1 + } + + for i < len(data) { + length := helperFindEmphChar(data[i:], c) + if length == 0 { + return 0, nil + } + i += length + if i >= len(data) { + return 0, nil + } + + if i+1 < len(data) && data[i+1] == c { + i++ + continue + } + + if data[i] == c && !isspace(data[i-1]) { + + if p.extensions&NoIntraEmphasis != 0 { + if !(i+1 == len(data) || isspace(data[i+1]) || ispunct(data[i+1])) { + continue + } + } + + emph := NewNode(Emph) + p.inline(emph, data[:i]) + return i + 1, emph + } + } + + return 0, nil +} + +func helperDoubleEmphasis(p *Markdown, data []byte, c byte) (int, *Node) { + i := 0 + + for i < len(data) { + length := helperFindEmphChar(data[i:], c) + if length == 0 { + return 0, nil + } + i += length + + if i+1 < len(data) && data[i] == c && data[i+1] == c && i > 0 && !isspace(data[i-1]) { + nodeType := Strong + if c == '~' { + nodeType = Del + } + node := NewNode(nodeType) + p.inline(node, data[:i]) + return i + 2, node + } + i++ + } + return 0, nil +} + +func helperTripleEmphasis(p *Markdown, data []byte, offset int, c byte) (int, *Node) { + i := 0 + origData := data + data = data[offset:] + + for i < len(data) { + length := helperFindEmphChar(data[i:], c) + if length == 0 { + return 0, nil + } + i += length + + // skip whitespace preceded symbols + if data[i] != c || isspace(data[i-1]) { + continue + } + + switch { + case i+2 < len(data) && data[i+1] == c && data[i+2] == c: + // triple symbol found + strong := NewNode(Strong) + em := NewNode(Emph) + strong.AppendChild(em) + p.inline(em, data[:i]) + return i + 3, strong + case (i+1 < len(data) && data[i+1] == c): + // double symbol found, hand over to emph1 + length, node := helperEmphasis(p, origData[offset-2:], c) + if length == 0 { + return 0, nil + } + return length - 2, node + default: + // single symbol found, hand over to emph2 + length, node := helperDoubleEmphasis(p, origData[offset-1:], c) + if length == 0 { + return 0, nil + } + return length - 1, node + } + } + return 0, nil +} + +func text(s []byte) *Node { + node := NewNode(Text) + node.Literal = s + return node +} + +func normalizeURI(s []byte) []byte { + return s // TODO: implement +} diff --git a/vendor/github.com/russross/blackfriday/v2/markdown.go b/vendor/github.com/russross/blackfriday/v2/markdown.go new file mode 100644 index 000000000..58d2e4538 --- /dev/null +++ b/vendor/github.com/russross/blackfriday/v2/markdown.go @@ -0,0 +1,950 @@ +// Blackfriday Markdown Processor +// Available at http://github.com/russross/blackfriday +// +// Copyright © 2011 Russ Ross . +// Distributed under the Simplified BSD License. +// See README.md for details. + +package blackfriday + +import ( + "bytes" + "fmt" + "io" + "strings" + "unicode/utf8" +) + +// +// Markdown parsing and processing +// + +// Version string of the package. Appears in the rendered document when +// CompletePage flag is on. +const Version = "2.0" + +// Extensions is a bitwise or'ed collection of enabled Blackfriday's +// extensions. +type Extensions int + +// These are the supported markdown parsing extensions. +// OR these values together to select multiple extensions. +const ( + NoExtensions Extensions = 0 + NoIntraEmphasis Extensions = 1 << iota // Ignore emphasis markers inside words + Tables // Render tables + FencedCode // Render fenced code blocks + Autolink // Detect embedded URLs that are not explicitly marked + Strikethrough // Strikethrough text using ~~test~~ + LaxHTMLBlocks // Loosen up HTML block parsing rules + SpaceHeadings // Be strict about prefix heading rules + HardLineBreak // Translate newlines into line breaks + TabSizeEight // Expand tabs to eight spaces instead of four + Footnotes // Pandoc-style footnotes + NoEmptyLineBeforeBlock // No need to insert an empty line to start a (code, quote, ordered list, unordered list) block + HeadingIDs // specify heading IDs with {#id} + Titleblock // Titleblock ala pandoc + AutoHeadingIDs // Create the heading ID from the text + BackslashLineBreak // Translate trailing backslashes into line breaks + DefinitionLists // Render definition lists + + CommonHTMLFlags HTMLFlags = UseXHTML | Smartypants | + SmartypantsFractions | SmartypantsDashes | SmartypantsLatexDashes + + CommonExtensions Extensions = NoIntraEmphasis | Tables | FencedCode | + Autolink | Strikethrough | SpaceHeadings | HeadingIDs | + BackslashLineBreak | DefinitionLists +) + +// ListType contains bitwise or'ed flags for list and list item objects. +type ListType int + +// These are the possible flag values for the ListItem renderer. +// Multiple flag values may be ORed together. +// These are mostly of interest if you are writing a new output format. +const ( + ListTypeOrdered ListType = 1 << iota + ListTypeDefinition + ListTypeTerm + + ListItemContainsBlock + ListItemBeginningOfList // TODO: figure out if this is of any use now + ListItemEndOfList +) + +// CellAlignFlags holds a type of alignment in a table cell. +type CellAlignFlags int + +// These are the possible flag values for the table cell renderer. +// Only a single one of these values will be used; they are not ORed together. +// These are mostly of interest if you are writing a new output format. +const ( + TableAlignmentLeft CellAlignFlags = 1 << iota + TableAlignmentRight + TableAlignmentCenter = (TableAlignmentLeft | TableAlignmentRight) +) + +// The size of a tab stop. +const ( + TabSizeDefault = 4 + TabSizeDouble = 8 +) + +// blockTags is a set of tags that are recognized as HTML block tags. +// Any of these can be included in markdown text without special escaping. +var blockTags = map[string]struct{}{ + "blockquote": {}, + "del": {}, + "div": {}, + "dl": {}, + "fieldset": {}, + "form": {}, + "h1": {}, + "h2": {}, + "h3": {}, + "h4": {}, + "h5": {}, + "h6": {}, + "iframe": {}, + "ins": {}, + "math": {}, + "noscript": {}, + "ol": {}, + "pre": {}, + "p": {}, + "script": {}, + "style": {}, + "table": {}, + "ul": {}, + + // HTML5 + "address": {}, + "article": {}, + "aside": {}, + "canvas": {}, + "figcaption": {}, + "figure": {}, + "footer": {}, + "header": {}, + "hgroup": {}, + "main": {}, + "nav": {}, + "output": {}, + "progress": {}, + "section": {}, + "video": {}, +} + +// Renderer is the rendering interface. This is mostly of interest if you are +// implementing a new rendering format. +// +// Only an HTML implementation is provided in this repository, see the README +// for external implementations. +type Renderer interface { + // RenderNode is the main rendering method. It will be called once for + // every leaf node and twice for every non-leaf node (first with + // entering=true, then with entering=false). The method should write its + // rendition of the node to the supplied writer w. + RenderNode(w io.Writer, node *Node, entering bool) WalkStatus + + // RenderHeader is a method that allows the renderer to produce some + // content preceding the main body of the output document. The header is + // understood in the broad sense here. For example, the default HTML + // renderer will write not only the HTML document preamble, but also the + // table of contents if it was requested. + // + // The method will be passed an entire document tree, in case a particular + // implementation needs to inspect it to produce output. + // + // The output should be written to the supplied writer w. If your + // implementation has no header to write, supply an empty implementation. + RenderHeader(w io.Writer, ast *Node) + + // RenderFooter is a symmetric counterpart of RenderHeader. + RenderFooter(w io.Writer, ast *Node) +} + +// Callback functions for inline parsing. One such function is defined +// for each character that triggers a response when parsing inline data. +type inlineParser func(p *Markdown, data []byte, offset int) (int, *Node) + +// Markdown is a type that holds extensions and the runtime state used by +// Parse, and the renderer. You can not use it directly, construct it with New. +type Markdown struct { + renderer Renderer + referenceOverride ReferenceOverrideFunc + refs map[string]*reference + inlineCallback [256]inlineParser + extensions Extensions + nesting int + maxNesting int + insideLink bool + + // Footnotes need to be ordered as well as available to quickly check for + // presence. If a ref is also a footnote, it's stored both in refs and here + // in notes. Slice is nil if footnotes not enabled. + notes []*reference + + doc *Node + tip *Node // = doc + oldTip *Node + lastMatchedContainer *Node // = doc + allClosed bool +} + +func (p *Markdown) getRef(refid string) (ref *reference, found bool) { + if p.referenceOverride != nil { + r, overridden := p.referenceOverride(refid) + if overridden { + if r == nil { + return nil, false + } + return &reference{ + link: []byte(r.Link), + title: []byte(r.Title), + noteID: 0, + hasBlock: false, + text: []byte(r.Text)}, true + } + } + // refs are case insensitive + ref, found = p.refs[strings.ToLower(refid)] + return ref, found +} + +func (p *Markdown) finalize(block *Node) { + above := block.Parent + block.open = false + p.tip = above +} + +func (p *Markdown) addChild(node NodeType, offset uint32) *Node { + return p.addExistingChild(NewNode(node), offset) +} + +func (p *Markdown) addExistingChild(node *Node, offset uint32) *Node { + for !p.tip.canContain(node.Type) { + p.finalize(p.tip) + } + p.tip.AppendChild(node) + p.tip = node + return node +} + +func (p *Markdown) closeUnmatchedBlocks() { + if !p.allClosed { + for p.oldTip != p.lastMatchedContainer { + parent := p.oldTip.Parent + p.finalize(p.oldTip) + p.oldTip = parent + } + p.allClosed = true + } +} + +// +// +// Public interface +// +// + +// Reference represents the details of a link. +// See the documentation in Options for more details on use-case. +type Reference struct { + // Link is usually the URL the reference points to. + Link string + // Title is the alternate text describing the link in more detail. + Title string + // Text is the optional text to override the ref with if the syntax used was + // [refid][] + Text string +} + +// ReferenceOverrideFunc is expected to be called with a reference string and +// return either a valid Reference type that the reference string maps to or +// nil. If overridden is false, the default reference logic will be executed. +// See the documentation in Options for more details on use-case. +type ReferenceOverrideFunc func(reference string) (ref *Reference, overridden bool) + +// New constructs a Markdown processor. You can use the same With* functions as +// for Run() to customize parser's behavior and the renderer. +func New(opts ...Option) *Markdown { + var p Markdown + for _, opt := range opts { + opt(&p) + } + p.refs = make(map[string]*reference) + p.maxNesting = 16 + p.insideLink = false + docNode := NewNode(Document) + p.doc = docNode + p.tip = docNode + p.oldTip = docNode + p.lastMatchedContainer = docNode + p.allClosed = true + // register inline parsers + p.inlineCallback[' '] = maybeLineBreak + p.inlineCallback['*'] = emphasis + p.inlineCallback['_'] = emphasis + if p.extensions&Strikethrough != 0 { + p.inlineCallback['~'] = emphasis + } + p.inlineCallback['`'] = codeSpan + p.inlineCallback['\n'] = lineBreak + p.inlineCallback['['] = link + p.inlineCallback['<'] = leftAngle + p.inlineCallback['\\'] = escape + p.inlineCallback['&'] = entity + p.inlineCallback['!'] = maybeImage + p.inlineCallback['^'] = maybeInlineFootnote + if p.extensions&Autolink != 0 { + p.inlineCallback['h'] = maybeAutoLink + p.inlineCallback['m'] = maybeAutoLink + p.inlineCallback['f'] = maybeAutoLink + p.inlineCallback['H'] = maybeAutoLink + p.inlineCallback['M'] = maybeAutoLink + p.inlineCallback['F'] = maybeAutoLink + } + if p.extensions&Footnotes != 0 { + p.notes = make([]*reference, 0) + } + return &p +} + +// Option customizes the Markdown processor's default behavior. +type Option func(*Markdown) + +// WithRenderer allows you to override the default renderer. +func WithRenderer(r Renderer) Option { + return func(p *Markdown) { + p.renderer = r + } +} + +// WithExtensions allows you to pick some of the many extensions provided by +// Blackfriday. You can bitwise OR them. +func WithExtensions(e Extensions) Option { + return func(p *Markdown) { + p.extensions = e + } +} + +// WithNoExtensions turns off all extensions and custom behavior. +func WithNoExtensions() Option { + return func(p *Markdown) { + p.extensions = NoExtensions + p.renderer = NewHTMLRenderer(HTMLRendererParameters{ + Flags: HTMLFlagsNone, + }) + } +} + +// WithRefOverride sets an optional function callback that is called every +// time a reference is resolved. +// +// In Markdown, the link reference syntax can be made to resolve a link to +// a reference instead of an inline URL, in one of the following ways: +// +// * [link text][refid] +// * [refid][] +// +// Usually, the refid is defined at the bottom of the Markdown document. If +// this override function is provided, the refid is passed to the override +// function first, before consulting the defined refids at the bottom. If +// the override function indicates an override did not occur, the refids at +// the bottom will be used to fill in the link details. +func WithRefOverride(o ReferenceOverrideFunc) Option { + return func(p *Markdown) { + p.referenceOverride = o + } +} + +// Run is the main entry point to Blackfriday. It parses and renders a +// block of markdown-encoded text. +// +// The simplest invocation of Run takes one argument, input: +// output := Run(input) +// This will parse the input with CommonExtensions enabled and render it with +// the default HTMLRenderer (with CommonHTMLFlags). +// +// Variadic arguments opts can customize the default behavior. Since Markdown +// type does not contain exported fields, you can not use it directly. Instead, +// use the With* functions. For example, this will call the most basic +// functionality, with no extensions: +// output := Run(input, WithNoExtensions()) +// +// You can use any number of With* arguments, even contradicting ones. They +// will be applied in order of appearance and the latter will override the +// former: +// output := Run(input, WithNoExtensions(), WithExtensions(exts), +// WithRenderer(yourRenderer)) +func Run(input []byte, opts ...Option) []byte { + r := NewHTMLRenderer(HTMLRendererParameters{ + Flags: CommonHTMLFlags, + }) + optList := []Option{WithRenderer(r), WithExtensions(CommonExtensions)} + optList = append(optList, opts...) + parser := New(optList...) + ast := parser.Parse(input) + var buf bytes.Buffer + parser.renderer.RenderHeader(&buf, ast) + ast.Walk(func(node *Node, entering bool) WalkStatus { + return parser.renderer.RenderNode(&buf, node, entering) + }) + parser.renderer.RenderFooter(&buf, ast) + return buf.Bytes() +} + +// Parse is an entry point to the parsing part of Blackfriday. It takes an +// input markdown document and produces a syntax tree for its contents. This +// tree can then be rendered with a default or custom renderer, or +// analyzed/transformed by the caller to whatever non-standard needs they have. +// The return value is the root node of the syntax tree. +func (p *Markdown) Parse(input []byte) *Node { + p.block(input) + // Walk the tree and finish up some of unfinished blocks + for p.tip != nil { + p.finalize(p.tip) + } + // Walk the tree again and process inline markdown in each block + p.doc.Walk(func(node *Node, entering bool) WalkStatus { + if node.Type == Paragraph || node.Type == Heading || node.Type == TableCell { + p.inline(node, node.content) + node.content = nil + } + return GoToNext + }) + p.parseRefsToAST() + return p.doc +} + +func (p *Markdown) parseRefsToAST() { + if p.extensions&Footnotes == 0 || len(p.notes) == 0 { + return + } + p.tip = p.doc + block := p.addBlock(List, nil) + block.IsFootnotesList = true + block.ListFlags = ListTypeOrdered + flags := ListItemBeginningOfList + // Note: this loop is intentionally explicit, not range-form. This is + // because the body of the loop will append nested footnotes to p.notes and + // we need to process those late additions. Range form would only walk over + // the fixed initial set. + for i := 0; i < len(p.notes); i++ { + ref := p.notes[i] + p.addExistingChild(ref.footnote, 0) + block := ref.footnote + block.ListFlags = flags | ListTypeOrdered + block.RefLink = ref.link + if ref.hasBlock { + flags |= ListItemContainsBlock + p.block(ref.title) + } else { + p.inline(block, ref.title) + } + flags &^= ListItemBeginningOfList | ListItemContainsBlock + } + above := block.Parent + finalizeList(block) + p.tip = above + block.Walk(func(node *Node, entering bool) WalkStatus { + if node.Type == Paragraph || node.Type == Heading { + p.inline(node, node.content) + node.content = nil + } + return GoToNext + }) +} + +// +// Link references +// +// This section implements support for references that (usually) appear +// as footnotes in a document, and can be referenced anywhere in the document. +// The basic format is: +// +// [1]: http://www.google.com/ "Google" +// [2]: http://www.github.com/ "Github" +// +// Anywhere in the document, the reference can be linked by referring to its +// label, i.e., 1 and 2 in this example, as in: +// +// This library is hosted on [Github][2], a git hosting site. +// +// Actual footnotes as specified in Pandoc and supported by some other Markdown +// libraries such as php-markdown are also taken care of. They look like this: +// +// This sentence needs a bit of further explanation.[^note] +// +// [^note]: This is the explanation. +// +// Footnotes should be placed at the end of the document in an ordered list. +// Finally, there are inline footnotes such as: +// +// Inline footnotes^[Also supported.] provide a quick inline explanation, +// but are rendered at the bottom of the document. +// + +// reference holds all information necessary for a reference-style links or +// footnotes. +// +// Consider this markdown with reference-style links: +// +// [link][ref] +// +// [ref]: /url/ "tooltip title" +// +// It will be ultimately converted to this HTML: +// +//

    link

    +// +// And a reference structure will be populated as follows: +// +// p.refs["ref"] = &reference{ +// link: "/url/", +// title: "tooltip title", +// } +// +// Alternatively, reference can contain information about a footnote. Consider +// this markdown: +// +// Text needing a footnote.[^a] +// +// [^a]: This is the note +// +// A reference structure will be populated as follows: +// +// p.refs["a"] = &reference{ +// link: "a", +// title: "This is the note", +// noteID: , +// } +// +// TODO: As you can see, it begs for splitting into two dedicated structures +// for refs and for footnotes. +type reference struct { + link []byte + title []byte + noteID int // 0 if not a footnote ref + hasBlock bool + footnote *Node // a link to the Item node within a list of footnotes + + text []byte // only gets populated by refOverride feature with Reference.Text +} + +func (r *reference) String() string { + return fmt.Sprintf("{link: %q, title: %q, text: %q, noteID: %d, hasBlock: %v}", + r.link, r.title, r.text, r.noteID, r.hasBlock) +} + +// Check whether or not data starts with a reference link. +// If so, it is parsed and stored in the list of references +// (in the render struct). +// Returns the number of bytes to skip to move past it, +// or zero if the first line is not a reference. +func isReference(p *Markdown, data []byte, tabSize int) int { + // up to 3 optional leading spaces + if len(data) < 4 { + return 0 + } + i := 0 + for i < 3 && data[i] == ' ' { + i++ + } + + noteID := 0 + + // id part: anything but a newline between brackets + if data[i] != '[' { + return 0 + } + i++ + if p.extensions&Footnotes != 0 { + if i < len(data) && data[i] == '^' { + // we can set it to anything here because the proper noteIds will + // be assigned later during the second pass. It just has to be != 0 + noteID = 1 + i++ + } + } + idOffset := i + for i < len(data) && data[i] != '\n' && data[i] != '\r' && data[i] != ']' { + i++ + } + if i >= len(data) || data[i] != ']' { + return 0 + } + idEnd := i + // footnotes can have empty ID, like this: [^], but a reference can not be + // empty like this: []. Break early if it's not a footnote and there's no ID + if noteID == 0 && idOffset == idEnd { + return 0 + } + // spacer: colon (space | tab)* newline? (space | tab)* + i++ + if i >= len(data) || data[i] != ':' { + return 0 + } + i++ + for i < len(data) && (data[i] == ' ' || data[i] == '\t') { + i++ + } + if i < len(data) && (data[i] == '\n' || data[i] == '\r') { + i++ + if i < len(data) && data[i] == '\n' && data[i-1] == '\r' { + i++ + } + } + for i < len(data) && (data[i] == ' ' || data[i] == '\t') { + i++ + } + if i >= len(data) { + return 0 + } + + var ( + linkOffset, linkEnd int + titleOffset, titleEnd int + lineEnd int + raw []byte + hasBlock bool + ) + + if p.extensions&Footnotes != 0 && noteID != 0 { + linkOffset, linkEnd, raw, hasBlock = scanFootnote(p, data, i, tabSize) + lineEnd = linkEnd + } else { + linkOffset, linkEnd, titleOffset, titleEnd, lineEnd = scanLinkRef(p, data, i) + } + if lineEnd == 0 { + return 0 + } + + // a valid ref has been found + + ref := &reference{ + noteID: noteID, + hasBlock: hasBlock, + } + + if noteID > 0 { + // reusing the link field for the id since footnotes don't have links + ref.link = data[idOffset:idEnd] + // if footnote, it's not really a title, it's the contained text + ref.title = raw + } else { + ref.link = data[linkOffset:linkEnd] + ref.title = data[titleOffset:titleEnd] + } + + // id matches are case-insensitive + id := string(bytes.ToLower(data[idOffset:idEnd])) + + p.refs[id] = ref + + return lineEnd +} + +func scanLinkRef(p *Markdown, data []byte, i int) (linkOffset, linkEnd, titleOffset, titleEnd, lineEnd int) { + // link: whitespace-free sequence, optionally between angle brackets + if data[i] == '<' { + i++ + } + linkOffset = i + for i < len(data) && data[i] != ' ' && data[i] != '\t' && data[i] != '\n' && data[i] != '\r' { + i++ + } + linkEnd = i + if data[linkOffset] == '<' && data[linkEnd-1] == '>' { + linkOffset++ + linkEnd-- + } + + // optional spacer: (space | tab)* (newline | '\'' | '"' | '(' ) + for i < len(data) && (data[i] == ' ' || data[i] == '\t') { + i++ + } + if i < len(data) && data[i] != '\n' && data[i] != '\r' && data[i] != '\'' && data[i] != '"' && data[i] != '(' { + return + } + + // compute end-of-line + if i >= len(data) || data[i] == '\r' || data[i] == '\n' { + lineEnd = i + } + if i+1 < len(data) && data[i] == '\r' && data[i+1] == '\n' { + lineEnd++ + } + + // optional (space|tab)* spacer after a newline + if lineEnd > 0 { + i = lineEnd + 1 + for i < len(data) && (data[i] == ' ' || data[i] == '\t') { + i++ + } + } + + // optional title: any non-newline sequence enclosed in '"() alone on its line + if i+1 < len(data) && (data[i] == '\'' || data[i] == '"' || data[i] == '(') { + i++ + titleOffset = i + + // look for EOL + for i < len(data) && data[i] != '\n' && data[i] != '\r' { + i++ + } + if i+1 < len(data) && data[i] == '\n' && data[i+1] == '\r' { + titleEnd = i + 1 + } else { + titleEnd = i + } + + // step back + i-- + for i > titleOffset && (data[i] == ' ' || data[i] == '\t') { + i-- + } + if i > titleOffset && (data[i] == '\'' || data[i] == '"' || data[i] == ')') { + lineEnd = titleEnd + titleEnd = i + } + } + + return +} + +// The first bit of this logic is the same as Parser.listItem, but the rest +// is much simpler. This function simply finds the entire block and shifts it +// over by one tab if it is indeed a block (just returns the line if it's not). +// blockEnd is the end of the section in the input buffer, and contents is the +// extracted text that was shifted over one tab. It will need to be rendered at +// the end of the document. +func scanFootnote(p *Markdown, data []byte, i, indentSize int) (blockStart, blockEnd int, contents []byte, hasBlock bool) { + if i == 0 || len(data) == 0 { + return + } + + // skip leading whitespace on first line + for i < len(data) && data[i] == ' ' { + i++ + } + + blockStart = i + + // find the end of the line + blockEnd = i + for i < len(data) && data[i-1] != '\n' { + i++ + } + + // get working buffer + var raw bytes.Buffer + + // put the first line into the working buffer + raw.Write(data[blockEnd:i]) + blockEnd = i + + // process the following lines + containsBlankLine := false + +gatherLines: + for blockEnd < len(data) { + i++ + + // find the end of this line + for i < len(data) && data[i-1] != '\n' { + i++ + } + + // if it is an empty line, guess that it is part of this item + // and move on to the next line + if p.isEmpty(data[blockEnd:i]) > 0 { + containsBlankLine = true + blockEnd = i + continue + } + + n := 0 + if n = isIndented(data[blockEnd:i], indentSize); n == 0 { + // this is the end of the block. + // we don't want to include this last line in the index. + break gatherLines + } + + // if there were blank lines before this one, insert a new one now + if containsBlankLine { + raw.WriteByte('\n') + containsBlankLine = false + } + + // get rid of that first tab, write to buffer + raw.Write(data[blockEnd+n : i]) + hasBlock = true + + blockEnd = i + } + + if data[blockEnd-1] != '\n' { + raw.WriteByte('\n') + } + + contents = raw.Bytes() + + return +} + +// +// +// Miscellaneous helper functions +// +// + +// Test if a character is a punctuation symbol. +// Taken from a private function in regexp in the stdlib. +func ispunct(c byte) bool { + for _, r := range []byte("!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~") { + if c == r { + return true + } + } + return false +} + +// Test if a character is a whitespace character. +func isspace(c byte) bool { + return ishorizontalspace(c) || isverticalspace(c) +} + +// Test if a character is a horizontal whitespace character. +func ishorizontalspace(c byte) bool { + return c == ' ' || c == '\t' +} + +// Test if a character is a vertical character. +func isverticalspace(c byte) bool { + return c == '\n' || c == '\r' || c == '\f' || c == '\v' +} + +// Test if a character is letter. +func isletter(c byte) bool { + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') +} + +// Test if a character is a letter or a digit. +// TODO: check when this is looking for ASCII alnum and when it should use unicode +func isalnum(c byte) bool { + return (c >= '0' && c <= '9') || isletter(c) +} + +// Replace tab characters with spaces, aligning to the next TAB_SIZE column. +// always ends output with a newline +func expandTabs(out *bytes.Buffer, line []byte, tabSize int) { + // first, check for common cases: no tabs, or only tabs at beginning of line + i, prefix := 0, 0 + slowcase := false + for i = 0; i < len(line); i++ { + if line[i] == '\t' { + if prefix == i { + prefix++ + } else { + slowcase = true + break + } + } + } + + // no need to decode runes if all tabs are at the beginning of the line + if !slowcase { + for i = 0; i < prefix*tabSize; i++ { + out.WriteByte(' ') + } + out.Write(line[prefix:]) + return + } + + // the slow case: we need to count runes to figure out how + // many spaces to insert for each tab + column := 0 + i = 0 + for i < len(line) { + start := i + for i < len(line) && line[i] != '\t' { + _, size := utf8.DecodeRune(line[i:]) + i += size + column++ + } + + if i > start { + out.Write(line[start:i]) + } + + if i >= len(line) { + break + } + + for { + out.WriteByte(' ') + column++ + if column%tabSize == 0 { + break + } + } + + i++ + } +} + +// Find if a line counts as indented or not. +// Returns number of characters the indent is (0 = not indented). +func isIndented(data []byte, indentSize int) int { + if len(data) == 0 { + return 0 + } + if data[0] == '\t' { + return 1 + } + if len(data) < indentSize { + return 0 + } + for i := 0; i < indentSize; i++ { + if data[i] != ' ' { + return 0 + } + } + return indentSize +} + +// Create a url-safe slug for fragments +func slugify(in []byte) []byte { + if len(in) == 0 { + return in + } + out := make([]byte, 0, len(in)) + sym := false + + for _, ch := range in { + if isalnum(ch) { + sym = false + out = append(out, ch) + } else if sym { + continue + } else { + out = append(out, '-') + sym = true + } + } + var a, b int + var ch byte + for a, ch = range out { + if ch != '-' { + break + } + } + for b = len(out) - 1; b > 0; b-- { + if out[b] != '-' { + break + } + } + return out[a : b+1] +} diff --git a/vendor/github.com/russross/blackfriday/v2/node.go b/vendor/github.com/russross/blackfriday/v2/node.go new file mode 100644 index 000000000..51b9e8c1b --- /dev/null +++ b/vendor/github.com/russross/blackfriday/v2/node.go @@ -0,0 +1,354 @@ +package blackfriday + +import ( + "bytes" + "fmt" +) + +// NodeType specifies a type of a single node of a syntax tree. Usually one +// node (and its type) corresponds to a single markdown feature, e.g. emphasis +// or code block. +type NodeType int + +// Constants for identifying different types of nodes. See NodeType. +const ( + Document NodeType = iota + BlockQuote + List + Item + Paragraph + Heading + HorizontalRule + Emph + Strong + Del + Link + Image + Text + HTMLBlock + CodeBlock + Softbreak + Hardbreak + Code + HTMLSpan + Table + TableCell + TableHead + TableBody + TableRow +) + +var nodeTypeNames = []string{ + Document: "Document", + BlockQuote: "BlockQuote", + List: "List", + Item: "Item", + Paragraph: "Paragraph", + Heading: "Heading", + HorizontalRule: "HorizontalRule", + Emph: "Emph", + Strong: "Strong", + Del: "Del", + Link: "Link", + Image: "Image", + Text: "Text", + HTMLBlock: "HTMLBlock", + CodeBlock: "CodeBlock", + Softbreak: "Softbreak", + Hardbreak: "Hardbreak", + Code: "Code", + HTMLSpan: "HTMLSpan", + Table: "Table", + TableCell: "TableCell", + TableHead: "TableHead", + TableBody: "TableBody", + TableRow: "TableRow", +} + +func (t NodeType) String() string { + return nodeTypeNames[t] +} + +// ListData contains fields relevant to a List and Item node type. +type ListData struct { + ListFlags ListType + Tight bool // Skip

    s around list item data if true + BulletChar byte // '*', '+' or '-' in bullet lists + Delimiter byte // '.' or ')' after the number in ordered lists + RefLink []byte // If not nil, turns this list item into a footnote item and triggers different rendering + IsFootnotesList bool // This is a list of footnotes +} + +// LinkData contains fields relevant to a Link node type. +type LinkData struct { + Destination []byte // Destination is what goes into a href + Title []byte // Title is the tooltip thing that goes in a title attribute + NoteID int // NoteID contains a serial number of a footnote, zero if it's not a footnote + Footnote *Node // If it's a footnote, this is a direct link to the footnote Node. Otherwise nil. +} + +// CodeBlockData contains fields relevant to a CodeBlock node type. +type CodeBlockData struct { + IsFenced bool // Specifies whether it's a fenced code block or an indented one + Info []byte // This holds the info string + FenceChar byte + FenceLength int + FenceOffset int +} + +// TableCellData contains fields relevant to a TableCell node type. +type TableCellData struct { + IsHeader bool // This tells if it's under the header row + Align CellAlignFlags // This holds the value for align attribute +} + +// HeadingData contains fields relevant to a Heading node type. +type HeadingData struct { + Level int // This holds the heading level number + HeadingID string // This might hold heading ID, if present + IsTitleblock bool // Specifies whether it's a title block +} + +// Node is a single element in the abstract syntax tree of the parsed document. +// It holds connections to the structurally neighboring nodes and, for certain +// types of nodes, additional information that might be needed when rendering. +type Node struct { + Type NodeType // Determines the type of the node + Parent *Node // Points to the parent + FirstChild *Node // Points to the first child, if any + LastChild *Node // Points to the last child, if any + Prev *Node // Previous sibling; nil if it's the first child + Next *Node // Next sibling; nil if it's the last child + + Literal []byte // Text contents of the leaf nodes + + HeadingData // Populated if Type is Heading + ListData // Populated if Type is List + CodeBlockData // Populated if Type is CodeBlock + LinkData // Populated if Type is Link + TableCellData // Populated if Type is TableCell + + content []byte // Markdown content of the block nodes + open bool // Specifies an open block node that has not been finished to process yet +} + +// NewNode allocates a node of a specified type. +func NewNode(typ NodeType) *Node { + return &Node{ + Type: typ, + open: true, + } +} + +func (n *Node) String() string { + ellipsis := "" + snippet := n.Literal + if len(snippet) > 16 { + snippet = snippet[:16] + ellipsis = "..." + } + return fmt.Sprintf("%s: '%s%s'", n.Type, snippet, ellipsis) +} + +// Unlink removes node 'n' from the tree. +// It panics if the node is nil. +func (n *Node) Unlink() { + if n.Prev != nil { + n.Prev.Next = n.Next + } else if n.Parent != nil { + n.Parent.FirstChild = n.Next + } + if n.Next != nil { + n.Next.Prev = n.Prev + } else if n.Parent != nil { + n.Parent.LastChild = n.Prev + } + n.Parent = nil + n.Next = nil + n.Prev = nil +} + +// AppendChild adds a node 'child' as a child of 'n'. +// It panics if either node is nil. +func (n *Node) AppendChild(child *Node) { + child.Unlink() + child.Parent = n + if n.LastChild != nil { + n.LastChild.Next = child + child.Prev = n.LastChild + n.LastChild = child + } else { + n.FirstChild = child + n.LastChild = child + } +} + +// InsertBefore inserts 'sibling' immediately before 'n'. +// It panics if either node is nil. +func (n *Node) InsertBefore(sibling *Node) { + sibling.Unlink() + sibling.Prev = n.Prev + if sibling.Prev != nil { + sibling.Prev.Next = sibling + } + sibling.Next = n + n.Prev = sibling + sibling.Parent = n.Parent + if sibling.Prev == nil { + sibling.Parent.FirstChild = sibling + } +} + +func (n *Node) isContainer() bool { + switch n.Type { + case Document: + fallthrough + case BlockQuote: + fallthrough + case List: + fallthrough + case Item: + fallthrough + case Paragraph: + fallthrough + case Heading: + fallthrough + case Emph: + fallthrough + case Strong: + fallthrough + case Del: + fallthrough + case Link: + fallthrough + case Image: + fallthrough + case Table: + fallthrough + case TableHead: + fallthrough + case TableBody: + fallthrough + case TableRow: + fallthrough + case TableCell: + return true + default: + return false + } +} + +func (n *Node) canContain(t NodeType) bool { + if n.Type == List { + return t == Item + } + if n.Type == Document || n.Type == BlockQuote || n.Type == Item { + return t != Item + } + if n.Type == Table { + return t == TableHead || t == TableBody + } + if n.Type == TableHead || n.Type == TableBody { + return t == TableRow + } + if n.Type == TableRow { + return t == TableCell + } + return false +} + +// WalkStatus allows NodeVisitor to have some control over the tree traversal. +// It is returned from NodeVisitor and different values allow Node.Walk to +// decide which node to go to next. +type WalkStatus int + +const ( + // GoToNext is the default traversal of every node. + GoToNext WalkStatus = iota + // SkipChildren tells walker to skip all children of current node. + SkipChildren + // Terminate tells walker to terminate the traversal. + Terminate +) + +// NodeVisitor is a callback to be called when traversing the syntax tree. +// Called twice for every node: once with entering=true when the branch is +// first visited, then with entering=false after all the children are done. +type NodeVisitor func(node *Node, entering bool) WalkStatus + +// Walk is a convenience method that instantiates a walker and starts a +// traversal of subtree rooted at n. +func (n *Node) Walk(visitor NodeVisitor) { + w := newNodeWalker(n) + for w.current != nil { + status := visitor(w.current, w.entering) + switch status { + case GoToNext: + w.next() + case SkipChildren: + w.entering = false + w.next() + case Terminate: + return + } + } +} + +type nodeWalker struct { + current *Node + root *Node + entering bool +} + +func newNodeWalker(root *Node) *nodeWalker { + return &nodeWalker{ + current: root, + root: root, + entering: true, + } +} + +func (nw *nodeWalker) next() { + if (!nw.current.isContainer() || !nw.entering) && nw.current == nw.root { + nw.current = nil + return + } + if nw.entering && nw.current.isContainer() { + if nw.current.FirstChild != nil { + nw.current = nw.current.FirstChild + nw.entering = true + } else { + nw.entering = false + } + } else if nw.current.Next == nil { + nw.current = nw.current.Parent + nw.entering = false + } else { + nw.current = nw.current.Next + nw.entering = true + } +} + +func dump(ast *Node) { + fmt.Println(dumpString(ast)) +} + +func dumpR(ast *Node, depth int) string { + if ast == nil { + return "" + } + indent := bytes.Repeat([]byte("\t"), depth) + content := ast.Literal + if content == nil { + content = ast.content + } + result := fmt.Sprintf("%s%s(%q)\n", indent, ast.Type, content) + for n := ast.FirstChild; n != nil; n = n.Next { + result += dumpR(n, depth+1) + } + return result +} + +func dumpString(ast *Node) string { + return dumpR(ast, 0) +} diff --git a/vendor/github.com/russross/blackfriday/v2/smartypants.go b/vendor/github.com/russross/blackfriday/v2/smartypants.go new file mode 100644 index 000000000..3a220e942 --- /dev/null +++ b/vendor/github.com/russross/blackfriday/v2/smartypants.go @@ -0,0 +1,457 @@ +// +// Blackfriday Markdown Processor +// Available at http://github.com/russross/blackfriday +// +// Copyright © 2011 Russ Ross . +// Distributed under the Simplified BSD License. +// See README.md for details. +// + +// +// +// SmartyPants rendering +// +// + +package blackfriday + +import ( + "bytes" + "io" +) + +// SPRenderer is a struct containing state of a Smartypants renderer. +type SPRenderer struct { + inSingleQuote bool + inDoubleQuote bool + callbacks [256]smartCallback +} + +func wordBoundary(c byte) bool { + return c == 0 || isspace(c) || ispunct(c) +} + +func tolower(c byte) byte { + if c >= 'A' && c <= 'Z' { + return c - 'A' + 'a' + } + return c +} + +func isdigit(c byte) bool { + return c >= '0' && c <= '9' +} + +func smartQuoteHelper(out *bytes.Buffer, previousChar byte, nextChar byte, quote byte, isOpen *bool, addNBSP bool) bool { + // edge of the buffer is likely to be a tag that we don't get to see, + // so we treat it like text sometimes + + // enumerate all sixteen possibilities for (previousChar, nextChar) + // each can be one of {0, space, punct, other} + switch { + case previousChar == 0 && nextChar == 0: + // context is not any help here, so toggle + *isOpen = !*isOpen + case isspace(previousChar) && nextChar == 0: + // [ "] might be [ "foo...] + *isOpen = true + case ispunct(previousChar) && nextChar == 0: + // [!"] hmm... could be [Run!"] or [("...] + *isOpen = false + case /* isnormal(previousChar) && */ nextChar == 0: + // [a"] is probably a close + *isOpen = false + case previousChar == 0 && isspace(nextChar): + // [" ] might be [...foo" ] + *isOpen = false + case isspace(previousChar) && isspace(nextChar): + // [ " ] context is not any help here, so toggle + *isOpen = !*isOpen + case ispunct(previousChar) && isspace(nextChar): + // [!" ] is probably a close + *isOpen = false + case /* isnormal(previousChar) && */ isspace(nextChar): + // [a" ] this is one of the easy cases + *isOpen = false + case previousChar == 0 && ispunct(nextChar): + // ["!] hmm... could be ["$1.95] or ["!...] + *isOpen = false + case isspace(previousChar) && ispunct(nextChar): + // [ "!] looks more like [ "$1.95] + *isOpen = true + case ispunct(previousChar) && ispunct(nextChar): + // [!"!] context is not any help here, so toggle + *isOpen = !*isOpen + case /* isnormal(previousChar) && */ ispunct(nextChar): + // [a"!] is probably a close + *isOpen = false + case previousChar == 0 /* && isnormal(nextChar) */ : + // ["a] is probably an open + *isOpen = true + case isspace(previousChar) /* && isnormal(nextChar) */ : + // [ "a] this is one of the easy cases + *isOpen = true + case ispunct(previousChar) /* && isnormal(nextChar) */ : + // [!"a] is probably an open + *isOpen = true + default: + // [a'b] maybe a contraction? + *isOpen = false + } + + // Note that with the limited lookahead, this non-breaking + // space will also be appended to single double quotes. + if addNBSP && !*isOpen { + out.WriteString(" ") + } + + out.WriteByte('&') + if *isOpen { + out.WriteByte('l') + } else { + out.WriteByte('r') + } + out.WriteByte(quote) + out.WriteString("quo;") + + if addNBSP && *isOpen { + out.WriteString(" ") + } + + return true +} + +func (r *SPRenderer) smartSingleQuote(out *bytes.Buffer, previousChar byte, text []byte) int { + if len(text) >= 2 { + t1 := tolower(text[1]) + + if t1 == '\'' { + nextChar := byte(0) + if len(text) >= 3 { + nextChar = text[2] + } + if smartQuoteHelper(out, previousChar, nextChar, 'd', &r.inDoubleQuote, false) { + return 1 + } + } + + if (t1 == 's' || t1 == 't' || t1 == 'm' || t1 == 'd') && (len(text) < 3 || wordBoundary(text[2])) { + out.WriteString("’") + return 0 + } + + if len(text) >= 3 { + t2 := tolower(text[2]) + + if ((t1 == 'r' && t2 == 'e') || (t1 == 'l' && t2 == 'l') || (t1 == 'v' && t2 == 'e')) && + (len(text) < 4 || wordBoundary(text[3])) { + out.WriteString("’") + return 0 + } + } + } + + nextChar := byte(0) + if len(text) > 1 { + nextChar = text[1] + } + if smartQuoteHelper(out, previousChar, nextChar, 's', &r.inSingleQuote, false) { + return 0 + } + + out.WriteByte(text[0]) + return 0 +} + +func (r *SPRenderer) smartParens(out *bytes.Buffer, previousChar byte, text []byte) int { + if len(text) >= 3 { + t1 := tolower(text[1]) + t2 := tolower(text[2]) + + if t1 == 'c' && t2 == ')' { + out.WriteString("©") + return 2 + } + + if t1 == 'r' && t2 == ')' { + out.WriteString("®") + return 2 + } + + if len(text) >= 4 && t1 == 't' && t2 == 'm' && text[3] == ')' { + out.WriteString("™") + return 3 + } + } + + out.WriteByte(text[0]) + return 0 +} + +func (r *SPRenderer) smartDash(out *bytes.Buffer, previousChar byte, text []byte) int { + if len(text) >= 2 { + if text[1] == '-' { + out.WriteString("—") + return 1 + } + + if wordBoundary(previousChar) && wordBoundary(text[1]) { + out.WriteString("–") + return 0 + } + } + + out.WriteByte(text[0]) + return 0 +} + +func (r *SPRenderer) smartDashLatex(out *bytes.Buffer, previousChar byte, text []byte) int { + if len(text) >= 3 && text[1] == '-' && text[2] == '-' { + out.WriteString("—") + return 2 + } + if len(text) >= 2 && text[1] == '-' { + out.WriteString("–") + return 1 + } + + out.WriteByte(text[0]) + return 0 +} + +func (r *SPRenderer) smartAmpVariant(out *bytes.Buffer, previousChar byte, text []byte, quote byte, addNBSP bool) int { + if bytes.HasPrefix(text, []byte(""")) { + nextChar := byte(0) + if len(text) >= 7 { + nextChar = text[6] + } + if smartQuoteHelper(out, previousChar, nextChar, quote, &r.inDoubleQuote, addNBSP) { + return 5 + } + } + + if bytes.HasPrefix(text, []byte("�")) { + return 3 + } + + out.WriteByte('&') + return 0 +} + +func (r *SPRenderer) smartAmp(angledQuotes, addNBSP bool) func(*bytes.Buffer, byte, []byte) int { + var quote byte = 'd' + if angledQuotes { + quote = 'a' + } + + return func(out *bytes.Buffer, previousChar byte, text []byte) int { + return r.smartAmpVariant(out, previousChar, text, quote, addNBSP) + } +} + +func (r *SPRenderer) smartPeriod(out *bytes.Buffer, previousChar byte, text []byte) int { + if len(text) >= 3 && text[1] == '.' && text[2] == '.' { + out.WriteString("…") + return 2 + } + + if len(text) >= 5 && text[1] == ' ' && text[2] == '.' && text[3] == ' ' && text[4] == '.' { + out.WriteString("…") + return 4 + } + + out.WriteByte(text[0]) + return 0 +} + +func (r *SPRenderer) smartBacktick(out *bytes.Buffer, previousChar byte, text []byte) int { + if len(text) >= 2 && text[1] == '`' { + nextChar := byte(0) + if len(text) >= 3 { + nextChar = text[2] + } + if smartQuoteHelper(out, previousChar, nextChar, 'd', &r.inDoubleQuote, false) { + return 1 + } + } + + out.WriteByte(text[0]) + return 0 +} + +func (r *SPRenderer) smartNumberGeneric(out *bytes.Buffer, previousChar byte, text []byte) int { + if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 { + // is it of the form digits/digits(word boundary)?, i.e., \d+/\d+\b + // note: check for regular slash (/) or fraction slash (⁄, 0x2044, or 0xe2 81 84 in utf-8) + // and avoid changing dates like 1/23/2005 into fractions. + numEnd := 0 + for len(text) > numEnd && isdigit(text[numEnd]) { + numEnd++ + } + if numEnd == 0 { + out.WriteByte(text[0]) + return 0 + } + denStart := numEnd + 1 + if len(text) > numEnd+3 && text[numEnd] == 0xe2 && text[numEnd+1] == 0x81 && text[numEnd+2] == 0x84 { + denStart = numEnd + 3 + } else if len(text) < numEnd+2 || text[numEnd] != '/' { + out.WriteByte(text[0]) + return 0 + } + denEnd := denStart + for len(text) > denEnd && isdigit(text[denEnd]) { + denEnd++ + } + if denEnd == denStart { + out.WriteByte(text[0]) + return 0 + } + if len(text) == denEnd || wordBoundary(text[denEnd]) && text[denEnd] != '/' { + out.WriteString("") + out.Write(text[:numEnd]) + out.WriteString("") + out.Write(text[denStart:denEnd]) + out.WriteString("") + return denEnd - 1 + } + } + + out.WriteByte(text[0]) + return 0 +} + +func (r *SPRenderer) smartNumber(out *bytes.Buffer, previousChar byte, text []byte) int { + if wordBoundary(previousChar) && previousChar != '/' && len(text) >= 3 { + if text[0] == '1' && text[1] == '/' && text[2] == '2' { + if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' { + out.WriteString("½") + return 2 + } + } + + if text[0] == '1' && text[1] == '/' && text[2] == '4' { + if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' || (len(text) >= 5 && tolower(text[3]) == 't' && tolower(text[4]) == 'h') { + out.WriteString("¼") + return 2 + } + } + + if text[0] == '3' && text[1] == '/' && text[2] == '4' { + if len(text) < 4 || wordBoundary(text[3]) && text[3] != '/' || (len(text) >= 6 && tolower(text[3]) == 't' && tolower(text[4]) == 'h' && tolower(text[5]) == 's') { + out.WriteString("¾") + return 2 + } + } + } + + out.WriteByte(text[0]) + return 0 +} + +func (r *SPRenderer) smartDoubleQuoteVariant(out *bytes.Buffer, previousChar byte, text []byte, quote byte) int { + nextChar := byte(0) + if len(text) > 1 { + nextChar = text[1] + } + if !smartQuoteHelper(out, previousChar, nextChar, quote, &r.inDoubleQuote, false) { + out.WriteString(""") + } + + return 0 +} + +func (r *SPRenderer) smartDoubleQuote(out *bytes.Buffer, previousChar byte, text []byte) int { + return r.smartDoubleQuoteVariant(out, previousChar, text, 'd') +} + +func (r *SPRenderer) smartAngledDoubleQuote(out *bytes.Buffer, previousChar byte, text []byte) int { + return r.smartDoubleQuoteVariant(out, previousChar, text, 'a') +} + +func (r *SPRenderer) smartLeftAngle(out *bytes.Buffer, previousChar byte, text []byte) int { + i := 0 + + for i < len(text) && text[i] != '>' { + i++ + } + + out.Write(text[:i+1]) + return i +} + +type smartCallback func(out *bytes.Buffer, previousChar byte, text []byte) int + +// NewSmartypantsRenderer constructs a Smartypants renderer object. +func NewSmartypantsRenderer(flags HTMLFlags) *SPRenderer { + var ( + r SPRenderer + + smartAmpAngled = r.smartAmp(true, false) + smartAmpAngledNBSP = r.smartAmp(true, true) + smartAmpRegular = r.smartAmp(false, false) + smartAmpRegularNBSP = r.smartAmp(false, true) + + addNBSP = flags&SmartypantsQuotesNBSP != 0 + ) + + if flags&SmartypantsAngledQuotes == 0 { + r.callbacks['"'] = r.smartDoubleQuote + if !addNBSP { + r.callbacks['&'] = smartAmpRegular + } else { + r.callbacks['&'] = smartAmpRegularNBSP + } + } else { + r.callbacks['"'] = r.smartAngledDoubleQuote + if !addNBSP { + r.callbacks['&'] = smartAmpAngled + } else { + r.callbacks['&'] = smartAmpAngledNBSP + } + } + r.callbacks['\''] = r.smartSingleQuote + r.callbacks['('] = r.smartParens + if flags&SmartypantsDashes != 0 { + if flags&SmartypantsLatexDashes == 0 { + r.callbacks['-'] = r.smartDash + } else { + r.callbacks['-'] = r.smartDashLatex + } + } + r.callbacks['.'] = r.smartPeriod + if flags&SmartypantsFractions == 0 { + r.callbacks['1'] = r.smartNumber + r.callbacks['3'] = r.smartNumber + } else { + for ch := '1'; ch <= '9'; ch++ { + r.callbacks[ch] = r.smartNumberGeneric + } + } + r.callbacks['<'] = r.smartLeftAngle + r.callbacks['`'] = r.smartBacktick + return &r +} + +// Process is the entry point of the Smartypants renderer. +func (r *SPRenderer) Process(w io.Writer, text []byte) { + mark := 0 + for i := 0; i < len(text); i++ { + if action := r.callbacks[text[i]]; action != nil { + if i > mark { + w.Write(text[mark:i]) + } + previousChar := byte(0) + if i > 0 { + previousChar = text[i-1] + } + var tmp bytes.Buffer + i += action(&tmp, previousChar, text[i:]) + w.Write(tmp.Bytes()) + mark = i + 1 + } + } + if mark < len(text) { + w.Write(text[mark:]) + } +} diff --git a/vendor/github.com/shurcooL/sanitized_anchor_name/.travis.yml b/vendor/github.com/shurcooL/sanitized_anchor_name/.travis.yml new file mode 100644 index 000000000..93b1fcdb3 --- /dev/null +++ b/vendor/github.com/shurcooL/sanitized_anchor_name/.travis.yml @@ -0,0 +1,16 @@ +sudo: false +language: go +go: + - 1.x + - master +matrix: + allow_failures: + - go: master + fast_finish: true +install: + - # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step). +script: + - go get -t -v ./... + - diff -u <(echo -n) <(gofmt -d -s .) + - go tool vet . + - go test -v -race ./... diff --git a/vendor/github.com/shurcooL/sanitized_anchor_name/LICENSE b/vendor/github.com/shurcooL/sanitized_anchor_name/LICENSE new file mode 100644 index 000000000..c35c17af9 --- /dev/null +++ b/vendor/github.com/shurcooL/sanitized_anchor_name/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2015 Dmitri Shuralyov + +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. diff --git a/vendor/github.com/shurcooL/sanitized_anchor_name/README.md b/vendor/github.com/shurcooL/sanitized_anchor_name/README.md new file mode 100644 index 000000000..670bf0fe6 --- /dev/null +++ b/vendor/github.com/shurcooL/sanitized_anchor_name/README.md @@ -0,0 +1,36 @@ +sanitized_anchor_name +===================== + +[![Build Status](https://travis-ci.org/shurcooL/sanitized_anchor_name.svg?branch=master)](https://travis-ci.org/shurcooL/sanitized_anchor_name) [![GoDoc](https://godoc.org/github.com/shurcooL/sanitized_anchor_name?status.svg)](https://godoc.org/github.com/shurcooL/sanitized_anchor_name) + +Package sanitized_anchor_name provides a func to create sanitized anchor names. + +Its logic can be reused by multiple packages to create interoperable anchor names +and links to those anchors. + +At this time, it does not try to ensure that generated anchor names +are unique, that responsibility falls on the caller. + +Installation +------------ + +```bash +go get -u github.com/shurcooL/sanitized_anchor_name +``` + +Example +------- + +```Go +anchorName := sanitized_anchor_name.Create("This is a header") + +fmt.Println(anchorName) + +// Output: +// this-is-a-header +``` + +License +------- + +- [MIT License](LICENSE) diff --git a/vendor/github.com/shurcooL/sanitized_anchor_name/go.mod b/vendor/github.com/shurcooL/sanitized_anchor_name/go.mod new file mode 100644 index 000000000..1e2553475 --- /dev/null +++ b/vendor/github.com/shurcooL/sanitized_anchor_name/go.mod @@ -0,0 +1 @@ +module github.com/shurcooL/sanitized_anchor_name diff --git a/vendor/github.com/shurcooL/sanitized_anchor_name/main.go b/vendor/github.com/shurcooL/sanitized_anchor_name/main.go new file mode 100644 index 000000000..6a77d1243 --- /dev/null +++ b/vendor/github.com/shurcooL/sanitized_anchor_name/main.go @@ -0,0 +1,29 @@ +// Package sanitized_anchor_name provides a func to create sanitized anchor names. +// +// Its logic can be reused by multiple packages to create interoperable anchor names +// and links to those anchors. +// +// At this time, it does not try to ensure that generated anchor names +// are unique, that responsibility falls on the caller. +package sanitized_anchor_name // import "github.com/shurcooL/sanitized_anchor_name" + +import "unicode" + +// Create returns a sanitized anchor name for the given text. +func Create(text string) string { + var anchorName []rune + var futureDash = false + for _, r := range text { + switch { + case unicode.IsLetter(r) || unicode.IsNumber(r): + if futureDash && len(anchorName) > 0 { + anchorName = append(anchorName, '-') + } + futureDash = false + anchorName = append(anchorName, unicode.ToLower(r)) + default: + futureDash = true + } + } + return string(anchorName) +} diff --git a/vendor/github.com/spf13/cobra/.gitignore b/vendor/github.com/spf13/cobra/.gitignore index 1b8c7c261..c7b459e4d 100644 --- a/vendor/github.com/spf13/cobra/.gitignore +++ b/vendor/github.com/spf13/cobra/.gitignore @@ -32,5 +32,8 @@ Session.vim tags *.exe - cobra.test +bin + +.idea/ +*.iml diff --git a/vendor/github.com/spf13/cobra/.travis.yml b/vendor/github.com/spf13/cobra/.travis.yml index 5afcb2096..a9bd4e547 100644 --- a/vendor/github.com/spf13/cobra/.travis.yml +++ b/vendor/github.com/spf13/cobra/.travis.yml @@ -1,21 +1,29 @@ language: go -matrix: - include: - - go: 1.9.4 - - go: 1.10.0 - - go: tip - allow_failures: - - go: tip +stages: + - diff + - test + - build + +go: + - 1.12.x + - 1.13.x + - tip before_install: - - mkdir -p bin - - curl -Lso bin/shellcheck https://github.com/caarlos0/shellcheck-docker/releases/download/v0.4.3/shellcheck - - chmod +x bin/shellcheck -script: - - PATH=$PATH:$PWD/bin go test -v ./... - - go build - - diff -u <(echo -n) <(gofmt -d -s .) - - if [ -z $NOVET ]; then - diff -u <(echo -n) <(go tool vet . 2>&1 | grep -vE 'ExampleCommand|bash_completions.*Fprint'); - fi + - go get -u github.com/kyoh86/richgo + - go get -u github.com/mitchellh/gox + +matrix: + allow_failures: + - go: tip + include: + - stage: diff + go: 1.13.x + script: make fmt + - stage: build + go: 1.13.x + script: make cobra_generator + +script: + - make test diff --git a/vendor/github.com/spf13/cobra/Makefile b/vendor/github.com/spf13/cobra/Makefile new file mode 100644 index 000000000..e9740d1e1 --- /dev/null +++ b/vendor/github.com/spf13/cobra/Makefile @@ -0,0 +1,36 @@ +BIN="./bin" +SRC=$(shell find . -name "*.go") + +ifeq (, $(shell which richgo)) +$(warning "could not find richgo in $(PATH), run: go get github.com/kyoh86/richgo") +endif + +.PHONY: fmt vet test cobra_generator install_deps clean + +default: all + +all: fmt vet test cobra_generator + +fmt: + $(info ******************** checking formatting ********************) + @test -z $(shell gofmt -l $(SRC)) || (gofmt -d $(SRC); exit 1) + +test: install_deps vet + $(info ******************** running tests ********************) + richgo test -v ./... + +cobra_generator: install_deps + $(info ******************** building generator ********************) + mkdir -p $(BIN) + make -C cobra all + +install_deps: + $(info ******************** downloading dependencies ********************) + go get -v ./... + +vet: + $(info ******************** vetting ********************) + go vet ./... + +clean: + rm -rf $(BIN) diff --git a/vendor/github.com/spf13/cobra/README.md b/vendor/github.com/spf13/cobra/README.md index 851fcc087..9d7993426 100644 --- a/vendor/github.com/spf13/cobra/README.md +++ b/vendor/github.com/spf13/cobra/README.md @@ -2,29 +2,35 @@ Cobra is both a library for creating powerful modern CLI applications as well as a program to generate applications and command files. -Many of the most widely used Go projects are built using Cobra including: - -* [Kubernetes](http://kubernetes.io/) -* [Hugo](http://gohugo.io) -* [rkt](https://github.com/coreos/rkt) -* [etcd](https://github.com/coreos/etcd) -* [Moby (former Docker)](https://github.com/moby/moby) -* [Docker (distribution)](https://github.com/docker/distribution) -* [OpenShift](https://www.openshift.com/) -* [Delve](https://github.com/derekparker/delve) -* [GopherJS](http://www.gopherjs.org/) -* [CockroachDB](http://www.cockroachlabs.com/) -* [Bleve](http://www.blevesearch.com/) -* [ProjectAtomic (enterprise)](http://www.projectatomic.io/) -* [GiantSwarm's swarm](https://github.com/giantswarm/cli) -* [Nanobox](https://github.com/nanobox-io/nanobox)/[Nanopack](https://github.com/nanopack) -* [rclone](http://rclone.org/) -* [nehm](https://github.com/bogem/nehm) -* [Pouch](https://github.com/alibaba/pouch) +Many of the most widely used Go projects are built using Cobra, such as: +[Kubernetes](http://kubernetes.io/), +[Hugo](http://gohugo.io), +[rkt](https://github.com/coreos/rkt), +[etcd](https://github.com/coreos/etcd), +[Moby (former Docker)](https://github.com/moby/moby), +[Docker (distribution)](https://github.com/docker/distribution), +[OpenShift](https://www.openshift.com/), +[Delve](https://github.com/derekparker/delve), +[GopherJS](http://www.gopherjs.org/), +[CockroachDB](http://www.cockroachlabs.com/), +[Bleve](http://www.blevesearch.com/), +[ProjectAtomic (enterprise)](http://www.projectatomic.io/), +[Giant Swarm's gsctl](https://github.com/giantswarm/gsctl), +[Nanobox](https://github.com/nanobox-io/nanobox)/[Nanopack](https://github.com/nanopack), +[rclone](http://rclone.org/), +[nehm](https://github.com/bogem/nehm), +[Pouch](https://github.com/alibaba/pouch), +[Istio](https://istio.io), +[Prototool](https://github.com/uber/prototool), +[mattermost-server](https://github.com/mattermost/mattermost-server), +[Gardener](https://github.com/gardener/gardenctl), +[Linkerd](https://linkerd.io/), +[Github CLI](https://github.com/cli/cli) +etc. [![Build Status](https://travis-ci.org/spf13/cobra.svg "Travis CI status")](https://travis-ci.org/spf13/cobra) -[![CircleCI status](https://circleci.com/gh/spf13/cobra.png?circle-token=:circle-token "CircleCI status")](https://circleci.com/gh/spf13/cobra) [![GoDoc](https://godoc.org/github.com/spf13/cobra?status.svg)](https://godoc.org/github.com/spf13/cobra) +[![Go Report Card](https://goreportcard.com/badge/github.com/spf13/cobra)](https://goreportcard.com/report/github.com/spf13/cobra) # Table of Contents @@ -45,6 +51,7 @@ Many of the most widely used Go projects are built using Cobra including: * [Suggestions when "unknown command" happens](#suggestions-when-unknown-command-happens) * [Generating documentation for your command](#generating-documentation-for-your-command) * [Generating bash completions](#generating-bash-completions) + * [Generating zsh completions](#generating-zsh-completions) - [Contributing](#contributing) - [License](#license) @@ -152,9 +159,6 @@ In a Cobra app, typically the main.go file is very bare. It serves one purpose: package main import ( - "fmt" - "os" - "{pathToYourApp}/cmd" ) @@ -206,51 +210,78 @@ You will additionally define flags and handle configuration in your init() funct For example cmd/root.go: ```go -import ( - "fmt" - "os" +package cmd - homedir "github.com/mitchellh/go-homedir" - "github.com/spf13/cobra" - "github.com/spf13/viper" +import ( + "fmt" + "os" + + homedir "github.com/mitchellh/go-homedir" + "github.com/spf13/cobra" + "github.com/spf13/viper" ) +var ( + // Used for flags. + cfgFile string + userLicense string + + rootCmd = &cobra.Command{ + Use: "cobra", + Short: "A generator for Cobra based Applications", + Long: `Cobra is a CLI library for Go that empowers applications. +This application is a tool to generate the needed files +to quickly create a Cobra application.`, + } +) + +// Execute executes the root command. +func Execute() error { + return rootCmd.Execute() +} + func init() { - cobra.OnInitialize(initConfig) - rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cobra.yaml)") - rootCmd.PersistentFlags().StringVarP(&projectBase, "projectbase", "b", "", "base project directory eg. github.com/spf13/") - rootCmd.PersistentFlags().StringP("author", "a", "YOUR NAME", "Author name for copyright attribution") - rootCmd.PersistentFlags().StringVarP(&userLicense, "license", "l", "", "Name of license for the project (can provide `licensetext` in config)") - rootCmd.PersistentFlags().Bool("viper", true, "Use Viper for configuration") - viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author")) - viper.BindPFlag("projectbase", rootCmd.PersistentFlags().Lookup("projectbase")) - viper.BindPFlag("useViper", rootCmd.PersistentFlags().Lookup("viper")) - viper.SetDefault("author", "NAME HERE ") - viper.SetDefault("license", "apache") + cobra.OnInitialize(initConfig) + + rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.cobra.yaml)") + rootCmd.PersistentFlags().StringP("author", "a", "YOUR NAME", "author name for copyright attribution") + rootCmd.PersistentFlags().StringVarP(&userLicense, "license", "l", "", "name of license for the project") + rootCmd.PersistentFlags().Bool("viper", true, "use Viper for configuration") + viper.BindPFlag("author", rootCmd.PersistentFlags().Lookup("author")) + viper.BindPFlag("useViper", rootCmd.PersistentFlags().Lookup("viper")) + viper.SetDefault("author", "NAME HERE ") + viper.SetDefault("license", "apache") + + rootCmd.AddCommand(addCmd) + rootCmd.AddCommand(initCmd) +} + +func er(msg interface{}) { + fmt.Println("Error:", msg) + os.Exit(1) } func initConfig() { - // Don't forget to read config either from cfgFile or from home directory! - if cfgFile != "" { - // Use config file from the flag. - viper.SetConfigFile(cfgFile) - } else { - // Find home directory. - home, err := homedir.Dir() - if err != nil { - fmt.Println(err) - os.Exit(1) - } + if cfgFile != "" { + // Use config file from the flag. + viper.SetConfigFile(cfgFile) + } else { + // Find home directory. + home, err := homedir.Dir() + if err != nil { + er(err) + } - // Search config in home directory with name ".cobra" (without extension). - viper.AddConfigPath(home) - viper.SetConfigName(".cobra") - } + // Search config in home directory with name ".cobra" (without extension). + viper.AddConfigPath(home) + viper.SetConfigName(".cobra") + } - if err := viper.ReadInConfig(); err != nil { - fmt.Println("Can't read config:", err) - os.Exit(1) - } + viper.AutomaticEnv() + + if err := viper.ReadInConfig(); err == nil { + fmt.Println("Using config file:", viper.ConfigFileUsed()) + } } ``` @@ -265,9 +296,6 @@ In a Cobra app, typically the main.go file is very bare. It serves, one purpose, package main import ( - "fmt" - "os" - "{pathToYourApp}/cmd" ) @@ -339,7 +367,7 @@ rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose out A flag can also be assigned locally which will only apply to that specific command. ```go -rootCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from") +localCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from") ``` ### Local Flag on Parent Commands @@ -395,6 +423,7 @@ The following validators are built in: - `MinimumNArgs(int)` - the command will report an error if there are not at least N positional args. - `MaximumNArgs(int)` - the command will report an error if there are more than N positional args. - `ExactArgs(int)` - the command will report an error if there are not exactly N positional args. +- `ExactValidArgs(int)` - the command will report an error if there are not exactly N positional args OR if there are any positional args that are not in the `ValidArgs` field of `Command` - `RangeArgs(min, max)` - the command will report an error if the number of args is not between the minimum and maximum number of expected args. An example of setting the custom validator: @@ -404,7 +433,7 @@ var cmd = &cobra.Command{ Short: "hello", Args: func(cmd *cobra.Command, args []string) error { if len(args) < 1 { - return errors.New("requires at least one arg") + return errors.New("requires a color argument") } if myapp.IsValidColor(args[0]) { return nil @@ -459,12 +488,12 @@ For many years people have printed back to the screen.`, Echo works a lot like print, except it has a child command.`, Args: cobra.MinimumNArgs(1), Run: func(cmd *cobra.Command, args []string) { - fmt.Println("Print: " + strings.Join(args, " ")) + fmt.Println("Echo: " + strings.Join(args, " ")) }, } var cmdTimes = &cobra.Command{ - Use: "times [# times] [string to echo]", + Use: "times [string to echo]", Short: "Echo anything to the screen more times", Long: `echo things multiple times back to the user by providing a count and a string.`, @@ -721,6 +750,11 @@ Cobra can generate documentation based on subcommands, flags, etc. in the follow Cobra can generate a bash-completion file. If you add more information to your command, these completions can be amazingly powerful and flexible. Read more about it in [Bash Completions](bash_completions.md). +## Generating zsh completions + +Cobra can generate zsh-completion file. Read more about it in +[Zsh Completions](zsh_completions.md). + # Contributing 1. Fork it diff --git a/vendor/github.com/spf13/cobra/args.go b/vendor/github.com/spf13/cobra/args.go index a5d8a9273..70e9b2629 100644 --- a/vendor/github.com/spf13/cobra/args.go +++ b/vendor/github.com/spf13/cobra/args.go @@ -2,6 +2,7 @@ package cobra import ( "fmt" + "strings" ) type PositionalArgs func(cmd *Command, args []string) error @@ -34,8 +35,15 @@ func NoArgs(cmd *Command, args []string) error { // OnlyValidArgs returns an error if any args are not in the list of ValidArgs. func OnlyValidArgs(cmd *Command, args []string) error { if len(cmd.ValidArgs) > 0 { + // Remove any description that may be included in ValidArgs. + // A description is following a tab character. + var validArgs []string + for _, v := range cmd.ValidArgs { + validArgs = append(validArgs, strings.Split(v, "\t")[0]) + } + for _, v := range args { - if !stringInSlice(v, cmd.ValidArgs) { + if !stringInSlice(v, validArgs) { return fmt.Errorf("invalid argument %q for %q%s", v, cmd.CommandPath(), cmd.findSuggestions(args[0])) } } @@ -78,6 +86,18 @@ func ExactArgs(n int) PositionalArgs { } } +// ExactValidArgs returns an error if +// there are not exactly N positional args OR +// there are any positional args that are not in the `ValidArgs` field of `Command` +func ExactValidArgs(n int) PositionalArgs { + return func(cmd *Command, args []string) error { + if err := ExactArgs(n)(cmd, args); err != nil { + return err + } + return OnlyValidArgs(cmd, args) + } +} + // RangeArgs returns an error if the number of args is not within the expected range. func RangeArgs(min int, max int) PositionalArgs { return func(cmd *Command, args []string) error { diff --git a/vendor/github.com/spf13/cobra/bash_completions.go b/vendor/github.com/spf13/cobra/bash_completions.go index 8fa8f486f..1e27188c3 100644 --- a/vendor/github.com/spf13/cobra/bash_completions.go +++ b/vendor/github.com/spf13/cobra/bash_completions.go @@ -58,9 +58,71 @@ __%[1]s_contains_word() return 1 } +__%[1]s_handle_go_custom_completion() +{ + __%[1]s_debug "${FUNCNAME[0]}: cur is ${cur}, words[*] is ${words[*]}, #words[@] is ${#words[@]}" + + local out requestComp lastParam lastChar comp directive args + + # Prepare the command to request completions for the program. + # Calling ${words[0]} instead of directly %[1]s allows to handle aliases + args=("${words[@]:1}") + requestComp="${words[0]} %[2]s ${args[*]}" + + lastParam=${words[$((${#words[@]}-1))]} + lastChar=${lastParam:$((${#lastParam}-1)):1} + __%[1]s_debug "${FUNCNAME[0]}: lastParam ${lastParam}, lastChar ${lastChar}" + + if [ -z "${cur}" ] && [ "${lastChar}" != "=" ]; then + # If the last parameter is complete (there is a space following it) + # We add an extra empty parameter so we can indicate this to the go method. + __%[1]s_debug "${FUNCNAME[0]}: Adding extra empty parameter" + requestComp="${requestComp} \"\"" + fi + + __%[1]s_debug "${FUNCNAME[0]}: calling ${requestComp}" + # Use eval to handle any environment variables and such + out=$(eval "${requestComp}" 2>/dev/null) + + # Extract the directive integer at the very end of the output following a colon (:) + directive=${out##*:} + # Remove the directive + out=${out%%:*} + if [ "${directive}" = "${out}" ]; then + # There is not directive specified + directive=0 + fi + __%[1]s_debug "${FUNCNAME[0]}: the completion directive is: ${directive}" + __%[1]s_debug "${FUNCNAME[0]}: the completions are: ${out[*]}" + + if [ $((directive & %[3]d)) -ne 0 ]; then + # Error code. No completion. + __%[1]s_debug "${FUNCNAME[0]}: received error from custom completion go code" + return + else + if [ $((directive & %[4]d)) -ne 0 ]; then + if [[ $(type -t compopt) = "builtin" ]]; then + __%[1]s_debug "${FUNCNAME[0]}: activating no space" + compopt -o nospace + fi + fi + if [ $((directive & %[5]d)) -ne 0 ]; then + if [[ $(type -t compopt) = "builtin" ]]; then + __%[1]s_debug "${FUNCNAME[0]}: activating no file completion" + compopt +o default + fi + fi + + while IFS='' read -r comp; do + COMPREPLY+=("$comp") + done < <(compgen -W "${out[*]}" -- "$cur") + fi +} + __%[1]s_handle_reply() { __%[1]s_debug "${FUNCNAME[0]}" + local comp case $cur in -*) if [[ $(type -t compopt) = "builtin" ]]; then @@ -72,7 +134,9 @@ __%[1]s_handle_reply() else allflags=("${flags[*]} ${two_word_flags[*]}") fi - COMPREPLY=( $(compgen -W "${allflags[*]}" -- "$cur") ) + while IFS='' read -r comp; do + COMPREPLY+=("$comp") + done < <(compgen -W "${allflags[*]}" -- "$cur") if [[ $(type -t compopt) = "builtin" ]]; then [[ "${COMPREPLY[0]}" == *= ]] || compopt +o nospace fi @@ -118,18 +182,32 @@ __%[1]s_handle_reply() completions=("${commands[@]}") if [[ ${#must_have_one_noun[@]} -ne 0 ]]; then completions=("${must_have_one_noun[@]}") + elif [[ -n "${has_completion_function}" ]]; then + # if a go completion function is provided, defer to that function + completions=() + __%[1]s_handle_go_custom_completion fi if [[ ${#must_have_one_flag[@]} -ne 0 ]]; then completions+=("${must_have_one_flag[@]}") fi - COMPREPLY=( $(compgen -W "${completions[*]}" -- "$cur") ) + while IFS='' read -r comp; do + COMPREPLY+=("$comp") + done < <(compgen -W "${completions[*]}" -- "$cur") if [[ ${#COMPREPLY[@]} -eq 0 && ${#noun_aliases[@]} -gt 0 && ${#must_have_one_noun[@]} -ne 0 ]]; then - COMPREPLY=( $(compgen -W "${noun_aliases[*]}" -- "$cur") ) + while IFS='' read -r comp; do + COMPREPLY+=("$comp") + done < <(compgen -W "${noun_aliases[*]}" -- "$cur") fi if [[ ${#COMPREPLY[@]} -eq 0 ]]; then - declare -F __custom_func >/dev/null && __custom_func + if declare -F __%[1]s_custom_func >/dev/null; then + # try command name qualified custom func + __%[1]s_custom_func + else + # otherwise fall back to unqualified for compatibility + declare -F __custom_func >/dev/null && __custom_func + fi fi # available in bash-completion >= 2, not always present on macOS @@ -154,7 +232,7 @@ __%[1]s_handle_filename_extension_flag() __%[1]s_handle_subdirs_in_dir_flag() { local dir="$1" - pushd "${dir}" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 + pushd "${dir}" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 || return } __%[1]s_handle_flag() @@ -193,7 +271,8 @@ __%[1]s_handle_flag() fi # skip the argument to a two word flag - if __%[1]s_contains_word "${words[c]}" "${two_word_flags[@]}"; then + if [[ ${words[c]} != *"="* ]] && __%[1]s_contains_word "${words[c]}" "${two_word_flags[@]}"; then + __%[1]s_debug "${FUNCNAME[0]}: found a flag ${words[c]}, skip the next argument" c=$((c+1)) # if we are looking for a flags value, don't show commands if [[ $c -eq $cword ]]; then @@ -265,7 +344,7 @@ __%[1]s_handle_word() __%[1]s_handle_word } -`, name)) +`, name, ShellCompNoDescRequestCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp)) } func writePostscript(buf *bytes.Buffer, name string) { @@ -290,6 +369,7 @@ func writePostscript(buf *bytes.Buffer, name string) { local commands=("%[1]s") local must_have_one_flag=() local must_have_one_noun=() + local has_completion_function local last_command local nouns=() @@ -373,6 +453,10 @@ func writeFlag(buf *bytes.Buffer, flag *pflag.Flag, cmd *Command) { } format += "\")\n" buf.WriteString(fmt.Sprintf(format, name)) + if len(flag.NoOptDefVal) == 0 { + format = " two_word_flags+=(\"--%s\")\n" + buf.WriteString(fmt.Sprintf(format, name)) + } writeFlagHandler(buf, "--"+name, flag.Annotations, cmd) } @@ -386,7 +470,22 @@ func writeLocalNonPersistentFlag(buf *bytes.Buffer, flag *pflag.Flag) { buf.WriteString(fmt.Sprintf(format, name)) } +// Setup annotations for go completions for registered flags +func prepareCustomAnnotationsForFlags(cmd *Command) { + for flag := range flagCompletionFunctions { + // Make sure the completion script calls the __*_go_custom_completion function for + // every registered flag. We need to do this here (and not when the flag was registered + // for completion) so that we can know the root command name for the prefix + // of ___go_custom_completion + if flag.Annotations == nil { + flag.Annotations = map[string][]string{} + } + flag.Annotations[BashCompCustom] = []string{fmt.Sprintf("__%[1]s_handle_go_custom_completion", cmd.Root().Name())} + } +} + func writeFlags(buf *bytes.Buffer, cmd *Command) { + prepareCustomAnnotationsForFlags(cmd) buf.WriteString(` flags=() two_word_flags=() local_nonpersistent_flags=() @@ -449,8 +548,14 @@ func writeRequiredNouns(buf *bytes.Buffer, cmd *Command) { buf.WriteString(" must_have_one_noun=()\n") sort.Sort(sort.StringSlice(cmd.ValidArgs)) for _, value := range cmd.ValidArgs { + // Remove any description that may be included following a tab character. + // Descriptions are not supported by bash completion. + value = strings.Split(value, "\t")[0] buf.WriteString(fmt.Sprintf(" must_have_one_noun+=(%q)\n", value)) } + if cmd.ValidArgsFunction != nil { + buf.WriteString(" has_completion_function=1\n") + } } func writeCmdAliases(buf *bytes.Buffer, cmd *Command) { @@ -534,51 +639,3 @@ func (c *Command) GenBashCompletionFile(filename string) error { return c.GenBashCompletion(outFile) } - -// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag if it exists, -// and causes your command to report an error if invoked without the flag. -func (c *Command) MarkFlagRequired(name string) error { - return MarkFlagRequired(c.Flags(), name) -} - -// MarkPersistentFlagRequired adds the BashCompOneRequiredFlag annotation to the named persistent flag if it exists, -// and causes your command to report an error if invoked without the flag. -func (c *Command) MarkPersistentFlagRequired(name string) error { - return MarkFlagRequired(c.PersistentFlags(), name) -} - -// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag if it exists, -// and causes your command to report an error if invoked without the flag. -func MarkFlagRequired(flags *pflag.FlagSet, name string) error { - return flags.SetAnnotation(name, BashCompOneRequiredFlag, []string{"true"}) -} - -// MarkFlagFilename adds the BashCompFilenameExt annotation to the named flag, if it exists. -// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided. -func (c *Command) MarkFlagFilename(name string, extensions ...string) error { - return MarkFlagFilename(c.Flags(), name, extensions...) -} - -// MarkFlagCustom adds the BashCompCustom annotation to the named flag, if it exists. -// Generated bash autocompletion will call the bash function f for the flag. -func (c *Command) MarkFlagCustom(name string, f string) error { - return MarkFlagCustom(c.Flags(), name, f) -} - -// MarkPersistentFlagFilename adds the BashCompFilenameExt annotation to the named persistent flag, if it exists. -// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided. -func (c *Command) MarkPersistentFlagFilename(name string, extensions ...string) error { - return MarkFlagFilename(c.PersistentFlags(), name, extensions...) -} - -// MarkFlagFilename adds the BashCompFilenameExt annotation to the named flag in the flag set, if it exists. -// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided. -func MarkFlagFilename(flags *pflag.FlagSet, name string, extensions ...string) error { - return flags.SetAnnotation(name, BashCompFilenameExt, extensions) -} - -// MarkFlagCustom adds the BashCompCustom annotation to the named flag in the flag set, if it exists. -// Generated bash autocompletion will call the bash function f for the flag. -func MarkFlagCustom(flags *pflag.FlagSet, name string, f string) error { - return flags.SetAnnotation(name, BashCompCustom, []string{f}) -} diff --git a/vendor/github.com/spf13/cobra/bash_completions.md b/vendor/github.com/spf13/cobra/bash_completions.md index e79d4769d..e61a3a654 100644 --- a/vendor/github.com/spf13/cobra/bash_completions.md +++ b/vendor/github.com/spf13/cobra/bash_completions.md @@ -1,5 +1,40 @@ # Generating Bash Completions For Your Own cobra.Command +If you are using the generator you can create a completion command by running + +```bash +cobra add completion +``` + +Update the help text show how to install the bash_completion Linux show here [Kubectl docs show mac options](https://kubernetes.io/docs/tasks/tools/install-kubectl/#enabling-shell-autocompletion) + +Writing the shell script to stdout allows the most flexible use. + +```go +// completionCmd represents the completion command +var completionCmd = &cobra.Command{ + Use: "completion", + Short: "Generates bash completion scripts", + Long: `To load completion run + +. <(bitbucket completion) + +To configure your bash shell to load completions for each session add to your bashrc + +# ~/.bashrc or ~/.profile +. <(bitbucket completion) +`, + Run: func(cmd *cobra.Command, args []string) { + rootCmd.GenBashCompletion(os.Stdout); + }, +} +``` + +**Note:** The cobra generator may include messages printed to stdout for example if the config file is loaded, this will break the auto complete script + + +## Example from kubectl + Generating bash completions from a cobra command is incredibly easy. An actual program which does so for the kubernetes kubectl binary is as follows: ```go @@ -21,7 +56,149 @@ func main() { `out.sh` will get you completions of subcommands and flags. Copy it to `/etc/bash_completion.d/` as described [here](https://debian-administration.org/article/316/An_introduction_to_bash_completion_part_1) and reset your terminal to use autocompletion. If you make additional annotations to your code, you can get even more intelligent and flexible behavior. -## Creating your own custom functions +## Have the completions code complete your 'nouns' + +### Static completion of nouns + +This method allows you to provide a pre-defined list of completion choices for your nouns using the `validArgs` field. +For example, if you want `kubectl get [tab][tab]` to show a list of valid "nouns" you have to set them. Simplified code from `kubectl get` looks like: + +```go +validArgs []string = { "pod", "node", "service", "replicationcontroller" } + +cmd := &cobra.Command{ + Use: "get [(-o|--output=)json|yaml|template|...] (RESOURCE [NAME] | RESOURCE/NAME ...)", + Short: "Display one or many resources", + Long: get_long, + Example: get_example, + Run: func(cmd *cobra.Command, args []string) { + err := RunGet(f, out, cmd, args) + util.CheckErr(err) + }, + ValidArgs: validArgs, +} +``` + +Notice we put the "ValidArgs" on the "get" subcommand. Doing so will give results like + +```bash +# kubectl get [tab][tab] +node pod replicationcontroller service +``` + +### Plural form and shortcuts for nouns + +If your nouns have a number of aliases, you can define them alongside `ValidArgs` using `ArgAliases`: + +```go +argAliases []string = { "pods", "nodes", "services", "svc", "replicationcontrollers", "rc" } + +cmd := &cobra.Command{ + ... + ValidArgs: validArgs, + ArgAliases: argAliases +} +``` + +The aliases are not shown to the user on tab completion, but they are accepted as valid nouns by +the completion algorithm if entered manually, e.g. in: + +```bash +# kubectl get rc [tab][tab] +backend frontend database +``` + +Note that without declaring `rc` as an alias, the completion algorithm would show the list of nouns +in this example again instead of the replication controllers. + +### Dynamic completion of nouns + +In some cases it is not possible to provide a list of possible completions in advance. Instead, the list of completions must be determined at execution-time. Cobra provides two ways of defining such dynamic completion of nouns. Note that both these methods can be used along-side each other as long as they are not both used for the same command. + +**Note**: *Custom Completions written in Go* will automatically work for other shell-completion scripts (e.g., Fish shell), while *Custom Completions written in Bash* will only work for Bash shell-completion. It is therefore recommended to use *Custom Completions written in Go*. + +#### 1. Custom completions of nouns written in Go + +In a similar fashion as for static completions, you can use the `ValidArgsFunction` field to provide a Go function that Cobra will execute when it needs the list of completion choices for the nouns of a command. Note that either `ValidArgs` or `ValidArgsFunction` can be used for a single cobra command, but not both. +Simplified code from `helm status` looks like: + +```go +cmd := &cobra.Command{ + Use: "status RELEASE_NAME", + Short: "Display the status of the named release", + Long: status_long, + RunE: func(cmd *cobra.Command, args []string) { + RunGet(args[0]) + }, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) != 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + return getReleasesFromCluster(toComplete), cobra.ShellCompDirectiveNoFileComp + }, +} +``` +Where `getReleasesFromCluster()` is a Go function that obtains the list of current Helm releases running on the Kubernetes cluster. +Notice we put the `ValidArgsFunction` on the `status` subcommand. Let's assume the Helm releases on the cluster are: `harbor`, `notary`, `rook` and `thanos` then this dynamic completion will give results like + +```bash +# helm status [tab][tab] +harbor notary rook thanos +``` +You may have noticed the use of `cobra.ShellCompDirective`. These directives are bit fields allowing to control some shell completion behaviors for your particular completion. You can combine them with the bit-or operator such as `cobra.ShellCompDirectiveNoSpace | cobra.ShellCompDirectiveNoFileComp` +```go +// Indicates an error occurred and completions should be ignored. +ShellCompDirectiveError +// Indicates that the shell should not add a space after the completion, +// even if there is a single completion provided. +ShellCompDirectiveNoSpace +// Indicates that the shell should not provide file completion even when +// no completion is provided. +// This currently does not work for zsh or bash < 4 +ShellCompDirectiveNoFileComp +// Indicates that the shell will perform its default behavior after completions +// have been provided (this implies !ShellCompDirectiveNoSpace && !ShellCompDirectiveNoFileComp). +ShellCompDirectiveDefault +``` + +When using the `ValidArgsFunction`, Cobra will call your registered function after having parsed all flags and arguments provided in the command-line. You therefore don't need to do this parsing yourself. For example, when a user calls `helm status --namespace my-rook-ns [tab][tab]`, Cobra will call your registered `ValidArgsFunction` after having parsed the `--namespace` flag, as it would have done when calling the `RunE` function. + +##### Debugging + +Cobra achieves dynamic completions written in Go through the use of a hidden command called by the completion script. To debug your Go completion code, you can call this hidden command directly: +```bash +# helm __complete status har +harbor +:4 +Completion ended with directive: ShellCompDirectiveNoFileComp # This is on stderr +``` +***Important:*** If the noun to complete is empty, you must pass an empty parameter to the `__complete` command: +```bash +# helm __complete status "" +harbor +notary +rook +thanos +:4 +Completion ended with directive: ShellCompDirectiveNoFileComp # This is on stderr +``` +Calling the `__complete` command directly allows you to run the Go debugger to troubleshoot your code. You can also add printouts to your code; Cobra provides the following functions to use for printouts in Go completion code: +```go +// Prints to the completion script debug file (if BASH_COMP_DEBUG_FILE +// is set to a file path) and optionally prints to stderr. +cobra.CompDebug(msg string, printToStdErr bool) { +cobra.CompDebugln(msg string, printToStdErr bool) + +// Prints to the completion script debug file (if BASH_COMP_DEBUG_FILE +// is set to a file path) and to stderr. +cobra.CompError(msg string) +cobra.CompErrorln(msg string) +``` +***Important:*** You should **not** leave traces that print to stdout in your completion code as they will be interpreted as completion choices by the completion script. Instead, use the cobra-provided debugging traces functions mentioned above. + +#### 2. Custom completions of nouns written in Bash + +This method allows you to inject bash functions into the completion script. Those bash functions are responsible for providing the completion choices for your own completions. Some more actual code that works in kubernetes: @@ -47,7 +224,7 @@ __kubectl_get_resource() fi } -__custom_func() { +__kubectl_custom_func() { case ${last_command} in kubectl_get | kubectl_describe | kubectl_delete | kubectl_stop) __kubectl_get_resource @@ -74,59 +251,7 @@ Find more information at https://github.com/GoogleCloudPlatform/kubernetes.`, } ``` -The `BashCompletionFunction` option is really only valid/useful on the root command. Doing the above will cause `__custom_func()` to be called when the built in processor was unable to find a solution. In the case of kubernetes a valid command might look something like `kubectl get pod [mypod]`. If you type `kubectl get pod [tab][tab]` the `__customc_func()` will run because the cobra.Command only understood "kubectl" and "get." `__custom_func()` will see that the cobra.Command is "kubectl_get" and will thus call another helper `__kubectl_get_resource()`. `__kubectl_get_resource` will look at the 'nouns' collected. In our example the only noun will be `pod`. So it will call `__kubectl_parse_get pod`. `__kubectl_parse_get` will actually call out to kubernetes and get any pods. It will then set `COMPREPLY` to valid pods! - -## Have the completions code complete your 'nouns' - -In the above example "pod" was assumed to already be typed. But if you want `kubectl get [tab][tab]` to show a list of valid "nouns" you have to set them. Simplified code from `kubectl get` looks like: - -```go -validArgs []string = { "pod", "node", "service", "replicationcontroller" } - -cmd := &cobra.Command{ - Use: "get [(-o|--output=)json|yaml|template|...] (RESOURCE [NAME] | RESOURCE/NAME ...)", - Short: "Display one or many resources", - Long: get_long, - Example: get_example, - Run: func(cmd *cobra.Command, args []string) { - err := RunGet(f, out, cmd, args) - util.CheckErr(err) - }, - ValidArgs: validArgs, -} -``` - -Notice we put the "ValidArgs" on the "get" subcommand. Doing so will give results like - -```bash -# kubectl get [tab][tab] -node pod replicationcontroller service -``` - -## Plural form and shortcuts for nouns - -If your nouns have a number of aliases, you can define them alongside `ValidArgs` using `ArgAliases`: - -```go -argAliases []string = { "pods", "nodes", "services", "svc", "replicationcontrollers", "rc" } - -cmd := &cobra.Command{ - ... - ValidArgs: validArgs, - ArgAliases: argAliases -} -``` - -The aliases are not shown to the user on tab completion, but they are accepted as valid nouns by -the completion algorithm if entered manually, e.g. in: - -```bash -# kubectl get rc [tab][tab] -backend frontend database -``` - -Note that without declaring `rc` as an alias, the completion algorithm would show the list of nouns -in this example again instead of the replication controllers. +The `BashCompletionFunction` option is really only valid/useful on the root command. Doing the above will cause `__kubectl_custom_func()` (`___custom_func()`) to be called when the built in processor was unable to find a solution. In the case of kubernetes a valid command might look something like `kubectl get pod [mypod]`. If you type `kubectl get pod [tab][tab]` the `__kubectl_customc_func()` will run because the cobra.Command only understood "kubectl" and "get." `__kubectl_custom_func()` will see that the cobra.Command is "kubectl_get" and will thus call another helper `__kubectl_get_resource()`. `__kubectl_get_resource` will look at the 'nouns' collected. In our example the only noun will be `pod`. So it will call `__kubectl_parse_get pod`. `__kubectl_parse_get` will actually call out to kubernetes and get any pods. It will then set `COMPREPLY` to valid pods! ## Mark flags as required @@ -176,8 +301,45 @@ So while there are many other files in the CWD it only shows me subdirs and thos # Specify custom flag completion -Similar to the filename completion and filtering using cobra.BashCompFilenameExt, you can specify -a custom flag completion function with cobra.BashCompCustom: +As for nouns, Cobra provides two ways of defining dynamic completion of flags. Note that both these methods can be used along-side each other as long as they are not both used for the same flag. + +**Note**: *Custom Completions written in Go* will automatically work for other shell-completion scripts (e.g., Fish shell), while *Custom Completions written in Bash* will only work for Bash shell-completion. It is therefore recommended to use *Custom Completions written in Go*. + +## 1. Custom completions of flags written in Go + +To provide a Go function that Cobra will execute when it needs the list of completion choices for a flag, you must register the function in the following manner: + +```go +flagName := "output" +cmd.RegisterFlagCompletionFunc(flagName, func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return []string{"json", "table", "yaml"}, cobra.ShellCompDirectiveDefault +}) +``` +Notice that calling `RegisterFlagCompletionFunc()` is done through the `command` with which the flag is associated. In our example this dynamic completion will give results like so: + +```bash +# helm status --output [tab][tab] +json table yaml +``` + +### Debugging + +You can also easily debug your Go completion code for flags: +```bash +# helm __complete status --output "" +json +table +yaml +:4 +Completion ended with directive: ShellCompDirectiveNoFileComp # This is on stderr +``` +***Important:*** You should **not** leave traces that print to stdout in your completion code as they will be interpreted as completion choices by the completion script. Instead, use the cobra-provided debugging traces functions mentioned in the above section. + +## 2. Custom completions of flags written in Bash + +Alternatively, you can use bash code for flag custom completion. Similar to the filename +completion and filtering using `cobra.BashCompFilenameExt`, you can specify +a custom flag completion bash function with `cobra.BashCompCustom`: ```go annotation := make(map[string][]string) @@ -191,7 +353,7 @@ a custom flag completion function with cobra.BashCompCustom: cmd.Flags().AddFlag(flag) ``` -In addition add the `__handle_namespace_flag` implementation in the `BashCompletionFunction` +In addition add the `__kubectl_get_namespaces` implementation in the `BashCompletionFunction` value, e.g.: ```bash diff --git a/vendor/github.com/spf13/cobra/cobra.go b/vendor/github.com/spf13/cobra/cobra.go index 7010fd15b..d01becc8f 100644 --- a/vendor/github.com/spf13/cobra/cobra.go +++ b/vendor/github.com/spf13/cobra/cobra.go @@ -23,6 +23,7 @@ import ( "strconv" "strings" "text/template" + "time" "unicode" ) @@ -51,11 +52,17 @@ var EnableCommandSorting = true // if the CLI is started from explorer.exe. // To disable the mousetrap, just set this variable to blank string (""). // Works only on Microsoft Windows. -var MousetrapHelpText string = `This is a command line tool. +var MousetrapHelpText = `This is a command line tool. You need to open cmd.exe and run it from there. ` +// MousetrapDisplayDuration controls how long the MousetrapHelpText message is displayed on Windows +// if the CLI is started from explorer.exe. Set to 0 to wait for the return key to be pressed. +// To disable the mousetrap, just set MousetrapHelpText to blank string (""). +// Works only on Microsoft Windows. +var MousetrapDisplayDuration = 5 * time.Second + // AddTemplateFunc adds a template function that's available to Usage and Help // template generation. func AddTemplateFunc(name string, tmplFunc interface{}) { diff --git a/vendor/github.com/spf13/cobra/command.go b/vendor/github.com/spf13/cobra/command.go index 34d1bf367..88e6ed77d 100644 --- a/vendor/github.com/spf13/cobra/command.go +++ b/vendor/github.com/spf13/cobra/command.go @@ -17,6 +17,7 @@ package cobra import ( "bytes" + "context" "fmt" "io" "os" @@ -56,6 +57,10 @@ type Command struct { // ValidArgs is list of all valid non-flag arguments that are accepted in bash completions ValidArgs []string + // ValidArgsFunction is an optional function that provides valid non-flag arguments for bash completion. + // It is a dynamic version of using ValidArgs. + // Only one of ValidArgs and ValidArgsFunction can be used for a command. + ValidArgsFunction func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) // Expected arguments Args PositionalArgs @@ -80,7 +85,8 @@ type Command struct { // Version defines the version for this command. If this value is non-empty and the command does not // define a "version" flag, a "version" boolean flag will be added to the command and, if specified, - // will print content of the "Version" variable. + // will print content of the "Version" variable. A shorthand "v" flag will also be added if the + // command does not define one. Version string // The *Run functions are executed in the following order: @@ -140,9 +146,11 @@ type Command struct { // TraverseChildren parses flags on all parents before executing child command. TraverseChildren bool - //FParseErrWhitelist flag parse errors to be ignored + // FParseErrWhitelist flag parse errors to be ignored FParseErrWhitelist FParseErrWhitelist + ctx context.Context + // commands is the list of commands supported by this program. commands []*Command // parent is a parent command for this command. @@ -177,8 +185,6 @@ type Command struct { // that we can use on every pflag set and children commands globNormFunc func(f *flag.FlagSet, name string) flag.NormalizedName - // output is an output writer defined by user. - output io.Writer // usageFunc is usage func defined by user. usageFunc func(*Command) error // usageTemplate is usage template defined by user. @@ -195,6 +201,19 @@ type Command struct { helpCommand *Command // versionTemplate is the version template defined by user. versionTemplate string + + // inReader is a reader defined by the user that replaces stdin + inReader io.Reader + // outWriter is a writer defined by the user that replaces stdout + outWriter io.Writer + // errWriter is a writer defined by the user that replaces stderr + errWriter io.Writer +} + +// Context returns underlying command context. If command wasn't +// executed with ExecuteContext Context returns Background context. +func (c *Command) Context() context.Context { + return c.ctx } // SetArgs sets arguments for the command. It is set to os.Args[1:] by default, if desired, can be overridden @@ -205,8 +224,28 @@ func (c *Command) SetArgs(a []string) { // SetOutput sets the destination for usage and error messages. // If output is nil, os.Stderr is used. +// Deprecated: Use SetOut and/or SetErr instead func (c *Command) SetOutput(output io.Writer) { - c.output = output + c.outWriter = output + c.errWriter = output +} + +// SetOut sets the destination for usage messages. +// If newOut is nil, os.Stdout is used. +func (c *Command) SetOut(newOut io.Writer) { + c.outWriter = newOut +} + +// SetErr sets the destination for error messages. +// If newErr is nil, os.Stderr is used. +func (c *Command) SetErr(newErr io.Writer) { + c.errWriter = newErr +} + +// SetIn sets the source for input data +// If newIn is nil, os.Stdin is used. +func (c *Command) SetIn(newIn io.Reader) { + c.inReader = newIn } // SetUsageFunc sets usage function. Usage can be defined by application. @@ -267,9 +306,19 @@ func (c *Command) OutOrStderr() io.Writer { return c.getOut(os.Stderr) } +// ErrOrStderr returns output to stderr +func (c *Command) ErrOrStderr() io.Writer { + return c.getErr(os.Stderr) +} + +// InOrStdin returns input to stdin +func (c *Command) InOrStdin() io.Reader { + return c.getIn(os.Stdin) +} + func (c *Command) getOut(def io.Writer) io.Writer { - if c.output != nil { - return c.output + if c.outWriter != nil { + return c.outWriter } if c.HasParent() { return c.parent.getOut(def) @@ -277,6 +326,26 @@ func (c *Command) getOut(def io.Writer) io.Writer { return def } +func (c *Command) getErr(def io.Writer) io.Writer { + if c.errWriter != nil { + return c.errWriter + } + if c.HasParent() { + return c.parent.getErr(def) + } + return def +} + +func (c *Command) getIn(def io.Reader) io.Reader { + if c.inReader != nil { + return c.inReader + } + if c.HasParent() { + return c.parent.getIn(def) + } + return def +} + // UsageFunc returns either the function set by SetUsageFunc for this command // or a parent, or it returns a default usage function. func (c *Command) UsageFunc() (f func(*Command) error) { @@ -314,6 +383,8 @@ func (c *Command) HelpFunc() func(*Command, []string) { } return func(c *Command, a []string) { c.mergePersistentFlags() + // The help should be sent to stdout + // See https://github.com/spf13/cobra/issues/1002 err := tmpl(c.OutOrStdout(), c.HelpTemplate(), c) if err != nil { c.Println(err) @@ -329,13 +400,22 @@ func (c *Command) Help() error { return nil } -// UsageString return usage string. +// UsageString returns usage string. func (c *Command) UsageString() string { - tmpOutput := c.output + // Storing normal writers + tmpOutput := c.outWriter + tmpErr := c.errWriter + bb := new(bytes.Buffer) - c.SetOutput(bb) + c.outWriter = bb + c.errWriter = bb + c.Usage() - c.output = tmpOutput + + // Setting things back to normal + c.outWriter = tmpOutput + c.errWriter = tmpErr + return bb.String() } @@ -793,6 +873,13 @@ func (c *Command) preRun() { } } +// ExecuteContext is the same as Execute(), but sets the ctx on the command. +// Retrieve ctx by calling cmd.Context() inside your *Run lifecycle functions. +func (c *Command) ExecuteContext(ctx context.Context) error { + c.ctx = ctx + return c.Execute() +} + // Execute uses the args (os.Args[1:] by default) // and run through the command tree finding appropriate matches // for commands and then corresponding flags. @@ -803,6 +890,10 @@ func (c *Command) Execute() error { // ExecuteC executes the command. func (c *Command) ExecuteC() (cmd *Command, err error) { + if c.ctx == nil { + c.ctx = context.Background() + } + // Regardless of what command execute is called on, run on Root only if c.HasParent() { return c.Root().ExecuteC() @@ -817,15 +908,16 @@ func (c *Command) ExecuteC() (cmd *Command, err error) { // overriding c.InitDefaultHelpCmd() - var args []string + args := c.args // Workaround FAIL with "go test -v" or "cobra.test -test.v", see #155 if c.args == nil && filepath.Base(os.Args[0]) != "cobra.test" { args = os.Args[1:] - } else { - args = c.args } + // initialize the hidden command to be used for bash completion + c.initCompleteCmd(args) + var flags []string if c.TraverseChildren { cmd, flags, err = c.Traverse(args) @@ -849,6 +941,12 @@ func (c *Command) ExecuteC() (cmd *Command, err error) { cmd.commandCalledAs.name = cmd.Name() } + // We have to pass global context to children command + // if context is present on the parent command. + if cmd.ctx == nil { + cmd.ctx = c.ctx + } + err = cmd.execute(flags) if err != nil { // Always show help if requested, even if SilenceErrors is in @@ -932,7 +1030,11 @@ func (c *Command) InitDefaultVersionFlag() { } else { usage += c.Name() } - c.Flags().Bool("version", false, usage) + if c.Flags().ShorthandLookup("v") == nil { + c.Flags().BoolP("version", "v", false, usage) + } else { + c.Flags().Bool("version", false, usage) + } } } @@ -1070,6 +1172,21 @@ func (c *Command) Printf(format string, i ...interface{}) { c.Print(fmt.Sprintf(format, i...)) } +// PrintErr is a convenience method to Print to the defined Err output, fallback to Stderr if not set. +func (c *Command) PrintErr(i ...interface{}) { + fmt.Fprint(c.ErrOrStderr(), i...) +} + +// PrintErrln is a convenience method to Println to the defined Err output, fallback to Stderr if not set. +func (c *Command) PrintErrln(i ...interface{}) { + c.Print(fmt.Sprintln(i...)) +} + +// PrintErrf is a convenience method to Printf to the defined Err output, fallback to Stderr if not set. +func (c *Command) PrintErrf(format string, i ...interface{}) { + c.Print(fmt.Sprintf(format, i...)) +} + // CommandPath returns the full path to this command. func (c *Command) CommandPath() string { if c.HasParent() { @@ -1335,7 +1452,7 @@ func (c *Command) LocalFlags() *flag.FlagSet { return c.lflags } -// InheritedFlags returns all flags which were inherited from parents commands. +// InheritedFlags returns all flags which were inherited from parent commands. func (c *Command) InheritedFlags() *flag.FlagSet { c.mergePersistentFlags() @@ -1470,7 +1587,7 @@ func (c *Command) ParseFlags(args []string) error { beforeErrorBufLen := c.flagErrorBuf.Len() c.mergePersistentFlags() - //do it here after merging all flags and just before parse + // do it here after merging all flags and just before parse c.Flags().ParseErrorsWhitelist = flag.ParseErrorsWhitelist(c.FParseErrWhitelist) err := c.Flags().Parse(args) diff --git a/vendor/github.com/spf13/cobra/command_win.go b/vendor/github.com/spf13/cobra/command_win.go index edec728e4..8768b1736 100644 --- a/vendor/github.com/spf13/cobra/command_win.go +++ b/vendor/github.com/spf13/cobra/command_win.go @@ -3,6 +3,7 @@ package cobra import ( + "fmt" "os" "time" @@ -14,7 +15,12 @@ var preExecHookFn = preExecHook func preExecHook(c *Command) { if MousetrapHelpText != "" && mousetrap.StartedByExplorer() { c.Print(MousetrapHelpText) - time.Sleep(5 * time.Second) + if MousetrapDisplayDuration > 0 { + time.Sleep(MousetrapDisplayDuration) + } else { + c.Println("Press return to continue...") + fmt.Scanln() + } os.Exit(1) } } diff --git a/vendor/github.com/spf13/cobra/custom_completions.go b/vendor/github.com/spf13/cobra/custom_completions.go new file mode 100644 index 000000000..ba57327c1 --- /dev/null +++ b/vendor/github.com/spf13/cobra/custom_completions.go @@ -0,0 +1,384 @@ +package cobra + +import ( + "errors" + "fmt" + "os" + "strings" + + "github.com/spf13/pflag" +) + +const ( + // ShellCompRequestCmd is the name of the hidden command that is used to request + // completion results from the program. It is used by the shell completion scripts. + ShellCompRequestCmd = "__complete" + // ShellCompNoDescRequestCmd is the name of the hidden command that is used to request + // completion results without their description. It is used by the shell completion scripts. + ShellCompNoDescRequestCmd = "__completeNoDesc" +) + +// Global map of flag completion functions. +var flagCompletionFunctions = map[*pflag.Flag]func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective){} + +// ShellCompDirective is a bit map representing the different behaviors the shell +// can be instructed to have once completions have been provided. +type ShellCompDirective int + +const ( + // ShellCompDirectiveError indicates an error occurred and completions should be ignored. + ShellCompDirectiveError ShellCompDirective = 1 << iota + + // ShellCompDirectiveNoSpace indicates that the shell should not add a space + // after the completion even if there is a single completion provided. + ShellCompDirectiveNoSpace + + // ShellCompDirectiveNoFileComp indicates that the shell should not provide + // file completion even when no completion is provided. + // This currently does not work for zsh or bash < 4 + ShellCompDirectiveNoFileComp + + // ShellCompDirectiveDefault indicates to let the shell perform its default + // behavior after completions have been provided. + ShellCompDirectiveDefault ShellCompDirective = 0 +) + +// RegisterFlagCompletionFunc should be called to register a function to provide completion for a flag. +func (c *Command) RegisterFlagCompletionFunc(flagName string, f func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective)) error { + flag := c.Flag(flagName) + if flag == nil { + return fmt.Errorf("RegisterFlagCompletionFunc: flag '%s' does not exist", flagName) + } + if _, exists := flagCompletionFunctions[flag]; exists { + return fmt.Errorf("RegisterFlagCompletionFunc: flag '%s' already registered", flagName) + } + flagCompletionFunctions[flag] = f + return nil +} + +// Returns a string listing the different directive enabled in the specified parameter +func (d ShellCompDirective) string() string { + var directives []string + if d&ShellCompDirectiveError != 0 { + directives = append(directives, "ShellCompDirectiveError") + } + if d&ShellCompDirectiveNoSpace != 0 { + directives = append(directives, "ShellCompDirectiveNoSpace") + } + if d&ShellCompDirectiveNoFileComp != 0 { + directives = append(directives, "ShellCompDirectiveNoFileComp") + } + if len(directives) == 0 { + directives = append(directives, "ShellCompDirectiveDefault") + } + + if d > ShellCompDirectiveError+ShellCompDirectiveNoSpace+ShellCompDirectiveNoFileComp { + return fmt.Sprintf("ERROR: unexpected ShellCompDirective value: %d", d) + } + return strings.Join(directives, ", ") +} + +// Adds a special hidden command that can be used to request custom completions. +func (c *Command) initCompleteCmd(args []string) { + completeCmd := &Command{ + Use: fmt.Sprintf("%s [command-line]", ShellCompRequestCmd), + Aliases: []string{ShellCompNoDescRequestCmd}, + DisableFlagsInUseLine: true, + Hidden: true, + DisableFlagParsing: true, + Args: MinimumNArgs(1), + Short: "Request shell completion choices for the specified command-line", + Long: fmt.Sprintf("%[2]s is a special command that is used by the shell completion logic\n%[1]s", + "to request completion choices for the specified command-line.", ShellCompRequestCmd), + Run: func(cmd *Command, args []string) { + finalCmd, completions, directive, err := cmd.getCompletions(args) + if err != nil { + CompErrorln(err.Error()) + // Keep going for multiple reasons: + // 1- There could be some valid completions even though there was an error + // 2- Even without completions, we need to print the directive + } + + noDescriptions := (cmd.CalledAs() == ShellCompNoDescRequestCmd) + for _, comp := range completions { + if noDescriptions { + // Remove any description that may be included following a tab character. + comp = strings.Split(comp, "\t")[0] + } + // Print each possible completion to stdout for the completion script to consume. + fmt.Fprintln(finalCmd.OutOrStdout(), comp) + } + + if directive > ShellCompDirectiveError+ShellCompDirectiveNoSpace+ShellCompDirectiveNoFileComp { + directive = ShellCompDirectiveDefault + } + + // As the last printout, print the completion directive for the completion script to parse. + // The directive integer must be that last character following a single colon (:). + // The completion script expects : + fmt.Fprintf(finalCmd.OutOrStdout(), ":%d\n", directive) + + // Print some helpful info to stderr for the user to understand. + // Output from stderr must be ignored by the completion script. + fmt.Fprintf(finalCmd.ErrOrStderr(), "Completion ended with directive: %s\n", directive.string()) + }, + } + c.AddCommand(completeCmd) + subCmd, _, err := c.Find(args) + if err != nil || subCmd.Name() != ShellCompRequestCmd { + // Only create this special command if it is actually being called. + // This reduces possible side-effects of creating such a command; + // for example, having this command would cause problems to a + // cobra program that only consists of the root command, since this + // command would cause the root command to suddenly have a subcommand. + c.RemoveCommand(completeCmd) + } +} + +func (c *Command) getCompletions(args []string) (*Command, []string, ShellCompDirective, error) { + var completions []string + + // The last argument, which is not completely typed by the user, + // should not be part of the list of arguments + toComplete := args[len(args)-1] + trimmedArgs := args[:len(args)-1] + + // Find the real command for which completion must be performed + finalCmd, finalArgs, err := c.Root().Find(trimmedArgs) + if err != nil { + // Unable to find the real command. E.g., someInvalidCmd + return c, completions, ShellCompDirectiveDefault, fmt.Errorf("Unable to find a command for arguments: %v", trimmedArgs) + } + + // When doing completion of a flag name, as soon as an argument starts with + // a '-' we know it is a flag. We cannot use isFlagArg() here as it requires + // the flag to be complete + if len(toComplete) > 0 && toComplete[0] == '-' && !strings.Contains(toComplete, "=") { + // We are completing a flag name + finalCmd.NonInheritedFlags().VisitAll(func(flag *pflag.Flag) { + completions = append(completions, getFlagNameCompletions(flag, toComplete)...) + }) + finalCmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) { + completions = append(completions, getFlagNameCompletions(flag, toComplete)...) + }) + + directive := ShellCompDirectiveDefault + if len(completions) > 0 { + if strings.HasSuffix(completions[0], "=") { + directive = ShellCompDirectiveNoSpace + } + } + return finalCmd, completions, directive, nil + } + + var flag *pflag.Flag + if !finalCmd.DisableFlagParsing { + // We only do flag completion if we are allowed to parse flags + // This is important for commands which have requested to do their own flag completion. + flag, finalArgs, toComplete, err = checkIfFlagCompletion(finalCmd, finalArgs, toComplete) + if err != nil { + // Error while attempting to parse flags + return finalCmd, completions, ShellCompDirectiveDefault, err + } + } + + if flag == nil { + // Complete subcommand names + for _, subCmd := range finalCmd.Commands() { + if subCmd.IsAvailableCommand() && strings.HasPrefix(subCmd.Name(), toComplete) { + completions = append(completions, fmt.Sprintf("%s\t%s", subCmd.Name(), subCmd.Short)) + } + } + + if len(finalCmd.ValidArgs) > 0 { + // Always complete ValidArgs, even if we are completing a subcommand name. + // This is for commands that have both subcommands and ValidArgs. + for _, validArg := range finalCmd.ValidArgs { + if strings.HasPrefix(validArg, toComplete) { + completions = append(completions, validArg) + } + } + + // If there are ValidArgs specified (even if they don't match), we stop completion. + // Only one of ValidArgs or ValidArgsFunction can be used for a single command. + return finalCmd, completions, ShellCompDirectiveNoFileComp, nil + } + + // Always let the logic continue so as to add any ValidArgsFunction completions, + // even if we already found sub-commands. + // This is for commands that have subcommands but also specify a ValidArgsFunction. + } + + // Parse the flags and extract the arguments to prepare for calling the completion function + if err = finalCmd.ParseFlags(finalArgs); err != nil { + return finalCmd, completions, ShellCompDirectiveDefault, fmt.Errorf("Error while parsing flags from args %v: %s", finalArgs, err.Error()) + } + + // We only remove the flags from the arguments if DisableFlagParsing is not set. + // This is important for commands which have requested to do their own flag completion. + if !finalCmd.DisableFlagParsing { + finalArgs = finalCmd.Flags().Args() + } + + // Find the completion function for the flag or command + var completionFn func(cmd *Command, args []string, toComplete string) ([]string, ShellCompDirective) + if flag != nil { + completionFn = flagCompletionFunctions[flag] + } else { + completionFn = finalCmd.ValidArgsFunction + } + if completionFn == nil { + // Go custom completion not supported/needed for this flag or command + return finalCmd, completions, ShellCompDirectiveDefault, nil + } + + // Call the registered completion function to get the completions + comps, directive := completionFn(finalCmd, finalArgs, toComplete) + completions = append(completions, comps...) + return finalCmd, completions, directive, nil +} + +func getFlagNameCompletions(flag *pflag.Flag, toComplete string) []string { + if nonCompletableFlag(flag) { + return []string{} + } + + var completions []string + flagName := "--" + flag.Name + if strings.HasPrefix(flagName, toComplete) { + // Flag without the = + completions = append(completions, fmt.Sprintf("%s\t%s", flagName, flag.Usage)) + + if len(flag.NoOptDefVal) == 0 { + // Flag requires a value, so it can be suffixed with = + flagName += "=" + completions = append(completions, fmt.Sprintf("%s\t%s", flagName, flag.Usage)) + } + } + + flagName = "-" + flag.Shorthand + if len(flag.Shorthand) > 0 && strings.HasPrefix(flagName, toComplete) { + completions = append(completions, fmt.Sprintf("%s\t%s", flagName, flag.Usage)) + } + + return completions +} + +func checkIfFlagCompletion(finalCmd *Command, args []string, lastArg string) (*pflag.Flag, []string, string, error) { + var flagName string + trimmedArgs := args + flagWithEqual := false + if isFlagArg(lastArg) { + if index := strings.Index(lastArg, "="); index >= 0 { + flagName = strings.TrimLeft(lastArg[:index], "-") + lastArg = lastArg[index+1:] + flagWithEqual = true + } else { + return nil, nil, "", errors.New("Unexpected completion request for flag") + } + } + + if len(flagName) == 0 { + if len(args) > 0 { + prevArg := args[len(args)-1] + if isFlagArg(prevArg) { + // Only consider the case where the flag does not contain an =. + // If the flag contains an = it means it has already been fully processed, + // so we don't need to deal with it here. + if index := strings.Index(prevArg, "="); index < 0 { + flagName = strings.TrimLeft(prevArg, "-") + + // Remove the uncompleted flag or else there could be an error created + // for an invalid value for that flag + trimmedArgs = args[:len(args)-1] + } + } + } + } + + if len(flagName) == 0 { + // Not doing flag completion + return nil, trimmedArgs, lastArg, nil + } + + flag := findFlag(finalCmd, flagName) + if flag == nil { + // Flag not supported by this command, nothing to complete + err := fmt.Errorf("Subcommand '%s' does not support flag '%s'", finalCmd.Name(), flagName) + return nil, nil, "", err + } + + if !flagWithEqual { + if len(flag.NoOptDefVal) != 0 { + // We had assumed dealing with a two-word flag but the flag is a boolean flag. + // In that case, there is no value following it, so we are not really doing flag completion. + // Reset everything to do noun completion. + trimmedArgs = args + flag = nil + } + } + + return flag, trimmedArgs, lastArg, nil +} + +func findFlag(cmd *Command, name string) *pflag.Flag { + flagSet := cmd.Flags() + if len(name) == 1 { + // First convert the short flag into a long flag + // as the cmd.Flag() search only accepts long flags + if short := flagSet.ShorthandLookup(name); short != nil { + name = short.Name + } else { + set := cmd.InheritedFlags() + if short = set.ShorthandLookup(name); short != nil { + name = short.Name + } else { + return nil + } + } + } + return cmd.Flag(name) +} + +// CompDebug prints the specified string to the same file as where the +// completion script prints its logs. +// Note that completion printouts should never be on stdout as they would +// be wrongly interpreted as actual completion choices by the completion script. +func CompDebug(msg string, printToStdErr bool) { + msg = fmt.Sprintf("[Debug] %s", msg) + + // Such logs are only printed when the user has set the environment + // variable BASH_COMP_DEBUG_FILE to the path of some file to be used. + if path := os.Getenv("BASH_COMP_DEBUG_FILE"); path != "" { + f, err := os.OpenFile(path, + os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err == nil { + defer f.Close() + f.WriteString(msg) + } + } + + if printToStdErr { + // Must print to stderr for this not to be read by the completion script. + fmt.Fprintf(os.Stderr, msg) + } +} + +// CompDebugln prints the specified string with a newline at the end +// to the same file as where the completion script prints its logs. +// Such logs are only printed when the user has set the environment +// variable BASH_COMP_DEBUG_FILE to the path of some file to be used. +func CompDebugln(msg string, printToStdErr bool) { + CompDebug(fmt.Sprintf("%s\n", msg), printToStdErr) +} + +// CompError prints the specified completion message to stderr. +func CompError(msg string) { + msg = fmt.Sprintf("[Error] %s", msg) + CompDebug(msg, true) +} + +// CompErrorln prints the specified completion message to stderr with a newline at the end. +func CompErrorln(msg string) { + CompError(fmt.Sprintf("%s\n", msg)) +} diff --git a/vendor/github.com/spf13/cobra/fish_completions.go b/vendor/github.com/spf13/cobra/fish_completions.go new file mode 100644 index 000000000..c83609c83 --- /dev/null +++ b/vendor/github.com/spf13/cobra/fish_completions.go @@ -0,0 +1,172 @@ +package cobra + +import ( + "bytes" + "fmt" + "io" + "os" +) + +func genFishComp(buf *bytes.Buffer, name string, includeDesc bool) { + compCmd := ShellCompRequestCmd + if !includeDesc { + compCmd = ShellCompNoDescRequestCmd + } + buf.WriteString(fmt.Sprintf("# fish completion for %-36s -*- shell-script -*-\n", name)) + buf.WriteString(fmt.Sprintf(` +function __%[1]s_debug + set file "$BASH_COMP_DEBUG_FILE" + if test -n "$file" + echo "$argv" >> $file + end +end + +function __%[1]s_perform_completion + __%[1]s_debug "Starting __%[1]s_perform_completion with: $argv" + + set args (string split -- " " "$argv") + set lastArg "$args[-1]" + + __%[1]s_debug "args: $args" + __%[1]s_debug "last arg: $lastArg" + + set emptyArg "" + if test -z "$lastArg" + __%[1]s_debug "Setting emptyArg" + set emptyArg \"\" + end + __%[1]s_debug "emptyArg: $emptyArg" + + set requestComp "$args[1] %[2]s $args[2..-1] $emptyArg" + __%[1]s_debug "Calling $requestComp" + + set results (eval $requestComp 2> /dev/null) + set comps $results[1..-2] + set directiveLine $results[-1] + + # For Fish, when completing a flag with an = (e.g., -n=) + # completions must be prefixed with the flag + set flagPrefix (string match -r -- '-.*=' "$lastArg") + + __%[1]s_debug "Comps: $comps" + __%[1]s_debug "DirectiveLine: $directiveLine" + __%[1]s_debug "flagPrefix: $flagPrefix" + + for comp in $comps + printf "%%s%%s\n" "$flagPrefix" "$comp" + end + + printf "%%s\n" "$directiveLine" +end + +# This function does three things: +# 1- Obtain the completions and store them in the global __%[1]s_comp_results +# 2- Set the __%[1]s_comp_do_file_comp flag if file completion should be performed +# and unset it otherwise +# 3- Return true if the completion results are not empty +function __%[1]s_prepare_completions + # Start fresh + set --erase __%[1]s_comp_do_file_comp + set --erase __%[1]s_comp_results + + # Check if the command-line is already provided. This is useful for testing. + if not set --query __%[1]s_comp_commandLine + set __%[1]s_comp_commandLine (commandline) + end + __%[1]s_debug "commandLine is: $__%[1]s_comp_commandLine" + + set results (__%[1]s_perform_completion "$__%[1]s_comp_commandLine") + set --erase __%[1]s_comp_commandLine + __%[1]s_debug "Completion results: $results" + + if test -z "$results" + __%[1]s_debug "No completion, probably due to a failure" + # Might as well do file completion, in case it helps + set --global __%[1]s_comp_do_file_comp 1 + return 0 + end + + set directive (string sub --start 2 $results[-1]) + set --global __%[1]s_comp_results $results[1..-2] + + __%[1]s_debug "Completions are: $__%[1]s_comp_results" + __%[1]s_debug "Directive is: $directive" + + if test -z "$directive" + set directive 0 + end + + set compErr (math (math --scale 0 $directive / %[3]d) %% 2) + if test $compErr -eq 1 + __%[1]s_debug "Received error directive: aborting." + # Might as well do file completion, in case it helps + set --global __%[1]s_comp_do_file_comp 1 + return 0 + end + + set nospace (math (math --scale 0 $directive / %[4]d) %% 2) + set nofiles (math (math --scale 0 $directive / %[5]d) %% 2) + + __%[1]s_debug "nospace: $nospace, nofiles: $nofiles" + + # Important not to quote the variable for count to work + set numComps (count $__%[1]s_comp_results) + __%[1]s_debug "numComps: $numComps" + + if test $numComps -eq 1; and test $nospace -ne 0 + # To support the "nospace" directive we trick the shell + # by outputting an extra, longer completion. + __%[1]s_debug "Adding second completion to perform nospace directive" + set --append __%[1]s_comp_results $__%[1]s_comp_results[1]. + end + + if test $numComps -eq 0; and test $nofiles -eq 0 + __%[1]s_debug "Requesting file completion" + set --global __%[1]s_comp_do_file_comp 1 + end + + # If we don't want file completion, we must return true even if there + # are no completions found. This is because fish will perform the last + # completion command, even if its condition is false, if no other + # completion command was triggered + return (not set --query __%[1]s_comp_do_file_comp) +end + +# Remove any pre-existing completions for the program since we will be handling all of them +# TODO this cleanup is not sufficient. Fish completions are only loaded once the user triggers +# them, so the below deletion will not work as it is run too early. What else can we do? +complete -c %[1]s -e + +# The order in which the below two lines are defined is very important so that __%[1]s_prepare_completions +# is called first. It is __%[1]s_prepare_completions that sets up the __%[1]s_comp_do_file_comp variable. +# +# This completion will be run second as complete commands are added FILO. +# It triggers file completion choices when __%[1]s_comp_do_file_comp is set. +complete -c %[1]s -n 'set --query __%[1]s_comp_do_file_comp' + +# This completion will be run first as complete commands are added FILO. +# The call to __%[1]s_prepare_completions will setup both __%[1]s_comp_results abd __%[1]s_comp_do_file_comp. +# It provides the program's completion choices. +complete -c %[1]s -n '__%[1]s_prepare_completions' -f -a '$__%[1]s_comp_results' + +`, name, compCmd, ShellCompDirectiveError, ShellCompDirectiveNoSpace, ShellCompDirectiveNoFileComp)) +} + +// GenFishCompletion generates fish completion file and writes to the passed writer. +func (c *Command) GenFishCompletion(w io.Writer, includeDesc bool) error { + buf := new(bytes.Buffer) + genFishComp(buf, c.Name(), includeDesc) + _, err := buf.WriteTo(w) + return err +} + +// GenFishCompletionFile generates fish completion file. +func (c *Command) GenFishCompletionFile(filename string, includeDesc bool) error { + outFile, err := os.Create(filename) + if err != nil { + return err + } + defer outFile.Close() + + return c.GenFishCompletion(outFile, includeDesc) +} diff --git a/vendor/github.com/spf13/cobra/fish_completions.md b/vendor/github.com/spf13/cobra/fish_completions.md new file mode 100644 index 000000000..6bfe5f88e --- /dev/null +++ b/vendor/github.com/spf13/cobra/fish_completions.md @@ -0,0 +1,7 @@ +## Generating Fish Completions for your own cobra.Command + +Cobra supports native Fish completions generated from the root `cobra.Command`. You can use the `command.GenFishCompletion()` or `command.GenFishCompletionFile()` functions. You must provide these functions with a parameter indicating if the completions should be annotated with a description; Cobra will provide the description automatically based on usage information. You can choose to make this option configurable by your users. + +### Limitations + +* Custom completions implemented using the `ValidArgsFunction` and `RegisterFlagCompletionFunc()` are supported automatically but the ones implemented in Bash scripting are not. diff --git a/vendor/github.com/spf13/cobra/go.mod b/vendor/github.com/spf13/cobra/go.mod new file mode 100644 index 000000000..dea1030ba --- /dev/null +++ b/vendor/github.com/spf13/cobra/go.mod @@ -0,0 +1,12 @@ +module github.com/spf13/cobra + +go 1.12 + +require ( + github.com/cpuguy83/go-md2man/v2 v2.0.0 + github.com/inconshreveable/mousetrap v1.0.0 + github.com/mitchellh/go-homedir v1.1.0 + github.com/spf13/pflag v1.0.3 + github.com/spf13/viper v1.4.0 + gopkg.in/yaml.v2 v2.2.2 +) diff --git a/vendor/github.com/spf13/cobra/go.sum b/vendor/github.com/spf13/cobra/go.sum new file mode 100644 index 000000000..3aaa2ac0f --- /dev/null +++ b/vendor/github.com/spf13/cobra/go.sum @@ -0,0 +1,149 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +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/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +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.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +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/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +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/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +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/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/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-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +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-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/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-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +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/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +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/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/vendor/github.com/spf13/cobra/powershell_completions.go b/vendor/github.com/spf13/cobra/powershell_completions.go new file mode 100644 index 000000000..756c61b9d --- /dev/null +++ b/vendor/github.com/spf13/cobra/powershell_completions.go @@ -0,0 +1,100 @@ +// PowerShell completions are based on the amazing work from clap: +// https://github.com/clap-rs/clap/blob/3294d18efe5f264d12c9035f404c7d189d4824e1/src/completions/powershell.rs +// +// The generated scripts require PowerShell v5.0+ (which comes Windows 10, but +// can be downloaded separately for windows 7 or 8.1). + +package cobra + +import ( + "bytes" + "fmt" + "io" + "os" + "strings" + + "github.com/spf13/pflag" +) + +var powerShellCompletionTemplate = `using namespace System.Management.Automation +using namespace System.Management.Automation.Language +Register-ArgumentCompleter -Native -CommandName '%s' -ScriptBlock { + param($wordToComplete, $commandAst, $cursorPosition) + $commandElements = $commandAst.CommandElements + $command = @( + '%s' + for ($i = 1; $i -lt $commandElements.Count; $i++) { + $element = $commandElements[$i] + if ($element -isnot [StringConstantExpressionAst] -or + $element.StringConstantType -ne [StringConstantType]::BareWord -or + $element.Value.StartsWith('-')) { + break + } + $element.Value + } + ) -join ';' + $completions = @(switch ($command) {%s + }) + $completions.Where{ $_.CompletionText -like "$wordToComplete*" } | + Sort-Object -Property ListItemText +}` + +func generatePowerShellSubcommandCases(out io.Writer, cmd *Command, previousCommandName string) { + var cmdName string + if previousCommandName == "" { + cmdName = cmd.Name() + } else { + cmdName = fmt.Sprintf("%s;%s", previousCommandName, cmd.Name()) + } + + fmt.Fprintf(out, "\n '%s' {", cmdName) + + cmd.Flags().VisitAll(func(flag *pflag.Flag) { + if nonCompletableFlag(flag) { + return + } + usage := escapeStringForPowerShell(flag.Usage) + if len(flag.Shorthand) > 0 { + fmt.Fprintf(out, "\n [CompletionResult]::new('-%s', '%s', [CompletionResultType]::ParameterName, '%s')", flag.Shorthand, flag.Shorthand, usage) + } + fmt.Fprintf(out, "\n [CompletionResult]::new('--%s', '%s', [CompletionResultType]::ParameterName, '%s')", flag.Name, flag.Name, usage) + }) + + for _, subCmd := range cmd.Commands() { + usage := escapeStringForPowerShell(subCmd.Short) + fmt.Fprintf(out, "\n [CompletionResult]::new('%s', '%s', [CompletionResultType]::ParameterValue, '%s')", subCmd.Name(), subCmd.Name(), usage) + } + + fmt.Fprint(out, "\n break\n }") + + for _, subCmd := range cmd.Commands() { + generatePowerShellSubcommandCases(out, subCmd, cmdName) + } +} + +func escapeStringForPowerShell(s string) string { + return strings.Replace(s, "'", "''", -1) +} + +// GenPowerShellCompletion generates PowerShell completion file and writes to the passed writer. +func (c *Command) GenPowerShellCompletion(w io.Writer) error { + buf := new(bytes.Buffer) + + var subCommandCases bytes.Buffer + generatePowerShellSubcommandCases(&subCommandCases, c, "") + fmt.Fprintf(buf, powerShellCompletionTemplate, c.Name(), c.Name(), subCommandCases.String()) + + _, err := buf.WriteTo(w) + return err +} + +// GenPowerShellCompletionFile generates PowerShell completion file. +func (c *Command) GenPowerShellCompletionFile(filename string) error { + outFile, err := os.Create(filename) + if err != nil { + return err + } + defer outFile.Close() + + return c.GenPowerShellCompletion(outFile) +} diff --git a/vendor/github.com/spf13/cobra/powershell_completions.md b/vendor/github.com/spf13/cobra/powershell_completions.md new file mode 100644 index 000000000..afed80240 --- /dev/null +++ b/vendor/github.com/spf13/cobra/powershell_completions.md @@ -0,0 +1,14 @@ +# Generating PowerShell Completions For Your Own cobra.Command + +Cobra can generate PowerShell completion scripts. Users need PowerShell version 5.0 or above, which comes with Windows 10 and can be downloaded separately for Windows 7 or 8.1. They can then write the completions to a file and source this file from their PowerShell profile, which is referenced by the `$Profile` environment variable. See `Get-Help about_Profiles` for more info about PowerShell profiles. + +# What's supported + +- Completion for subcommands using their `.Short` description +- Completion for non-hidden flags using their `.Name` and `.Shorthand` + +# What's not yet supported + +- Command aliases +- Required, filename or custom flags (they will work like normal flags) +- Custom completion scripts diff --git a/vendor/github.com/spf13/cobra/shell_completions.go b/vendor/github.com/spf13/cobra/shell_completions.go new file mode 100644 index 000000000..ba0af9cb5 --- /dev/null +++ b/vendor/github.com/spf13/cobra/shell_completions.go @@ -0,0 +1,85 @@ +package cobra + +import ( + "github.com/spf13/pflag" +) + +// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag if it exists, +// and causes your command to report an error if invoked without the flag. +func (c *Command) MarkFlagRequired(name string) error { + return MarkFlagRequired(c.Flags(), name) +} + +// MarkPersistentFlagRequired adds the BashCompOneRequiredFlag annotation to the named persistent flag if it exists, +// and causes your command to report an error if invoked without the flag. +func (c *Command) MarkPersistentFlagRequired(name string) error { + return MarkFlagRequired(c.PersistentFlags(), name) +} + +// MarkFlagRequired adds the BashCompOneRequiredFlag annotation to the named flag if it exists, +// and causes your command to report an error if invoked without the flag. +func MarkFlagRequired(flags *pflag.FlagSet, name string) error { + return flags.SetAnnotation(name, BashCompOneRequiredFlag, []string{"true"}) +} + +// MarkFlagFilename adds the BashCompFilenameExt annotation to the named flag, if it exists. +// Generated bash autocompletion will select filenames for the flag, limiting to named extensions if provided. +func (c *Command) MarkFlagFilename(name string, extensions ...string) error { + return MarkFlagFilename(c.Flags(), name, extensions...) +} + +// MarkFlagCustom adds the BashCompCustom annotation to the named flag, if it exists. +// Generated bash autocompletion will call the bash function f for the flag. +func (c *Command) MarkFlagCustom(name string, f string) error { + return MarkFlagCustom(c.Flags(), name, f) +} + +// MarkPersistentFlagFilename instructs the various shell completion +// implementations to limit completions for this persistent flag to the +// specified extensions (patterns). +// +// Shell Completion compatibility matrix: bash, zsh +func (c *Command) MarkPersistentFlagFilename(name string, extensions ...string) error { + return MarkFlagFilename(c.PersistentFlags(), name, extensions...) +} + +// MarkFlagFilename instructs the various shell completion implementations to +// limit completions for this flag to the specified extensions (patterns). +// +// Shell Completion compatibility matrix: bash, zsh +func MarkFlagFilename(flags *pflag.FlagSet, name string, extensions ...string) error { + return flags.SetAnnotation(name, BashCompFilenameExt, extensions) +} + +// MarkFlagCustom instructs the various shell completion implementations to +// limit completions for this flag to the specified extensions (patterns). +// +// Shell Completion compatibility matrix: bash, zsh +func MarkFlagCustom(flags *pflag.FlagSet, name string, f string) error { + return flags.SetAnnotation(name, BashCompCustom, []string{f}) +} + +// MarkFlagDirname instructs the various shell completion implementations to +// complete only directories with this named flag. +// +// Shell Completion compatibility matrix: zsh +func (c *Command) MarkFlagDirname(name string) error { + return MarkFlagDirname(c.Flags(), name) +} + +// MarkPersistentFlagDirname instructs the various shell completion +// implementations to complete only directories with this persistent named flag. +// +// Shell Completion compatibility matrix: zsh +func (c *Command) MarkPersistentFlagDirname(name string) error { + return MarkFlagDirname(c.PersistentFlags(), name) +} + +// MarkFlagDirname instructs the various shell completion implementations to +// complete only directories with this specified flag. +// +// Shell Completion compatibility matrix: zsh +func MarkFlagDirname(flags *pflag.FlagSet, name string) error { + zshPattern := "-(/)" + return flags.SetAnnotation(name, zshCompDirname, []string{zshPattern}) +} diff --git a/vendor/github.com/spf13/cobra/zsh_completions.go b/vendor/github.com/spf13/cobra/zsh_completions.go index 889c22e27..12755482f 100644 --- a/vendor/github.com/spf13/cobra/zsh_completions.go +++ b/vendor/github.com/spf13/cobra/zsh_completions.go @@ -1,13 +1,102 @@ package cobra import ( - "bytes" + "encoding/json" "fmt" "io" "os" + "sort" "strings" + "text/template" + + "github.com/spf13/pflag" ) +const ( + zshCompArgumentAnnotation = "cobra_annotations_zsh_completion_argument_annotation" + zshCompArgumentFilenameComp = "cobra_annotations_zsh_completion_argument_file_completion" + zshCompArgumentWordComp = "cobra_annotations_zsh_completion_argument_word_completion" + zshCompDirname = "cobra_annotations_zsh_dirname" +) + +var ( + zshCompFuncMap = template.FuncMap{ + "genZshFuncName": zshCompGenFuncName, + "extractFlags": zshCompExtractFlag, + "genFlagEntryForZshArguments": zshCompGenFlagEntryForArguments, + "extractArgsCompletions": zshCompExtractArgumentCompletionHintsForRendering, + } + zshCompletionText = ` +{{/* should accept Command (that contains subcommands) as parameter */}} +{{define "argumentsC" -}} +{{ $cmdPath := genZshFuncName .}} +function {{$cmdPath}} { + local -a commands + + _arguments -C \{{- range extractFlags .}} + {{genFlagEntryForZshArguments .}} \{{- end}} + "1: :->cmnds" \ + "*::arg:->args" + + case $state in + cmnds) + commands=({{range .Commands}}{{if not .Hidden}} + "{{.Name}}:{{.Short}}"{{end}}{{end}} + ) + _describe "command" commands + ;; + esac + + case "$words[1]" in {{- range .Commands}}{{if not .Hidden}} + {{.Name}}) + {{$cmdPath}}_{{.Name}} + ;;{{end}}{{end}} + esac +} +{{range .Commands}}{{if not .Hidden}} +{{template "selectCmdTemplate" .}} +{{- end}}{{end}} +{{- end}} + +{{/* should accept Command without subcommands as parameter */}} +{{define "arguments" -}} +function {{genZshFuncName .}} { +{{" _arguments"}}{{range extractFlags .}} \ + {{genFlagEntryForZshArguments . -}} +{{end}}{{range extractArgsCompletions .}} \ + {{.}}{{end}} +} +{{end}} + +{{/* dispatcher for commands with or without subcommands */}} +{{define "selectCmdTemplate" -}} +{{if .Hidden}}{{/* ignore hidden*/}}{{else -}} +{{if .Commands}}{{template "argumentsC" .}}{{else}}{{template "arguments" .}}{{end}} +{{- end}} +{{- end}} + +{{/* template entry point */}} +{{define "Main" -}} +#compdef _{{.Name}} {{.Name}} + +{{template "selectCmdTemplate" .}} +{{end}} +` +) + +// zshCompArgsAnnotation is used to encode/decode zsh completion for +// arguments to/from Command.Annotations. +type zshCompArgsAnnotation map[int]zshCompArgHint + +type zshCompArgHint struct { + // Indicates the type of the completion to use. One of: + // zshCompArgumentFilenameComp or zshCompArgumentWordComp + Tipe string `json:"type"` + + // A value for the type above (globs for file completion or words) + Options []string `json:"options"` +} + // GenZshCompletionFile generates zsh completion file. func (c *Command) GenZshCompletionFile(filename string) error { outFile, err := os.Create(filename) @@ -19,108 +108,229 @@ func (c *Command) GenZshCompletionFile(filename string) error { return c.GenZshCompletion(outFile) } -// GenZshCompletion generates a zsh completion file and writes to the passed writer. +// GenZshCompletion generates a zsh completion file and writes to the passed +// writer. The completion always run on the root command regardless of the +// command it was called from. func (c *Command) GenZshCompletion(w io.Writer) error { - buf := new(bytes.Buffer) - - writeHeader(buf, c) - maxDepth := maxDepth(c) - writeLevelMapping(buf, maxDepth) - writeLevelCases(buf, maxDepth, c) - - _, err := buf.WriteTo(w) - return err -} - -func writeHeader(w io.Writer, cmd *Command) { - fmt.Fprintf(w, "#compdef %s\n\n", cmd.Name()) -} - -func maxDepth(c *Command) int { - if len(c.Commands()) == 0 { - return 0 + tmpl, err := template.New("Main").Funcs(zshCompFuncMap).Parse(zshCompletionText) + if err != nil { + return fmt.Errorf("error creating zsh completion template: %v", err) } - maxDepthSub := 0 - for _, s := range c.Commands() { - subDepth := maxDepth(s) - if subDepth > maxDepthSub { - maxDepthSub = subDepth + return tmpl.Execute(w, c.Root()) +} + +// MarkZshCompPositionalArgumentFile marks the specified argument (first +// argument is 1) as completed by file selection. patterns (e.g. "*.txt") are +// optional - if not provided the completion will search for all files. +func (c *Command) MarkZshCompPositionalArgumentFile(argPosition int, patterns ...string) error { + if argPosition < 1 { + return fmt.Errorf("Invalid argument position (%d)", argPosition) + } + annotation, err := c.zshCompGetArgsAnnotations() + if err != nil { + return err + } + if c.zshcompArgsAnnotationnIsDuplicatePosition(annotation, argPosition) { + return fmt.Errorf("Duplicate annotation for positional argument at index %d", argPosition) + } + annotation[argPosition] = zshCompArgHint{ + Tipe: zshCompArgumentFilenameComp, + Options: patterns, + } + return c.zshCompSetArgsAnnotations(annotation) +} + +// MarkZshCompPositionalArgumentWords marks the specified positional argument +// (first argument is 1) as completed by the provided words. At east one word +// must be provided, spaces within words will be offered completion with +// "word\ word". +func (c *Command) MarkZshCompPositionalArgumentWords(argPosition int, words ...string) error { + if argPosition < 1 { + return fmt.Errorf("Invalid argument position (%d)", argPosition) + } + if len(words) == 0 { + return fmt.Errorf("Trying to set empty word list for positional argument %d", argPosition) + } + annotation, err := c.zshCompGetArgsAnnotations() + if err != nil { + return err + } + if c.zshcompArgsAnnotationnIsDuplicatePosition(annotation, argPosition) { + return fmt.Errorf("Duplicate annotation for positional argument at index %d", argPosition) + } + annotation[argPosition] = zshCompArgHint{ + Tipe: zshCompArgumentWordComp, + Options: words, + } + return c.zshCompSetArgsAnnotations(annotation) +} + +func zshCompExtractArgumentCompletionHintsForRendering(c *Command) ([]string, error) { + var result []string + annotation, err := c.zshCompGetArgsAnnotations() + if err != nil { + return nil, err + } + for k, v := range annotation { + s, err := zshCompRenderZshCompArgHint(k, v) + if err != nil { + return nil, err + } + result = append(result, s) + } + if len(c.ValidArgs) > 0 { + if _, positionOneExists := annotation[1]; !positionOneExists { + s, err := zshCompRenderZshCompArgHint(1, zshCompArgHint{ + Tipe: zshCompArgumentWordComp, + Options: c.ValidArgs, + }) + if err != nil { + return nil, err + } + result = append(result, s) } } - return 1 + maxDepthSub + sort.Strings(result) + return result, nil } -func writeLevelMapping(w io.Writer, numLevels int) { - fmt.Fprintln(w, `_arguments \`) - for i := 1; i <= numLevels; i++ { - fmt.Fprintf(w, ` '%d: :->level%d' \`, i, i) - fmt.Fprintln(w) - } - fmt.Fprintf(w, ` '%d: :%s'`, numLevels+1, "_files") - fmt.Fprintln(w) -} - -func writeLevelCases(w io.Writer, maxDepth int, root *Command) { - fmt.Fprintln(w, "case $state in") - defer fmt.Fprintln(w, "esac") - - for i := 1; i <= maxDepth; i++ { - fmt.Fprintf(w, " level%d)\n", i) - writeLevel(w, root, i) - fmt.Fprintln(w, " ;;") - } - fmt.Fprintln(w, " *)") - fmt.Fprintln(w, " _arguments '*: :_files'") - fmt.Fprintln(w, " ;;") -} - -func writeLevel(w io.Writer, root *Command, i int) { - fmt.Fprintf(w, " case $words[%d] in\n", i) - defer fmt.Fprintln(w, " esac") - - commands := filterByLevel(root, i) - byParent := groupByParent(commands) - - for p, c := range byParent { - names := names(c) - fmt.Fprintf(w, " %s)\n", p) - fmt.Fprintf(w, " _arguments '%d: :(%s)'\n", i, strings.Join(names, " ")) - fmt.Fprintln(w, " ;;") - } - fmt.Fprintln(w, " *)") - fmt.Fprintln(w, " _arguments '*: :_files'") - fmt.Fprintln(w, " ;;") - -} - -func filterByLevel(c *Command, l int) []*Command { - cs := make([]*Command, 0) - if l == 0 { - cs = append(cs, c) - return cs - } - for _, s := range c.Commands() { - cs = append(cs, filterByLevel(s, l-1)...) - } - return cs -} - -func groupByParent(commands []*Command) map[string][]*Command { - m := make(map[string][]*Command) - for _, c := range commands { - parent := c.Parent() - if parent == nil { - continue +func zshCompRenderZshCompArgHint(i int, z zshCompArgHint) (string, error) { + switch t := z.Tipe; t { + case zshCompArgumentFilenameComp: + var globs []string + for _, g := range z.Options { + globs = append(globs, fmt.Sprintf(`-g "%s"`, g)) } - m[parent.Name()] = append(m[parent.Name()], c) + return fmt.Sprintf(`'%d: :_files %s'`, i, strings.Join(globs, " ")), nil + case zshCompArgumentWordComp: + var words []string + for _, w := range z.Options { + words = append(words, fmt.Sprintf("%q", w)) + } + return fmt.Sprintf(`'%d: :(%s)'`, i, strings.Join(words, " ")), nil + default: + return "", fmt.Errorf("Invalid zsh argument completion annotation: %s", t) } - return m } -func names(commands []*Command) []string { - ns := make([]string, len(commands)) - for i, c := range commands { - ns[i] = c.Name() - } - return ns +func (c *Command) zshcompArgsAnnotationnIsDuplicatePosition(annotation zshCompArgsAnnotation, position int) bool { + _, dup := annotation[position] + return dup +} + +func (c *Command) zshCompGetArgsAnnotations() (zshCompArgsAnnotation, error) { + annotation := make(zshCompArgsAnnotation) + annotationString, ok := c.Annotations[zshCompArgumentAnnotation] + if !ok { + return annotation, nil + } + err := json.Unmarshal([]byte(annotationString), &annotation) + if err != nil { + return annotation, fmt.Errorf("Error unmarshaling zsh argument annotation: %v", err) + } + return annotation, nil +} + +func (c *Command) zshCompSetArgsAnnotations(annotation zshCompArgsAnnotation) error { + jsn, err := json.Marshal(annotation) + if err != nil { + return fmt.Errorf("Error marshaling zsh argument annotation: %v", err) + } + if c.Annotations == nil { + c.Annotations = make(map[string]string) + } + c.Annotations[zshCompArgumentAnnotation] = string(jsn) + return nil +} + +func zshCompGenFuncName(c *Command) string { + if c.HasParent() { + return zshCompGenFuncName(c.Parent()) + "_" + c.Name() + } + return "_" + c.Name() +} + +func zshCompExtractFlag(c *Command) []*pflag.Flag { + var flags []*pflag.Flag + c.LocalFlags().VisitAll(func(f *pflag.Flag) { + if !f.Hidden { + flags = append(flags, f) + } + }) + c.InheritedFlags().VisitAll(func(f *pflag.Flag) { + if !f.Hidden { + flags = append(flags, f) + } + }) + return flags +} + +// zshCompGenFlagEntryForArguments returns an entry that matches _arguments +// zsh-completion parameters. It's too complicated to generate in a template. +func zshCompGenFlagEntryForArguments(f *pflag.Flag) string { + if f.Name == "" || f.Shorthand == "" { + return zshCompGenFlagEntryForSingleOptionFlag(f) + } + return zshCompGenFlagEntryForMultiOptionFlag(f) +} + +func zshCompGenFlagEntryForSingleOptionFlag(f *pflag.Flag) string { + var option, multiMark, extras string + + if zshCompFlagCouldBeSpecifiedMoreThenOnce(f) { + multiMark = "*" + } + + option = "--" + f.Name + if option == "--" { + option = "-" + f.Shorthand + } + extras = zshCompGenFlagEntryExtras(f) + + return fmt.Sprintf(`'%s%s[%s]%s'`, multiMark, option, zshCompQuoteFlagDescription(f.Usage), extras) +} + +func zshCompGenFlagEntryForMultiOptionFlag(f *pflag.Flag) string { + var options, parenMultiMark, curlyMultiMark, extras string + + if zshCompFlagCouldBeSpecifiedMoreThenOnce(f) { + parenMultiMark = "*" + curlyMultiMark = "\\*" + } + + options = fmt.Sprintf(`'(%s-%s %s--%s)'{%s-%s,%s--%s}`, + parenMultiMark, f.Shorthand, parenMultiMark, f.Name, curlyMultiMark, f.Shorthand, curlyMultiMark, f.Name) + extras = zshCompGenFlagEntryExtras(f) + + return fmt.Sprintf(`%s'[%s]%s'`, options, zshCompQuoteFlagDescription(f.Usage), extras) +} + +func zshCompGenFlagEntryExtras(f *pflag.Flag) string { + if f.NoOptDefVal != "" { + return "" + } + + extras := ":" // allow options for flag (even without assistance) + for key, values := range f.Annotations { + switch key { + case zshCompDirname: + extras = fmt.Sprintf(":filename:_files -g %q", values[0]) + case BashCompFilenameExt: + extras = ":filename:_files" + for _, pattern := range values { + extras = extras + fmt.Sprintf(` -g "%s"`, pattern) + } + } + } + + return extras +} + +func zshCompFlagCouldBeSpecifiedMoreThenOnce(f *pflag.Flag) bool { + return strings.Contains(f.Value.Type(), "Slice") || + strings.Contains(f.Value.Type(), "Array") +} + +func zshCompQuoteFlagDescription(s string) string { + return strings.Replace(s, "'", `'\''`, -1) } diff --git a/vendor/github.com/spf13/cobra/zsh_completions.md b/vendor/github.com/spf13/cobra/zsh_completions.md new file mode 100644 index 000000000..df9c2eac9 --- /dev/null +++ b/vendor/github.com/spf13/cobra/zsh_completions.md @@ -0,0 +1,39 @@ +## Generating Zsh Completion for your cobra.Command + +Cobra supports native Zsh completion generated from the root `cobra.Command`. +The generated completion script should be put somewhere in your `$fpath` named +`_`. + +### What's Supported + +* Completion for all non-hidden subcommands using their `.Short` description. +* Completion for all non-hidden flags using the following rules: + * Filename completion works by marking the flag with `cmd.MarkFlagFilename...` + family of commands. + * The requirement for argument to the flag is decided by the `.NoOptDefVal` + flag value - if it's empty then completion will expect an argument. + * Flags of one of the various `*Array` and `*Slice` types supports multiple + specifications (with or without argument depending on the specific type). +* Completion of positional arguments using the following rules: + * Argument position for all options below starts at `1`. If argument position + `0` is requested it will raise an error. + * Use `command.MarkZshCompPositionalArgumentFile` to complete filenames. Glob + patterns (e.g. `"*.log"`) are optional - if not specified it will offer to + complete all file types. + * Use `command.MarkZshCompPositionalArgumentWords` to offer specific words for + completion. At least one word is required. + * It's possible to specify completion for some arguments and leave some + unspecified (e.g. offer words for second argument but nothing for first + argument). This will cause no completion for first argument but words + completion for second argument. + * If no argument completion was specified for 1st argument (but optionally was + specified for 2nd) and the command has `ValidArgs` it will be used as + completion options for 1st argument. + * Argument completions only offered for commands with no subcommands. + +### What's not yet Supported + +* Custom completion scripts are not supported yet (We should probably create zsh + specific one, doesn't make sense to re-use the bash one as the functions will + be different). +* Whatever other feature you're looking for and doesn't exist :) diff --git a/vendor/github.com/spf13/viper/.editorconfig b/vendor/github.com/spf13/viper/.editorconfig new file mode 100644 index 000000000..63afcbcdd --- /dev/null +++ b/vendor/github.com/spf13/viper/.editorconfig @@ -0,0 +1,15 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.go] +indent_style = tab + +[{Makefile, *.mk}] +indent_style = tab diff --git a/vendor/github.com/spf13/viper/.gitignore b/vendor/github.com/spf13/viper/.gitignore index 01b5c44b9..896250839 100644 --- a/vendor/github.com/spf13/viper/.gitignore +++ b/vendor/github.com/spf13/viper/.gitignore @@ -1,29 +1,5 @@ -# Compiled Object files, Static and Dynamic libs (Shared Objects) -*.o -*.a -*.so - -# Folders -_obj -_test - -# Architecture specific extensions/prefixes -*.[568vq] -[568vq].out - -*.cgo1.go -*.cgo2.c -_cgo_defun.c -_cgo_gotypes.go -_cgo_export.* - -_testmain.go - -*.exe -*.test -*.bench - -.vscode - -# exclude dependencies in the `/vendor` folder -vendor +/.idea/ +/bin/ +/build/ +/var/ +/vendor/ diff --git a/vendor/github.com/spf13/viper/.golangci.yml b/vendor/github.com/spf13/viper/.golangci.yml new file mode 100644 index 000000000..a0755ce7e --- /dev/null +++ b/vendor/github.com/spf13/viper/.golangci.yml @@ -0,0 +1,27 @@ +linters-settings: + golint: + min-confidence: 0.1 + goimports: + local-prefixes: github.com/spf13/viper + +linters: + enable-all: true + disable: + - funlen + - maligned + + # TODO: fix me + - wsl + - gochecknoinits + - gosimple + - gochecknoglobals + - errcheck + - lll + - godox + - scopelint + - gocyclo + - gocognit + - gocritic + +service: + golangci-lint-version: 1.21.x diff --git a/vendor/github.com/spf13/viper/.travis.yml b/vendor/github.com/spf13/viper/.travis.yml deleted file mode 100644 index bb83057ba..000000000 --- a/vendor/github.com/spf13/viper/.travis.yml +++ /dev/null @@ -1,31 +0,0 @@ -go_import_path: github.com/spf13/viper - -language: go - -env: - global: - - GO111MODULE="on" - -go: - - 1.11.x - - tip - -os: - - linux - - osx - -matrix: - allow_failures: - - go: tip - fast_finish: true - -script: - - go install ./... - - diff -u <(echo -n) <(gofmt -d .) - - go test -v ./... - -after_success: - - go get -u -d github.com/spf13/hugo - - cd $GOPATH/src/github.com/spf13/hugo && make && ./hugo -s docs && cd - - -sudo: false diff --git a/vendor/github.com/spf13/viper/Makefile b/vendor/github.com/spf13/viper/Makefile new file mode 100644 index 000000000..1c2cab03f --- /dev/null +++ b/vendor/github.com/spf13/viper/Makefile @@ -0,0 +1,76 @@ +# A Self-Documenting Makefile: http://marmelab.com/blog/2016/02/29/auto-documented-makefile.html + +OS = $(shell uname | tr A-Z a-z) +export PATH := $(abspath bin/):${PATH} + +# Build variables +BUILD_DIR ?= build +export CGO_ENABLED ?= 0 +export GOOS = $(shell go env GOOS) +ifeq (${VERBOSE}, 1) +ifeq ($(filter -v,${GOARGS}),) + GOARGS += -v +endif +TEST_FORMAT = short-verbose +endif + +# Dependency versions +GOTESTSUM_VERSION = 0.4.0 +GOLANGCI_VERSION = 1.21.0 + +# Add the ability to override some variables +# Use with care +-include override.mk + +.PHONY: clear +clear: ## Clear the working area and the project + rm -rf bin/ + +.PHONY: check +check: test lint ## Run tests and linters + +bin/gotestsum: bin/gotestsum-${GOTESTSUM_VERSION} + @ln -sf gotestsum-${GOTESTSUM_VERSION} bin/gotestsum +bin/gotestsum-${GOTESTSUM_VERSION}: + @mkdir -p bin + curl -L https://github.com/gotestyourself/gotestsum/releases/download/v${GOTESTSUM_VERSION}/gotestsum_${GOTESTSUM_VERSION}_${OS}_amd64.tar.gz | tar -zOxf - gotestsum > ./bin/gotestsum-${GOTESTSUM_VERSION} && chmod +x ./bin/gotestsum-${GOTESTSUM_VERSION} + +TEST_PKGS ?= ./... +.PHONY: test +test: TEST_FORMAT ?= short +test: SHELL = /bin/bash +test: export CGO_ENABLED=1 +test: bin/gotestsum ## Run tests + @mkdir -p ${BUILD_DIR} + bin/gotestsum --no-summary=skipped --junitfile ${BUILD_DIR}/coverage.xml --format ${TEST_FORMAT} -- -race -coverprofile=${BUILD_DIR}/coverage.txt -covermode=atomic $(filter-out -v,${GOARGS}) $(if ${TEST_PKGS},${TEST_PKGS},./...) + +bin/golangci-lint: bin/golangci-lint-${GOLANGCI_VERSION} + @ln -sf golangci-lint-${GOLANGCI_VERSION} bin/golangci-lint +bin/golangci-lint-${GOLANGCI_VERSION}: + @mkdir -p bin + curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b ./bin/ v${GOLANGCI_VERSION} + @mv bin/golangci-lint $@ + +.PHONY: lint +lint: bin/golangci-lint ## Run linter + bin/golangci-lint run + +.PHONY: fix +fix: bin/golangci-lint ## Fix lint violations + bin/golangci-lint run --fix + +# Add custom targets here +-include custom.mk + +.PHONY: list +list: ## List all make targets + @${MAKE} -pRrn : -f $(MAKEFILE_LIST) 2>/dev/null | awk -v RS= -F: '/^# File/,/^# Finished Make data base/ {if ($$1 !~ "^[#.]") {print $$1}}' | egrep -v -e '^[^[:alnum:]]' -e '^$@$$' | sort + +.PHONY: help +.DEFAULT_GOAL := help +help: + @grep -h -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + +# Variable outputting/exporting rules +var-%: ; @echo $($*) +varexport-%: ; @echo $*=$($*) diff --git a/vendor/github.com/spf13/viper/README.md b/vendor/github.com/spf13/viper/README.md index 0208eac84..dfd8034fd 100644 --- a/vendor/github.com/spf13/viper/README.md +++ b/vendor/github.com/spf13/viper/README.md @@ -1,6 +1,13 @@ -![viper logo](https://cloud.githubusercontent.com/assets/173412/10886745/998df88a-8151-11e5-9448-4736db51020d.png) +![Viper](.github/logo.png?raw=true) -Go configuration with fangs! +[![Mentioned in Awesome Go](https://awesome.re/mentioned-badge-flat.svg)](https://github.com/avelino/awesome-go#configuration) + +[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/spf13/viper/CI?style=flat-square)](https://github.com/spf13/viper/actions?query=workflow%3ACI) +[![Join the chat at https://gitter.im/spf13/viper](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/spf13/viper?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Go Report Card](https://goreportcard.com/badge/github.com/spf13/viper?style=flat-square)](https://goreportcard.com/report/github.com/spf13/viper) +[![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/mod/github.com/spf13/viper) + +**Go configuration with fangs!** Many Go projects are built using Viper including: @@ -12,8 +19,14 @@ Many Go projects are built using Viper including: * [BloomApi](https://www.bloomapi.com/) * [doctl](https://github.com/digitalocean/doctl) * [Clairctl](https://github.com/jgsqware/clairctl) +* [Mercure](https://mercure.rocks) -[![Build Status](https://travis-ci.org/spf13/viper.svg)](https://travis-ci.org/spf13/viper) [![Join the chat at https://gitter.im/spf13/viper](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/spf13/viper?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) [![GoDoc](https://godoc.org/github.com/spf13/viper?status.svg)](https://godoc.org/github.com/spf13/viper) + +## Install + +```console +go get github.com/spf13/viper +``` ## What is Viper? @@ -23,7 +36,7 @@ to work within an application, and can handle all types of configuration needs and formats. It supports: * setting defaults -* reading from JSON, TOML, YAML, HCL, and Java properties config files +* reading from JSON, TOML, YAML, HCL, envfile and Java properties config files * live watching and re-reading of config files (optional) * reading from environment variables * reading from remote config systems (etcd or Consul), and watching changes @@ -31,8 +44,8 @@ and formats. It supports: * reading from buffer * setting explicit values -Viper can be thought of as a registry for all of your applications -configuration needs. +Viper can be thought of as a registry for all of your applications configuration needs. + ## Why Viper? @@ -42,34 +55,31 @@ Viper is here to help with that. Viper does the following for you: -1. Find, load, and unmarshal a configuration file in JSON, TOML, YAML, HCL, or Java properties formats. -2. Provide a mechanism to set default values for your different - configuration options. -3. Provide a mechanism to set override values for options specified through - command line flags. -4. Provide an alias system to easily rename parameters without breaking existing - code. -5. Make it easy to tell the difference between when a user has provided a - command line or config file which is the same as the default. +1. Find, load, and unmarshal a configuration file in JSON, TOML, YAML, HCL, INI, envfile or Java properties formats. +2. Provide a mechanism to set default values for your different configuration options. +3. Provide a mechanism to set override values for options specified through command line flags. +4. Provide an alias system to easily rename parameters without breaking existing code. +5. Make it easy to tell the difference between when a user has provided a command line or config file which is the same as the default. -Viper uses the following precedence order. Each item takes precedence over the -item below it: +Viper uses the following precedence order. Each item takes precedence over the item below it: - * explicit call to Set + * explicit call to `Set` * flag * env * config * key/value store * default -Viper configuration keys are case insensitive. +**Important:** Viper configuration keys are case insensitive. +There are ongoing discussions about making that optional. + ## Putting Values into Viper ### Establishing Defaults A good configuration system will support default values. A default value is not -required for a key, but it’s useful in the event that a key hasn’t been set via +required for a key, but it’s useful in the event that a key hasn't been set via config file, environment variable, remote configuration or flag. Examples: @@ -83,7 +93,7 @@ viper.SetDefault("Taxonomies", map[string]string{"tag": "tags", "category": "cat ### Reading Config Files Viper requires minimal configuration so it knows where to look for config files. -Viper supports JSON, TOML, YAML, HCL, and Java Properties files. Viper can search multiple paths, but +Viper supports JSON, TOML, YAML, HCL, INI, envfile and Java Properties files. Viper can search multiple paths, but currently a single Viper instance only supports a single configuration file. Viper does not default to any configuration search paths leaving defaults decision to an application. @@ -94,6 +104,7 @@ where a configuration file is expected. ```go viper.SetConfigName("config") // name of config file (without extension) +viper.SetConfigType("yaml") // REQUIRED if the config file does not have the extension in the name viper.AddConfigPath("/etc/appname/") // path to look for the config file in viper.AddConfigPath("$HOME/.appname") // call multiple times to add many search paths viper.AddConfigPath(".") // optionally look for config in the working directory @@ -103,6 +114,44 @@ if err != nil { // Handle errors reading the config file } ``` +You can handle the specific case where no config file is found like this: + +```go +if err := viper.ReadInConfig(); err != nil { + if _, ok := err.(viper.ConfigFileNotFoundError); ok { + // Config file not found; ignore error if desired + } else { + // Config file was found but another error was produced + } +} + +// Config file found and successfully parsed +``` + +*NOTE [since 1.6]:* You can also have a file without an extension and specify the format programmaticaly. For those configuration files that lie in the home of the user without any extension like `.bashrc` + +### Writing Config Files + +Reading from config files is useful, but at times you want to store all modifications made at run time. +For that, a bunch of commands are available, each with its own purpose: + +* WriteConfig - writes the current viper configuration to the predefined path, if exists. Errors if no predefined path. Will overwrite the current config file, if it exists. +* SafeWriteConfig - writes the current viper configuration to the predefined path. Errors if no predefined path. Will not overwrite the current config file, if it exists. +* WriteConfigAs - writes the current viper configuration to the given filepath. Will overwrite the given file, if it exists. +* SafeWriteConfigAs - writes the current viper configuration to the given filepath. Will not overwrite the given file, if it exists. + +As a rule of the thumb, everything marked with safe won't overwrite any file, but just create if not existent, whilst the default behavior is to create or truncate. + +A small examples section: + +```go +viper.WriteConfig() // writes current config to predefined path set by 'viper.AddConfigPath()' and 'viper.SetConfigName' +viper.SafeWriteConfig() +viper.WriteConfigAs("/path/to/my/.config") +viper.SafeWriteConfigAs("/path/to/my/.config") // will error since it has already been written +viper.SafeWriteConfigAs("/path/to/my/.other_config") +``` + ### Watching and re-reading config files Viper supports the ability to have your application live read a config file while running. @@ -186,7 +235,7 @@ with ENV: * `BindEnv(string...) : error` * `SetEnvPrefix(string)` * `SetEnvKeyReplacer(string...) *strings.Replacer` - * `AllowEmptyEnvVar(bool)` + * `AllowEmptyEnv(bool)` _When working with ENV variables, it’s important to recognize that Viper treats ENV variables as case sensitive._ @@ -199,9 +248,9 @@ prefix. `BindEnv` takes one or two parameters. The first parameter is the key name, the second is the name of the environment variable. The name of the environment variable is case sensitive. If the ENV variable name is not provided, then -Viper will automatically assume that the key name matches the ENV variable name, -but the ENV variable is IN ALL CAPS. When you explicitly provide the ENV -variable name, it **does not** automatically add the prefix. +Viper will automatically assume that the ENV variable matches the following format: prefix + "_" + the key name in ALL CAPS. When you explicitly provide the ENV variable name (the second parameter), +it **does not** automatically add the prefix. For example if the second parameter is "id", +Viper will look for the ENV variable "ID". One important thing to recognize when working with ENV variables is that the value will be read each time it is accessed. Viper does not fix the value when @@ -218,6 +267,9 @@ keys to an extent. This is useful if you want to use `-` or something in your `Get()` calls, but want your environmental variables to use `_` delimiters. An example of using it can be found in `viper_test.go`. +Alternatively, you can use `EnvKeyReplacer` with `NewWithOptions` factory function. +Unlike `SetEnvKeyReplacer`, it accepts a `StringReplacer` interface allowing you to write custom string replacing logic. + By default empty environment variables are considered unset and will fall back to the next configuration source. To treat empty environment variables as set, use the `AllowEmptyEnv` method. @@ -346,12 +398,12 @@ package: `import _ "github.com/spf13/viper/remote"` -Viper will read a config string (as JSON, TOML, YAML or HCL) retrieved from a path +Viper will read a config string (as JSON, TOML, YAML, HCL or envfile) retrieved from a path in a Key/Value store such as etcd or Consul. These values take precedence over default values, but are overridden by configuration values retrieved from disk, flags, or environment variables. -Viper uses [crypt](https://github.com/xordataexchange/crypt) to retrieve +Viper uses [crypt](https://github.com/bketelsen/crypt) to retrieve configuration from the K/V store, which means that you can store your configuration values encrypted and have them automatically decrypted if you have the correct gpg keyring. Encryption is optional. @@ -363,7 +415,7 @@ independently of it. K/V store. `crypt` defaults to etcd on http://127.0.0.1:4001. ```bash -$ go get github.com/xordataexchange/crypt/bin/crypt +$ go get github.com/bketelsen/crypt/bin/crypt $ crypt set -plaintext /config/hugo.json /Users/hugo/settings/config.json ``` @@ -381,12 +433,12 @@ how to use Consul. #### etcd ```go viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001","/config/hugo.json") -viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop" +viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv" err := viper.ReadRemoteConfig() ``` #### Consul -You need to set a key to Consul key/value storage with JSON value containing your desired config. +You need to set a key to Consul key/value storage with JSON value containing your desired config. For example, create a Consul key/value store key `MY_CONSUL_KEY` with value: ```json @@ -405,11 +457,21 @@ fmt.Println(viper.Get("port")) // 8080 fmt.Println(viper.Get("hostname")) // myhostname.com ``` +#### Firestore + +```go +viper.AddRemoteProvider("firestore", "google-cloud-project-id", "collection/document") +viper.SetConfigType("json") // Config's format: "json", "toml", "yaml", "yml" +err := viper.ReadRemoteConfig() +``` + +Of course, you're allowed to use `SecureRemoteProvider` also + ### Remote Key/Value Store Example - Encrypted ```go viper.AddSecureRemoteProvider("etcd","http://127.0.0.1:4001","/config/hugo.json","/etc/secrets/mykeyring.gpg") -viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop" +viper.SetConfigType("json") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv" err := viper.ReadRemoteConfig() ``` @@ -420,7 +482,7 @@ err := viper.ReadRemoteConfig() var runtime_viper = viper.New() runtime_viper.AddRemoteProvider("etcd", "http://127.0.0.1:4001", "/config/hugo.yml") -runtime_viper.SetConfigType("yaml") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop" +runtime_viper.SetConfigType("yaml") // because there is no file extension in a stream of bytes, supported extensions are "json", "toml", "yaml", "yml", "properties", "props", "prop", "env", "dotenv" // read from remote config the first time. err := runtime_viper.ReadRemoteConfig() @@ -456,6 +518,7 @@ The following functions and methods exist: * `GetBool(key string) : bool` * `GetFloat64(key string) : float64` * `GetInt(key string) : int` + * `GetIntSlice(key string) : []int` * `GetString(key string) : string` * `GetStringMap(key string) : map[string]interface{}` * `GetStringMapString(key string) : map[string]string` @@ -611,30 +674,89 @@ type config struct { var C config -err := Unmarshal(&C) +err := viper.Unmarshal(&C) if err != nil { t.Fatalf("unable to decode into struct, %v", err) } ``` +If you want to unmarshal configuration where the keys themselves contain dot (the default key delimiter), +you have to change the delimiter: + +```go +v := viper.NewWithOptions(viper.KeyDelimiter("::")) + +v.SetDefault("chart::values", map[string]interface{}{ + "ingress": map[string]interface{}{ + "annotations": map[string]interface{}{ + "traefik.frontend.rule.type": "PathPrefix", + "traefik.ingress.kubernetes.io/ssl-redirect": "true", + }, + }, +}) + +type config struct { + Chart struct{ + Values map[string]interface{} + } +} + +var C config + +v.Unmarshal(&C) +``` + +Viper also supports unmarshaling into embedded structs: + +```go +/* +Example config: + +module: + enabled: true + token: 89h3f98hbwf987h3f98wenf89ehf +*/ +type config struct { + Module struct { + Enabled bool + + moduleConfig `mapstructure:",squash"` + } +} + +// moduleConfig could be in a module specific package +type moduleConfig struct { + Token string +} + +var C config + +err := viper.Unmarshal(&C) +if err != nil { + t.Fatalf("unable to decode into struct, %v", err) +} +``` + +Viper uses [github.com/mitchellh/mapstructure](https://github.com/mitchellh/mapstructure) under the hood for unmarshaling values which uses `mapstructure` tags by default. + ### Marshalling to string -You may need to marhsal all the settings held in viper into a string rather than write them to a file. +You may need to marshal all the settings held in viper into a string rather than write them to a file. You can use your favorite format's marshaller with the config returned by `AllSettings()`. ```go import ( yaml "gopkg.in/yaml.v2" // ... -) +) func yamlStringSettings() string { c := viper.AllSettings() - bs, err := yaml.Marshal(c) - if err != nil { - t.Fatalf("unable to marshal config to YAML: %v", err) + bs, err := yaml.Marshal(c) + if err != nil { + log.Fatalf("unable to marshal config to YAML: %v", err) } - return string(bs) + return string(bs) } ``` @@ -672,13 +794,6 @@ different vipers. ## Q & A -Q: Why not INI files? - -A: Ini files are pretty awful. There’s no standard format, and they are hard to -validate. Viper is designed to work with JSON, TOML or YAML files. If someone -really wants to add this feature, I’d be happy to merge it. It’s easy to specify -which formats your application will permit. - Q: Why is it called “Viper”? A: Viper is designed to be a [companion](http://en.wikipedia.org/wiki/Viper_(G.I._Joe)) diff --git a/vendor/github.com/spf13/viper/flags.go b/vendor/github.com/spf13/viper/flags.go index dd32f4e1c..b5ddbf5d4 100644 --- a/vendor/github.com/spf13/viper/flags.go +++ b/vendor/github.com/spf13/viper/flags.go @@ -36,7 +36,7 @@ type pflagValue struct { flag *pflag.Flag } -// HasChanges returns whether the flag has changes or not. +// HasChanged returns whether the flag has changes or not. func (p pflagValue) HasChanged() bool { return p.flag.Changed } diff --git a/vendor/github.com/spf13/viper/go.mod b/vendor/github.com/spf13/viper/go.mod index 279430055..7d108dcc2 100644 --- a/vendor/github.com/spf13/viper/go.mod +++ b/vendor/github.com/spf13/viper/go.mod @@ -1,43 +1,40 @@ module github.com/spf13/viper +go 1.12 + require ( - github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 // indirect + github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c github.com/coreos/bbolt v1.3.2 // indirect - github.com/coreos/etcd v3.3.10+incompatible // indirect - github.com/coreos/go-semver v0.2.0 // indirect github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e // indirect github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect github.com/fsnotify/fsnotify v1.4.7 github.com/gogo/protobuf v1.2.1 // indirect github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef // indirect - github.com/google/btree v1.0.0 // indirect - github.com/gorilla/websocket v1.4.0 // indirect + github.com/gorilla/websocket v1.4.2 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 // indirect github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect github.com/grpc-ecosystem/grpc-gateway v1.9.0 // indirect github.com/hashicorp/hcl v1.0.0 github.com/jonboulle/clockwork v0.1.0 // indirect - github.com/magiconair/properties v1.8.0 + github.com/magiconair/properties v1.8.1 github.com/mitchellh/mapstructure v1.1.2 github.com/pelletier/go-toml v1.2.0 github.com/prometheus/client_golang v0.9.3 // indirect + github.com/smartystreets/goconvey v1.6.4 // indirect github.com/soheilhy/cmux v0.1.4 // indirect github.com/spf13/afero v1.1.2 github.com/spf13/cast v1.3.0 github.com/spf13/jwalterweatherman v1.0.0 github.com/spf13/pflag v1.0.3 - github.com/stretchr/testify v1.2.2 + github.com/stretchr/testify v1.3.0 + github.com/subosito/gotenv v1.2.0 github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect - github.com/ugorji/go v1.1.4 // indirect github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect - github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 go.etcd.io/bbolt v1.3.2 // indirect go.uber.org/atomic v1.4.0 // indirect go.uber.org/multierr v1.1.0 // indirect go.uber.org/zap v1.10.0 // indirect - golang.org/x/net v0.0.0-20190522155817-f3200d17e092 // indirect - golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect - google.golang.org/grpc v1.21.0 // indirect - gopkg.in/yaml.v2 v2.2.2 + gopkg.in/ini.v1 v1.51.0 + gopkg.in/yaml.v2 v2.2.4 ) diff --git a/vendor/github.com/spf13/viper/go.sum b/vendor/github.com/spf13/viper/go.sum index 97afaffe2..463aa7dbf 100644 --- a/vendor/github.com/spf13/viper/go.sum +++ b/vendor/github.com/spf13/viper/go.sum @@ -1,35 +1,62 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3 h1:AVXDdKsrtX33oR9fbCMu/+c1o8Ofjq6Ku/MInaLVg5Y= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go/bigquery v1.0.1 h1:hL+ycaJpVE9M7nLoiXb/Pn10ENE2u+oddxbD8uu0ZVU= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/datastore v1.0.0 h1:Kt+gOPPp2LEPWp8CSfxhsM8ik9CcyE/gYu+0r+RnZvM= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/firestore v1.1.0 h1:9x7Bx0A9R5/M9jibeJeZWqjeVEIxYW9fZYqB9a70/bY= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1 h1:W9tAK3E57P75u0XLLR82LZyw8VpAnhmyTOxW9qzmyj8= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/storage v1.0.0 h1:VV2nUM3wwLLGh9lSABFgZMjInyUbJeaRSE64WuAIQ+4= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6 h1:G1bPvciwNyF7IUmKXNt9Ak3m6u9DE1rF+RmtIkBpVdA= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c h1:+0HFd5KSZ/mm3JmhmrDukiId5iR6w4+BdFtfSy4yWIc= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.10+incompatible h1:jFneRYjIvLMLhDLCzuTuU4rSJUjRplcJQ7pD7MnhC04= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-semver v0.2.0 h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/etcd v3.3.13+incompatible h1:8F3hqu9fGYLBifCmRCJsicFqDx/D68Rt3q1JMazcgBQ= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= @@ -42,24 +69,79 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.9.0 h1:bM6ZAFZmc/wPFaRDi0d5L7hGEZEx/2u+Tmr2evNHDiI= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul/api v1.1.0 h1:BNQPM9ytxj6jbjjdRPioQ94T6YXriSopn0i8COv6SRA= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1 h1:LnuDWGNsoajlhGyHJvuWW6FVqRl8JOTPqS6CPTsYjhY= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0 h1:Rqb66Oo1X/eSV1x66xbDccZjhJigjg0+e82kpwzSwCI= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3 h1:EmmoJme1matNzb+hMpDuR/0sbJSUisxyqBGG676r31M= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2 h1:YZ7UKsJv+hKjqGVUUbtE3HNj79Eln2oQ75tniF6iPt0= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024 h1:rBMNdlhTLzJjJSDIjNEXX1Pz3Hmwmz91v+zycvx9PJc= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= @@ -71,20 +153,41 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN 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/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c h1:Lgl0gzECD8GnQ5QCWA8o6BtfL6mDH5rQgM4/fX3avOs= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8= github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= @@ -99,8 +202,16 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzr github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= @@ -112,19 +223,23 @@ github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9 github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= 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.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.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw= -github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77 h1:ESFSdwYZvkeru3RtdrYueztKhOBCSAAzS4Gf+k0tEow= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= @@ -132,47 +247,142 @@ go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/ go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136 h1:A1gGSx58LAGVHUUsOf7IiR0u8Xb6W51gRwfDBhkdcaw= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/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-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/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-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco= -golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/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-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/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-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384 h1:TFlARGu6Czu1z7q93HTxcP1P+/ZFC/IKythI5RzrnRg= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc h1:NCy3Ohtk6Iny5V/reW2Ktypo4zIpWBdRJ1uFMjBxdg8= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0 h1:Q3Ui3V3/CVinFWFiW39Iw0kMuVrRzYX0wN6OPFp0lTA= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a h1:Ob5/580gVHBJZgXnff1cZDbG+xLtMVE5mDRTe+nIsX4= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.21.0 h1:G+97AoqBnmZIT91cLG/EkCoK9NSelj64P8bOHHNmGn0= -google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 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/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/vendor/github.com/spf13/viper/util.go b/vendor/github.com/spf13/viper/util.go index 952cad44c..b78896963 100644 --- a/vendor/github.com/spf13/viper/util.go +++ b/vendor/github.com/spf13/viper/util.go @@ -114,11 +114,11 @@ func absPathify(inPath string) string { return "" } -// Check if File / Directory Exists +// Check if file Exists func exists(fs afero.Fs, path string) (bool, error) { - _, err := fs.Stat(path) + stat, err := fs.Stat(path) if err == nil { - return true, nil + return !stat.IsDir(), nil } if os.IsNotExist(err) { return false, nil diff --git a/vendor/github.com/spf13/viper/viper.go b/vendor/github.com/spf13/viper/viper.go index a3d37f8c2..f61f4ed75 100644 --- a/vendor/github.com/spf13/viper/viper.go +++ b/vendor/github.com/spf13/viper/viper.go @@ -3,7 +3,7 @@ // Use of this source code is governed by an MIT-style // license that can be found in the LICENSE file. -// Viper is a application configuration system. +// Viper is an application configuration system. // It believes that applications can be configured a variety of ways // via flags, ENVIRONMENT variables, configuration files retrieved // from the file system, or a remote key/value store. @@ -23,6 +23,7 @@ import ( "bytes" "encoding/csv" "encoding/json" + "errors" "fmt" "io" "log" @@ -33,18 +34,19 @@ import ( "sync" "time" - yaml "gopkg.in/yaml.v2" - "github.com/fsnotify/fsnotify" "github.com/hashicorp/hcl" "github.com/hashicorp/hcl/hcl/printer" "github.com/magiconair/properties" "github.com/mitchellh/mapstructure" - toml "github.com/pelletier/go-toml" + "github.com/pelletier/go-toml" "github.com/spf13/afero" "github.com/spf13/cast" jww "github.com/spf13/jwalterweatherman" "github.com/spf13/pflag" + "github.com/subosito/gotenv" + "gopkg.in/ini.v1" + "gopkg.in/yaml.v2" ) // ConfigMarshalError happens when failing to marshal the configuration. @@ -114,6 +116,14 @@ func (fnfe ConfigFileNotFoundError) Error() string { return fmt.Sprintf("Config File %q Not Found in %q", fnfe.name, fnfe.locations) } +// ConfigFileAlreadyExistsError denotes failure to write new configuration file. +type ConfigFileAlreadyExistsError string + +// Error returns the formatted error when configuration already exists. +func (faee ConfigFileAlreadyExistsError) Error() string { + return fmt.Sprintf("Config File %q Already Exists", string(faee)) +} + // A DecoderConfigOption can be passed to viper.Unmarshal to configure // mapstructure.DecoderConfig options type DecoderConfigOption func(*mapstructure.DecoderConfig) @@ -187,7 +197,7 @@ type Viper struct { envPrefix string automaticEnvApplied bool - envKeyReplacer *strings.Replacer + envKeyReplacer StringReplacer allowEmptyEnv bool config map[string]interface{} @@ -225,13 +235,59 @@ func New() *Viper { return v } -// Intended for testing, will reset all to default settings. +// Option configures Viper using the functional options paradigm popularized by Rob Pike and Dave Cheney. +// If you're unfamiliar with this style, +// see https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html and +// https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis. +type Option interface { + apply(v *Viper) +} + +type optionFunc func(v *Viper) + +func (fn optionFunc) apply(v *Viper) { + fn(v) +} + +// KeyDelimiter sets the delimiter used for determining key parts. +// By default it's value is ".". +func KeyDelimiter(d string) Option { + return optionFunc(func(v *Viper) { + v.keyDelim = d + }) +} + +// StringReplacer applies a set of replacements to a string. +type StringReplacer interface { + // Replace returns a copy of s with all replacements performed. + Replace(s string) string +} + +// EnvKeyReplacer sets a replacer used for mapping environment variables to internal keys. +func EnvKeyReplacer(r StringReplacer) Option { + return optionFunc(func(v *Viper) { + v.envKeyReplacer = r + }) +} + +// NewWithOptions creates a new Viper instance. +func NewWithOptions(opts ...Option) *Viper { + v := New() + + for _, opt := range opts { + opt.apply(v) + } + + return v +} + +// Reset is intended for testing, will reset all to default settings. // In the public interface for the viper package so applications // can use it in their testing as well. func Reset() { v = New() - SupportedExts = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl"} - SupportedRemoteProviders = []string{"etcd", "consul"} + SupportedExts = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl", "dotenv", "env", "ini"} + SupportedRemoteProviders = []string{"etcd", "consul", "firestore"} } type defaultRemoteProvider struct { @@ -269,10 +325,10 @@ type RemoteProvider interface { } // SupportedExts are universally supported extensions. -var SupportedExts = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl"} +var SupportedExts = []string{"json", "toml", "yaml", "yml", "properties", "props", "prop", "hcl", "dotenv", "env", "ini"} // SupportedRemoteProviders are universally supported remote providers. -var SupportedRemoteProviders = []string{"etcd", "consul"} +var SupportedRemoteProviders = []string{"etcd", "consul", "firestore"} func OnConfigChange(run func(in fsnotify.Event)) { v.OnConfigChange(run) } func (v *Viper) OnConfigChange(run func(in fsnotify.Event)) { @@ -294,6 +350,7 @@ func (v *Viper) WatchConfig() { filename, err := v.getConfigFile() if err != nil { log.Printf("error: %v\n", err) + initWG.Done() return } @@ -343,7 +400,7 @@ func (v *Viper) WatchConfig() { } }() watcher.Add(configDir) - initWG.Done() // done initalizing the watch in this go routine, so the parent routine can move on... + initWG.Done() // done initializing the watch in this go routine, so the parent routine can move on... eventsWG.Wait() // now, wait for event loop to end in this go-routine... }() initWG.Wait() // make sure that the go routine above fully ended before returning @@ -420,7 +477,7 @@ func (v *Viper) AddConfigPath(in string) { // AddRemoteProvider adds a remote configuration source. // Remote Providers are searched in the order they are added. -// provider is a string value, "etcd" or "consul" are currently supported. +// provider is a string value: "etcd", "consul" or "firestore" are currently supported. // endpoint is the url. etcd requires http://ip:port consul requires ip:port // path is the path in the k/v store to retrieve configuration // To retrieve a config file called myapp.json from /configs/myapp.json @@ -449,14 +506,14 @@ func (v *Viper) AddRemoteProvider(provider, endpoint, path string) error { // AddSecureRemoteProvider adds a remote configuration source. // Secure Remote Providers are searched in the order they are added. -// provider is a string value, "etcd" or "consul" are currently supported. +// provider is a string value: "etcd", "consul" or "firestore" are currently supported. // endpoint is the url. etcd requires http://ip:port consul requires ip:port // secretkeyring is the filepath to your openpgp secret keyring. e.g. /etc/secrets/myring.gpg // path is the path in the k/v store to retrieve configuration // To retrieve a config file called myapp.json from /configs/myapp.json // you should set path to /configs and set config name (SetConfigName()) to // "myapp" -// Secure Remote Providers are implemented with github.com/xordataexchange/crypt +// Secure Remote Providers are implemented with github.com/bketelsen/crypt func AddSecureRemoteProvider(provider, endpoint, path, secretkeyring string) error { return v.AddSecureRemoteProvider(provider, endpoint, path, secretkeyring) } @@ -668,7 +725,7 @@ func GetViper() *Viper { func Get(key string) interface{} { return v.Get(key) } func (v *Viper) Get(key string) interface{} { lcaseKey := strings.ToLower(key) - val := v.find(lcaseKey) + val := v.find(lcaseKey, true) if val == nil { return nil } @@ -705,6 +762,8 @@ func (v *Viper) Get(key string) interface{} { return cast.ToDuration(val) case []string: return cast.ToStringSlice(val) + case []int: + return cast.ToIntSlice(val) } } @@ -794,6 +853,12 @@ func (v *Viper) GetDuration(key string) time.Duration { return cast.ToDuration(v.Get(key)) } +// GetIntSlice returns the value associated with the key as a slice of int values. +func GetIntSlice(key string) []int { return v.GetIntSlice(key) } +func (v *Viper) GetIntSlice(key string) []int { + return cast.ToIntSlice(v.Get(key)) +} + // GetStringSlice returns the value associated with the key as a slice of strings. func GetStringSlice(key string) []string { return v.GetStringSlice(key) } func (v *Viper) GetStringSlice(key string) []string { @@ -884,8 +949,11 @@ func decode(input interface{}, config *mapstructure.DecoderConfig) error { // UnmarshalExact unmarshals the config into a Struct, erroring if a field is nonexistent // in the destination struct. -func (v *Viper) UnmarshalExact(rawVal interface{}) error { - config := defaultDecoderConfig(rawVal) +func UnmarshalExact(rawVal interface{}, opts ...DecoderConfigOption) error { + return v.UnmarshalExact(rawVal, opts...) +} +func (v *Viper) UnmarshalExact(rawVal interface{}, opts ...DecoderConfigOption) error { + config := defaultDecoderConfig(rawVal, opts...) config.ErrorUnused = true err := decode(v.AllSettings(), config) @@ -928,11 +996,6 @@ func (v *Viper) BindFlagValues(flags FlagValueSet) (err error) { } // BindFlagValue binds a specific key to a FlagValue. -// Example (where serverCmd is a Cobra instance): -// -// serverCmd.Flags().Int("port", 1138, "Port to run Application server on") -// Viper.BindFlagValue("port", serverCmd.Flags().Lookup("port")) -// func BindFlagValue(key string, flag FlagValue) error { return v.BindFlagValue(key, flag) } func (v *Viper) BindFlagValue(key string, flag FlagValue) error { if flag == nil { @@ -950,7 +1013,7 @@ func BindEnv(input ...string) error { return v.BindEnv(input...) } func (v *Viper) BindEnv(input ...string) error { var key, envkey string if len(input) == 0 { - return fmt.Errorf("BindEnv missing key to bind to") + return fmt.Errorf("missing key to bind to") } key = strings.ToLower(input[0]) @@ -967,12 +1030,15 @@ func (v *Viper) BindEnv(input ...string) error { } // Given a key, find the value. -// Viper will check in the following order: -// flag, env, config file, key/value store, default. +// // Viper will check to see if an alias exists first. +// Viper will then check in the following order: +// flag, env, config file, key/value store. +// Lastly, if no value was found and flagDefault is true, and if the key +// corresponds to a flag, the flag's default value is returned. +// // Note: this assumes a lower-cased key given. -func (v *Viper) find(lcaseKey string) interface{} { - +func (v *Viper) find(lcaseKey string, flagDefault bool) interface{} { var ( val interface{} exists bool @@ -1012,6 +1078,13 @@ func (v *Viper) find(lcaseKey string) interface{} { s = strings.TrimSuffix(s, "]") res, _ := readAsCSV(s) return res + case "intSlice": + s := strings.TrimPrefix(flag.ValueString(), "[") + s = strings.TrimSuffix(s, "]") + res, _ := readAsCSV(s) + return cast.ToIntSlice(res) + case "stringToString": + return stringToStringConv(flag.ValueString()) default: return flag.ValueString() } @@ -1068,24 +1141,33 @@ func (v *Viper) find(lcaseKey string) interface{} { return nil } - // last chance: if no other value is returned and a flag does exist for the value, - // get the flag's value even if the flag's value has not changed - if flag, exists := v.pflags[lcaseKey]; exists { - switch flag.ValueType() { - case "int", "int8", "int16", "int32", "int64": - return cast.ToInt(flag.ValueString()) - case "bool": - return cast.ToBool(flag.ValueString()) - case "stringSlice": - s := strings.TrimPrefix(flag.ValueString(), "[") - s = strings.TrimSuffix(s, "]") - res, _ := readAsCSV(s) - return res - default: - return flag.ValueString() + if flagDefault { + // last chance: if no value is found and a flag does exist for the key, + // get the flag's default value even if the flag's value has not been set. + if flag, exists := v.pflags[lcaseKey]; exists { + switch flag.ValueType() { + case "int", "int8", "int16", "int32", "int64": + return cast.ToInt(flag.ValueString()) + case "bool": + return cast.ToBool(flag.ValueString()) + case "stringSlice": + s := strings.TrimPrefix(flag.ValueString(), "[") + s = strings.TrimSuffix(s, "]") + res, _ := readAsCSV(s) + return res + case "intSlice": + s := strings.TrimPrefix(flag.ValueString(), "[") + s = strings.TrimSuffix(s, "]") + res, _ := readAsCSV(s) + return cast.ToIntSlice(res) + case "stringToString": + return stringToStringConv(flag.ValueString()) + default: + return flag.ValueString() + } } + // last item, no need to check shadowing } - // last item, no need to check shadowing return nil } @@ -1099,12 +1181,36 @@ func readAsCSV(val string) ([]string, error) { return csvReader.Read() } +// mostly copied from pflag's implementation of this operation here https://github.com/spf13/pflag/blob/master/string_to_string.go#L79 +// alterations are: errors are swallowed, map[string]interface{} is returned in order to enable cast.ToStringMap +func stringToStringConv(val string) interface{} { + val = strings.Trim(val, "[]") + // An empty string would cause an empty map + if len(val) == 0 { + return map[string]interface{}{} + } + r := csv.NewReader(strings.NewReader(val)) + ss, err := r.Read() + if err != nil { + return nil + } + out := make(map[string]interface{}, len(ss)) + for _, pair := range ss { + kv := strings.SplitN(pair, "=", 2) + if len(kv) != 2 { + return nil + } + out[kv[0]] = kv[1] + } + return out +} + // IsSet checks to see if the key has been set in any of the data locations. // IsSet is case-insensitive for a key. func IsSet(key string) bool { return v.IsSet(key) } func (v *Viper) IsSet(key string) bool { lcaseKey := strings.ToLower(key) - val := v.find(lcaseKey) + val := v.find(lcaseKey, false) return val != nil } @@ -1123,8 +1229,8 @@ func (v *Viper) SetEnvKeyReplacer(r *strings.Replacer) { v.envKeyReplacer = r } -// Aliases provide another accessor for the same key. -// This enables one to change a name without breaking the application +// RegisterAlias creates an alias that provides another accessor for the same key. +// This enables one to change a name without breaking the application. func RegisterAlias(alias string, key string) { v.RegisterAlias(alias, key) } func (v *Viper) RegisterAlias(alias string, key string) { v.registerAlias(alias, strings.ToLower(key)) @@ -1311,11 +1417,10 @@ func (v *Viper) WriteConfig() error { // SafeWriteConfig writes current configuration to file only if the file does not exist. func SafeWriteConfig() error { return v.SafeWriteConfig() } func (v *Viper) SafeWriteConfig() error { - filename, err := v.getConfigFile() - if err != nil { - return err + if len(v.configPaths) < 1 { + return errors.New("missing configuration for 'configPath'") } - return v.writeConfig(filename, false) + return v.SafeWriteConfigAs(filepath.Join(v.configPaths[0], v.configName+"."+v.configType)) } // WriteConfigAs writes current configuration to a given filename. @@ -1327,38 +1432,48 @@ func (v *Viper) WriteConfigAs(filename string) error { // SafeWriteConfigAs writes current configuration to a given filename if it does not exist. func SafeWriteConfigAs(filename string) error { return v.SafeWriteConfigAs(filename) } func (v *Viper) SafeWriteConfigAs(filename string) error { + alreadyExists, err := afero.Exists(v.fs, filename) + if alreadyExists && err == nil { + return ConfigFileAlreadyExistsError(filename) + } return v.writeConfig(filename, false) } -func writeConfig(filename string, force bool) error { return v.writeConfig(filename, force) } func (v *Viper) writeConfig(filename string, force bool) error { jww.INFO.Println("Attempting to write configuration to file.") + var configType string + ext := filepath.Ext(filename) - if len(ext) <= 1 { - return fmt.Errorf("Filename: %s requires valid extension.", filename) + if ext != "" { + configType = ext[1:] + } else { + configType = v.configType } - configType := ext[1:] + if configType == "" { + return fmt.Errorf("config type could not be determined for %s", filename) + } + if !stringInSlice(configType, SupportedExts) { return UnsupportedConfigError(configType) } if v.config == nil { v.config = make(map[string]interface{}) } - var flags int - if force == true { - flags = os.O_CREATE | os.O_TRUNC | os.O_WRONLY - } else { - if _, err := os.Stat(filename); os.IsNotExist(err) { - flags = os.O_WRONLY - } else { - return fmt.Errorf("File: %s exists. Use WriteConfig to overwrite.", filename) - } + flags := os.O_CREATE | os.O_TRUNC | os.O_WRONLY + if !force { + flags |= os.O_EXCL } f, err := v.fs.OpenFile(filename, flags, v.configPermissions) if err != nil { return err } - return v.marshalWriter(f, configType) + defer f.Close() + + if err := v.marshalWriter(f, configType); err != nil { + return err + } + + return f.Sync() } // Unmarshal a Reader into a map. @@ -1382,7 +1497,7 @@ func (v *Viper) unmarshalReader(in io.Reader, c map[string]interface{}) error { } case "hcl": - obj, err := hcl.Parse(string(buf.Bytes())) + obj, err := hcl.Parse(buf.String()) if err != nil { return ConfigParseError{err} } @@ -1400,6 +1515,15 @@ func (v *Viper) unmarshalReader(in io.Reader, c map[string]interface{}) error { c[k] = v } + case "dotenv", "env": + env, err := gotenv.StrictParse(buf) + if err != nil { + return ConfigParseError{err} + } + for k, v := range env { + c[k] = v + } + case "properties", "props", "prop": v.properties = properties.NewProperties() var err error @@ -1415,6 +1539,23 @@ func (v *Viper) unmarshalReader(in io.Reader, c map[string]interface{}) error { // set innermost value deepestMap[lastKey] = value } + + case "ini": + cfg := ini.Empty() + err := cfg.Append(buf.Bytes()) + if err != nil { + return ConfigParseError{err} + } + sections := cfg.Sections() + for i := 0; i < len(sections); i++ { + section := sections[i] + keys := section.Keys() + for j := 0; j < len(keys); j++ { + key := keys[j] + value := cfg.Section(section.Name()).Key(key.Name()).String() + c[section.Name()+"."+key.Name()] = value + } + } } insensitiviseMap(c) @@ -1422,9 +1563,6 @@ func (v *Viper) unmarshalReader(in io.Reader, c map[string]interface{}) error { } // Marshal a map into Writer. -func marshalWriter(f afero.File, configType string) error { - return v.marshalWriter(f, configType) -} func (v *Viper) marshalWriter(f afero.File, configType string) error { c := v.AllSettings() switch configType { @@ -1440,6 +1578,9 @@ func (v *Viper) marshalWriter(f afero.File, configType string) error { case "hcl": b, err := json.Marshal(c) + if err != nil { + return ConfigMarshalError{err} + } ast, err := hcl.Parse(string(b)) if err != nil { return ConfigMarshalError{err} @@ -1465,6 +1606,18 @@ func (v *Viper) marshalWriter(f afero.File, configType string) error { return ConfigMarshalError{err} } + case "dotenv", "env": + lines := []string{} + for _, key := range v.AllKeys() { + envName := strings.ToUpper(strings.Replace(key, ".", "_", -1)) + val := v.Get(key) + lines = append(lines, fmt.Sprintf("%v=%v", envName, val)) + } + s := strings.Join(lines, "\n") + if _, err := f.WriteString(s); err != nil { + return ConfigMarshalError{err} + } + case "toml": t, err := toml.TreeFromMap(c) if err != nil { @@ -1483,6 +1636,22 @@ func (v *Viper) marshalWriter(f afero.File, configType string) error { if _, err = f.WriteString(string(b)); err != nil { return ConfigMarshalError{err} } + + case "ini": + keys := v.AllKeys() + cfg := ini.Empty() + ini.PrettyFormat = false + for i := 0; i < len(keys); i++ { + key := keys[i] + lastSep := strings.LastIndex(key, ".") + sectionName := key[:(lastSep)] + keyName := key[(lastSep + 1):] + if sectionName == "default" { + sectionName = "" + } + cfg.Section(sectionName).Key(keyName).SetValue(v.Get(key).(string)) + } + cfg.WriteTo(f) } return nil } @@ -1629,7 +1798,7 @@ func (v *Viper) getRemoteConfig(provider RemoteProvider) (map[string]interface{} func (v *Viper) watchKeyValueConfigOnChannel() error { for _, rp := range v.remoteProviders { respc, _ := RemoteConfig.WatchChannel(rp) - //Todo: Add quit channel + // Todo: Add quit channel go func(rc <-chan *RemoteResponse) { for { b := <-rc @@ -1665,7 +1834,7 @@ func (v *Viper) watchRemoteConfig(provider RemoteProvider) (map[string]interface } // AllKeys returns all keys holding a value, regardless of where they are set. -// Nested keys are returned with a v.keyDelim (= ".") separator +// Nested keys are returned with a v.keyDelim separator func AllKeys() []string { return v.AllKeys() } func (v *Viper) AllKeys() []string { m := map[string]bool{} @@ -1679,7 +1848,7 @@ func (v *Viper) AllKeys() []string { m = v.flattenAndMergeMap(m, v.defaults, "") // convert set of paths to list - a := []string{} + a := make([]string, 0, len(m)) for x := range m { a = append(a, x) } @@ -1688,7 +1857,7 @@ func (v *Viper) AllKeys() []string { // flattenAndMergeMap recursively flattens the given map into a map[string]bool // of key paths (used as a set, easier to manipulate than a []string): -// - each path is merged into a single key string, delimited with v.keyDelim (= ".") +// - each path is merged into a single key string, delimited with v.keyDelim // - if a path is shadowed by an earlier value in the initial shadow map, // it is skipped. // The resulting set of paths is merged to the given shadow set at the same time. @@ -1728,7 +1897,7 @@ func (v *Viper) flattenAndMergeMap(shadow map[string]bool, m map[string]interfac func (v *Viper) mergeFlatMap(shadow map[string]bool, m map[string]interface{}) map[string]bool { // scan keys outer: - for k, _ := range m { + for k := range m { path := strings.Split(k, v.keyDelim) // scan intermediate paths var parentKey string @@ -1837,6 +2006,12 @@ func (v *Viper) searchInPath(in string) (filename string) { } } + if v.configType != "" { + if b, _ := exists(v.fs, filepath.Join(in, v.configName)); b { + return filepath.Join(in, v.configName) + } + } + return "" } diff --git a/vendor/github.com/stretchr/objx/.codeclimate.yml b/vendor/github.com/stretchr/objx/.codeclimate.yml new file mode 100644 index 000000000..010d4ccd5 --- /dev/null +++ b/vendor/github.com/stretchr/objx/.codeclimate.yml @@ -0,0 +1,13 @@ +engines: + gofmt: + enabled: true + golint: + enabled: true + govet: + enabled: true + +exclude_patterns: +- ".github/" +- "vendor/" +- "codegen/" +- "doc.go" diff --git a/vendor/github.com/stretchr/objx/.gitignore b/vendor/github.com/stretchr/objx/.gitignore new file mode 100644 index 000000000..ea58090bd --- /dev/null +++ b/vendor/github.com/stretchr/objx/.gitignore @@ -0,0 +1,11 @@ +# Binaries for programs and plugins +*.exe +*.dll +*.so +*.dylib + +# Test binary, build with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out diff --git a/vendor/github.com/stretchr/objx/.travis.yml b/vendor/github.com/stretchr/objx/.travis.yml new file mode 100644 index 000000000..a63efa59d --- /dev/null +++ b/vendor/github.com/stretchr/objx/.travis.yml @@ -0,0 +1,25 @@ +language: go +go: + - 1.8 + - 1.9 + - tip + +env: + global: + - CC_TEST_REPORTER_ID=68feaa3410049ce73e145287acbcdacc525087a30627f96f04e579e75bd71c00 + +before_script: + - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter + - chmod +x ./cc-test-reporter + - ./cc-test-reporter before-build + +install: +- go get github.com/go-task/task/cmd/task + +script: +- task dl-deps +- task lint +- task test-coverage + +after_script: + - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT diff --git a/vendor/github.com/stretchr/objx/Gopkg.lock b/vendor/github.com/stretchr/objx/Gopkg.lock new file mode 100644 index 000000000..eebe342a9 --- /dev/null +++ b/vendor/github.com/stretchr/objx/Gopkg.lock @@ -0,0 +1,30 @@ +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. + + +[[projects]] + name = "github.com/davecgh/go-spew" + packages = ["spew"] + revision = "346938d642f2ec3594ed81d874461961cd0faa76" + version = "v1.1.0" + +[[projects]] + name = "github.com/pmezard/go-difflib" + packages = ["difflib"] + revision = "792786c7400a136282c1664665ae0a8db921c6c2" + version = "v1.0.0" + +[[projects]] + name = "github.com/stretchr/testify" + packages = [ + "assert", + "require" + ] + revision = "b91bfb9ebec76498946beb6af7c0230c7cc7ba6c" + version = "v1.2.0" + +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "2d160a7dea4ffd13c6c31dab40373822f9d78c73beba016d662bef8f7a998876" + solver-name = "gps-cdcl" + solver-version = 1 diff --git a/vendor/github.com/stretchr/objx/Gopkg.toml b/vendor/github.com/stretchr/objx/Gopkg.toml new file mode 100644 index 000000000..d70f1570b --- /dev/null +++ b/vendor/github.com/stretchr/objx/Gopkg.toml @@ -0,0 +1,8 @@ +[prune] + unused-packages = true + non-go = true + go-tests = true + +[[constraint]] + name = "github.com/stretchr/testify" + version = "~1.2.0" diff --git a/vendor/github.com/stretchr/objx/LICENSE b/vendor/github.com/stretchr/objx/LICENSE new file mode 100644 index 000000000..44d4d9d5a --- /dev/null +++ b/vendor/github.com/stretchr/objx/LICENSE @@ -0,0 +1,22 @@ +The MIT License + +Copyright (c) 2014 Stretchr, Inc. +Copyright (c) 2017-2018 objx contributors + +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. diff --git a/vendor/github.com/stretchr/objx/README.md b/vendor/github.com/stretchr/objx/README.md new file mode 100644 index 000000000..be5750c94 --- /dev/null +++ b/vendor/github.com/stretchr/objx/README.md @@ -0,0 +1,80 @@ +# Objx +[![Build Status](https://travis-ci.org/stretchr/objx.svg?branch=master)](https://travis-ci.org/stretchr/objx) +[![Go Report Card](https://goreportcard.com/badge/github.com/stretchr/objx)](https://goreportcard.com/report/github.com/stretchr/objx) +[![Maintainability](https://api.codeclimate.com/v1/badges/1d64bc6c8474c2074f2b/maintainability)](https://codeclimate.com/github/stretchr/objx/maintainability) +[![Test Coverage](https://api.codeclimate.com/v1/badges/1d64bc6c8474c2074f2b/test_coverage)](https://codeclimate.com/github/stretchr/objx/test_coverage) +[![Sourcegraph](https://sourcegraph.com/github.com/stretchr/objx/-/badge.svg)](https://sourcegraph.com/github.com/stretchr/objx) +[![GoDoc](https://godoc.org/github.com/stretchr/objx?status.svg)](https://godoc.org/github.com/stretchr/objx) + +Objx - Go package for dealing with maps, slices, JSON and other data. + +Get started: + +- Install Objx with [one line of code](#installation), or [update it with another](#staying-up-to-date) +- Check out the API Documentation http://godoc.org/github.com/stretchr/objx + +## Overview +Objx provides the `objx.Map` type, which is a `map[string]interface{}` that exposes a powerful `Get` method (among others) that allows you to easily and quickly get access to data within the map, without having to worry too much about type assertions, missing data, default values etc. + +### Pattern +Objx uses a preditable pattern to make access data from within `map[string]interface{}` easy. Call one of the `objx.` functions to create your `objx.Map` to get going: + + m, err := objx.FromJSON(json) + +NOTE: Any methods or functions with the `Must` prefix will panic if something goes wrong, the rest will be optimistic and try to figure things out without panicking. + +Use `Get` to access the value you're interested in. You can use dot and array +notation too: + + m.Get("places[0].latlng") + +Once you have sought the `Value` you're interested in, you can use the `Is*` methods to determine its type. + + if m.Get("code").IsStr() { // Your code... } + +Or you can just assume the type, and use one of the strong type methods to extract the real value: + + m.Get("code").Int() + +If there's no value there (or if it's the wrong type) then a default value will be returned, or you can be explicit about the default value. + + Get("code").Int(-1) + +If you're dealing with a slice of data as a value, Objx provides many useful methods for iterating, manipulating and selecting that data. You can find out more by exploring the index below. + +### Reading data +A simple example of how to use Objx: + + // Use MustFromJSON to make an objx.Map from some JSON + m := objx.MustFromJSON(`{"name": "Mat", "age": 30}`) + + // Get the details + name := m.Get("name").Str() + age := m.Get("age").Int() + + // Get their nickname (or use their name if they don't have one) + nickname := m.Get("nickname").Str(name) + +### Ranging +Since `objx.Map` is a `map[string]interface{}` you can treat it as such. For example, to `range` the data, do what you would expect: + + m := objx.MustFromJSON(json) + for key, value := range m { + // Your code... + } + +## Installation +To install Objx, use go get: + + go get github.com/stretchr/objx + +### Staying up to date +To update Objx to the latest version, run: + + go get -u github.com/stretchr/objx + +### Supported go versions +We support the lastest two major Go versions, which are 1.8 and 1.9 at the moment. + +## Contributing +Please feel free to submit issues, fork the repository and send pull requests! diff --git a/vendor/github.com/stretchr/objx/Taskfile.yml b/vendor/github.com/stretchr/objx/Taskfile.yml new file mode 100644 index 000000000..f8035641f --- /dev/null +++ b/vendor/github.com/stretchr/objx/Taskfile.yml @@ -0,0 +1,32 @@ +default: + deps: [test] + +dl-deps: + desc: Downloads cli dependencies + cmds: + - go get -u github.com/golang/lint/golint + - go get -u github.com/golang/dep/cmd/dep + +update-deps: + desc: Updates dependencies + cmds: + - dep ensure + - dep ensure -update + +lint: + desc: Runs golint + cmds: + - go fmt $(go list ./... | grep -v /vendor/) + - go vet $(go list ./... | grep -v /vendor/) + - golint $(ls *.go | grep -v "doc.go") + silent: true + +test: + desc: Runs go tests + cmds: + - go test -race . + +test-coverage: + desc: Runs go tests and calucates test coverage + cmds: + - go test -coverprofile=c.out . diff --git a/vendor/github.com/stretchr/objx/accessors.go b/vendor/github.com/stretchr/objx/accessors.go new file mode 100644 index 000000000..204356a22 --- /dev/null +++ b/vendor/github.com/stretchr/objx/accessors.go @@ -0,0 +1,148 @@ +package objx + +import ( + "regexp" + "strconv" + "strings" +) + +// arrayAccesRegexString is the regex used to extract the array number +// from the access path +const arrayAccesRegexString = `^(.+)\[([0-9]+)\]$` + +// arrayAccesRegex is the compiled arrayAccesRegexString +var arrayAccesRegex = regexp.MustCompile(arrayAccesRegexString) + +// Get gets the value using the specified selector and +// returns it inside a new Obj object. +// +// If it cannot find the value, Get will return a nil +// value inside an instance of Obj. +// +// Get can only operate directly on map[string]interface{} and []interface. +// +// Example +// +// To access the title of the third chapter of the second book, do: +// +// o.Get("books[1].chapters[2].title") +func (m Map) Get(selector string) *Value { + rawObj := access(m, selector, nil, false) + return &Value{data: rawObj} +} + +// Set sets the value using the specified selector and +// returns the object on which Set was called. +// +// Set can only operate directly on map[string]interface{} and []interface +// +// Example +// +// To set the title of the third chapter of the second book, do: +// +// o.Set("books[1].chapters[2].title","Time to Go") +func (m Map) Set(selector string, value interface{}) Map { + access(m, selector, value, true) + return m +} + +// access accesses the object using the selector and performs the +// appropriate action. +func access(current, selector, value interface{}, isSet bool) interface{} { + switch selector.(type) { + case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: + if array, ok := current.([]interface{}); ok { + index := intFromInterface(selector) + if index >= len(array) { + return nil + } + return array[index] + } + return nil + + case string: + selStr := selector.(string) + selSegs := strings.SplitN(selStr, PathSeparator, 2) + thisSel := selSegs[0] + index := -1 + var err error + + if strings.Contains(thisSel, "[") { + arrayMatches := arrayAccesRegex.FindStringSubmatch(thisSel) + if len(arrayMatches) > 0 { + // Get the key into the map + thisSel = arrayMatches[1] + + // Get the index into the array at the key + index, err = strconv.Atoi(arrayMatches[2]) + + if err != nil { + // This should never happen. If it does, something has gone + // seriously wrong. Panic. + panic("objx: Array index is not an integer. Must use array[int].") + } + } + } + if curMap, ok := current.(Map); ok { + current = map[string]interface{}(curMap) + } + // get the object in question + switch current.(type) { + case map[string]interface{}: + curMSI := current.(map[string]interface{}) + if len(selSegs) <= 1 && isSet { + curMSI[thisSel] = value + return nil + } + current = curMSI[thisSel] + default: + current = nil + } + // do we need to access the item of an array? + if index > -1 { + if array, ok := current.([]interface{}); ok { + if index < len(array) { + current = array[index] + } else { + current = nil + } + } + } + if len(selSegs) > 1 { + current = access(current, selSegs[1], value, isSet) + } + } + return current +} + +// intFromInterface converts an interface object to the largest +// representation of an unsigned integer using a type switch and +// assertions +func intFromInterface(selector interface{}) int { + var value int + switch selector.(type) { + case int: + value = selector.(int) + case int8: + value = int(selector.(int8)) + case int16: + value = int(selector.(int16)) + case int32: + value = int(selector.(int32)) + case int64: + value = int(selector.(int64)) + case uint: + value = int(selector.(uint)) + case uint8: + value = int(selector.(uint8)) + case uint16: + value = int(selector.(uint16)) + case uint32: + value = int(selector.(uint32)) + case uint64: + value = int(selector.(uint64)) + default: + return 0 + } + return value +} diff --git a/vendor/github.com/stretchr/objx/constants.go b/vendor/github.com/stretchr/objx/constants.go new file mode 100644 index 000000000..f9eb42a25 --- /dev/null +++ b/vendor/github.com/stretchr/objx/constants.go @@ -0,0 +1,13 @@ +package objx + +const ( + // PathSeparator is the character used to separate the elements + // of the keypath. + // + // For example, `location.address.city` + PathSeparator string = "." + + // SignatureSeparator is the character that is used to + // separate the Base64 string from the security signature. + SignatureSeparator = "_" +) diff --git a/vendor/github.com/stretchr/objx/conversions.go b/vendor/github.com/stretchr/objx/conversions.go new file mode 100644 index 000000000..5e020f310 --- /dev/null +++ b/vendor/github.com/stretchr/objx/conversions.go @@ -0,0 +1,108 @@ +package objx + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "net/url" +) + +// JSON converts the contained object to a JSON string +// representation +func (m Map) JSON() (string, error) { + result, err := json.Marshal(m) + if err != nil { + err = errors.New("objx: JSON encode failed with: " + err.Error()) + } + return string(result), err +} + +// MustJSON converts the contained object to a JSON string +// representation and panics if there is an error +func (m Map) MustJSON() string { + result, err := m.JSON() + if err != nil { + panic(err.Error()) + } + return result +} + +// Base64 converts the contained object to a Base64 string +// representation of the JSON string representation +func (m Map) Base64() (string, error) { + var buf bytes.Buffer + + jsonData, err := m.JSON() + if err != nil { + return "", err + } + + encoder := base64.NewEncoder(base64.StdEncoding, &buf) + _, err = encoder.Write([]byte(jsonData)) + if err != nil { + return "", err + } + _ = encoder.Close() + + return buf.String(), nil +} + +// MustBase64 converts the contained object to a Base64 string +// representation of the JSON string representation and panics +// if there is an error +func (m Map) MustBase64() string { + result, err := m.Base64() + if err != nil { + panic(err.Error()) + } + return result +} + +// SignedBase64 converts the contained object to a Base64 string +// representation of the JSON string representation and signs it +// using the provided key. +func (m Map) SignedBase64(key string) (string, error) { + base64, err := m.Base64() + if err != nil { + return "", err + } + + sig := HashWithKey(base64, key) + return base64 + SignatureSeparator + sig, nil +} + +// MustSignedBase64 converts the contained object to a Base64 string +// representation of the JSON string representation and signs it +// using the provided key and panics if there is an error +func (m Map) MustSignedBase64(key string) string { + result, err := m.SignedBase64(key) + if err != nil { + panic(err.Error()) + } + return result +} + +/* + URL Query + ------------------------------------------------ +*/ + +// URLValues creates a url.Values object from an Obj. This +// function requires that the wrapped object be a map[string]interface{} +func (m Map) URLValues() url.Values { + vals := make(url.Values) + for k, v := range m { + //TODO: can this be done without sprintf? + vals.Set(k, fmt.Sprintf("%v", v)) + } + return vals +} + +// URLQuery gets an encoded URL query representing the given +// Obj. This function requires that the wrapped object be a +// map[string]interface{} +func (m Map) URLQuery() (string, error) { + return m.URLValues().Encode(), nil +} diff --git a/vendor/github.com/stretchr/objx/doc.go b/vendor/github.com/stretchr/objx/doc.go new file mode 100644 index 000000000..6d6af1a83 --- /dev/null +++ b/vendor/github.com/stretchr/objx/doc.go @@ -0,0 +1,66 @@ +/* +Objx - Go package for dealing with maps, slices, JSON and other data. + +Overview + +Objx provides the `objx.Map` type, which is a `map[string]interface{}` that exposes +a powerful `Get` method (among others) that allows you to easily and quickly get +access to data within the map, without having to worry too much about type assertions, +missing data, default values etc. + +Pattern + +Objx uses a preditable pattern to make access data from within `map[string]interface{}` easy. +Call one of the `objx.` functions to create your `objx.Map` to get going: + + m, err := objx.FromJSON(json) + +NOTE: Any methods or functions with the `Must` prefix will panic if something goes wrong, +the rest will be optimistic and try to figure things out without panicking. + +Use `Get` to access the value you're interested in. You can use dot and array +notation too: + + m.Get("places[0].latlng") + +Once you have sought the `Value` you're interested in, you can use the `Is*` methods to determine its type. + + if m.Get("code").IsStr() { // Your code... } + +Or you can just assume the type, and use one of the strong type methods to extract the real value: + + m.Get("code").Int() + +If there's no value there (or if it's the wrong type) then a default value will be returned, +or you can be explicit about the default value. + + Get("code").Int(-1) + +If you're dealing with a slice of data as a value, Objx provides many useful methods for iterating, +manipulating and selecting that data. You can find out more by exploring the index below. + +Reading data + +A simple example of how to use Objx: + + // Use MustFromJSON to make an objx.Map from some JSON + m := objx.MustFromJSON(`{"name": "Mat", "age": 30}`) + + // Get the details + name := m.Get("name").Str() + age := m.Get("age").Int() + + // Get their nickname (or use their name if they don't have one) + nickname := m.Get("nickname").Str(name) + +Ranging + +Since `objx.Map` is a `map[string]interface{}` you can treat it as such. +For example, to `range` the data, do what you would expect: + + m := objx.MustFromJSON(json) + for key, value := range m { + // Your code... + } +*/ +package objx diff --git a/vendor/github.com/stretchr/objx/map.go b/vendor/github.com/stretchr/objx/map.go new file mode 100644 index 000000000..406bc8926 --- /dev/null +++ b/vendor/github.com/stretchr/objx/map.go @@ -0,0 +1,190 @@ +package objx + +import ( + "encoding/base64" + "encoding/json" + "errors" + "io/ioutil" + "net/url" + "strings" +) + +// MSIConvertable is an interface that defines methods for converting your +// custom types to a map[string]interface{} representation. +type MSIConvertable interface { + // MSI gets a map[string]interface{} (msi) representing the + // object. + MSI() map[string]interface{} +} + +// Map provides extended functionality for working with +// untyped data, in particular map[string]interface (msi). +type Map map[string]interface{} + +// Value returns the internal value instance +func (m Map) Value() *Value { + return &Value{data: m} +} + +// Nil represents a nil Map. +var Nil = New(nil) + +// New creates a new Map containing the map[string]interface{} in the data argument. +// If the data argument is not a map[string]interface, New attempts to call the +// MSI() method on the MSIConvertable interface to create one. +func New(data interface{}) Map { + if _, ok := data.(map[string]interface{}); !ok { + if converter, ok := data.(MSIConvertable); ok { + data = converter.MSI() + } else { + return nil + } + } + return Map(data.(map[string]interface{})) +} + +// MSI creates a map[string]interface{} and puts it inside a new Map. +// +// The arguments follow a key, value pattern. +// +// +// Returns nil if any key argument is non-string or if there are an odd number of arguments. +// +// Example +// +// To easily create Maps: +// +// m := objx.MSI("name", "Mat", "age", 29, "subobj", objx.MSI("active", true)) +// +// // creates an Map equivalent to +// m := objx.Map{"name": "Mat", "age": 29, "subobj": objx.Map{"active": true}} +func MSI(keyAndValuePairs ...interface{}) Map { + newMap := Map{} + keyAndValuePairsLen := len(keyAndValuePairs) + if keyAndValuePairsLen%2 != 0 { + return nil + } + for i := 0; i < keyAndValuePairsLen; i = i + 2 { + key := keyAndValuePairs[i] + value := keyAndValuePairs[i+1] + + // make sure the key is a string + keyString, keyStringOK := key.(string) + if !keyStringOK { + return nil + } + newMap[keyString] = value + } + return newMap +} + +// ****** Conversion Constructors + +// MustFromJSON creates a new Map containing the data specified in the +// jsonString. +// +// Panics if the JSON is invalid. +func MustFromJSON(jsonString string) Map { + o, err := FromJSON(jsonString) + if err != nil { + panic("objx: MustFromJSON failed with error: " + err.Error()) + } + return o +} + +// FromJSON creates a new Map containing the data specified in the +// jsonString. +// +// Returns an error if the JSON is invalid. +func FromJSON(jsonString string) (Map, error) { + var data interface{} + err := json.Unmarshal([]byte(jsonString), &data) + if err != nil { + return Nil, err + } + return New(data), nil +} + +// FromBase64 creates a new Obj containing the data specified +// in the Base64 string. +// +// The string is an encoded JSON string returned by Base64 +func FromBase64(base64String string) (Map, error) { + decoder := base64.NewDecoder(base64.StdEncoding, strings.NewReader(base64String)) + decoded, err := ioutil.ReadAll(decoder) + if err != nil { + return nil, err + } + return FromJSON(string(decoded)) +} + +// MustFromBase64 creates a new Obj containing the data specified +// in the Base64 string and panics if there is an error. +// +// The string is an encoded JSON string returned by Base64 +func MustFromBase64(base64String string) Map { + result, err := FromBase64(base64String) + if err != nil { + panic("objx: MustFromBase64 failed with error: " + err.Error()) + } + return result +} + +// FromSignedBase64 creates a new Obj containing the data specified +// in the Base64 string. +// +// The string is an encoded JSON string returned by SignedBase64 +func FromSignedBase64(base64String, key string) (Map, error) { + parts := strings.Split(base64String, SignatureSeparator) + if len(parts) != 2 { + return nil, errors.New("objx: Signed base64 string is malformed") + } + + sig := HashWithKey(parts[0], key) + if parts[1] != sig { + return nil, errors.New("objx: Signature for base64 data does not match") + } + return FromBase64(parts[0]) +} + +// MustFromSignedBase64 creates a new Obj containing the data specified +// in the Base64 string and panics if there is an error. +// +// The string is an encoded JSON string returned by Base64 +func MustFromSignedBase64(base64String, key string) Map { + result, err := FromSignedBase64(base64String, key) + if err != nil { + panic("objx: MustFromSignedBase64 failed with error: " + err.Error()) + } + return result +} + +// FromURLQuery generates a new Obj by parsing the specified +// query. +// +// For queries with multiple values, the first value is selected. +func FromURLQuery(query string) (Map, error) { + vals, err := url.ParseQuery(query) + if err != nil { + return nil, err + } + m := Map{} + for k, vals := range vals { + m[k] = vals[0] + } + return m, nil +} + +// MustFromURLQuery generates a new Obj by parsing the specified +// query. +// +// For queries with multiple values, the first value is selected. +// +// Panics if it encounters an error +func MustFromURLQuery(query string) Map { + o, err := FromURLQuery(query) + if err != nil { + panic("objx: MustFromURLQuery failed with error: " + err.Error()) + } + return o +} diff --git a/vendor/github.com/stretchr/objx/mutations.go b/vendor/github.com/stretchr/objx/mutations.go new file mode 100644 index 000000000..c3400a3f7 --- /dev/null +++ b/vendor/github.com/stretchr/objx/mutations.go @@ -0,0 +1,77 @@ +package objx + +// Exclude returns a new Map with the keys in the specified []string +// excluded. +func (m Map) Exclude(exclude []string) Map { + excluded := make(Map) + for k, v := range m { + if !contains(exclude, k) { + excluded[k] = v + } + } + return excluded +} + +// Copy creates a shallow copy of the Obj. +func (m Map) Copy() Map { + copied := Map{} + for k, v := range m { + copied[k] = v + } + return copied +} + +// Merge blends the specified map with a copy of this map and returns the result. +// +// Keys that appear in both will be selected from the specified map. +// This method requires that the wrapped object be a map[string]interface{} +func (m Map) Merge(merge Map) Map { + return m.Copy().MergeHere(merge) +} + +// MergeHere blends the specified map with this map and returns the current map. +// +// Keys that appear in both will be selected from the specified map. The original map +// will be modified. This method requires that +// the wrapped object be a map[string]interface{} +func (m Map) MergeHere(merge Map) Map { + for k, v := range merge { + m[k] = v + } + return m +} + +// Transform builds a new Obj giving the transformer a chance +// to change the keys and values as it goes. This method requires that +// the wrapped object be a map[string]interface{} +func (m Map) Transform(transformer func(key string, value interface{}) (string, interface{})) Map { + newMap := Map{} + for k, v := range m { + modifiedKey, modifiedVal := transformer(k, v) + newMap[modifiedKey] = modifiedVal + } + return newMap +} + +// TransformKeys builds a new map using the specified key mapping. +// +// Unspecified keys will be unaltered. +// This method requires that the wrapped object be a map[string]interface{} +func (m Map) TransformKeys(mapping map[string]string) Map { + return m.Transform(func(key string, value interface{}) (string, interface{}) { + if newKey, ok := mapping[key]; ok { + return newKey, value + } + return key, value + }) +} + +// Checks if a string slice contains a string +func contains(s []string, e string) bool { + for _, a := range s { + if a == e { + return true + } + } + return false +} diff --git a/vendor/github.com/stretchr/objx/security.go b/vendor/github.com/stretchr/objx/security.go new file mode 100644 index 000000000..692be8e2a --- /dev/null +++ b/vendor/github.com/stretchr/objx/security.go @@ -0,0 +1,12 @@ +package objx + +import ( + "crypto/sha1" + "encoding/hex" +) + +// HashWithKey hashes the specified string using the security key +func HashWithKey(data, key string) string { + d := sha1.Sum([]byte(data + ":" + key)) + return hex.EncodeToString(d[:]) +} diff --git a/vendor/github.com/stretchr/objx/tests.go b/vendor/github.com/stretchr/objx/tests.go new file mode 100644 index 000000000..d9e0b479a --- /dev/null +++ b/vendor/github.com/stretchr/objx/tests.go @@ -0,0 +1,17 @@ +package objx + +// Has gets whether there is something at the specified selector +// or not. +// +// If m is nil, Has will always return false. +func (m Map) Has(selector string) bool { + if m == nil { + return false + } + return !m.Get(selector).IsNil() +} + +// IsNil gets whether the data is nil or not. +func (v *Value) IsNil() bool { + return v == nil || v.data == nil +} diff --git a/vendor/github.com/stretchr/objx/type_specific_codegen.go b/vendor/github.com/stretchr/objx/type_specific_codegen.go new file mode 100644 index 000000000..202a91f8c --- /dev/null +++ b/vendor/github.com/stretchr/objx/type_specific_codegen.go @@ -0,0 +1,2501 @@ +package objx + +/* + Inter (interface{} and []interface{}) +*/ + +// Inter gets the value as a interface{}, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Inter(optionalDefault ...interface{}) interface{} { + if s, ok := v.data.(interface{}); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustInter gets the value as a interface{}. +// +// Panics if the object is not a interface{}. +func (v *Value) MustInter() interface{} { + return v.data.(interface{}) +} + +// InterSlice gets the value as a []interface{}, returns the optionalDefault +// value or nil if the value is not a []interface{}. +func (v *Value) InterSlice(optionalDefault ...[]interface{}) []interface{} { + if s, ok := v.data.([]interface{}); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustInterSlice gets the value as a []interface{}. +// +// Panics if the object is not a []interface{}. +func (v *Value) MustInterSlice() []interface{} { + return v.data.([]interface{}) +} + +// IsInter gets whether the object contained is a interface{} or not. +func (v *Value) IsInter() bool { + _, ok := v.data.(interface{}) + return ok +} + +// IsInterSlice gets whether the object contained is a []interface{} or not. +func (v *Value) IsInterSlice() bool { + _, ok := v.data.([]interface{}) + return ok +} + +// EachInter calls the specified callback for each object +// in the []interface{}. +// +// Panics if the object is the wrong type. +func (v *Value) EachInter(callback func(int, interface{}) bool) *Value { + for index, val := range v.MustInterSlice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereInter uses the specified decider function to select items +// from the []interface{}. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereInter(decider func(int, interface{}) bool) *Value { + var selected []interface{} + v.EachInter(func(index int, val interface{}) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupInter uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]interface{}. +func (v *Value) GroupInter(grouper func(int, interface{}) string) *Value { + groups := make(map[string][]interface{}) + v.EachInter(func(index int, val interface{}) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]interface{}, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceInter uses the specified function to replace each interface{}s +// by iterating each item. The data in the returned result will be a +// []interface{} containing the replaced items. +func (v *Value) ReplaceInter(replacer func(int, interface{}) interface{}) *Value { + arr := v.MustInterSlice() + replaced := make([]interface{}, len(arr)) + v.EachInter(func(index int, val interface{}) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectInter uses the specified collector function to collect a value +// for each of the interface{}s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectInter(collector func(int, interface{}) interface{}) *Value { + arr := v.MustInterSlice() + collected := make([]interface{}, len(arr)) + v.EachInter(func(index int, val interface{}) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + MSI (map[string]interface{} and []map[string]interface{}) +*/ + +// MSI gets the value as a map[string]interface{}, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) MSI(optionalDefault ...map[string]interface{}) map[string]interface{} { + if s, ok := v.data.(map[string]interface{}); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustMSI gets the value as a map[string]interface{}. +// +// Panics if the object is not a map[string]interface{}. +func (v *Value) MustMSI() map[string]interface{} { + return v.data.(map[string]interface{}) +} + +// MSISlice gets the value as a []map[string]interface{}, returns the optionalDefault +// value or nil if the value is not a []map[string]interface{}. +func (v *Value) MSISlice(optionalDefault ...[]map[string]interface{}) []map[string]interface{} { + if s, ok := v.data.([]map[string]interface{}); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustMSISlice gets the value as a []map[string]interface{}. +// +// Panics if the object is not a []map[string]interface{}. +func (v *Value) MustMSISlice() []map[string]interface{} { + return v.data.([]map[string]interface{}) +} + +// IsMSI gets whether the object contained is a map[string]interface{} or not. +func (v *Value) IsMSI() bool { + _, ok := v.data.(map[string]interface{}) + return ok +} + +// IsMSISlice gets whether the object contained is a []map[string]interface{} or not. +func (v *Value) IsMSISlice() bool { + _, ok := v.data.([]map[string]interface{}) + return ok +} + +// EachMSI calls the specified callback for each object +// in the []map[string]interface{}. +// +// Panics if the object is the wrong type. +func (v *Value) EachMSI(callback func(int, map[string]interface{}) bool) *Value { + for index, val := range v.MustMSISlice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereMSI uses the specified decider function to select items +// from the []map[string]interface{}. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereMSI(decider func(int, map[string]interface{}) bool) *Value { + var selected []map[string]interface{} + v.EachMSI(func(index int, val map[string]interface{}) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupMSI uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]map[string]interface{}. +func (v *Value) GroupMSI(grouper func(int, map[string]interface{}) string) *Value { + groups := make(map[string][]map[string]interface{}) + v.EachMSI(func(index int, val map[string]interface{}) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]map[string]interface{}, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceMSI uses the specified function to replace each map[string]interface{}s +// by iterating each item. The data in the returned result will be a +// []map[string]interface{} containing the replaced items. +func (v *Value) ReplaceMSI(replacer func(int, map[string]interface{}) map[string]interface{}) *Value { + arr := v.MustMSISlice() + replaced := make([]map[string]interface{}, len(arr)) + v.EachMSI(func(index int, val map[string]interface{}) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectMSI uses the specified collector function to collect a value +// for each of the map[string]interface{}s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectMSI(collector func(int, map[string]interface{}) interface{}) *Value { + arr := v.MustMSISlice() + collected := make([]interface{}, len(arr)) + v.EachMSI(func(index int, val map[string]interface{}) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + ObjxMap ((Map) and [](Map)) +*/ + +// ObjxMap gets the value as a (Map), returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) ObjxMap(optionalDefault ...(Map)) Map { + if s, ok := v.data.((Map)); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return New(nil) +} + +// MustObjxMap gets the value as a (Map). +// +// Panics if the object is not a (Map). +func (v *Value) MustObjxMap() Map { + return v.data.((Map)) +} + +// ObjxMapSlice gets the value as a [](Map), returns the optionalDefault +// value or nil if the value is not a [](Map). +func (v *Value) ObjxMapSlice(optionalDefault ...[](Map)) [](Map) { + if s, ok := v.data.([](Map)); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustObjxMapSlice gets the value as a [](Map). +// +// Panics if the object is not a [](Map). +func (v *Value) MustObjxMapSlice() [](Map) { + return v.data.([](Map)) +} + +// IsObjxMap gets whether the object contained is a (Map) or not. +func (v *Value) IsObjxMap() bool { + _, ok := v.data.((Map)) + return ok +} + +// IsObjxMapSlice gets whether the object contained is a [](Map) or not. +func (v *Value) IsObjxMapSlice() bool { + _, ok := v.data.([](Map)) + return ok +} + +// EachObjxMap calls the specified callback for each object +// in the [](Map). +// +// Panics if the object is the wrong type. +func (v *Value) EachObjxMap(callback func(int, Map) bool) *Value { + for index, val := range v.MustObjxMapSlice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereObjxMap uses the specified decider function to select items +// from the [](Map). The object contained in the result will contain +// only the selected items. +func (v *Value) WhereObjxMap(decider func(int, Map) bool) *Value { + var selected [](Map) + v.EachObjxMap(func(index int, val Map) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupObjxMap uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][](Map). +func (v *Value) GroupObjxMap(grouper func(int, Map) string) *Value { + groups := make(map[string][](Map)) + v.EachObjxMap(func(index int, val Map) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([](Map), 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceObjxMap uses the specified function to replace each (Map)s +// by iterating each item. The data in the returned result will be a +// [](Map) containing the replaced items. +func (v *Value) ReplaceObjxMap(replacer func(int, Map) Map) *Value { + arr := v.MustObjxMapSlice() + replaced := make([](Map), len(arr)) + v.EachObjxMap(func(index int, val Map) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectObjxMap uses the specified collector function to collect a value +// for each of the (Map)s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectObjxMap(collector func(int, Map) interface{}) *Value { + arr := v.MustObjxMapSlice() + collected := make([]interface{}, len(arr)) + v.EachObjxMap(func(index int, val Map) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Bool (bool and []bool) +*/ + +// Bool gets the value as a bool, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Bool(optionalDefault ...bool) bool { + if s, ok := v.data.(bool); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return false +} + +// MustBool gets the value as a bool. +// +// Panics if the object is not a bool. +func (v *Value) MustBool() bool { + return v.data.(bool) +} + +// BoolSlice gets the value as a []bool, returns the optionalDefault +// value or nil if the value is not a []bool. +func (v *Value) BoolSlice(optionalDefault ...[]bool) []bool { + if s, ok := v.data.([]bool); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustBoolSlice gets the value as a []bool. +// +// Panics if the object is not a []bool. +func (v *Value) MustBoolSlice() []bool { + return v.data.([]bool) +} + +// IsBool gets whether the object contained is a bool or not. +func (v *Value) IsBool() bool { + _, ok := v.data.(bool) + return ok +} + +// IsBoolSlice gets whether the object contained is a []bool or not. +func (v *Value) IsBoolSlice() bool { + _, ok := v.data.([]bool) + return ok +} + +// EachBool calls the specified callback for each object +// in the []bool. +// +// Panics if the object is the wrong type. +func (v *Value) EachBool(callback func(int, bool) bool) *Value { + for index, val := range v.MustBoolSlice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereBool uses the specified decider function to select items +// from the []bool. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereBool(decider func(int, bool) bool) *Value { + var selected []bool + v.EachBool(func(index int, val bool) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupBool uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]bool. +func (v *Value) GroupBool(grouper func(int, bool) string) *Value { + groups := make(map[string][]bool) + v.EachBool(func(index int, val bool) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]bool, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceBool uses the specified function to replace each bools +// by iterating each item. The data in the returned result will be a +// []bool containing the replaced items. +func (v *Value) ReplaceBool(replacer func(int, bool) bool) *Value { + arr := v.MustBoolSlice() + replaced := make([]bool, len(arr)) + v.EachBool(func(index int, val bool) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectBool uses the specified collector function to collect a value +// for each of the bools in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectBool(collector func(int, bool) interface{}) *Value { + arr := v.MustBoolSlice() + collected := make([]interface{}, len(arr)) + v.EachBool(func(index int, val bool) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Str (string and []string) +*/ + +// Str gets the value as a string, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Str(optionalDefault ...string) string { + if s, ok := v.data.(string); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return "" +} + +// MustStr gets the value as a string. +// +// Panics if the object is not a string. +func (v *Value) MustStr() string { + return v.data.(string) +} + +// StrSlice gets the value as a []string, returns the optionalDefault +// value or nil if the value is not a []string. +func (v *Value) StrSlice(optionalDefault ...[]string) []string { + if s, ok := v.data.([]string); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustStrSlice gets the value as a []string. +// +// Panics if the object is not a []string. +func (v *Value) MustStrSlice() []string { + return v.data.([]string) +} + +// IsStr gets whether the object contained is a string or not. +func (v *Value) IsStr() bool { + _, ok := v.data.(string) + return ok +} + +// IsStrSlice gets whether the object contained is a []string or not. +func (v *Value) IsStrSlice() bool { + _, ok := v.data.([]string) + return ok +} + +// EachStr calls the specified callback for each object +// in the []string. +// +// Panics if the object is the wrong type. +func (v *Value) EachStr(callback func(int, string) bool) *Value { + for index, val := range v.MustStrSlice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereStr uses the specified decider function to select items +// from the []string. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereStr(decider func(int, string) bool) *Value { + var selected []string + v.EachStr(func(index int, val string) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupStr uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]string. +func (v *Value) GroupStr(grouper func(int, string) string) *Value { + groups := make(map[string][]string) + v.EachStr(func(index int, val string) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]string, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceStr uses the specified function to replace each strings +// by iterating each item. The data in the returned result will be a +// []string containing the replaced items. +func (v *Value) ReplaceStr(replacer func(int, string) string) *Value { + arr := v.MustStrSlice() + replaced := make([]string, len(arr)) + v.EachStr(func(index int, val string) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectStr uses the specified collector function to collect a value +// for each of the strings in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectStr(collector func(int, string) interface{}) *Value { + arr := v.MustStrSlice() + collected := make([]interface{}, len(arr)) + v.EachStr(func(index int, val string) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Int (int and []int) +*/ + +// Int gets the value as a int, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Int(optionalDefault ...int) int { + if s, ok := v.data.(int); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustInt gets the value as a int. +// +// Panics if the object is not a int. +func (v *Value) MustInt() int { + return v.data.(int) +} + +// IntSlice gets the value as a []int, returns the optionalDefault +// value or nil if the value is not a []int. +func (v *Value) IntSlice(optionalDefault ...[]int) []int { + if s, ok := v.data.([]int); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustIntSlice gets the value as a []int. +// +// Panics if the object is not a []int. +func (v *Value) MustIntSlice() []int { + return v.data.([]int) +} + +// IsInt gets whether the object contained is a int or not. +func (v *Value) IsInt() bool { + _, ok := v.data.(int) + return ok +} + +// IsIntSlice gets whether the object contained is a []int or not. +func (v *Value) IsIntSlice() bool { + _, ok := v.data.([]int) + return ok +} + +// EachInt calls the specified callback for each object +// in the []int. +// +// Panics if the object is the wrong type. +func (v *Value) EachInt(callback func(int, int) bool) *Value { + for index, val := range v.MustIntSlice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereInt uses the specified decider function to select items +// from the []int. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereInt(decider func(int, int) bool) *Value { + var selected []int + v.EachInt(func(index int, val int) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupInt uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]int. +func (v *Value) GroupInt(grouper func(int, int) string) *Value { + groups := make(map[string][]int) + v.EachInt(func(index int, val int) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]int, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceInt uses the specified function to replace each ints +// by iterating each item. The data in the returned result will be a +// []int containing the replaced items. +func (v *Value) ReplaceInt(replacer func(int, int) int) *Value { + arr := v.MustIntSlice() + replaced := make([]int, len(arr)) + v.EachInt(func(index int, val int) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectInt uses the specified collector function to collect a value +// for each of the ints in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectInt(collector func(int, int) interface{}) *Value { + arr := v.MustIntSlice() + collected := make([]interface{}, len(arr)) + v.EachInt(func(index int, val int) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Int8 (int8 and []int8) +*/ + +// Int8 gets the value as a int8, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Int8(optionalDefault ...int8) int8 { + if s, ok := v.data.(int8); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustInt8 gets the value as a int8. +// +// Panics if the object is not a int8. +func (v *Value) MustInt8() int8 { + return v.data.(int8) +} + +// Int8Slice gets the value as a []int8, returns the optionalDefault +// value or nil if the value is not a []int8. +func (v *Value) Int8Slice(optionalDefault ...[]int8) []int8 { + if s, ok := v.data.([]int8); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustInt8Slice gets the value as a []int8. +// +// Panics if the object is not a []int8. +func (v *Value) MustInt8Slice() []int8 { + return v.data.([]int8) +} + +// IsInt8 gets whether the object contained is a int8 or not. +func (v *Value) IsInt8() bool { + _, ok := v.data.(int8) + return ok +} + +// IsInt8Slice gets whether the object contained is a []int8 or not. +func (v *Value) IsInt8Slice() bool { + _, ok := v.data.([]int8) + return ok +} + +// EachInt8 calls the specified callback for each object +// in the []int8. +// +// Panics if the object is the wrong type. +func (v *Value) EachInt8(callback func(int, int8) bool) *Value { + for index, val := range v.MustInt8Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereInt8 uses the specified decider function to select items +// from the []int8. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereInt8(decider func(int, int8) bool) *Value { + var selected []int8 + v.EachInt8(func(index int, val int8) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupInt8 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]int8. +func (v *Value) GroupInt8(grouper func(int, int8) string) *Value { + groups := make(map[string][]int8) + v.EachInt8(func(index int, val int8) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]int8, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceInt8 uses the specified function to replace each int8s +// by iterating each item. The data in the returned result will be a +// []int8 containing the replaced items. +func (v *Value) ReplaceInt8(replacer func(int, int8) int8) *Value { + arr := v.MustInt8Slice() + replaced := make([]int8, len(arr)) + v.EachInt8(func(index int, val int8) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectInt8 uses the specified collector function to collect a value +// for each of the int8s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectInt8(collector func(int, int8) interface{}) *Value { + arr := v.MustInt8Slice() + collected := make([]interface{}, len(arr)) + v.EachInt8(func(index int, val int8) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Int16 (int16 and []int16) +*/ + +// Int16 gets the value as a int16, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Int16(optionalDefault ...int16) int16 { + if s, ok := v.data.(int16); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustInt16 gets the value as a int16. +// +// Panics if the object is not a int16. +func (v *Value) MustInt16() int16 { + return v.data.(int16) +} + +// Int16Slice gets the value as a []int16, returns the optionalDefault +// value or nil if the value is not a []int16. +func (v *Value) Int16Slice(optionalDefault ...[]int16) []int16 { + if s, ok := v.data.([]int16); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustInt16Slice gets the value as a []int16. +// +// Panics if the object is not a []int16. +func (v *Value) MustInt16Slice() []int16 { + return v.data.([]int16) +} + +// IsInt16 gets whether the object contained is a int16 or not. +func (v *Value) IsInt16() bool { + _, ok := v.data.(int16) + return ok +} + +// IsInt16Slice gets whether the object contained is a []int16 or not. +func (v *Value) IsInt16Slice() bool { + _, ok := v.data.([]int16) + return ok +} + +// EachInt16 calls the specified callback for each object +// in the []int16. +// +// Panics if the object is the wrong type. +func (v *Value) EachInt16(callback func(int, int16) bool) *Value { + for index, val := range v.MustInt16Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereInt16 uses the specified decider function to select items +// from the []int16. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereInt16(decider func(int, int16) bool) *Value { + var selected []int16 + v.EachInt16(func(index int, val int16) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupInt16 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]int16. +func (v *Value) GroupInt16(grouper func(int, int16) string) *Value { + groups := make(map[string][]int16) + v.EachInt16(func(index int, val int16) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]int16, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceInt16 uses the specified function to replace each int16s +// by iterating each item. The data in the returned result will be a +// []int16 containing the replaced items. +func (v *Value) ReplaceInt16(replacer func(int, int16) int16) *Value { + arr := v.MustInt16Slice() + replaced := make([]int16, len(arr)) + v.EachInt16(func(index int, val int16) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectInt16 uses the specified collector function to collect a value +// for each of the int16s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectInt16(collector func(int, int16) interface{}) *Value { + arr := v.MustInt16Slice() + collected := make([]interface{}, len(arr)) + v.EachInt16(func(index int, val int16) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Int32 (int32 and []int32) +*/ + +// Int32 gets the value as a int32, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Int32(optionalDefault ...int32) int32 { + if s, ok := v.data.(int32); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustInt32 gets the value as a int32. +// +// Panics if the object is not a int32. +func (v *Value) MustInt32() int32 { + return v.data.(int32) +} + +// Int32Slice gets the value as a []int32, returns the optionalDefault +// value or nil if the value is not a []int32. +func (v *Value) Int32Slice(optionalDefault ...[]int32) []int32 { + if s, ok := v.data.([]int32); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustInt32Slice gets the value as a []int32. +// +// Panics if the object is not a []int32. +func (v *Value) MustInt32Slice() []int32 { + return v.data.([]int32) +} + +// IsInt32 gets whether the object contained is a int32 or not. +func (v *Value) IsInt32() bool { + _, ok := v.data.(int32) + return ok +} + +// IsInt32Slice gets whether the object contained is a []int32 or not. +func (v *Value) IsInt32Slice() bool { + _, ok := v.data.([]int32) + return ok +} + +// EachInt32 calls the specified callback for each object +// in the []int32. +// +// Panics if the object is the wrong type. +func (v *Value) EachInt32(callback func(int, int32) bool) *Value { + for index, val := range v.MustInt32Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereInt32 uses the specified decider function to select items +// from the []int32. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereInt32(decider func(int, int32) bool) *Value { + var selected []int32 + v.EachInt32(func(index int, val int32) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupInt32 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]int32. +func (v *Value) GroupInt32(grouper func(int, int32) string) *Value { + groups := make(map[string][]int32) + v.EachInt32(func(index int, val int32) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]int32, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceInt32 uses the specified function to replace each int32s +// by iterating each item. The data in the returned result will be a +// []int32 containing the replaced items. +func (v *Value) ReplaceInt32(replacer func(int, int32) int32) *Value { + arr := v.MustInt32Slice() + replaced := make([]int32, len(arr)) + v.EachInt32(func(index int, val int32) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectInt32 uses the specified collector function to collect a value +// for each of the int32s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectInt32(collector func(int, int32) interface{}) *Value { + arr := v.MustInt32Slice() + collected := make([]interface{}, len(arr)) + v.EachInt32(func(index int, val int32) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Int64 (int64 and []int64) +*/ + +// Int64 gets the value as a int64, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Int64(optionalDefault ...int64) int64 { + if s, ok := v.data.(int64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustInt64 gets the value as a int64. +// +// Panics if the object is not a int64. +func (v *Value) MustInt64() int64 { + return v.data.(int64) +} + +// Int64Slice gets the value as a []int64, returns the optionalDefault +// value or nil if the value is not a []int64. +func (v *Value) Int64Slice(optionalDefault ...[]int64) []int64 { + if s, ok := v.data.([]int64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustInt64Slice gets the value as a []int64. +// +// Panics if the object is not a []int64. +func (v *Value) MustInt64Slice() []int64 { + return v.data.([]int64) +} + +// IsInt64 gets whether the object contained is a int64 or not. +func (v *Value) IsInt64() bool { + _, ok := v.data.(int64) + return ok +} + +// IsInt64Slice gets whether the object contained is a []int64 or not. +func (v *Value) IsInt64Slice() bool { + _, ok := v.data.([]int64) + return ok +} + +// EachInt64 calls the specified callback for each object +// in the []int64. +// +// Panics if the object is the wrong type. +func (v *Value) EachInt64(callback func(int, int64) bool) *Value { + for index, val := range v.MustInt64Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereInt64 uses the specified decider function to select items +// from the []int64. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereInt64(decider func(int, int64) bool) *Value { + var selected []int64 + v.EachInt64(func(index int, val int64) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupInt64 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]int64. +func (v *Value) GroupInt64(grouper func(int, int64) string) *Value { + groups := make(map[string][]int64) + v.EachInt64(func(index int, val int64) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]int64, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceInt64 uses the specified function to replace each int64s +// by iterating each item. The data in the returned result will be a +// []int64 containing the replaced items. +func (v *Value) ReplaceInt64(replacer func(int, int64) int64) *Value { + arr := v.MustInt64Slice() + replaced := make([]int64, len(arr)) + v.EachInt64(func(index int, val int64) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectInt64 uses the specified collector function to collect a value +// for each of the int64s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectInt64(collector func(int, int64) interface{}) *Value { + arr := v.MustInt64Slice() + collected := make([]interface{}, len(arr)) + v.EachInt64(func(index int, val int64) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Uint (uint and []uint) +*/ + +// Uint gets the value as a uint, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Uint(optionalDefault ...uint) uint { + if s, ok := v.data.(uint); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustUint gets the value as a uint. +// +// Panics if the object is not a uint. +func (v *Value) MustUint() uint { + return v.data.(uint) +} + +// UintSlice gets the value as a []uint, returns the optionalDefault +// value or nil if the value is not a []uint. +func (v *Value) UintSlice(optionalDefault ...[]uint) []uint { + if s, ok := v.data.([]uint); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustUintSlice gets the value as a []uint. +// +// Panics if the object is not a []uint. +func (v *Value) MustUintSlice() []uint { + return v.data.([]uint) +} + +// IsUint gets whether the object contained is a uint or not. +func (v *Value) IsUint() bool { + _, ok := v.data.(uint) + return ok +} + +// IsUintSlice gets whether the object contained is a []uint or not. +func (v *Value) IsUintSlice() bool { + _, ok := v.data.([]uint) + return ok +} + +// EachUint calls the specified callback for each object +// in the []uint. +// +// Panics if the object is the wrong type. +func (v *Value) EachUint(callback func(int, uint) bool) *Value { + for index, val := range v.MustUintSlice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereUint uses the specified decider function to select items +// from the []uint. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereUint(decider func(int, uint) bool) *Value { + var selected []uint + v.EachUint(func(index int, val uint) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupUint uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]uint. +func (v *Value) GroupUint(grouper func(int, uint) string) *Value { + groups := make(map[string][]uint) + v.EachUint(func(index int, val uint) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]uint, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceUint uses the specified function to replace each uints +// by iterating each item. The data in the returned result will be a +// []uint containing the replaced items. +func (v *Value) ReplaceUint(replacer func(int, uint) uint) *Value { + arr := v.MustUintSlice() + replaced := make([]uint, len(arr)) + v.EachUint(func(index int, val uint) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectUint uses the specified collector function to collect a value +// for each of the uints in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectUint(collector func(int, uint) interface{}) *Value { + arr := v.MustUintSlice() + collected := make([]interface{}, len(arr)) + v.EachUint(func(index int, val uint) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Uint8 (uint8 and []uint8) +*/ + +// Uint8 gets the value as a uint8, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Uint8(optionalDefault ...uint8) uint8 { + if s, ok := v.data.(uint8); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustUint8 gets the value as a uint8. +// +// Panics if the object is not a uint8. +func (v *Value) MustUint8() uint8 { + return v.data.(uint8) +} + +// Uint8Slice gets the value as a []uint8, returns the optionalDefault +// value or nil if the value is not a []uint8. +func (v *Value) Uint8Slice(optionalDefault ...[]uint8) []uint8 { + if s, ok := v.data.([]uint8); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustUint8Slice gets the value as a []uint8. +// +// Panics if the object is not a []uint8. +func (v *Value) MustUint8Slice() []uint8 { + return v.data.([]uint8) +} + +// IsUint8 gets whether the object contained is a uint8 or not. +func (v *Value) IsUint8() bool { + _, ok := v.data.(uint8) + return ok +} + +// IsUint8Slice gets whether the object contained is a []uint8 or not. +func (v *Value) IsUint8Slice() bool { + _, ok := v.data.([]uint8) + return ok +} + +// EachUint8 calls the specified callback for each object +// in the []uint8. +// +// Panics if the object is the wrong type. +func (v *Value) EachUint8(callback func(int, uint8) bool) *Value { + for index, val := range v.MustUint8Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereUint8 uses the specified decider function to select items +// from the []uint8. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereUint8(decider func(int, uint8) bool) *Value { + var selected []uint8 + v.EachUint8(func(index int, val uint8) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupUint8 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]uint8. +func (v *Value) GroupUint8(grouper func(int, uint8) string) *Value { + groups := make(map[string][]uint8) + v.EachUint8(func(index int, val uint8) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]uint8, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceUint8 uses the specified function to replace each uint8s +// by iterating each item. The data in the returned result will be a +// []uint8 containing the replaced items. +func (v *Value) ReplaceUint8(replacer func(int, uint8) uint8) *Value { + arr := v.MustUint8Slice() + replaced := make([]uint8, len(arr)) + v.EachUint8(func(index int, val uint8) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectUint8 uses the specified collector function to collect a value +// for each of the uint8s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectUint8(collector func(int, uint8) interface{}) *Value { + arr := v.MustUint8Slice() + collected := make([]interface{}, len(arr)) + v.EachUint8(func(index int, val uint8) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Uint16 (uint16 and []uint16) +*/ + +// Uint16 gets the value as a uint16, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Uint16(optionalDefault ...uint16) uint16 { + if s, ok := v.data.(uint16); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustUint16 gets the value as a uint16. +// +// Panics if the object is not a uint16. +func (v *Value) MustUint16() uint16 { + return v.data.(uint16) +} + +// Uint16Slice gets the value as a []uint16, returns the optionalDefault +// value or nil if the value is not a []uint16. +func (v *Value) Uint16Slice(optionalDefault ...[]uint16) []uint16 { + if s, ok := v.data.([]uint16); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustUint16Slice gets the value as a []uint16. +// +// Panics if the object is not a []uint16. +func (v *Value) MustUint16Slice() []uint16 { + return v.data.([]uint16) +} + +// IsUint16 gets whether the object contained is a uint16 or not. +func (v *Value) IsUint16() bool { + _, ok := v.data.(uint16) + return ok +} + +// IsUint16Slice gets whether the object contained is a []uint16 or not. +func (v *Value) IsUint16Slice() bool { + _, ok := v.data.([]uint16) + return ok +} + +// EachUint16 calls the specified callback for each object +// in the []uint16. +// +// Panics if the object is the wrong type. +func (v *Value) EachUint16(callback func(int, uint16) bool) *Value { + for index, val := range v.MustUint16Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereUint16 uses the specified decider function to select items +// from the []uint16. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereUint16(decider func(int, uint16) bool) *Value { + var selected []uint16 + v.EachUint16(func(index int, val uint16) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupUint16 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]uint16. +func (v *Value) GroupUint16(grouper func(int, uint16) string) *Value { + groups := make(map[string][]uint16) + v.EachUint16(func(index int, val uint16) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]uint16, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceUint16 uses the specified function to replace each uint16s +// by iterating each item. The data in the returned result will be a +// []uint16 containing the replaced items. +func (v *Value) ReplaceUint16(replacer func(int, uint16) uint16) *Value { + arr := v.MustUint16Slice() + replaced := make([]uint16, len(arr)) + v.EachUint16(func(index int, val uint16) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectUint16 uses the specified collector function to collect a value +// for each of the uint16s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectUint16(collector func(int, uint16) interface{}) *Value { + arr := v.MustUint16Slice() + collected := make([]interface{}, len(arr)) + v.EachUint16(func(index int, val uint16) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Uint32 (uint32 and []uint32) +*/ + +// Uint32 gets the value as a uint32, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Uint32(optionalDefault ...uint32) uint32 { + if s, ok := v.data.(uint32); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustUint32 gets the value as a uint32. +// +// Panics if the object is not a uint32. +func (v *Value) MustUint32() uint32 { + return v.data.(uint32) +} + +// Uint32Slice gets the value as a []uint32, returns the optionalDefault +// value or nil if the value is not a []uint32. +func (v *Value) Uint32Slice(optionalDefault ...[]uint32) []uint32 { + if s, ok := v.data.([]uint32); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustUint32Slice gets the value as a []uint32. +// +// Panics if the object is not a []uint32. +func (v *Value) MustUint32Slice() []uint32 { + return v.data.([]uint32) +} + +// IsUint32 gets whether the object contained is a uint32 or not. +func (v *Value) IsUint32() bool { + _, ok := v.data.(uint32) + return ok +} + +// IsUint32Slice gets whether the object contained is a []uint32 or not. +func (v *Value) IsUint32Slice() bool { + _, ok := v.data.([]uint32) + return ok +} + +// EachUint32 calls the specified callback for each object +// in the []uint32. +// +// Panics if the object is the wrong type. +func (v *Value) EachUint32(callback func(int, uint32) bool) *Value { + for index, val := range v.MustUint32Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereUint32 uses the specified decider function to select items +// from the []uint32. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereUint32(decider func(int, uint32) bool) *Value { + var selected []uint32 + v.EachUint32(func(index int, val uint32) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupUint32 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]uint32. +func (v *Value) GroupUint32(grouper func(int, uint32) string) *Value { + groups := make(map[string][]uint32) + v.EachUint32(func(index int, val uint32) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]uint32, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceUint32 uses the specified function to replace each uint32s +// by iterating each item. The data in the returned result will be a +// []uint32 containing the replaced items. +func (v *Value) ReplaceUint32(replacer func(int, uint32) uint32) *Value { + arr := v.MustUint32Slice() + replaced := make([]uint32, len(arr)) + v.EachUint32(func(index int, val uint32) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectUint32 uses the specified collector function to collect a value +// for each of the uint32s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectUint32(collector func(int, uint32) interface{}) *Value { + arr := v.MustUint32Slice() + collected := make([]interface{}, len(arr)) + v.EachUint32(func(index int, val uint32) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Uint64 (uint64 and []uint64) +*/ + +// Uint64 gets the value as a uint64, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Uint64(optionalDefault ...uint64) uint64 { + if s, ok := v.data.(uint64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustUint64 gets the value as a uint64. +// +// Panics if the object is not a uint64. +func (v *Value) MustUint64() uint64 { + return v.data.(uint64) +} + +// Uint64Slice gets the value as a []uint64, returns the optionalDefault +// value or nil if the value is not a []uint64. +func (v *Value) Uint64Slice(optionalDefault ...[]uint64) []uint64 { + if s, ok := v.data.([]uint64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustUint64Slice gets the value as a []uint64. +// +// Panics if the object is not a []uint64. +func (v *Value) MustUint64Slice() []uint64 { + return v.data.([]uint64) +} + +// IsUint64 gets whether the object contained is a uint64 or not. +func (v *Value) IsUint64() bool { + _, ok := v.data.(uint64) + return ok +} + +// IsUint64Slice gets whether the object contained is a []uint64 or not. +func (v *Value) IsUint64Slice() bool { + _, ok := v.data.([]uint64) + return ok +} + +// EachUint64 calls the specified callback for each object +// in the []uint64. +// +// Panics if the object is the wrong type. +func (v *Value) EachUint64(callback func(int, uint64) bool) *Value { + for index, val := range v.MustUint64Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereUint64 uses the specified decider function to select items +// from the []uint64. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereUint64(decider func(int, uint64) bool) *Value { + var selected []uint64 + v.EachUint64(func(index int, val uint64) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupUint64 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]uint64. +func (v *Value) GroupUint64(grouper func(int, uint64) string) *Value { + groups := make(map[string][]uint64) + v.EachUint64(func(index int, val uint64) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]uint64, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceUint64 uses the specified function to replace each uint64s +// by iterating each item. The data in the returned result will be a +// []uint64 containing the replaced items. +func (v *Value) ReplaceUint64(replacer func(int, uint64) uint64) *Value { + arr := v.MustUint64Slice() + replaced := make([]uint64, len(arr)) + v.EachUint64(func(index int, val uint64) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectUint64 uses the specified collector function to collect a value +// for each of the uint64s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectUint64(collector func(int, uint64) interface{}) *Value { + arr := v.MustUint64Slice() + collected := make([]interface{}, len(arr)) + v.EachUint64(func(index int, val uint64) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Uintptr (uintptr and []uintptr) +*/ + +// Uintptr gets the value as a uintptr, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Uintptr(optionalDefault ...uintptr) uintptr { + if s, ok := v.data.(uintptr); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustUintptr gets the value as a uintptr. +// +// Panics if the object is not a uintptr. +func (v *Value) MustUintptr() uintptr { + return v.data.(uintptr) +} + +// UintptrSlice gets the value as a []uintptr, returns the optionalDefault +// value or nil if the value is not a []uintptr. +func (v *Value) UintptrSlice(optionalDefault ...[]uintptr) []uintptr { + if s, ok := v.data.([]uintptr); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustUintptrSlice gets the value as a []uintptr. +// +// Panics if the object is not a []uintptr. +func (v *Value) MustUintptrSlice() []uintptr { + return v.data.([]uintptr) +} + +// IsUintptr gets whether the object contained is a uintptr or not. +func (v *Value) IsUintptr() bool { + _, ok := v.data.(uintptr) + return ok +} + +// IsUintptrSlice gets whether the object contained is a []uintptr or not. +func (v *Value) IsUintptrSlice() bool { + _, ok := v.data.([]uintptr) + return ok +} + +// EachUintptr calls the specified callback for each object +// in the []uintptr. +// +// Panics if the object is the wrong type. +func (v *Value) EachUintptr(callback func(int, uintptr) bool) *Value { + for index, val := range v.MustUintptrSlice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereUintptr uses the specified decider function to select items +// from the []uintptr. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereUintptr(decider func(int, uintptr) bool) *Value { + var selected []uintptr + v.EachUintptr(func(index int, val uintptr) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupUintptr uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]uintptr. +func (v *Value) GroupUintptr(grouper func(int, uintptr) string) *Value { + groups := make(map[string][]uintptr) + v.EachUintptr(func(index int, val uintptr) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]uintptr, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceUintptr uses the specified function to replace each uintptrs +// by iterating each item. The data in the returned result will be a +// []uintptr containing the replaced items. +func (v *Value) ReplaceUintptr(replacer func(int, uintptr) uintptr) *Value { + arr := v.MustUintptrSlice() + replaced := make([]uintptr, len(arr)) + v.EachUintptr(func(index int, val uintptr) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectUintptr uses the specified collector function to collect a value +// for each of the uintptrs in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectUintptr(collector func(int, uintptr) interface{}) *Value { + arr := v.MustUintptrSlice() + collected := make([]interface{}, len(arr)) + v.EachUintptr(func(index int, val uintptr) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Float32 (float32 and []float32) +*/ + +// Float32 gets the value as a float32, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Float32(optionalDefault ...float32) float32 { + if s, ok := v.data.(float32); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustFloat32 gets the value as a float32. +// +// Panics if the object is not a float32. +func (v *Value) MustFloat32() float32 { + return v.data.(float32) +} + +// Float32Slice gets the value as a []float32, returns the optionalDefault +// value or nil if the value is not a []float32. +func (v *Value) Float32Slice(optionalDefault ...[]float32) []float32 { + if s, ok := v.data.([]float32); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustFloat32Slice gets the value as a []float32. +// +// Panics if the object is not a []float32. +func (v *Value) MustFloat32Slice() []float32 { + return v.data.([]float32) +} + +// IsFloat32 gets whether the object contained is a float32 or not. +func (v *Value) IsFloat32() bool { + _, ok := v.data.(float32) + return ok +} + +// IsFloat32Slice gets whether the object contained is a []float32 or not. +func (v *Value) IsFloat32Slice() bool { + _, ok := v.data.([]float32) + return ok +} + +// EachFloat32 calls the specified callback for each object +// in the []float32. +// +// Panics if the object is the wrong type. +func (v *Value) EachFloat32(callback func(int, float32) bool) *Value { + for index, val := range v.MustFloat32Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereFloat32 uses the specified decider function to select items +// from the []float32. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereFloat32(decider func(int, float32) bool) *Value { + var selected []float32 + v.EachFloat32(func(index int, val float32) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupFloat32 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]float32. +func (v *Value) GroupFloat32(grouper func(int, float32) string) *Value { + groups := make(map[string][]float32) + v.EachFloat32(func(index int, val float32) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]float32, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceFloat32 uses the specified function to replace each float32s +// by iterating each item. The data in the returned result will be a +// []float32 containing the replaced items. +func (v *Value) ReplaceFloat32(replacer func(int, float32) float32) *Value { + arr := v.MustFloat32Slice() + replaced := make([]float32, len(arr)) + v.EachFloat32(func(index int, val float32) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectFloat32 uses the specified collector function to collect a value +// for each of the float32s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectFloat32(collector func(int, float32) interface{}) *Value { + arr := v.MustFloat32Slice() + collected := make([]interface{}, len(arr)) + v.EachFloat32(func(index int, val float32) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Float64 (float64 and []float64) +*/ + +// Float64 gets the value as a float64, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Float64(optionalDefault ...float64) float64 { + if s, ok := v.data.(float64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustFloat64 gets the value as a float64. +// +// Panics if the object is not a float64. +func (v *Value) MustFloat64() float64 { + return v.data.(float64) +} + +// Float64Slice gets the value as a []float64, returns the optionalDefault +// value or nil if the value is not a []float64. +func (v *Value) Float64Slice(optionalDefault ...[]float64) []float64 { + if s, ok := v.data.([]float64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustFloat64Slice gets the value as a []float64. +// +// Panics if the object is not a []float64. +func (v *Value) MustFloat64Slice() []float64 { + return v.data.([]float64) +} + +// IsFloat64 gets whether the object contained is a float64 or not. +func (v *Value) IsFloat64() bool { + _, ok := v.data.(float64) + return ok +} + +// IsFloat64Slice gets whether the object contained is a []float64 or not. +func (v *Value) IsFloat64Slice() bool { + _, ok := v.data.([]float64) + return ok +} + +// EachFloat64 calls the specified callback for each object +// in the []float64. +// +// Panics if the object is the wrong type. +func (v *Value) EachFloat64(callback func(int, float64) bool) *Value { + for index, val := range v.MustFloat64Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereFloat64 uses the specified decider function to select items +// from the []float64. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereFloat64(decider func(int, float64) bool) *Value { + var selected []float64 + v.EachFloat64(func(index int, val float64) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupFloat64 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]float64. +func (v *Value) GroupFloat64(grouper func(int, float64) string) *Value { + groups := make(map[string][]float64) + v.EachFloat64(func(index int, val float64) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]float64, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceFloat64 uses the specified function to replace each float64s +// by iterating each item. The data in the returned result will be a +// []float64 containing the replaced items. +func (v *Value) ReplaceFloat64(replacer func(int, float64) float64) *Value { + arr := v.MustFloat64Slice() + replaced := make([]float64, len(arr)) + v.EachFloat64(func(index int, val float64) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectFloat64 uses the specified collector function to collect a value +// for each of the float64s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectFloat64(collector func(int, float64) interface{}) *Value { + arr := v.MustFloat64Slice() + collected := make([]interface{}, len(arr)) + v.EachFloat64(func(index int, val float64) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Complex64 (complex64 and []complex64) +*/ + +// Complex64 gets the value as a complex64, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Complex64(optionalDefault ...complex64) complex64 { + if s, ok := v.data.(complex64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustComplex64 gets the value as a complex64. +// +// Panics if the object is not a complex64. +func (v *Value) MustComplex64() complex64 { + return v.data.(complex64) +} + +// Complex64Slice gets the value as a []complex64, returns the optionalDefault +// value or nil if the value is not a []complex64. +func (v *Value) Complex64Slice(optionalDefault ...[]complex64) []complex64 { + if s, ok := v.data.([]complex64); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustComplex64Slice gets the value as a []complex64. +// +// Panics if the object is not a []complex64. +func (v *Value) MustComplex64Slice() []complex64 { + return v.data.([]complex64) +} + +// IsComplex64 gets whether the object contained is a complex64 or not. +func (v *Value) IsComplex64() bool { + _, ok := v.data.(complex64) + return ok +} + +// IsComplex64Slice gets whether the object contained is a []complex64 or not. +func (v *Value) IsComplex64Slice() bool { + _, ok := v.data.([]complex64) + return ok +} + +// EachComplex64 calls the specified callback for each object +// in the []complex64. +// +// Panics if the object is the wrong type. +func (v *Value) EachComplex64(callback func(int, complex64) bool) *Value { + for index, val := range v.MustComplex64Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereComplex64 uses the specified decider function to select items +// from the []complex64. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereComplex64(decider func(int, complex64) bool) *Value { + var selected []complex64 + v.EachComplex64(func(index int, val complex64) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupComplex64 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]complex64. +func (v *Value) GroupComplex64(grouper func(int, complex64) string) *Value { + groups := make(map[string][]complex64) + v.EachComplex64(func(index int, val complex64) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]complex64, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceComplex64 uses the specified function to replace each complex64s +// by iterating each item. The data in the returned result will be a +// []complex64 containing the replaced items. +func (v *Value) ReplaceComplex64(replacer func(int, complex64) complex64) *Value { + arr := v.MustComplex64Slice() + replaced := make([]complex64, len(arr)) + v.EachComplex64(func(index int, val complex64) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectComplex64 uses the specified collector function to collect a value +// for each of the complex64s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectComplex64(collector func(int, complex64) interface{}) *Value { + arr := v.MustComplex64Slice() + collected := make([]interface{}, len(arr)) + v.EachComplex64(func(index int, val complex64) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} + +/* + Complex128 (complex128 and []complex128) +*/ + +// Complex128 gets the value as a complex128, returns the optionalDefault +// value or a system default object if the value is the wrong type. +func (v *Value) Complex128(optionalDefault ...complex128) complex128 { + if s, ok := v.data.(complex128); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return 0 +} + +// MustComplex128 gets the value as a complex128. +// +// Panics if the object is not a complex128. +func (v *Value) MustComplex128() complex128 { + return v.data.(complex128) +} + +// Complex128Slice gets the value as a []complex128, returns the optionalDefault +// value or nil if the value is not a []complex128. +func (v *Value) Complex128Slice(optionalDefault ...[]complex128) []complex128 { + if s, ok := v.data.([]complex128); ok { + return s + } + if len(optionalDefault) == 1 { + return optionalDefault[0] + } + return nil +} + +// MustComplex128Slice gets the value as a []complex128. +// +// Panics if the object is not a []complex128. +func (v *Value) MustComplex128Slice() []complex128 { + return v.data.([]complex128) +} + +// IsComplex128 gets whether the object contained is a complex128 or not. +func (v *Value) IsComplex128() bool { + _, ok := v.data.(complex128) + return ok +} + +// IsComplex128Slice gets whether the object contained is a []complex128 or not. +func (v *Value) IsComplex128Slice() bool { + _, ok := v.data.([]complex128) + return ok +} + +// EachComplex128 calls the specified callback for each object +// in the []complex128. +// +// Panics if the object is the wrong type. +func (v *Value) EachComplex128(callback func(int, complex128) bool) *Value { + for index, val := range v.MustComplex128Slice() { + carryon := callback(index, val) + if !carryon { + break + } + } + return v +} + +// WhereComplex128 uses the specified decider function to select items +// from the []complex128. The object contained in the result will contain +// only the selected items. +func (v *Value) WhereComplex128(decider func(int, complex128) bool) *Value { + var selected []complex128 + v.EachComplex128(func(index int, val complex128) bool { + shouldSelect := decider(index, val) + if !shouldSelect { + selected = append(selected, val) + } + return true + }) + return &Value{data: selected} +} + +// GroupComplex128 uses the specified grouper function to group the items +// keyed by the return of the grouper. The object contained in the +// result will contain a map[string][]complex128. +func (v *Value) GroupComplex128(grouper func(int, complex128) string) *Value { + groups := make(map[string][]complex128) + v.EachComplex128(func(index int, val complex128) bool { + group := grouper(index, val) + if _, ok := groups[group]; !ok { + groups[group] = make([]complex128, 0) + } + groups[group] = append(groups[group], val) + return true + }) + return &Value{data: groups} +} + +// ReplaceComplex128 uses the specified function to replace each complex128s +// by iterating each item. The data in the returned result will be a +// []complex128 containing the replaced items. +func (v *Value) ReplaceComplex128(replacer func(int, complex128) complex128) *Value { + arr := v.MustComplex128Slice() + replaced := make([]complex128, len(arr)) + v.EachComplex128(func(index int, val complex128) bool { + replaced[index] = replacer(index, val) + return true + }) + return &Value{data: replaced} +} + +// CollectComplex128 uses the specified collector function to collect a value +// for each of the complex128s in the slice. The data returned will be a +// []interface{}. +func (v *Value) CollectComplex128(collector func(int, complex128) interface{}) *Value { + arr := v.MustComplex128Slice() + collected := make([]interface{}, len(arr)) + v.EachComplex128(func(index int, val complex128) bool { + collected[index] = collector(index, val) + return true + }) + return &Value{data: collected} +} diff --git a/vendor/github.com/stretchr/objx/value.go b/vendor/github.com/stretchr/objx/value.go new file mode 100644 index 000000000..e4b4a1433 --- /dev/null +++ b/vendor/github.com/stretchr/objx/value.go @@ -0,0 +1,53 @@ +package objx + +import ( + "fmt" + "strconv" +) + +// Value provides methods for extracting interface{} data in various +// types. +type Value struct { + // data contains the raw data being managed by this Value + data interface{} +} + +// Data returns the raw data contained by this Value +func (v *Value) Data() interface{} { + return v.data +} + +// String returns the value always as a string +func (v *Value) String() string { + switch { + case v.IsStr(): + return v.Str() + case v.IsBool(): + return strconv.FormatBool(v.Bool()) + case v.IsFloat32(): + return strconv.FormatFloat(float64(v.Float32()), 'f', -1, 32) + case v.IsFloat64(): + return strconv.FormatFloat(v.Float64(), 'f', -1, 64) + case v.IsInt(): + return strconv.FormatInt(int64(v.Int()), 10) + case v.IsInt8(): + return strconv.FormatInt(int64(v.Int8()), 10) + case v.IsInt16(): + return strconv.FormatInt(int64(v.Int16()), 10) + case v.IsInt32(): + return strconv.FormatInt(int64(v.Int32()), 10) + case v.IsInt64(): + return strconv.FormatInt(v.Int64(), 10) + case v.IsUint(): + return strconv.FormatUint(uint64(v.Uint()), 10) + case v.IsUint8(): + return strconv.FormatUint(uint64(v.Uint8()), 10) + case v.IsUint16(): + return strconv.FormatUint(uint64(v.Uint16()), 10) + case v.IsUint32(): + return strconv.FormatUint(uint64(v.Uint32()), 10) + case v.IsUint64(): + return strconv.FormatUint(v.Uint64(), 10) + } + return fmt.Sprintf("%#v", v.Data()) +} diff --git a/vendor/github.com/stretchr/testify/mock/doc.go b/vendor/github.com/stretchr/testify/mock/doc.go new file mode 100644 index 000000000..7324128ef --- /dev/null +++ b/vendor/github.com/stretchr/testify/mock/doc.go @@ -0,0 +1,44 @@ +// Package mock provides a system by which it is possible to mock your objects +// and verify calls are happening as expected. +// +// Example Usage +// +// The mock package provides an object, Mock, that tracks activity on another object. It is usually +// embedded into a test object as shown below: +// +// type MyTestObject struct { +// // add a Mock object instance +// mock.Mock +// +// // other fields go here as normal +// } +// +// When implementing the methods of an interface, you wire your functions up +// to call the Mock.Called(args...) method, and return the appropriate values. +// +// For example, to mock a method that saves the name and age of a person and returns +// the year of their birth or an error, you might write this: +// +// func (o *MyTestObject) SavePersonDetails(firstname, lastname string, age int) (int, error) { +// args := o.Called(firstname, lastname, age) +// return args.Int(0), args.Error(1) +// } +// +// The Int, Error and Bool methods are examples of strongly typed getters that take the argument +// index position. Given this argument list: +// +// (12, true, "Something") +// +// You could read them out strongly typed like this: +// +// args.Int(0) +// args.Bool(1) +// args.String(2) +// +// For objects of your own type, use the generic Arguments.Get(index) method and make a type assertion: +// +// return args.Get(0).(*MyObject), args.Get(1).(*AnotherObjectOfMine) +// +// This may cause a panic if the object you are getting is nil (the type assertion will fail), in those +// cases you should check for nil first. +package mock diff --git a/vendor/github.com/stretchr/testify/mock/mock.go b/vendor/github.com/stretchr/testify/mock/mock.go new file mode 100644 index 000000000..58e0798da --- /dev/null +++ b/vendor/github.com/stretchr/testify/mock/mock.go @@ -0,0 +1,917 @@ +package mock + +import ( + "errors" + "fmt" + "reflect" + "regexp" + "runtime" + "strings" + "sync" + "time" + + "github.com/davecgh/go-spew/spew" + "github.com/pmezard/go-difflib/difflib" + "github.com/stretchr/objx" + "github.com/stretchr/testify/assert" +) + +// TestingT is an interface wrapper around *testing.T +type TestingT interface { + Logf(format string, args ...interface{}) + Errorf(format string, args ...interface{}) + FailNow() +} + +/* + Call +*/ + +// Call represents a method call and is used for setting expectations, +// as well as recording activity. +type Call struct { + Parent *Mock + + // The name of the method that was or will be called. + Method string + + // Holds the arguments of the method. + Arguments Arguments + + // Holds the arguments that should be returned when + // this method is called. + ReturnArguments Arguments + + // Holds the caller info for the On() call + callerInfo []string + + // The number of times to return the return arguments when setting + // expectations. 0 means to always return the value. + Repeatability int + + // Amount of times this call has been called + totalCalls int + + // Call to this method can be optional + optional bool + + // Holds a channel that will be used to block the Return until it either + // receives a message or is closed. nil means it returns immediately. + WaitFor <-chan time.Time + + waitTime time.Duration + + // Holds a handler used to manipulate arguments content that are passed by + // reference. It's useful when mocking methods such as unmarshalers or + // decoders. + RunFn func(Arguments) +} + +func newCall(parent *Mock, methodName string, callerInfo []string, methodArguments ...interface{}) *Call { + return &Call{ + Parent: parent, + Method: methodName, + Arguments: methodArguments, + ReturnArguments: make([]interface{}, 0), + callerInfo: callerInfo, + Repeatability: 0, + WaitFor: nil, + RunFn: nil, + } +} + +func (c *Call) lock() { + c.Parent.mutex.Lock() +} + +func (c *Call) unlock() { + c.Parent.mutex.Unlock() +} + +// Return specifies the return arguments for the expectation. +// +// Mock.On("DoSomething").Return(errors.New("failed")) +func (c *Call) Return(returnArguments ...interface{}) *Call { + c.lock() + defer c.unlock() + + c.ReturnArguments = returnArguments + + return c +} + +// Once indicates that that the mock should only return the value once. +// +// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Once() +func (c *Call) Once() *Call { + return c.Times(1) +} + +// Twice indicates that that the mock should only return the value twice. +// +// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Twice() +func (c *Call) Twice() *Call { + return c.Times(2) +} + +// Times indicates that that the mock should only return the indicated number +// of times. +// +// Mock.On("MyMethod", arg1, arg2).Return(returnArg1, returnArg2).Times(5) +func (c *Call) Times(i int) *Call { + c.lock() + defer c.unlock() + c.Repeatability = i + return c +} + +// WaitUntil sets the channel that will block the mock's return until its closed +// or a message is received. +// +// Mock.On("MyMethod", arg1, arg2).WaitUntil(time.After(time.Second)) +func (c *Call) WaitUntil(w <-chan time.Time) *Call { + c.lock() + defer c.unlock() + c.WaitFor = w + return c +} + +// After sets how long to block until the call returns +// +// Mock.On("MyMethod", arg1, arg2).After(time.Second) +func (c *Call) After(d time.Duration) *Call { + c.lock() + defer c.unlock() + c.waitTime = d + return c +} + +// Run sets a handler to be called before returning. It can be used when +// mocking a method (such as an unmarshaler) that takes a pointer to a struct and +// sets properties in such struct +// +// Mock.On("Unmarshal", AnythingOfType("*map[string]interface{}").Return().Run(func(args Arguments) { +// arg := args.Get(0).(*map[string]interface{}) +// arg["foo"] = "bar" +// }) +func (c *Call) Run(fn func(args Arguments)) *Call { + c.lock() + defer c.unlock() + c.RunFn = fn + return c +} + +// Maybe allows the method call to be optional. Not calling an optional method +// will not cause an error while asserting expectations +func (c *Call) Maybe() *Call { + c.lock() + defer c.unlock() + c.optional = true + return c +} + +// On chains a new expectation description onto the mocked interface. This +// allows syntax like. +// +// Mock. +// On("MyMethod", 1).Return(nil). +// On("MyOtherMethod", 'a', 'b', 'c').Return(errors.New("Some Error")) +//go:noinline +func (c *Call) On(methodName string, arguments ...interface{}) *Call { + return c.Parent.On(methodName, arguments...) +} + +// Mock is the workhorse used to track activity on another object. +// For an example of its usage, refer to the "Example Usage" section at the top +// of this document. +type Mock struct { + // Represents the calls that are expected of + // an object. + ExpectedCalls []*Call + + // Holds the calls that were made to this mocked object. + Calls []Call + + // test is An optional variable that holds the test struct, to be used when an + // invalid mock call was made. + test TestingT + + // TestData holds any data that might be useful for testing. Testify ignores + // this data completely allowing you to do whatever you like with it. + testData objx.Map + + mutex sync.Mutex +} + +// TestData holds any data that might be useful for testing. Testify ignores +// this data completely allowing you to do whatever you like with it. +func (m *Mock) TestData() objx.Map { + + if m.testData == nil { + m.testData = make(objx.Map) + } + + return m.testData +} + +/* + Setting expectations +*/ + +// Test sets the test struct variable of the mock object +func (m *Mock) Test(t TestingT) { + m.mutex.Lock() + defer m.mutex.Unlock() + m.test = t +} + +// fail fails the current test with the given formatted format and args. +// In case that a test was defined, it uses the test APIs for failing a test, +// otherwise it uses panic. +func (m *Mock) fail(format string, args ...interface{}) { + m.mutex.Lock() + defer m.mutex.Unlock() + + if m.test == nil { + panic(fmt.Sprintf(format, args...)) + } + m.test.Errorf(format, args...) + m.test.FailNow() +} + +// On starts a description of an expectation of the specified method +// being called. +// +// Mock.On("MyMethod", arg1, arg2) +func (m *Mock) On(methodName string, arguments ...interface{}) *Call { + for _, arg := range arguments { + if v := reflect.ValueOf(arg); v.Kind() == reflect.Func { + panic(fmt.Sprintf("cannot use Func in expectations. Use mock.AnythingOfType(\"%T\")", arg)) + } + } + + m.mutex.Lock() + defer m.mutex.Unlock() + c := newCall(m, methodName, assert.CallerInfo(), arguments...) + m.ExpectedCalls = append(m.ExpectedCalls, c) + return c +} + +// /* +// Recording and responding to activity +// */ + +func (m *Mock) findExpectedCall(method string, arguments ...interface{}) (int, *Call) { + var expectedCall *Call + + for i, call := range m.ExpectedCalls { + if call.Method == method { + _, diffCount := call.Arguments.Diff(arguments) + if diffCount == 0 { + expectedCall = call + if call.Repeatability > -1 { + return i, call + } + } + } + } + + return -1, expectedCall +} + +func (m *Mock) findClosestCall(method string, arguments ...interface{}) (*Call, string) { + var diffCount int + var closestCall *Call + var err string + + for _, call := range m.expectedCalls() { + if call.Method == method { + + errInfo, tempDiffCount := call.Arguments.Diff(arguments) + if tempDiffCount < diffCount || diffCount == 0 { + diffCount = tempDiffCount + closestCall = call + err = errInfo + } + + } + } + + return closestCall, err +} + +func callString(method string, arguments Arguments, includeArgumentValues bool) string { + + var argValsString string + if includeArgumentValues { + var argVals []string + for argIndex, arg := range arguments { + argVals = append(argVals, fmt.Sprintf("%d: %#v", argIndex, arg)) + } + argValsString = fmt.Sprintf("\n\t\t%s", strings.Join(argVals, "\n\t\t")) + } + + return fmt.Sprintf("%s(%s)%s", method, arguments.String(), argValsString) +} + +// Called tells the mock object that a method has been called, and gets an array +// of arguments to return. Panics if the call is unexpected (i.e. not preceded by +// appropriate .On .Return() calls) +// If Call.WaitFor is set, blocks until the channel is closed or receives a message. +func (m *Mock) Called(arguments ...interface{}) Arguments { + // get the calling function's name + pc, _, _, ok := runtime.Caller(1) + if !ok { + panic("Couldn't get the caller information") + } + functionPath := runtime.FuncForPC(pc).Name() + //Next four lines are required to use GCCGO function naming conventions. + //For Ex: github_com_docker_libkv_store_mock.WatchTree.pN39_github_com_docker_libkv_store_mock.Mock + //uses interface information unlike golang github.com/docker/libkv/store/mock.(*Mock).WatchTree + //With GCCGO we need to remove interface information starting from pN

    . + re := regexp.MustCompile("\\.pN\\d+_") + if re.MatchString(functionPath) { + functionPath = re.Split(functionPath, -1)[0] + } + parts := strings.Split(functionPath, ".") + functionName := parts[len(parts)-1] + return m.MethodCalled(functionName, arguments...) +} + +// MethodCalled tells the mock object that the given method has been called, and gets +// an array of arguments to return. Panics if the call is unexpected (i.e. not preceded +// by appropriate .On .Return() calls) +// If Call.WaitFor is set, blocks until the channel is closed or receives a message. +func (m *Mock) MethodCalled(methodName string, arguments ...interface{}) Arguments { + m.mutex.Lock() + //TODO: could combine expected and closes in single loop + found, call := m.findExpectedCall(methodName, arguments...) + + if found < 0 { + // expected call found but it has already been called with repeatable times + if call != nil { + m.mutex.Unlock() + m.fail("\nassert: mock: The method has been called over %d times.\n\tEither do one more Mock.On(\"%s\").Return(...), or remove extra call.\n\tThis call was unexpected:\n\t\t%s\n\tat: %s", call.totalCalls, methodName, callString(methodName, arguments, true), assert.CallerInfo()) + } + // we have to fail here - because we don't know what to do + // as the return arguments. This is because: + // + // a) this is a totally unexpected call to this method, + // b) the arguments are not what was expected, or + // c) the developer has forgotten to add an accompanying On...Return pair. + closestCall, mismatch := m.findClosestCall(methodName, arguments...) + m.mutex.Unlock() + + if closestCall != nil { + m.fail("\n\nmock: Unexpected Method Call\n-----------------------------\n\n%s\n\nThe closest call I have is: \n\n%s\n\n%s\nDiff: %s", + callString(methodName, arguments, true), + callString(methodName, closestCall.Arguments, true), + diffArguments(closestCall.Arguments, arguments), + strings.TrimSpace(mismatch), + ) + } else { + m.fail("\nassert: mock: I don't know what to return because the method call was unexpected.\n\tEither do Mock.On(\"%s\").Return(...) first, or remove the %s() call.\n\tThis method was unexpected:\n\t\t%s\n\tat: %s", methodName, methodName, callString(methodName, arguments, true), assert.CallerInfo()) + } + } + + if call.Repeatability == 1 { + call.Repeatability = -1 + } else if call.Repeatability > 1 { + call.Repeatability-- + } + call.totalCalls++ + + // add the call + m.Calls = append(m.Calls, *newCall(m, methodName, assert.CallerInfo(), arguments...)) + m.mutex.Unlock() + + // block if specified + if call.WaitFor != nil { + <-call.WaitFor + } else { + time.Sleep(call.waitTime) + } + + m.mutex.Lock() + runFn := call.RunFn + m.mutex.Unlock() + + if runFn != nil { + runFn(arguments) + } + + m.mutex.Lock() + returnArgs := call.ReturnArguments + m.mutex.Unlock() + + return returnArgs +} + +/* + Assertions +*/ + +type assertExpectationser interface { + AssertExpectations(TestingT) bool +} + +// AssertExpectationsForObjects asserts that everything specified with On and Return +// of the specified objects was in fact called as expected. +// +// Calls may have occurred in any order. +func AssertExpectationsForObjects(t TestingT, testObjects ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + for _, obj := range testObjects { + if m, ok := obj.(Mock); ok { + t.Logf("Deprecated mock.AssertExpectationsForObjects(myMock.Mock) use mock.AssertExpectationsForObjects(myMock)") + obj = &m + } + m := obj.(assertExpectationser) + if !m.AssertExpectations(t) { + t.Logf("Expectations didn't match for Mock: %+v", reflect.TypeOf(m)) + return false + } + } + return true +} + +// AssertExpectations asserts that everything specified with On and Return was +// in fact called as expected. Calls may have occurred in any order. +func (m *Mock) AssertExpectations(t TestingT) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + m.mutex.Lock() + defer m.mutex.Unlock() + var somethingMissing bool + var failedExpectations int + + // iterate through each expectation + expectedCalls := m.expectedCalls() + for _, expectedCall := range expectedCalls { + if !expectedCall.optional && !m.methodWasCalled(expectedCall.Method, expectedCall.Arguments) && expectedCall.totalCalls == 0 { + somethingMissing = true + failedExpectations++ + t.Logf("FAIL:\t%s(%s)\n\t\tat: %s", expectedCall.Method, expectedCall.Arguments.String(), expectedCall.callerInfo) + } else { + if expectedCall.Repeatability > 0 { + somethingMissing = true + failedExpectations++ + t.Logf("FAIL:\t%s(%s)\n\t\tat: %s", expectedCall.Method, expectedCall.Arguments.String(), expectedCall.callerInfo) + } else { + t.Logf("PASS:\t%s(%s)", expectedCall.Method, expectedCall.Arguments.String()) + } + } + } + + if somethingMissing { + t.Errorf("FAIL: %d out of %d expectation(s) were met.\n\tThe code you are testing needs to make %d more call(s).\n\tat: %s", len(expectedCalls)-failedExpectations, len(expectedCalls), failedExpectations, assert.CallerInfo()) + } + + return !somethingMissing +} + +// AssertNumberOfCalls asserts that the method was called expectedCalls times. +func (m *Mock) AssertNumberOfCalls(t TestingT, methodName string, expectedCalls int) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + m.mutex.Lock() + defer m.mutex.Unlock() + var actualCalls int + for _, call := range m.calls() { + if call.Method == methodName { + actualCalls++ + } + } + return assert.Equal(t, expectedCalls, actualCalls, fmt.Sprintf("Expected number of calls (%d) does not match the actual number of calls (%d).", expectedCalls, actualCalls)) +} + +// AssertCalled asserts that the method was called. +// It can produce a false result when an argument is a pointer type and the underlying value changed after calling the mocked method. +func (m *Mock) AssertCalled(t TestingT, methodName string, arguments ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + m.mutex.Lock() + defer m.mutex.Unlock() + if !m.methodWasCalled(methodName, arguments) { + var calledWithArgs []string + for _, call := range m.calls() { + calledWithArgs = append(calledWithArgs, fmt.Sprintf("%v", call.Arguments)) + } + if len(calledWithArgs) == 0 { + return assert.Fail(t, "Should have called with given arguments", + fmt.Sprintf("Expected %q to have been called with:\n%v\nbut no actual calls happened", methodName, arguments)) + } + return assert.Fail(t, "Should have called with given arguments", + fmt.Sprintf("Expected %q to have been called with:\n%v\nbut actual calls were:\n %v", methodName, arguments, strings.Join(calledWithArgs, "\n"))) + } + return true +} + +// AssertNotCalled asserts that the method was not called. +// It can produce a false result when an argument is a pointer type and the underlying value changed after calling the mocked method. +func (m *Mock) AssertNotCalled(t TestingT, methodName string, arguments ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + m.mutex.Lock() + defer m.mutex.Unlock() + if m.methodWasCalled(methodName, arguments) { + return assert.Fail(t, "Should not have called with given arguments", + fmt.Sprintf("Expected %q to not have been called with:\n%v\nbut actually it was.", methodName, arguments)) + } + return true +} + +func (m *Mock) methodWasCalled(methodName string, expected []interface{}) bool { + for _, call := range m.calls() { + if call.Method == methodName { + + _, differences := Arguments(expected).Diff(call.Arguments) + + if differences == 0 { + // found the expected call + return true + } + + } + } + // we didn't find the expected call + return false +} + +func (m *Mock) expectedCalls() []*Call { + return append([]*Call{}, m.ExpectedCalls...) +} + +func (m *Mock) calls() []Call { + return append([]Call{}, m.Calls...) +} + +/* + Arguments +*/ + +// Arguments holds an array of method arguments or return values. +type Arguments []interface{} + +const ( + // Anything is used in Diff and Assert when the argument being tested + // shouldn't be taken into consideration. + Anything = "mock.Anything" +) + +// AnythingOfTypeArgument is a string that contains the type of an argument +// for use when type checking. Used in Diff and Assert. +type AnythingOfTypeArgument string + +// AnythingOfType returns an AnythingOfTypeArgument object containing the +// name of the type to check for. Used in Diff and Assert. +// +// For example: +// Assert(t, AnythingOfType("string"), AnythingOfType("int")) +func AnythingOfType(t string) AnythingOfTypeArgument { + return AnythingOfTypeArgument(t) +} + +// IsTypeArgument is a struct that contains the type of an argument +// for use when type checking. This is an alternative to AnythingOfType. +// Used in Diff and Assert. +type IsTypeArgument struct { + t interface{} +} + +// IsType returns an IsTypeArgument object containing the type to check for. +// You can provide a zero-value of the type to check. This is an +// alternative to AnythingOfType. Used in Diff and Assert. +// +// For example: +// Assert(t, IsType(""), IsType(0)) +func IsType(t interface{}) *IsTypeArgument { + return &IsTypeArgument{t: t} +} + +// argumentMatcher performs custom argument matching, returning whether or +// not the argument is matched by the expectation fixture function. +type argumentMatcher struct { + // fn is a function which accepts one argument, and returns a bool. + fn reflect.Value +} + +func (f argumentMatcher) Matches(argument interface{}) bool { + expectType := f.fn.Type().In(0) + expectTypeNilSupported := false + switch expectType.Kind() { + case reflect.Interface, reflect.Chan, reflect.Func, reflect.Map, reflect.Slice, reflect.Ptr: + expectTypeNilSupported = true + } + + argType := reflect.TypeOf(argument) + var arg reflect.Value + if argType == nil { + arg = reflect.New(expectType).Elem() + } else { + arg = reflect.ValueOf(argument) + } + + if argType == nil && !expectTypeNilSupported { + panic(errors.New("attempting to call matcher with nil for non-nil expected type")) + } + if argType == nil || argType.AssignableTo(expectType) { + result := f.fn.Call([]reflect.Value{arg}) + return result[0].Bool() + } + return false +} + +func (f argumentMatcher) String() string { + return fmt.Sprintf("func(%s) bool", f.fn.Type().In(0).Name()) +} + +// MatchedBy can be used to match a mock call based on only certain properties +// from a complex struct or some calculation. It takes a function that will be +// evaluated with the called argument and will return true when there's a match +// and false otherwise. +// +// Example: +// m.On("Do", MatchedBy(func(req *http.Request) bool { return req.Host == "example.com" })) +// +// |fn|, must be a function accepting a single argument (of the expected type) +// which returns a bool. If |fn| doesn't match the required signature, +// MatchedBy() panics. +func MatchedBy(fn interface{}) argumentMatcher { + fnType := reflect.TypeOf(fn) + + if fnType.Kind() != reflect.Func { + panic(fmt.Sprintf("assert: arguments: %s is not a func", fn)) + } + if fnType.NumIn() != 1 { + panic(fmt.Sprintf("assert: arguments: %s does not take exactly one argument", fn)) + } + if fnType.NumOut() != 1 || fnType.Out(0).Kind() != reflect.Bool { + panic(fmt.Sprintf("assert: arguments: %s does not return a bool", fn)) + } + + return argumentMatcher{fn: reflect.ValueOf(fn)} +} + +// Get Returns the argument at the specified index. +func (args Arguments) Get(index int) interface{} { + if index+1 > len(args) { + panic(fmt.Sprintf("assert: arguments: Cannot call Get(%d) because there are %d argument(s).", index, len(args))) + } + return args[index] +} + +// Is gets whether the objects match the arguments specified. +func (args Arguments) Is(objects ...interface{}) bool { + for i, obj := range args { + if obj != objects[i] { + return false + } + } + return true +} + +// Diff gets a string describing the differences between the arguments +// and the specified objects. +// +// Returns the diff string and number of differences found. +func (args Arguments) Diff(objects []interface{}) (string, int) { + //TODO: could return string as error and nil for No difference + + var output = "\n" + var differences int + + var maxArgCount = len(args) + if len(objects) > maxArgCount { + maxArgCount = len(objects) + } + + for i := 0; i < maxArgCount; i++ { + var actual, expected interface{} + var actualFmt, expectedFmt string + + if len(objects) <= i { + actual = "(Missing)" + actualFmt = "(Missing)" + } else { + actual = objects[i] + actualFmt = fmt.Sprintf("(%[1]T=%[1]v)", actual) + } + + if len(args) <= i { + expected = "(Missing)" + expectedFmt = "(Missing)" + } else { + expected = args[i] + expectedFmt = fmt.Sprintf("(%[1]T=%[1]v)", expected) + } + + if matcher, ok := expected.(argumentMatcher); ok { + if matcher.Matches(actual) { + output = fmt.Sprintf("%s\t%d: PASS: %s matched by %s\n", output, i, actualFmt, matcher) + } else { + differences++ + output = fmt.Sprintf("%s\t%d: FAIL: %s not matched by %s\n", output, i, actualFmt, matcher) + } + } else if reflect.TypeOf(expected) == reflect.TypeOf((*AnythingOfTypeArgument)(nil)).Elem() { + + // type checking + if reflect.TypeOf(actual).Name() != string(expected.(AnythingOfTypeArgument)) && reflect.TypeOf(actual).String() != string(expected.(AnythingOfTypeArgument)) { + // not match + differences++ + output = fmt.Sprintf("%s\t%d: FAIL: type %s != type %s - %s\n", output, i, expected, reflect.TypeOf(actual).Name(), actualFmt) + } + + } else if reflect.TypeOf(expected) == reflect.TypeOf((*IsTypeArgument)(nil)) { + t := expected.(*IsTypeArgument).t + if reflect.TypeOf(t) != reflect.TypeOf(actual) { + differences++ + output = fmt.Sprintf("%s\t%d: FAIL: type %s != type %s - %s\n", output, i, reflect.TypeOf(t).Name(), reflect.TypeOf(actual).Name(), actualFmt) + } + } else { + + // normal checking + + if assert.ObjectsAreEqual(expected, Anything) || assert.ObjectsAreEqual(actual, Anything) || assert.ObjectsAreEqual(actual, expected) { + // match + output = fmt.Sprintf("%s\t%d: PASS: %s == %s\n", output, i, actualFmt, expectedFmt) + } else { + // not match + differences++ + output = fmt.Sprintf("%s\t%d: FAIL: %s != %s\n", output, i, actualFmt, expectedFmt) + } + } + + } + + if differences == 0 { + return "No differences.", differences + } + + return output, differences + +} + +// Assert compares the arguments with the specified objects and fails if +// they do not exactly match. +func (args Arguments) Assert(t TestingT, objects ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + + // get the differences + diff, diffCount := args.Diff(objects) + + if diffCount == 0 { + return true + } + + // there are differences... report them... + t.Logf(diff) + t.Errorf("%sArguments do not match.", assert.CallerInfo()) + + return false + +} + +// String gets the argument at the specified index. Panics if there is no argument, or +// if the argument is of the wrong type. +// +// If no index is provided, String() returns a complete string representation +// of the arguments. +func (args Arguments) String(indexOrNil ...int) string { + + if len(indexOrNil) == 0 { + // normal String() method - return a string representation of the args + var argsStr []string + for _, arg := range args { + argsStr = append(argsStr, fmt.Sprintf("%s", reflect.TypeOf(arg))) + } + return strings.Join(argsStr, ",") + } else if len(indexOrNil) == 1 { + // Index has been specified - get the argument at that index + var index = indexOrNil[0] + var s string + var ok bool + if s, ok = args.Get(index).(string); !ok { + panic(fmt.Sprintf("assert: arguments: String(%d) failed because object wasn't correct type: %s", index, args.Get(index))) + } + return s + } + + panic(fmt.Sprintf("assert: arguments: Wrong number of arguments passed to String. Must be 0 or 1, not %d", len(indexOrNil))) + +} + +// Int gets the argument at the specified index. Panics if there is no argument, or +// if the argument is of the wrong type. +func (args Arguments) Int(index int) int { + var s int + var ok bool + if s, ok = args.Get(index).(int); !ok { + panic(fmt.Sprintf("assert: arguments: Int(%d) failed because object wasn't correct type: %v", index, args.Get(index))) + } + return s +} + +// Error gets the argument at the specified index. Panics if there is no argument, or +// if the argument is of the wrong type. +func (args Arguments) Error(index int) error { + obj := args.Get(index) + var s error + var ok bool + if obj == nil { + return nil + } + if s, ok = obj.(error); !ok { + panic(fmt.Sprintf("assert: arguments: Error(%d) failed because object wasn't correct type: %v", index, args.Get(index))) + } + return s +} + +// Bool gets the argument at the specified index. Panics if there is no argument, or +// if the argument is of the wrong type. +func (args Arguments) Bool(index int) bool { + var s bool + var ok bool + if s, ok = args.Get(index).(bool); !ok { + panic(fmt.Sprintf("assert: arguments: Bool(%d) failed because object wasn't correct type: %v", index, args.Get(index))) + } + return s +} + +func typeAndKind(v interface{}) (reflect.Type, reflect.Kind) { + t := reflect.TypeOf(v) + k := t.Kind() + + if k == reflect.Ptr { + t = t.Elem() + k = t.Kind() + } + return t, k +} + +func diffArguments(expected Arguments, actual Arguments) string { + if len(expected) != len(actual) { + return fmt.Sprintf("Provided %v arguments, mocked for %v arguments", len(expected), len(actual)) + } + + for x := range expected { + if diffString := diff(expected[x], actual[x]); diffString != "" { + return fmt.Sprintf("Difference found in argument %v:\n\n%s", x, diffString) + } + } + + return "" +} + +// diff returns a diff of both values as long as both are of the same type and +// are a struct, map, slice or array. Otherwise it returns an empty string. +func diff(expected interface{}, actual interface{}) string { + if expected == nil || actual == nil { + return "" + } + + et, ek := typeAndKind(expected) + at, _ := typeAndKind(actual) + + if et != at { + return "" + } + + if ek != reflect.Struct && ek != reflect.Map && ek != reflect.Slice && ek != reflect.Array { + return "" + } + + e := spewConfig.Sdump(expected) + a := spewConfig.Sdump(actual) + + diff, _ := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{ + A: difflib.SplitLines(e), + B: difflib.SplitLines(a), + FromFile: "Expected", + FromDate: "", + ToFile: "Actual", + ToDate: "", + Context: 1, + }) + + return diff +} + +var spewConfig = spew.ConfigState{ + Indent: " ", + DisablePointerAddresses: true, + DisableCapacities: true, + SortKeys: true, +} + +type tHelper interface { + Helper() +} diff --git a/vendor/github.com/subosito/gotenv/.env b/vendor/github.com/subosito/gotenv/.env new file mode 100644 index 000000000..6405eca71 --- /dev/null +++ b/vendor/github.com/subosito/gotenv/.env @@ -0,0 +1 @@ +HELLO=world diff --git a/vendor/github.com/subosito/gotenv/.env.invalid b/vendor/github.com/subosito/gotenv/.env.invalid new file mode 100644 index 000000000..016d5e0ce --- /dev/null +++ b/vendor/github.com/subosito/gotenv/.env.invalid @@ -0,0 +1 @@ +lol$wut diff --git a/vendor/github.com/subosito/gotenv/.gitignore b/vendor/github.com/subosito/gotenv/.gitignore new file mode 100644 index 000000000..2b8d45610 --- /dev/null +++ b/vendor/github.com/subosito/gotenv/.gitignore @@ -0,0 +1,3 @@ +*.test +*.out +annotate.json diff --git a/vendor/github.com/subosito/gotenv/.travis.yml b/vendor/github.com/subosito/gotenv/.travis.yml new file mode 100644 index 000000000..3370d5f40 --- /dev/null +++ b/vendor/github.com/subosito/gotenv/.travis.yml @@ -0,0 +1,10 @@ +language: go +go: + - 1.x +os: + - linux + - osx +script: + - go test -test.v -coverprofile=coverage.out -covermode=count +after_success: + - bash <(curl -s https://codecov.io/bash) diff --git a/vendor/github.com/subosito/gotenv/CHANGELOG.md b/vendor/github.com/subosito/gotenv/CHANGELOG.md new file mode 100644 index 000000000..67f687382 --- /dev/null +++ b/vendor/github.com/subosito/gotenv/CHANGELOG.md @@ -0,0 +1,47 @@ +# Changelog + +## [1.2.0] - 2019-08-03 + +### Added + +- Add `Must` helper to raise an error as panic. It can be used with `Load` and `OverLoad`. +- Add more tests to be 100% coverage. +- Add CHANGELOG +- Add more OS for the test: OSX and Windows + +### Changed + +- Reduce complexity and improve source code for having `A+` score in [goreportcard](https://goreportcard.com/report/github.com/subosito/gotenv). +- Updated README with mentions to all available functions + +### Removed + +- Remove `ErrFormat` +- Remove `MustLoad` and `MustOverload`, replaced with `Must` helper. + +## [1.1.1] - 2018-06-05 + +### Changed + +- Replace `os.Getenv` with `os.LookupEnv` to ensure that the environment variable is not set, by [radding](https://github.com/radding) + +## [1.1.0] - 2017-03-20 + +### Added + +- Supports carriage return in env +- Handle files with UTF-8 BOM + +### Changed + +- Whitespace handling + +### Fixed + +- Incorrect variable expansion +- Handling escaped '$' characters + +## [1.0.0] - 2014-10-05 + +First stable release. + diff --git a/vendor/github.com/subosito/gotenv/LICENSE b/vendor/github.com/subosito/gotenv/LICENSE new file mode 100644 index 000000000..f64ccaedc --- /dev/null +++ b/vendor/github.com/subosito/gotenv/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2013 Alif Rachmawadi + +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. diff --git a/vendor/github.com/subosito/gotenv/README.md b/vendor/github.com/subosito/gotenv/README.md new file mode 100644 index 000000000..d610cdf0b --- /dev/null +++ b/vendor/github.com/subosito/gotenv/README.md @@ -0,0 +1,131 @@ +# gotenv + +[![Build Status](https://travis-ci.org/subosito/gotenv.svg?branch=master)](https://travis-ci.org/subosito/gotenv) +[![Build status](https://ci.appveyor.com/api/projects/status/wb2e075xkfl0m0v2/branch/master?svg=true)](https://ci.appveyor.com/project/subosito/gotenv/branch/master) +[![Coverage Status](https://badgen.net/codecov/c/github/subosito/gotenv)](https://codecov.io/gh/subosito/gotenv) +[![Go Report Card](https://goreportcard.com/badge/github.com/subosito/gotenv)](https://goreportcard.com/report/github.com/subosito/gotenv) +[![GoDoc](https://godoc.org/github.com/subosito/gotenv?status.svg)](https://godoc.org/github.com/subosito/gotenv) + +Load environment variables dynamically in Go. + +## Usage + +Put the gotenv package on your `import` statement: + +```go +import "github.com/subosito/gotenv" +``` + +To modify your app environment variables, `gotenv` expose 2 main functions: + +- `gotenv.Load` +- `gotenv.Apply` + +By default, `gotenv.Load` will look for a file called `.env` in the current working directory. + +Behind the scene, it will then load `.env` file and export the valid variables to the environment variables. Make sure you call the method as soon as possible to ensure it loads all variables, say, put it on `init()` function. + +Once loaded you can use `os.Getenv()` to get the value of the variable. + +Let's say you have `.env` file: + +``` +APP_ID=1234567 +APP_SECRET=abcdef +``` + +Here's the example of your app: + +```go +package main + +import ( + "github.com/subosito/gotenv" + "log" + "os" +) + +func init() { + gotenv.Load() +} + +func main() { + log.Println(os.Getenv("APP_ID")) // "1234567" + log.Println(os.Getenv("APP_SECRET")) // "abcdef" +} +``` + +You can also load other than `.env` file if you wish. Just supply filenames when calling `Load()`. It will load them in order and the first value set for a variable will win.: + +```go +gotenv.Load(".env.production", "credentials") +``` + +While `gotenv.Load` loads entries from `.env` file, `gotenv.Apply` allows you to use any `io.Reader`: + +```go +gotenv.Apply(strings.NewReader("APP_ID=1234567")) + +log.Println(os.Getenv("APP_ID")) +// Output: "1234567" +``` + +Both `gotenv.Load` and `gotenv.Apply` **DO NOT** overrides existing environment variables. If you want to override existing ones, you can see section below. + +### Environment Overrides + +Besides above functions, `gotenv` also provides another functions that overrides existing: + +- `gotenv.OverLoad` +- `gotenv.OverApply` + + +Here's the example of this overrides behavior: + +```go +os.Setenv("HELLO", "world") + +// NOTE: using Apply existing value will be reserved +gotenv.Apply(strings.NewReader("HELLO=universe")) +fmt.Println(os.Getenv("HELLO")) +// Output: "world" + +// NOTE: using OverApply existing value will be overridden +gotenv.OverApply(strings.NewReader("HELLO=universe")) +fmt.Println(os.Getenv("HELLO")) +// Output: "universe" +``` + +### Throw a Panic + +Both `gotenv.Load` and `gotenv.OverLoad` returns an error on something wrong occurred, like your env file is not exist, and so on. To make it easier to use, `gotenv` also provides `gotenv.Must` helper, to let it panic when an error returned. + +```go +err := gotenv.Load(".env-is-not-exist") +fmt.Println("error", err) +// error: open .env-is-not-exist: no such file or directory + +gotenv.Must(gotenv.Load, ".env-is-not-exist") +// it will throw a panic +// panic: open .env-is-not-exist: no such file or directory +``` + +### Another Scenario + +Just in case you want to parse environment variables from any `io.Reader`, gotenv keeps its `Parse` and `StrictParse` function as public API so you can use that. + +```go +// import "strings" + +pairs := gotenv.Parse(strings.NewReader("FOO=test\nBAR=$FOO")) +// gotenv.Env{"FOO": "test", "BAR": "test"} + +err, pairs = gotenv.StrictParse(strings.NewReader(`FOO="bar"`)) +// gotenv.Env{"FOO": "bar"} +``` + +`Parse` ignores invalid lines and returns `Env` of valid environment variables, while `StrictParse` returns an error for invalid lines. + +## Notes + +The gotenv package is a Go port of [`dotenv`](https://github.com/bkeepers/dotenv) project with some additions made for Go. For general features, it aims to be compatible as close as possible. diff --git a/vendor/github.com/subosito/gotenv/appveyor.yml b/vendor/github.com/subosito/gotenv/appveyor.yml new file mode 100644 index 000000000..33b4c4046 --- /dev/null +++ b/vendor/github.com/subosito/gotenv/appveyor.yml @@ -0,0 +1,9 @@ +build: off +clone_folder: c:\gopath\src\github.com\subosito\gotenv +environment: + GOPATH: c:\gopath +stack: go 1.10 +before_test: + - go get -t +test_script: + - go test -v -cover -race diff --git a/vendor/github.com/subosito/gotenv/gotenv.go b/vendor/github.com/subosito/gotenv/gotenv.go new file mode 100644 index 000000000..745a34489 --- /dev/null +++ b/vendor/github.com/subosito/gotenv/gotenv.go @@ -0,0 +1,265 @@ +// Package gotenv provides functionality to dynamically load the environment variables +package gotenv + +import ( + "bufio" + "fmt" + "io" + "os" + "regexp" + "strings" +) + +const ( + // Pattern for detecting valid line format + linePattern = `\A\s*(?:export\s+)?([\w\.]+)(?:\s*=\s*|:\s+?)('(?:\'|[^'])*'|"(?:\"|[^"])*"|[^#\n]+)?\s*(?:\s*\#.*)?\z` + + // Pattern for detecting valid variable within a value + variablePattern = `(\\)?(\$)(\{?([A-Z0-9_]+)?\}?)` +) + +// Env holds key/value pair of valid environment variable +type Env map[string]string + +/* +Load is a function to load a file or multiple files and then export the valid variables into environment variables if they do not exist. +When it's called with no argument, it will load `.env` file on the current path and set the environment variables. +Otherwise, it will loop over the filenames parameter and set the proper environment variables. +*/ +func Load(filenames ...string) error { + return loadenv(false, filenames...) +} + +/* +OverLoad is a function to load a file or multiple files and then export and override the valid variables into environment variables. +*/ +func OverLoad(filenames ...string) error { + return loadenv(true, filenames...) +} + +/* +Must is wrapper function that will panic when supplied function returns an error. +*/ +func Must(fn func(filenames ...string) error, filenames ...string) { + if err := fn(filenames...); err != nil { + panic(err.Error()) + } +} + +/* +Apply is a function to load an io Reader then export the valid variables into environment variables if they do not exist. +*/ +func Apply(r io.Reader) error { + return parset(r, false) +} + +/* +OverApply is a function to load an io Reader then export and override the valid variables into environment variables. +*/ +func OverApply(r io.Reader) error { + return parset(r, true) +} + +func loadenv(override bool, filenames ...string) error { + if len(filenames) == 0 { + filenames = []string{".env"} + } + + for _, filename := range filenames { + f, err := os.Open(filename) + if err != nil { + return err + } + + err = parset(f, override) + if err != nil { + return err + } + + f.Close() + } + + return nil +} + +// parse and set :) +func parset(r io.Reader, override bool) error { + env, err := StrictParse(r) + if err != nil { + return err + } + + for key, val := range env { + setenv(key, val, override) + } + + return nil +} + +func setenv(key, val string, override bool) { + if override { + os.Setenv(key, val) + } else { + if _, present := os.LookupEnv(key); !present { + os.Setenv(key, val) + } + } +} + +// Parse is a function to parse line by line any io.Reader supplied and returns the valid Env key/value pair of valid variables. +// It expands the value of a variable from the environment variable but does not set the value to the environment itself. +// This function is skipping any invalid lines and only processing the valid one. +func Parse(r io.Reader) Env { + env, _ := StrictParse(r) + return env +} + +// StrictParse is a function to parse line by line any io.Reader supplied and returns the valid Env key/value pair of valid variables. +// It expands the value of a variable from the environment variable but does not set the value to the environment itself. +// This function is returning an error if there are any invalid lines. +func StrictParse(r io.Reader) (Env, error) { + env := make(Env) + scanner := bufio.NewScanner(r) + + i := 1 + bom := string([]byte{239, 187, 191}) + + for scanner.Scan() { + line := scanner.Text() + + if i == 1 { + line = strings.TrimPrefix(line, bom) + } + + i++ + + err := parseLine(line, env) + if err != nil { + return env, err + } + } + + return env, nil +} + +func parseLine(s string, env Env) error { + rl := regexp.MustCompile(linePattern) + rm := rl.FindStringSubmatch(s) + + if len(rm) == 0 { + return checkFormat(s, env) + } + + key := rm[1] + val := rm[2] + + // determine if string has quote prefix + hdq := strings.HasPrefix(val, `"`) + + // determine if string has single quote prefix + hsq := strings.HasPrefix(val, `'`) + + // trim whitespace + val = strings.Trim(val, " ") + + // remove quotes '' or "" + rq := regexp.MustCompile(`\A(['"])(.*)(['"])\z`) + val = rq.ReplaceAllString(val, "$2") + + if hdq { + val = strings.Replace(val, `\n`, "\n", -1) + val = strings.Replace(val, `\r`, "\r", -1) + + // Unescape all characters except $ so variables can be escaped properly + re := regexp.MustCompile(`\\([^$])`) + val = re.ReplaceAllString(val, "$1") + } + + rv := regexp.MustCompile(variablePattern) + fv := func(s string) string { + return varReplacement(s, hsq, env) + } + + val = rv.ReplaceAllStringFunc(val, fv) + val = parseVal(val, env) + + env[key] = val + return nil +} + +func parseExport(st string, env Env) error { + if strings.HasPrefix(st, "export") { + vs := strings.SplitN(st, " ", 2) + + if len(vs) > 1 { + if _, ok := env[vs[1]]; !ok { + return fmt.Errorf("line `%s` has an unset variable", st) + } + } + } + + return nil +} + +func varReplacement(s string, hsq bool, env Env) string { + if strings.HasPrefix(s, "\\") { + return strings.TrimPrefix(s, "\\") + } + + if hsq { + return s + } + + sn := `(\$)(\{?([A-Z0-9_]+)\}?)` + rn := regexp.MustCompile(sn) + mn := rn.FindStringSubmatch(s) + + if len(mn) == 0 { + return s + } + + v := mn[3] + + replace, ok := env[v] + if !ok { + replace = os.Getenv(v) + } + + return replace +} + +func checkFormat(s string, env Env) error { + st := strings.TrimSpace(s) + + if (st == "") || strings.HasPrefix(st, "#") { + return nil + } + + if err := parseExport(st, env); err != nil { + return err + } + + return fmt.Errorf("line `%s` doesn't match format", s) +} + +func parseVal(val string, env Env) string { + if strings.Contains(val, "=") { + if !(val == "\n" || val == "\r") { + kv := strings.Split(val, "\n") + + if len(kv) == 1 { + kv = strings.Split(val, "\r") + } + + if len(kv) > 1 { + val = kv[0] + + for i := 1; i < len(kv); i++ { + parseLine(kv[i], env) + } + } + } + } + + return val +} diff --git a/vendor/github.com/urfave/cli/.travis.yml b/vendor/github.com/urfave/cli/.travis.yml deleted file mode 100644 index cf8d0980d..000000000 --- a/vendor/github.com/urfave/cli/.travis.yml +++ /dev/null @@ -1,27 +0,0 @@ -language: go -sudo: false -dist: trusty -osx_image: xcode8.3 -go: 1.8.x - -os: -- linux -- osx - -cache: - directories: - - node_modules - -before_script: -- go get github.com/urfave/gfmrun/... || true -- go get golang.org/x/tools/cmd/goimports -- if [ ! -f node_modules/.bin/markdown-toc ] ; then - npm install markdown-toc ; - fi - -script: -- ./runtests gen -- ./runtests vet -- ./runtests test -- ./runtests gfmrun -- ./runtests toc diff --git a/vendor/github.com/urfave/cli/CHANGELOG.md b/vendor/github.com/urfave/cli/CHANGELOG.md deleted file mode 100644 index 401eae5a2..000000000 --- a/vendor/github.com/urfave/cli/CHANGELOG.md +++ /dev/null @@ -1,435 +0,0 @@ -# Change Log - -**ATTN**: This project uses [semantic versioning](http://semver.org/). - -## [Unreleased] - -## 1.20.0 - 2017-08-10 - -### Fixed - -* `HandleExitCoder` is now correctly iterates over all errors in - a `MultiError`. The exit code is the exit code of the last error or `1` if - there are no `ExitCoder`s in the `MultiError`. -* Fixed YAML file loading on Windows (previously would fail validate the file path) -* Subcommand `Usage`, `Description`, `ArgsUsage`, `OnUsageError` correctly - propogated -* `ErrWriter` is now passed downwards through command structure to avoid the - need to redefine it -* Pass `Command` context into `OnUsageError` rather than parent context so that - all fields are avaiable -* Errors occuring in `Before` funcs are no longer double printed -* Use `UsageText` in the help templates for commands and subcommands if - defined; otherwise build the usage as before (was previously ignoring this - field) -* `IsSet` and `GlobalIsSet` now correctly return whether a flag is set if - a program calls `Set` or `GlobalSet` directly after flag parsing (would - previously only return `true` if the flag was set during parsing) - -### Changed - -* No longer exit the program on command/subcommand error if the error raised is - not an `OsExiter`. This exiting behavior was introduced in 1.19.0, but was - determined to be a regression in functionality. See [the - PR](https://github.com/urfave/cli/pull/595) for discussion. - -### Added - -* `CommandsByName` type was added to make it easy to sort `Command`s by name, - alphabetically -* `altsrc` now handles loading of string and int arrays from TOML -* Support for definition of custom help templates for `App` via - `CustomAppHelpTemplate` -* Support for arbitrary key/value fields on `App` to be used with - `CustomAppHelpTemplate` via `ExtraInfo` -* `HelpFlag`, `VersionFlag`, and `BashCompletionFlag` changed to explictly be - `cli.Flag`s allowing for the use of custom flags satisfying the `cli.Flag` - interface to be used. - - -## [1.19.1] - 2016-11-21 - -### Fixed - -- Fixes regression introduced in 1.19.0 where using an `ActionFunc` as - the `Action` for a command would cause it to error rather than calling the - function. Should not have a affected declarative cases using `func(c - *cli.Context) err)`. -- Shell completion now handles the case where the user specifies - `--generate-bash-completion` immediately after a flag that takes an argument. - Previously it call the application with `--generate-bash-completion` as the - flag value. - -## [1.19.0] - 2016-11-19 -### Added -- `FlagsByName` was added to make it easy to sort flags (e.g. `sort.Sort(cli.FlagsByName(app.Flags))`) -- A `Description` field was added to `App` for a more detailed description of - the application (similar to the existing `Description` field on `Command`) -- Flag type code generation via `go generate` -- Write to stderr and exit 1 if action returns non-nil error -- Added support for TOML to the `altsrc` loader -- `SkipArgReorder` was added to allow users to skip the argument reordering. - This is useful if you want to consider all "flags" after an argument as - arguments rather than flags (the default behavior of the stdlib `flag` - library). This is backported functionality from the [removal of the flag - reordering](https://github.com/urfave/cli/pull/398) in the unreleased version - 2 -- For formatted errors (those implementing `ErrorFormatter`), the errors will - be formatted during output. Compatible with `pkg/errors`. - -### Changed -- Raise minimum tested/supported Go version to 1.2+ - -### Fixed -- Consider empty environment variables as set (previously environment variables - with the equivalent of `""` would be skipped rather than their value used). -- Return an error if the value in a given environment variable cannot be parsed - as the flag type. Previously these errors were silently swallowed. -- Print full error when an invalid flag is specified (which includes the invalid flag) -- `App.Writer` defaults to `stdout` when `nil` -- If no action is specified on a command or app, the help is now printed instead of `panic`ing -- `App.Metadata` is initialized automatically now (previously was `nil` unless initialized) -- Correctly show help message if `-h` is provided to a subcommand -- `context.(Global)IsSet` now respects environment variables. Previously it - would return `false` if a flag was specified in the environment rather than - as an argument -- Removed deprecation warnings to STDERR to avoid them leaking to the end-user -- `altsrc`s import paths were updated to use `gopkg.in/urfave/cli.v1`. This - fixes issues that occurred when `gopkg.in/urfave/cli.v1` was imported as well - as `altsrc` where Go would complain that the types didn't match - -## [1.18.1] - 2016-08-28 -### Fixed -- Removed deprecation warnings to STDERR to avoid them leaking to the end-user (backported) - -## [1.18.0] - 2016-06-27 -### Added -- `./runtests` test runner with coverage tracking by default -- testing on OS X -- testing on Windows -- `UintFlag`, `Uint64Flag`, and `Int64Flag` types and supporting code - -### Changed -- Use spaces for alignment in help/usage output instead of tabs, making the - output alignment consistent regardless of tab width - -### Fixed -- Printing of command aliases in help text -- Printing of visible flags for both struct and struct pointer flags -- Display the `help` subcommand when using `CommandCategories` -- No longer swallows `panic`s that occur within the `Action`s themselves when - detecting the signature of the `Action` field - -## [1.17.1] - 2016-08-28 -### Fixed -- Removed deprecation warnings to STDERR to avoid them leaking to the end-user - -## [1.17.0] - 2016-05-09 -### Added -- Pluggable flag-level help text rendering via `cli.DefaultFlagStringFunc` -- `context.GlobalBoolT` was added as an analogue to `context.GlobalBool` -- Support for hiding commands by setting `Hidden: true` -- this will hide the - commands in help output - -### Changed -- `Float64Flag`, `IntFlag`, and `DurationFlag` default values are no longer - quoted in help text output. -- All flag types now include `(default: {value})` strings following usage when a - default value can be (reasonably) detected. -- `IntSliceFlag` and `StringSliceFlag` usage strings are now more consistent - with non-slice flag types -- Apps now exit with a code of 3 if an unknown subcommand is specified - (previously they printed "No help topic for...", but still exited 0. This - makes it easier to script around apps built using `cli` since they can trust - that a 0 exit code indicated a successful execution. -- cleanups based on [Go Report Card - feedback](https://goreportcard.com/report/github.com/urfave/cli) - -## [1.16.1] - 2016-08-28 -### Fixed -- Removed deprecation warnings to STDERR to avoid them leaking to the end-user - -## [1.16.0] - 2016-05-02 -### Added -- `Hidden` field on all flag struct types to omit from generated help text - -### Changed -- `BashCompletionFlag` (`--enable-bash-completion`) is now omitted from -generated help text via the `Hidden` field - -### Fixed -- handling of error values in `HandleAction` and `HandleExitCoder` - -## [1.15.0] - 2016-04-30 -### Added -- This file! -- Support for placeholders in flag usage strings -- `App.Metadata` map for arbitrary data/state management -- `Set` and `GlobalSet` methods on `*cli.Context` for altering values after -parsing. -- Support for nested lookup of dot-delimited keys in structures loaded from -YAML. - -### Changed -- The `App.Action` and `Command.Action` now prefer a return signature of -`func(*cli.Context) error`, as defined by `cli.ActionFunc`. If a non-nil -`error` is returned, there may be two outcomes: - - If the error fulfills `cli.ExitCoder`, then `os.Exit` will be called - automatically - - Else the error is bubbled up and returned from `App.Run` -- Specifying an `Action` with the legacy return signature of -`func(*cli.Context)` will produce a deprecation message to stderr -- Specifying an `Action` that is not a `func` type will produce a non-zero exit -from `App.Run` -- Specifying an `Action` func that has an invalid (input) signature will -produce a non-zero exit from `App.Run` - -### Deprecated -- -`cli.App.RunAndExitOnError`, which should now be done by returning an error -that fulfills `cli.ExitCoder` to `cli.App.Run`. -- the legacy signature for -`cli.App.Action` of `func(*cli.Context)`, which should now have a return -signature of `func(*cli.Context) error`, as defined by `cli.ActionFunc`. - -### Fixed -- Added missing `*cli.Context.GlobalFloat64` method - -## [1.14.0] - 2016-04-03 (backfilled 2016-04-25) -### Added -- Codebeat badge -- Support for categorization via `CategorizedHelp` and `Categories` on app. - -### Changed -- Use `filepath.Base` instead of `path.Base` in `Name` and `HelpName`. - -### Fixed -- Ensure version is not shown in help text when `HideVersion` set. - -## [1.13.0] - 2016-03-06 (backfilled 2016-04-25) -### Added -- YAML file input support. -- `NArg` method on context. - -## [1.12.0] - 2016-02-17 (backfilled 2016-04-25) -### Added -- Custom usage error handling. -- Custom text support in `USAGE` section of help output. -- Improved help messages for empty strings. -- AppVeyor CI configuration. - -### Changed -- Removed `panic` from default help printer func. -- De-duping and optimizations. - -### Fixed -- Correctly handle `Before`/`After` at command level when no subcommands. -- Case of literal `-` argument causing flag reordering. -- Environment variable hints on Windows. -- Docs updates. - -## [1.11.1] - 2015-12-21 (backfilled 2016-04-25) -### Changed -- Use `path.Base` in `Name` and `HelpName` -- Export `GetName` on flag types. - -### Fixed -- Flag parsing when skipping is enabled. -- Test output cleanup. -- Move completion check to account for empty input case. - -## [1.11.0] - 2015-11-15 (backfilled 2016-04-25) -### Added -- Destination scan support for flags. -- Testing against `tip` in Travis CI config. - -### Changed -- Go version in Travis CI config. - -### Fixed -- Removed redundant tests. -- Use correct example naming in tests. - -## [1.10.2] - 2015-10-29 (backfilled 2016-04-25) -### Fixed -- Remove unused var in bash completion. - -## [1.10.1] - 2015-10-21 (backfilled 2016-04-25) -### Added -- Coverage and reference logos in README. - -### Fixed -- Use specified values in help and version parsing. -- Only display app version and help message once. - -## [1.10.0] - 2015-10-06 (backfilled 2016-04-25) -### Added -- More tests for existing functionality. -- `ArgsUsage` at app and command level for help text flexibility. - -### Fixed -- Honor `HideHelp` and `HideVersion` in `App.Run`. -- Remove juvenile word from README. - -## [1.9.0] - 2015-09-08 (backfilled 2016-04-25) -### Added -- `FullName` on command with accompanying help output update. -- Set default `$PROG` in bash completion. - -### Changed -- Docs formatting. - -### Fixed -- Removed self-referential imports in tests. - -## [1.8.0] - 2015-06-30 (backfilled 2016-04-25) -### Added -- Support for `Copyright` at app level. -- `Parent` func at context level to walk up context lineage. - -### Fixed -- Global flag processing at top level. - -## [1.7.1] - 2015-06-11 (backfilled 2016-04-25) -### Added -- Aggregate errors from `Before`/`After` funcs. -- Doc comments on flag structs. -- Include non-global flags when checking version and help. -- Travis CI config updates. - -### Fixed -- Ensure slice type flags have non-nil values. -- Collect global flags from the full command hierarchy. -- Docs prose. - -## [1.7.0] - 2015-05-03 (backfilled 2016-04-25) -### Changed -- `HelpPrinter` signature includes output writer. - -### Fixed -- Specify go 1.1+ in docs. -- Set `Writer` when running command as app. - -## [1.6.0] - 2015-03-23 (backfilled 2016-04-25) -### Added -- Multiple author support. -- `NumFlags` at context level. -- `Aliases` at command level. - -### Deprecated -- `ShortName` at command level. - -### Fixed -- Subcommand help output. -- Backward compatible support for deprecated `Author` and `Email` fields. -- Docs regarding `Names`/`Aliases`. - -## [1.5.0] - 2015-02-20 (backfilled 2016-04-25) -### Added -- `After` hook func support at app and command level. - -### Fixed -- Use parsed context when running command as subcommand. -- Docs prose. - -## [1.4.1] - 2015-01-09 (backfilled 2016-04-25) -### Added -- Support for hiding `-h / --help` flags, but not `help` subcommand. -- Stop flag parsing after `--`. - -### Fixed -- Help text for generic flags to specify single value. -- Use double quotes in output for defaults. -- Use `ParseInt` instead of `ParseUint` for int environment var values. -- Use `0` as base when parsing int environment var values. - -## [1.4.0] - 2014-12-12 (backfilled 2016-04-25) -### Added -- Support for environment variable lookup "cascade". -- Support for `Stdout` on app for output redirection. - -### Fixed -- Print command help instead of app help in `ShowCommandHelp`. - -## [1.3.1] - 2014-11-13 (backfilled 2016-04-25) -### Added -- Docs and example code updates. - -### Changed -- Default `-v / --version` flag made optional. - -## [1.3.0] - 2014-08-10 (backfilled 2016-04-25) -### Added -- `FlagNames` at context level. -- Exposed `VersionPrinter` var for more control over version output. -- Zsh completion hook. -- `AUTHOR` section in default app help template. -- Contribution guidelines. -- `DurationFlag` type. - -## [1.2.0] - 2014-08-02 -### Added -- Support for environment variable defaults on flags plus tests. - -## [1.1.0] - 2014-07-15 -### Added -- Bash completion. -- Optional hiding of built-in help command. -- Optional skipping of flag parsing at command level. -- `Author`, `Email`, and `Compiled` metadata on app. -- `Before` hook func support at app and command level. -- `CommandNotFound` func support at app level. -- Command reference available on context. -- `GenericFlag` type. -- `Float64Flag` type. -- `BoolTFlag` type. -- `IsSet` flag helper on context. -- More flag lookup funcs at context level. -- More tests & docs. - -### Changed -- Help template updates to account for presence/absence of flags. -- Separated subcommand help template. -- Exposed `HelpPrinter` var for more control over help output. - -## [1.0.0] - 2013-11-01 -### Added -- `help` flag in default app flag set and each command flag set. -- Custom handling of argument parsing errors. -- Command lookup by name at app level. -- `StringSliceFlag` type and supporting `StringSlice` type. -- `IntSliceFlag` type and supporting `IntSlice` type. -- Slice type flag lookups by name at context level. -- Export of app and command help functions. -- More tests & docs. - -## 0.1.0 - 2013-07-22 -### Added -- Initial implementation. - -[Unreleased]: https://github.com/urfave/cli/compare/v1.18.0...HEAD -[1.18.0]: https://github.com/urfave/cli/compare/v1.17.0...v1.18.0 -[1.17.0]: https://github.com/urfave/cli/compare/v1.16.0...v1.17.0 -[1.16.0]: https://github.com/urfave/cli/compare/v1.15.0...v1.16.0 -[1.15.0]: https://github.com/urfave/cli/compare/v1.14.0...v1.15.0 -[1.14.0]: https://github.com/urfave/cli/compare/v1.13.0...v1.14.0 -[1.13.0]: https://github.com/urfave/cli/compare/v1.12.0...v1.13.0 -[1.12.0]: https://github.com/urfave/cli/compare/v1.11.1...v1.12.0 -[1.11.1]: https://github.com/urfave/cli/compare/v1.11.0...v1.11.1 -[1.11.0]: https://github.com/urfave/cli/compare/v1.10.2...v1.11.0 -[1.10.2]: https://github.com/urfave/cli/compare/v1.10.1...v1.10.2 -[1.10.1]: https://github.com/urfave/cli/compare/v1.10.0...v1.10.1 -[1.10.0]: https://github.com/urfave/cli/compare/v1.9.0...v1.10.0 -[1.9.0]: https://github.com/urfave/cli/compare/v1.8.0...v1.9.0 -[1.8.0]: https://github.com/urfave/cli/compare/v1.7.1...v1.8.0 -[1.7.1]: https://github.com/urfave/cli/compare/v1.7.0...v1.7.1 -[1.7.0]: https://github.com/urfave/cli/compare/v1.6.0...v1.7.0 -[1.6.0]: https://github.com/urfave/cli/compare/v1.5.0...v1.6.0 -[1.5.0]: https://github.com/urfave/cli/compare/v1.4.1...v1.5.0 -[1.4.1]: https://github.com/urfave/cli/compare/v1.4.0...v1.4.1 -[1.4.0]: https://github.com/urfave/cli/compare/v1.3.1...v1.4.0 -[1.3.1]: https://github.com/urfave/cli/compare/v1.3.0...v1.3.1 -[1.3.0]: https://github.com/urfave/cli/compare/v1.2.0...v1.3.0 -[1.2.0]: https://github.com/urfave/cli/compare/v1.1.0...v1.2.0 -[1.1.0]: https://github.com/urfave/cli/compare/v1.0.0...v1.1.0 -[1.0.0]: https://github.com/urfave/cli/compare/v0.1.0...v1.0.0 diff --git a/vendor/github.com/urfave/cli/README.md b/vendor/github.com/urfave/cli/README.md deleted file mode 100644 index 2bbbd8ea9..000000000 --- a/vendor/github.com/urfave/cli/README.md +++ /dev/null @@ -1,1381 +0,0 @@ -cli -=== - -[![Build Status](https://travis-ci.org/urfave/cli.svg?branch=master)](https://travis-ci.org/urfave/cli) -[![Windows Build Status](https://ci.appveyor.com/api/projects/status/rtgk5xufi932pb2v?svg=true)](https://ci.appveyor.com/project/urfave/cli) -[![GoDoc](https://godoc.org/github.com/urfave/cli?status.svg)](https://godoc.org/github.com/urfave/cli) -[![codebeat](https://codebeat.co/badges/0a8f30aa-f975-404b-b878-5fab3ae1cc5f)](https://codebeat.co/projects/github-com-urfave-cli) -[![Go Report Card](https://goreportcard.com/badge/urfave/cli)](https://goreportcard.com/report/urfave/cli) -[![top level coverage](https://gocover.io/_badge/github.com/urfave/cli?0 "top level coverage")](http://gocover.io/github.com/urfave/cli) / -[![altsrc coverage](https://gocover.io/_badge/github.com/urfave/cli/altsrc?0 "altsrc coverage")](http://gocover.io/github.com/urfave/cli/altsrc) - -**Notice:** This is the library formerly known as -`github.com/codegangsta/cli` -- Github will automatically redirect requests -to this repository, but we recommend updating your references for clarity. - -cli is a simple, fast, and fun package for building command line apps in Go. The -goal is to enable developers to write fast and distributable command line -applications in an expressive way. - - - -- [Overview](#overview) -- [Installation](#installation) - * [Supported platforms](#supported-platforms) - * [Using the `v2` branch](#using-the-v2-branch) - * [Pinning to the `v1` releases](#pinning-to-the-v1-releases) -- [Getting Started](#getting-started) -- [Examples](#examples) - * [Arguments](#arguments) - * [Flags](#flags) - + [Placeholder Values](#placeholder-values) - + [Alternate Names](#alternate-names) - + [Ordering](#ordering) - + [Values from the Environment](#values-from-the-environment) - + [Values from alternate input sources (YAML, TOML, and others)](#values-from-alternate-input-sources-yaml-toml-and-others) - * [Subcommands](#subcommands) - * [Subcommands categories](#subcommands-categories) - * [Exit code](#exit-code) - * [Bash Completion](#bash-completion) - + [Enabling](#enabling) - + [Distribution](#distribution) - + [Customization](#customization) - * [Generated Help Text](#generated-help-text) - + [Customization](#customization-1) - * [Version Flag](#version-flag) - + [Customization](#customization-2) - + [Full API Example](#full-api-example) -- [Contribution Guidelines](#contribution-guidelines) - - - -## Overview - -Command line apps are usually so tiny that there is absolutely no reason why -your code should *not* be self-documenting. Things like generating help text and -parsing command flags/options should not hinder productivity when writing a -command line app. - -**This is where cli comes into play.** cli makes command line programming fun, -organized, and expressive! - -## Installation - -Make sure you have a working Go environment. Go version 1.2+ is supported. [See -the install instructions for Go](http://golang.org/doc/install.html). - -To install cli, simply run: -``` -$ go get github.com/urfave/cli -``` - -Make sure your `PATH` includes the `$GOPATH/bin` directory so your commands can -be easily used: -``` -export PATH=$PATH:$GOPATH/bin -``` - -### Supported platforms - -cli is tested against multiple versions of Go on Linux, and against the latest -released version of Go on OS X and Windows. For full details, see -[`./.travis.yml`](./.travis.yml) and [`./appveyor.yml`](./appveyor.yml). - -### Using the `v2` branch - -**Warning**: The `v2` branch is currently unreleased and considered unstable. - -There is currently a long-lived branch named `v2` that is intended to land as -the new `master` branch once development there has settled down. The current -`master` branch (mirrored as `v1`) is being manually merged into `v2` on -an irregular human-based schedule, but generally if one wants to "upgrade" to -`v2` *now* and accept the volatility (read: "awesomeness") that comes along with -that, please use whatever version pinning of your preference, such as via -`gopkg.in`: - -``` -$ go get gopkg.in/urfave/cli.v2 -``` - -``` go -... -import ( - "gopkg.in/urfave/cli.v2" // imports as package "cli" -) -... -``` - -### Pinning to the `v1` releases - -Similarly to the section above describing use of the `v2` branch, if one wants -to avoid any unexpected compatibility pains once `v2` becomes `master`, then -pinning to `v1` is an acceptable option, e.g.: - -``` -$ go get gopkg.in/urfave/cli.v1 -``` - -``` go -... -import ( - "gopkg.in/urfave/cli.v1" // imports as package "cli" -) -... -``` - -This will pull the latest tagged `v1` release (e.g. `v1.18.1` at the time of writing). - -## Getting Started - -One of the philosophies behind cli is that an API should be playful and full of -discovery. So a cli app can be as little as one line of code in `main()`. - - -``` go -package main - -import ( - "os" - - "github.com/urfave/cli" -) - -func main() { - cli.NewApp().Run(os.Args) -} -``` - -This app will run and show help text, but is not very useful. Let's give an -action to execute and some help documentation: - - -``` go -package main - -import ( - "fmt" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - app.Name = "boom" - app.Usage = "make an explosive entrance" - app.Action = func(c *cli.Context) error { - fmt.Println("boom! I say!") - return nil - } - - app.Run(os.Args) -} -``` - -Running this already gives you a ton of functionality, plus support for things -like subcommands and flags, which are covered below. - -## Examples - -Being a programmer can be a lonely job. Thankfully by the power of automation -that is not the case! Let's create a greeter app to fend off our demons of -loneliness! - -Start by creating a directory named `greet`, and within it, add a file, -`greet.go` with the following code in it: - - -``` go -package main - -import ( - "fmt" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - app.Name = "greet" - app.Usage = "fight the loneliness!" - app.Action = func(c *cli.Context) error { - fmt.Println("Hello friend!") - return nil - } - - app.Run(os.Args) -} -``` - -Install our command to the `$GOPATH/bin` directory: - -``` -$ go install -``` - -Finally run our new command: - -``` -$ greet -Hello friend! -``` - -cli also generates neat help text: - -``` -$ greet help -NAME: - greet - fight the loneliness! - -USAGE: - greet [global options] command [command options] [arguments...] - -VERSION: - 0.0.0 - -COMMANDS: - help, h Shows a list of commands or help for one command - -GLOBAL OPTIONS - --version Shows version information -``` - -### Arguments - -You can lookup arguments by calling the `Args` function on `cli.Context`, e.g.: - - -``` go -package main - -import ( - "fmt" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - - app.Action = func(c *cli.Context) error { - fmt.Printf("Hello %q", c.Args().Get(0)) - return nil - } - - app.Run(os.Args) -} -``` - -### Flags - -Setting and querying flags is simple. - - -``` go -package main - -import ( - "fmt" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - - app.Flags = []cli.Flag { - cli.StringFlag{ - Name: "lang", - Value: "english", - Usage: "language for the greeting", - }, - } - - app.Action = func(c *cli.Context) error { - name := "Nefertiti" - if c.NArg() > 0 { - name = c.Args().Get(0) - } - if c.String("lang") == "spanish" { - fmt.Println("Hola", name) - } else { - fmt.Println("Hello", name) - } - return nil - } - - app.Run(os.Args) -} -``` - -You can also set a destination variable for a flag, to which the content will be -scanned. - - -``` go -package main - -import ( - "os" - "fmt" - - "github.com/urfave/cli" -) - -func main() { - var language string - - app := cli.NewApp() - - app.Flags = []cli.Flag { - cli.StringFlag{ - Name: "lang", - Value: "english", - Usage: "language for the greeting", - Destination: &language, - }, - } - - app.Action = func(c *cli.Context) error { - name := "someone" - if c.NArg() > 0 { - name = c.Args()[0] - } - if language == "spanish" { - fmt.Println("Hola", name) - } else { - fmt.Println("Hello", name) - } - return nil - } - - app.Run(os.Args) -} -``` - -See full list of flags at http://godoc.org/github.com/urfave/cli - -#### Placeholder Values - -Sometimes it's useful to specify a flag's value within the usage string itself. -Such placeholders are indicated with back quotes. - -For example this: - - -```go -package main - -import ( - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - - app.Flags = []cli.Flag{ - cli.StringFlag{ - Name: "config, c", - Usage: "Load configuration from `FILE`", - }, - } - - app.Run(os.Args) -} -``` - -Will result in help output like: - -``` ---config FILE, -c FILE Load configuration from FILE -``` - -Note that only the first placeholder is used. Subsequent back-quoted words will -be left as-is. - -#### Alternate Names - -You can set alternate (or short) names for flags by providing a comma-delimited -list for the `Name`. e.g. - - -``` go -package main - -import ( - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - - app.Flags = []cli.Flag { - cli.StringFlag{ - Name: "lang, l", - Value: "english", - Usage: "language for the greeting", - }, - } - - app.Run(os.Args) -} -``` - -That flag can then be set with `--lang spanish` or `-l spanish`. Note that -giving two different forms of the same flag in the same command invocation is an -error. - -#### Ordering - -Flags for the application and commands are shown in the order they are defined. -However, it's possible to sort them from outside this library by using `FlagsByName` -or `CommandsByName` with `sort`. - -For example this: - - -``` go -package main - -import ( - "os" - "sort" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - - app.Flags = []cli.Flag { - cli.StringFlag{ - Name: "lang, l", - Value: "english", - Usage: "Language for the greeting", - }, - cli.StringFlag{ - Name: "config, c", - Usage: "Load configuration from `FILE`", - }, - } - - app.Commands = []cli.Command{ - { - Name: "complete", - Aliases: []string{"c"}, - Usage: "complete a task on the list", - Action: func(c *cli.Context) error { - return nil - }, - }, - { - Name: "add", - Aliases: []string{"a"}, - Usage: "add a task to the list", - Action: func(c *cli.Context) error { - return nil - }, - }, - } - - sort.Sort(cli.FlagsByName(app.Flags)) - sort.Sort(cli.CommandsByName(app.Commands)) - - app.Run(os.Args) -} -``` - -Will result in help output like: - -``` ---config FILE, -c FILE Load configuration from FILE ---lang value, -l value Language for the greeting (default: "english") -``` - -#### Values from the Environment - -You can also have the default value set from the environment via `EnvVar`. e.g. - - -``` go -package main - -import ( - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - - app.Flags = []cli.Flag { - cli.StringFlag{ - Name: "lang, l", - Value: "english", - Usage: "language for the greeting", - EnvVar: "APP_LANG", - }, - } - - app.Run(os.Args) -} -``` - -The `EnvVar` may also be given as a comma-delimited "cascade", where the first -environment variable that resolves is used as the default. - - -``` go -package main - -import ( - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - - app.Flags = []cli.Flag { - cli.StringFlag{ - Name: "lang, l", - Value: "english", - Usage: "language for the greeting", - EnvVar: "LEGACY_COMPAT_LANG,APP_LANG,LANG", - }, - } - - app.Run(os.Args) -} -``` - -#### Values from alternate input sources (YAML, TOML, and others) - -There is a separate package altsrc that adds support for getting flag values -from other file input sources. - -Currently supported input source formats: -* YAML -* TOML - -In order to get values for a flag from an alternate input source the following -code would be added to wrap an existing cli.Flag like below: - -``` go - altsrc.NewIntFlag(cli.IntFlag{Name: "test"}) -``` - -Initialization must also occur for these flags. Below is an example initializing -getting data from a yaml file below. - -``` go - command.Before = altsrc.InitInputSourceWithContext(command.Flags, NewYamlSourceFromFlagFunc("load")) -``` - -The code above will use the "load" string as a flag name to get the file name of -a yaml file from the cli.Context. It will then use that file name to initialize -the yaml input source for any flags that are defined on that command. As a note -the "load" flag used would also have to be defined on the command flags in order -for this code snipped to work. - -Currently only the aboved specified formats are supported but developers can -add support for other input sources by implementing the -altsrc.InputSourceContext for their given sources. - -Here is a more complete sample of a command using YAML support: - - -``` go -package notmain - -import ( - "fmt" - "os" - - "github.com/urfave/cli" - "github.com/urfave/cli/altsrc" -) - -func main() { - app := cli.NewApp() - - flags := []cli.Flag{ - altsrc.NewIntFlag(cli.IntFlag{Name: "test"}), - cli.StringFlag{Name: "load"}, - } - - app.Action = func(c *cli.Context) error { - fmt.Println("yaml ist rad") - return nil - } - - app.Before = altsrc.InitInputSourceWithContext(flags, altsrc.NewYamlSourceFromFlagFunc("load")) - app.Flags = flags - - app.Run(os.Args) -} -``` - -### Subcommands - -Subcommands can be defined for a more git-like command line app. - - -```go -package main - -import ( - "fmt" - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - - app.Commands = []cli.Command{ - { - Name: "add", - Aliases: []string{"a"}, - Usage: "add a task to the list", - Action: func(c *cli.Context) error { - fmt.Println("added task: ", c.Args().First()) - return nil - }, - }, - { - Name: "complete", - Aliases: []string{"c"}, - Usage: "complete a task on the list", - Action: func(c *cli.Context) error { - fmt.Println("completed task: ", c.Args().First()) - return nil - }, - }, - { - Name: "template", - Aliases: []string{"t"}, - Usage: "options for task templates", - Subcommands: []cli.Command{ - { - Name: "add", - Usage: "add a new template", - Action: func(c *cli.Context) error { - fmt.Println("new task template: ", c.Args().First()) - return nil - }, - }, - { - Name: "remove", - Usage: "remove an existing template", - Action: func(c *cli.Context) error { - fmt.Println("removed task template: ", c.Args().First()) - return nil - }, - }, - }, - }, - } - - app.Run(os.Args) -} -``` - -### Subcommands categories - -For additional organization in apps that have many subcommands, you can -associate a category for each command to group them together in the help -output. - -E.g. - -```go -package main - -import ( - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - - app.Commands = []cli.Command{ - { - Name: "noop", - }, - { - Name: "add", - Category: "template", - }, - { - Name: "remove", - Category: "template", - }, - } - - app.Run(os.Args) -} -``` - -Will include: - -``` -COMMANDS: - noop - - Template actions: - add - remove -``` - -### Exit code - -Calling `App.Run` will not automatically call `os.Exit`, which means that by -default the exit code will "fall through" to being `0`. An explicit exit code -may be set by returning a non-nil error that fulfills `cli.ExitCoder`, *or* a -`cli.MultiError` that includes an error that fulfills `cli.ExitCoder`, e.g.: - -``` go -package main - -import ( - "os" - - "github.com/urfave/cli" -) - -func main() { - app := cli.NewApp() - app.Flags = []cli.Flag{ - cli.BoolTFlag{ - Name: "ginger-crouton", - Usage: "is it in the soup?", - }, - } - app.Action = func(ctx *cli.Context) error { - if !ctx.Bool("ginger-crouton") { - return cli.NewExitError("it is not in the soup", 86) - } - return nil - } - - app.Run(os.Args) -} -``` - -### Bash Completion - -You can enable completion commands by setting the `EnableBashCompletion` -flag on the `App` object. By default, this setting will only auto-complete to -show an app's subcommands, but you can write your own completion methods for -the App or its subcommands. - - -``` go -package main - -import ( - "fmt" - "os" - - "github.com/urfave/cli" -) - -func main() { - tasks := []string{"cook", "clean", "laundry", "eat", "sleep", "code"} - - app := cli.NewApp() - app.EnableBashCompletion = true - app.Commands = []cli.Command{ - { - Name: "complete", - Aliases: []string{"c"}, - Usage: "complete a task on the list", - Action: func(c *cli.Context) error { - fmt.Println("completed task: ", c.Args().First()) - return nil - }, - BashComplete: func(c *cli.Context) { - // This will complete if no args are passed - if c.NArg() > 0 { - return - } - for _, t := range tasks { - fmt.Println(t) - } - }, - }, - } - - app.Run(os.Args) -} -``` - -#### Enabling - -Source the `autocomplete/bash_autocomplete` file in your `.bashrc` file while -setting the `PROG` variable to the name of your program: - -`PROG=myprogram source /.../cli/autocomplete/bash_autocomplete` - -#### Distribution - -Copy `autocomplete/bash_autocomplete` into `/etc/bash_completion.d/` and rename -it to the name of the program you wish to add autocomplete support for (or -automatically install it there if you are distributing a package). Don't forget -to source the file to make it active in the current shell. - -``` -sudo cp src/bash_autocomplete /etc/bash_completion.d/ -source /etc/bash_completion.d/ -``` - -Alternatively, you can just document that users should source the generic -`autocomplete/bash_autocomplete` in their bash configuration with `$PROG` set -to the name of their program (as above). - -#### Customization - -The default bash completion flag (`--generate-bash-completion`) is defined as -`cli.BashCompletionFlag`, and may be redefined if desired, e.g.: - - -``` go -package main - -import ( - "os" - - "github.com/urfave/cli" -) - -func main() { - cli.BashCompletionFlag = cli.BoolFlag{ - Name: "compgen", - Hidden: true, - } - - app := cli.NewApp() - app.EnableBashCompletion = true - app.Commands = []cli.Command{ - { - Name: "wat", - }, - } - app.Run(os.Args) -} -``` - -### Generated Help Text - -The default help flag (`-h/--help`) is defined as `cli.HelpFlag` and is checked -by the cli internals in order to print generated help text for the app, command, -or subcommand, and break execution. - -#### Customization - -All of the help text generation may be customized, and at multiple levels. The -templates are exposed as variables `AppHelpTemplate`, `CommandHelpTemplate`, and -`SubcommandHelpTemplate` which may be reassigned or augmented, and full override -is possible by assigning a compatible func to the `cli.HelpPrinter` variable, -e.g.: - - -``` go -package main - -import ( - "fmt" - "io" - "os" - - "github.com/urfave/cli" -) - -func main() { - // EXAMPLE: Append to an existing template - cli.AppHelpTemplate = fmt.Sprintf(`%s - -WEBSITE: http://awesometown.example.com - -SUPPORT: support@awesometown.example.com - -`, cli.AppHelpTemplate) - - // EXAMPLE: Override a template - cli.AppHelpTemplate = `NAME: - {{.Name}} - {{.Usage}} -USAGE: - {{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}} - {{if len .Authors}} -AUTHOR: - {{range .Authors}}{{ . }}{{end}} - {{end}}{{if .Commands}} -COMMANDS: -{{range .Commands}}{{if not .HideHelp}} {{join .Names ", "}}{{ "\t"}}{{.Usage}}{{ "\n" }}{{end}}{{end}}{{end}}{{if .VisibleFlags}} -GLOBAL OPTIONS: - {{range .VisibleFlags}}{{.}} - {{end}}{{end}}{{if .Copyright }} -COPYRIGHT: - {{.Copyright}} - {{end}}{{if .Version}} -VERSION: - {{.Version}} - {{end}} -` - - // EXAMPLE: Replace the `HelpPrinter` func - cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { - fmt.Println("Ha HA. I pwnd the help!!1") - } - - cli.NewApp().Run(os.Args) -} -``` - -The default flag may be customized to something other than `-h/--help` by -setting `cli.HelpFlag`, e.g.: - - -``` go -package main - -import ( - "os" - - "github.com/urfave/cli" -) - -func main() { - cli.HelpFlag = cli.BoolFlag{ - Name: "halp, haaaaalp", - Usage: "HALP", - EnvVar: "SHOW_HALP,HALPPLZ", - } - - cli.NewApp().Run(os.Args) -} -``` - -### Version Flag - -The default version flag (`-v/--version`) is defined as `cli.VersionFlag`, which -is checked by the cli internals in order to print the `App.Version` via -`cli.VersionPrinter` and break execution. - -#### Customization - -The default flag may be customized to something other than `-v/--version` by -setting `cli.VersionFlag`, e.g.: - - -``` go -package main - -import ( - "os" - - "github.com/urfave/cli" -) - -func main() { - cli.VersionFlag = cli.BoolFlag{ - Name: "print-version, V", - Usage: "print only the version", - } - - app := cli.NewApp() - app.Name = "partay" - app.Version = "19.99.0" - app.Run(os.Args) -} -``` - -Alternatively, the version printer at `cli.VersionPrinter` may be overridden, e.g.: - - -``` go -package main - -import ( - "fmt" - "os" - - "github.com/urfave/cli" -) - -var ( - Revision = "fafafaf" -) - -func main() { - cli.VersionPrinter = func(c *cli.Context) { - fmt.Printf("version=%s revision=%s\n", c.App.Version, Revision) - } - - app := cli.NewApp() - app.Name = "partay" - app.Version = "19.99.0" - app.Run(os.Args) -} -``` - -#### Full API Example - -**Notice**: This is a contrived (functioning) example meant strictly for API -demonstration purposes. Use of one's imagination is encouraged. - - -``` go -package main - -import ( - "errors" - "flag" - "fmt" - "io" - "io/ioutil" - "os" - "time" - - "github.com/urfave/cli" -) - -func init() { - cli.AppHelpTemplate += "\nCUSTOMIZED: you bet ur muffins\n" - cli.CommandHelpTemplate += "\nYMMV\n" - cli.SubcommandHelpTemplate += "\nor something\n" - - cli.HelpFlag = cli.BoolFlag{Name: "halp"} - cli.BashCompletionFlag = cli.BoolFlag{Name: "compgen", Hidden: true} - cli.VersionFlag = cli.BoolFlag{Name: "print-version, V"} - - cli.HelpPrinter = func(w io.Writer, templ string, data interface{}) { - fmt.Fprintf(w, "best of luck to you\n") - } - cli.VersionPrinter = func(c *cli.Context) { - fmt.Fprintf(c.App.Writer, "version=%s\n", c.App.Version) - } - cli.OsExiter = func(c int) { - fmt.Fprintf(cli.ErrWriter, "refusing to exit %d\n", c) - } - cli.ErrWriter = ioutil.Discard - cli.FlagStringer = func(fl cli.Flag) string { - return fmt.Sprintf("\t\t%s", fl.GetName()) - } -} - -type hexWriter struct{} - -func (w *hexWriter) Write(p []byte) (int, error) { - for _, b := range p { - fmt.Printf("%x", b) - } - fmt.Printf("\n") - - return len(p), nil -} - -type genericType struct{ - s string -} - -func (g *genericType) Set(value string) error { - g.s = value - return nil -} - -func (g *genericType) String() string { - return g.s -} - -func main() { - app := cli.NewApp() - app.Name = "kənˈtrīv" - app.Version = "19.99.0" - app.Compiled = time.Now() - app.Authors = []cli.Author{ - cli.Author{ - Name: "Example Human", - Email: "human@example.com", - }, - } - app.Copyright = "(c) 1999 Serious Enterprise" - app.HelpName = "contrive" - app.Usage = "demonstrate available API" - app.UsageText = "contrive - demonstrating the available API" - app.ArgsUsage = "[args and such]" - app.Commands = []cli.Command{ - cli.Command{ - Name: "doo", - Aliases: []string{"do"}, - Category: "motion", - Usage: "do the doo", - UsageText: "doo - does the dooing", - Description: "no really, there is a lot of dooing to be done", - ArgsUsage: "[arrgh]", - Flags: []cli.Flag{ - cli.BoolFlag{Name: "forever, forevvarr"}, - }, - Subcommands: cli.Commands{ - cli.Command{ - Name: "wop", - Action: wopAction, - }, - }, - SkipFlagParsing: false, - HideHelp: false, - Hidden: false, - HelpName: "doo!", - BashComplete: func(c *cli.Context) { - fmt.Fprintf(c.App.Writer, "--better\n") - }, - Before: func(c *cli.Context) error { - fmt.Fprintf(c.App.Writer, "brace for impact\n") - return nil - }, - After: func(c *cli.Context) error { - fmt.Fprintf(c.App.Writer, "did we lose anyone?\n") - return nil - }, - Action: func(c *cli.Context) error { - c.Command.FullName() - c.Command.HasName("wop") - c.Command.Names() - c.Command.VisibleFlags() - fmt.Fprintf(c.App.Writer, "dodododododoodododddooooododododooo\n") - if c.Bool("forever") { - c.Command.Run(c) - } - return nil - }, - OnUsageError: func(c *cli.Context, err error, isSubcommand bool) error { - fmt.Fprintf(c.App.Writer, "for shame\n") - return err - }, - }, - } - app.Flags = []cli.Flag{ - cli.BoolFlag{Name: "fancy"}, - cli.BoolTFlag{Name: "fancier"}, - cli.DurationFlag{Name: "howlong, H", Value: time.Second * 3}, - cli.Float64Flag{Name: "howmuch"}, - cli.GenericFlag{Name: "wat", Value: &genericType{}}, - cli.Int64Flag{Name: "longdistance"}, - cli.Int64SliceFlag{Name: "intervals"}, - cli.IntFlag{Name: "distance"}, - cli.IntSliceFlag{Name: "times"}, - cli.StringFlag{Name: "dance-move, d"}, - cli.StringSliceFlag{Name: "names, N"}, - cli.UintFlag{Name: "age"}, - cli.Uint64Flag{Name: "bigage"}, - } - app.EnableBashCompletion = true - app.HideHelp = false - app.HideVersion = false - app.BashComplete = func(c *cli.Context) { - fmt.Fprintf(c.App.Writer, "lipstick\nkiss\nme\nlipstick\nringo\n") - } - app.Before = func(c *cli.Context) error { - fmt.Fprintf(c.App.Writer, "HEEEERE GOES\n") - return nil - } - app.After = func(c *cli.Context) error { - fmt.Fprintf(c.App.Writer, "Phew!\n") - return nil - } - app.CommandNotFound = func(c *cli.Context, command string) { - fmt.Fprintf(c.App.Writer, "Thar be no %q here.\n", command) - } - app.OnUsageError = func(c *cli.Context, err error, isSubcommand bool) error { - if isSubcommand { - return err - } - - fmt.Fprintf(c.App.Writer, "WRONG: %#v\n", err) - return nil - } - app.Action = func(c *cli.Context) error { - cli.DefaultAppComplete(c) - cli.HandleExitCoder(errors.New("not an exit coder, though")) - cli.ShowAppHelp(c) - cli.ShowCommandCompletions(c, "nope") - cli.ShowCommandHelp(c, "also-nope") - cli.ShowCompletions(c) - cli.ShowSubcommandHelp(c) - cli.ShowVersion(c) - - categories := c.App.Categories() - categories.AddCommand("sounds", cli.Command{ - Name: "bloop", - }) - - for _, category := range c.App.Categories() { - fmt.Fprintf(c.App.Writer, "%s\n", category.Name) - fmt.Fprintf(c.App.Writer, "%#v\n", category.Commands) - fmt.Fprintf(c.App.Writer, "%#v\n", category.VisibleCommands()) - } - - fmt.Printf("%#v\n", c.App.Command("doo")) - if c.Bool("infinite") { - c.App.Run([]string{"app", "doo", "wop"}) - } - - if c.Bool("forevar") { - c.App.RunAsSubcommand(c) - } - c.App.Setup() - fmt.Printf("%#v\n", c.App.VisibleCategories()) - fmt.Printf("%#v\n", c.App.VisibleCommands()) - fmt.Printf("%#v\n", c.App.VisibleFlags()) - - fmt.Printf("%#v\n", c.Args().First()) - if len(c.Args()) > 0 { - fmt.Printf("%#v\n", c.Args()[1]) - } - fmt.Printf("%#v\n", c.Args().Present()) - fmt.Printf("%#v\n", c.Args().Tail()) - - set := flag.NewFlagSet("contrive", 0) - nc := cli.NewContext(c.App, set, c) - - fmt.Printf("%#v\n", nc.Args()) - fmt.Printf("%#v\n", nc.Bool("nope")) - fmt.Printf("%#v\n", nc.BoolT("nerp")) - fmt.Printf("%#v\n", nc.Duration("howlong")) - fmt.Printf("%#v\n", nc.Float64("hay")) - fmt.Printf("%#v\n", nc.Generic("bloop")) - fmt.Printf("%#v\n", nc.Int64("bonk")) - fmt.Printf("%#v\n", nc.Int64Slice("burnks")) - fmt.Printf("%#v\n", nc.Int("bips")) - fmt.Printf("%#v\n", nc.IntSlice("blups")) - fmt.Printf("%#v\n", nc.String("snurt")) - fmt.Printf("%#v\n", nc.StringSlice("snurkles")) - fmt.Printf("%#v\n", nc.Uint("flub")) - fmt.Printf("%#v\n", nc.Uint64("florb")) - fmt.Printf("%#v\n", nc.GlobalBool("global-nope")) - fmt.Printf("%#v\n", nc.GlobalBoolT("global-nerp")) - fmt.Printf("%#v\n", nc.GlobalDuration("global-howlong")) - fmt.Printf("%#v\n", nc.GlobalFloat64("global-hay")) - fmt.Printf("%#v\n", nc.GlobalGeneric("global-bloop")) - fmt.Printf("%#v\n", nc.GlobalInt("global-bips")) - fmt.Printf("%#v\n", nc.GlobalIntSlice("global-blups")) - fmt.Printf("%#v\n", nc.GlobalString("global-snurt")) - fmt.Printf("%#v\n", nc.GlobalStringSlice("global-snurkles")) - - fmt.Printf("%#v\n", nc.FlagNames()) - fmt.Printf("%#v\n", nc.GlobalFlagNames()) - fmt.Printf("%#v\n", nc.GlobalIsSet("wat")) - fmt.Printf("%#v\n", nc.GlobalSet("wat", "nope")) - fmt.Printf("%#v\n", nc.NArg()) - fmt.Printf("%#v\n", nc.NumFlags()) - fmt.Printf("%#v\n", nc.Parent()) - - nc.Set("wat", "also-nope") - - ec := cli.NewExitError("ohwell", 86) - fmt.Fprintf(c.App.Writer, "%d", ec.ExitCode()) - fmt.Printf("made it!\n") - return ec - } - - if os.Getenv("HEXY") != "" { - app.Writer = &hexWriter{} - app.ErrWriter = &hexWriter{} - } - - app.Metadata = map[string]interface{}{ - "layers": "many", - "explicable": false, - "whatever-values": 19.99, - } - - app.Run(os.Args) -} - -func wopAction(c *cli.Context) error { - fmt.Fprintf(c.App.Writer, ":wave: over here, eh\n") - return nil -} -``` - -## Contribution Guidelines - -Feel free to put up a pull request to fix a bug or maybe add a feature. I will -give it a code review and make sure that it does not break backwards -compatibility. If I or any other collaborators agree that it is in line with -the vision of the project, we will work with you to get the code into -a mergeable state and merge it into the master branch. - -If you have contributed something significant to the project, we will most -likely add you as a collaborator. As a collaborator you are given the ability -to merge others pull requests. It is very important that new code does not -break existing code, so be careful about what code you do choose to merge. - -If you feel like you have contributed to the project but have not yet been -added as a collaborator, we probably forgot to add you, please open an issue. diff --git a/vendor/github.com/urfave/cli/appveyor.yml b/vendor/github.com/urfave/cli/appveyor.yml deleted file mode 100644 index 1e1489c36..000000000 --- a/vendor/github.com/urfave/cli/appveyor.yml +++ /dev/null @@ -1,26 +0,0 @@ -version: "{build}" - -os: Windows Server 2016 - -image: Visual Studio 2017 - -clone_folder: c:\gopath\src\github.com\urfave\cli - -environment: - GOPATH: C:\gopath - GOVERSION: 1.8.x - PYTHON: C:\Python36-x64 - PYTHON_VERSION: 3.6.x - PYTHON_ARCH: 64 - -install: -- set PATH=%GOPATH%\bin;C:\go\bin;%PATH% -- go version -- go env -- go get github.com/urfave/gfmrun/... -- go get -v -t ./... - -build_script: -- python runtests vet -- python runtests test -- python runtests gfmrun diff --git a/vendor/github.com/urfave/cli/category.go b/vendor/github.com/urfave/cli/category.go deleted file mode 100644 index 1a6055023..000000000 --- a/vendor/github.com/urfave/cli/category.go +++ /dev/null @@ -1,44 +0,0 @@ -package cli - -// CommandCategories is a slice of *CommandCategory. -type CommandCategories []*CommandCategory - -// CommandCategory is a category containing commands. -type CommandCategory struct { - Name string - Commands Commands -} - -func (c CommandCategories) Less(i, j int) bool { - return c[i].Name < c[j].Name -} - -func (c CommandCategories) Len() int { - return len(c) -} - -func (c CommandCategories) Swap(i, j int) { - c[i], c[j] = c[j], c[i] -} - -// AddCommand adds a command to a category. -func (c CommandCategories) AddCommand(category string, command Command) CommandCategories { - for _, commandCategory := range c { - if commandCategory.Name == category { - commandCategory.Commands = append(commandCategory.Commands, command) - return c - } - } - return append(c, &CommandCategory{Name: category, Commands: []Command{command}}) -} - -// VisibleCommands returns a slice of the Commands with Hidden=false -func (c *CommandCategory) VisibleCommands() []Command { - ret := []Command{} - for _, command := range c.Commands { - if !command.Hidden { - ret = append(ret, command) - } - } - return ret -} diff --git a/vendor/github.com/urfave/cli/context.go b/vendor/github.com/urfave/cli/context.go deleted file mode 100644 index db94191e2..000000000 --- a/vendor/github.com/urfave/cli/context.go +++ /dev/null @@ -1,278 +0,0 @@ -package cli - -import ( - "errors" - "flag" - "reflect" - "strings" - "syscall" -) - -// Context is a type that is passed through to -// each Handler action in a cli application. Context -// can be used to retrieve context-specific Args and -// parsed command-line options. -type Context struct { - App *App - Command Command - shellComplete bool - flagSet *flag.FlagSet - setFlags map[string]bool - parentContext *Context -} - -// NewContext creates a new context. For use in when invoking an App or Command action. -func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context { - c := &Context{App: app, flagSet: set, parentContext: parentCtx} - - if parentCtx != nil { - c.shellComplete = parentCtx.shellComplete - } - - return c -} - -// NumFlags returns the number of flags set -func (c *Context) NumFlags() int { - return c.flagSet.NFlag() -} - -// Set sets a context flag to a value. -func (c *Context) Set(name, value string) error { - c.setFlags = nil - return c.flagSet.Set(name, value) -} - -// GlobalSet sets a context flag to a value on the global flagset -func (c *Context) GlobalSet(name, value string) error { - globalContext(c).setFlags = nil - return globalContext(c).flagSet.Set(name, value) -} - -// IsSet determines if the flag was actually set -func (c *Context) IsSet(name string) bool { - if c.setFlags == nil { - c.setFlags = make(map[string]bool) - - c.flagSet.Visit(func(f *flag.Flag) { - c.setFlags[f.Name] = true - }) - - c.flagSet.VisitAll(func(f *flag.Flag) { - if _, ok := c.setFlags[f.Name]; ok { - return - } - c.setFlags[f.Name] = false - }) - - // XXX hack to support IsSet for flags with EnvVar - // - // There isn't an easy way to do this with the current implementation since - // whether a flag was set via an environment variable is very difficult to - // determine here. Instead, we intend to introduce a backwards incompatible - // change in version 2 to add `IsSet` to the Flag interface to push the - // responsibility closer to where the information required to determine - // whether a flag is set by non-standard means such as environment - // variables is avaliable. - // - // See https://github.com/urfave/cli/issues/294 for additional discussion - flags := c.Command.Flags - if c.Command.Name == "" { // cannot == Command{} since it contains slice types - if c.App != nil { - flags = c.App.Flags - } - } - for _, f := range flags { - eachName(f.GetName(), func(name string) { - if isSet, ok := c.setFlags[name]; isSet || !ok { - return - } - - val := reflect.ValueOf(f) - if val.Kind() == reflect.Ptr { - val = val.Elem() - } - - envVarValue := val.FieldByName("EnvVar") - if !envVarValue.IsValid() { - return - } - - eachName(envVarValue.String(), func(envVar string) { - envVar = strings.TrimSpace(envVar) - if _, ok := syscall.Getenv(envVar); ok { - c.setFlags[name] = true - return - } - }) - }) - } - } - - return c.setFlags[name] -} - -// GlobalIsSet determines if the global flag was actually set -func (c *Context) GlobalIsSet(name string) bool { - ctx := c - if ctx.parentContext != nil { - ctx = ctx.parentContext - } - - for ; ctx != nil; ctx = ctx.parentContext { - if ctx.IsSet(name) { - return true - } - } - return false -} - -// FlagNames returns a slice of flag names used in this context. -func (c *Context) FlagNames() (names []string) { - for _, flag := range c.Command.Flags { - name := strings.Split(flag.GetName(), ",")[0] - if name == "help" { - continue - } - names = append(names, name) - } - return -} - -// GlobalFlagNames returns a slice of global flag names used by the app. -func (c *Context) GlobalFlagNames() (names []string) { - for _, flag := range c.App.Flags { - name := strings.Split(flag.GetName(), ",")[0] - if name == "help" || name == "version" { - continue - } - names = append(names, name) - } - return -} - -// Parent returns the parent context, if any -func (c *Context) Parent() *Context { - return c.parentContext -} - -// value returns the value of the flag coressponding to `name` -func (c *Context) value(name string) interface{} { - return c.flagSet.Lookup(name).Value.(flag.Getter).Get() -} - -// Args contains apps console arguments -type Args []string - -// Args returns the command line arguments associated with the context. -func (c *Context) Args() Args { - args := Args(c.flagSet.Args()) - return args -} - -// NArg returns the number of the command line arguments. -func (c *Context) NArg() int { - return len(c.Args()) -} - -// Get returns the nth argument, or else a blank string -func (a Args) Get(n int) string { - if len(a) > n { - return a[n] - } - return "" -} - -// First returns the first argument, or else a blank string -func (a Args) First() string { - return a.Get(0) -} - -// Tail returns the rest of the arguments (not the first one) -// or else an empty string slice -func (a Args) Tail() []string { - if len(a) >= 2 { - return []string(a)[1:] - } - return []string{} -} - -// Present checks if there are any arguments present -func (a Args) Present() bool { - return len(a) != 0 -} - -// Swap swaps arguments at the given indexes -func (a Args) Swap(from, to int) error { - if from >= len(a) || to >= len(a) { - return errors.New("index out of range") - } - a[from], a[to] = a[to], a[from] - return nil -} - -func globalContext(ctx *Context) *Context { - if ctx == nil { - return nil - } - - for { - if ctx.parentContext == nil { - return ctx - } - ctx = ctx.parentContext - } -} - -func lookupGlobalFlagSet(name string, ctx *Context) *flag.FlagSet { - if ctx.parentContext != nil { - ctx = ctx.parentContext - } - for ; ctx != nil; ctx = ctx.parentContext { - if f := ctx.flagSet.Lookup(name); f != nil { - return ctx.flagSet - } - } - return nil -} - -func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) { - switch ff.Value.(type) { - case *StringSlice: - default: - set.Set(name, ff.Value.String()) - } -} - -func normalizeFlags(flags []Flag, set *flag.FlagSet) error { - visited := make(map[string]bool) - set.Visit(func(f *flag.Flag) { - visited[f.Name] = true - }) - for _, f := range flags { - parts := strings.Split(f.GetName(), ",") - if len(parts) == 1 { - continue - } - var ff *flag.Flag - for _, name := range parts { - name = strings.Trim(name, " ") - if visited[name] { - if ff != nil { - return errors.New("Cannot use two forms of the same flag: " + name + " " + ff.Name) - } - ff = set.Lookup(name) - } - } - if ff == nil { - continue - } - for _, name := range parts { - name = strings.Trim(name, " ") - if !visited[name] { - copyFlag(name, ff, set) - } - } - } - return nil -} diff --git a/vendor/github.com/urfave/cli/flag-types.json b/vendor/github.com/urfave/cli/flag-types.json deleted file mode 100644 index 122310785..000000000 --- a/vendor/github.com/urfave/cli/flag-types.json +++ /dev/null @@ -1,93 +0,0 @@ -[ - { - "name": "Bool", - "type": "bool", - "value": false, - "context_default": "false", - "parser": "strconv.ParseBool(f.Value.String())" - }, - { - "name": "BoolT", - "type": "bool", - "value": false, - "doctail": " that is true by default", - "context_default": "false", - "parser": "strconv.ParseBool(f.Value.String())" - }, - { - "name": "Duration", - "type": "time.Duration", - "doctail": " (see https://golang.org/pkg/time/#ParseDuration)", - "context_default": "0", - "parser": "time.ParseDuration(f.Value.String())" - }, - { - "name": "Float64", - "type": "float64", - "context_default": "0", - "parser": "strconv.ParseFloat(f.Value.String(), 64)" - }, - { - "name": "Generic", - "type": "Generic", - "dest": false, - "context_default": "nil", - "context_type": "interface{}" - }, - { - "name": "Int64", - "type": "int64", - "context_default": "0", - "parser": "strconv.ParseInt(f.Value.String(), 0, 64)" - }, - { - "name": "Int", - "type": "int", - "context_default": "0", - "parser": "strconv.ParseInt(f.Value.String(), 0, 64)", - "parser_cast": "int(parsed)" - }, - { - "name": "IntSlice", - "type": "*IntSlice", - "dest": false, - "context_default": "nil", - "context_type": "[]int", - "parser": "(f.Value.(*IntSlice)).Value(), error(nil)" - }, - { - "name": "Int64Slice", - "type": "*Int64Slice", - "dest": false, - "context_default": "nil", - "context_type": "[]int64", - "parser": "(f.Value.(*Int64Slice)).Value(), error(nil)" - }, - { - "name": "String", - "type": "string", - "context_default": "\"\"", - "parser": "f.Value.String(), error(nil)" - }, - { - "name": "StringSlice", - "type": "*StringSlice", - "dest": false, - "context_default": "nil", - "context_type": "[]string", - "parser": "(f.Value.(*StringSlice)).Value(), error(nil)" - }, - { - "name": "Uint64", - "type": "uint64", - "context_default": "0", - "parser": "strconv.ParseUint(f.Value.String(), 0, 64)" - }, - { - "name": "Uint", - "type": "uint", - "context_default": "0", - "parser": "strconv.ParseUint(f.Value.String(), 0, 64)", - "parser_cast": "uint(parsed)" - } -] diff --git a/vendor/github.com/urfave/cli/flag.go b/vendor/github.com/urfave/cli/flag.go deleted file mode 100644 index 877ff3523..000000000 --- a/vendor/github.com/urfave/cli/flag.go +++ /dev/null @@ -1,799 +0,0 @@ -package cli - -import ( - "flag" - "fmt" - "reflect" - "runtime" - "strconv" - "strings" - "syscall" - "time" -) - -const defaultPlaceholder = "value" - -// BashCompletionFlag enables bash-completion for all commands and subcommands -var BashCompletionFlag Flag = BoolFlag{ - Name: "generate-bash-completion", - Hidden: true, -} - -// VersionFlag prints the version for the application -var VersionFlag Flag = BoolFlag{ - Name: "version, v", - Usage: "print the version", -} - -// HelpFlag prints the help for all commands and subcommands -// Set to the zero value (BoolFlag{}) to disable flag -- keeps subcommand -// unless HideHelp is set to true) -var HelpFlag Flag = BoolFlag{ - Name: "help, h", - Usage: "show help", -} - -// FlagStringer converts a flag definition to a string. This is used by help -// to display a flag. -var FlagStringer FlagStringFunc = stringifyFlag - -// FlagsByName is a slice of Flag. -type FlagsByName []Flag - -func (f FlagsByName) Len() int { - return len(f) -} - -func (f FlagsByName) Less(i, j int) bool { - return f[i].GetName() < f[j].GetName() -} - -func (f FlagsByName) Swap(i, j int) { - f[i], f[j] = f[j], f[i] -} - -// Flag is a common interface related to parsing flags in cli. -// For more advanced flag parsing techniques, it is recommended that -// this interface be implemented. -type Flag interface { - fmt.Stringer - // Apply Flag settings to the given flag set - Apply(*flag.FlagSet) - GetName() string -} - -// errorableFlag is an interface that allows us to return errors during apply -// it allows flags defined in this library to return errors in a fashion backwards compatible -// TODO remove in v2 and modify the existing Flag interface to return errors -type errorableFlag interface { - Flag - - ApplyWithError(*flag.FlagSet) error -} - -func flagSet(name string, flags []Flag) (*flag.FlagSet, error) { - set := flag.NewFlagSet(name, flag.ContinueOnError) - - for _, f := range flags { - //TODO remove in v2 when errorableFlag is removed - if ef, ok := f.(errorableFlag); ok { - if err := ef.ApplyWithError(set); err != nil { - return nil, err - } - } else { - f.Apply(set) - } - } - return set, nil -} - -func eachName(longName string, fn func(string)) { - parts := strings.Split(longName, ",") - for _, name := range parts { - name = strings.Trim(name, " ") - fn(name) - } -} - -// Generic is a generic parseable type identified by a specific flag -type Generic interface { - Set(value string) error - String() string -} - -// Apply takes the flagset and calls Set on the generic flag with the value -// provided by the user for parsing by the flag -// Ignores parsing errors -func (f GenericFlag) Apply(set *flag.FlagSet) { - f.ApplyWithError(set) -} - -// ApplyWithError takes the flagset and calls Set on the generic flag with the value -// provided by the user for parsing by the flag -func (f GenericFlag) ApplyWithError(set *flag.FlagSet) error { - val := f.Value - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - if err := val.Set(envVal); err != nil { - return fmt.Errorf("could not parse %s as value for flag %s: %s", envVal, f.Name, err) - } - break - } - } - } - - eachName(f.Name, func(name string) { - set.Var(f.Value, name, f.Usage) - }) - - return nil -} - -// StringSlice is an opaque type for []string to satisfy flag.Value and flag.Getter -type StringSlice []string - -// Set appends the string value to the list of values -func (f *StringSlice) Set(value string) error { - *f = append(*f, value) - return nil -} - -// String returns a readable representation of this value (for usage defaults) -func (f *StringSlice) String() string { - return fmt.Sprintf("%s", *f) -} - -// Value returns the slice of strings set by this flag -func (f *StringSlice) Value() []string { - return *f -} - -// Get returns the slice of strings set by this flag -func (f *StringSlice) Get() interface{} { - return *f -} - -// Apply populates the flag given the flag set and environment -// Ignores errors -func (f StringSliceFlag) Apply(set *flag.FlagSet) { - f.ApplyWithError(set) -} - -// ApplyWithError populates the flag given the flag set and environment -func (f StringSliceFlag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - newVal := &StringSlice{} - for _, s := range strings.Split(envVal, ",") { - s = strings.TrimSpace(s) - if err := newVal.Set(s); err != nil { - return fmt.Errorf("could not parse %s as string value for flag %s: %s", envVal, f.Name, err) - } - } - f.Value = newVal - break - } - } - } - - eachName(f.Name, func(name string) { - if f.Value == nil { - f.Value = &StringSlice{} - } - set.Var(f.Value, name, f.Usage) - }) - - return nil -} - -// IntSlice is an opaque type for []int to satisfy flag.Value and flag.Getter -type IntSlice []int - -// Set parses the value into an integer and appends it to the list of values -func (f *IntSlice) Set(value string) error { - tmp, err := strconv.Atoi(value) - if err != nil { - return err - } - *f = append(*f, tmp) - return nil -} - -// String returns a readable representation of this value (for usage defaults) -func (f *IntSlice) String() string { - return fmt.Sprintf("%#v", *f) -} - -// Value returns the slice of ints set by this flag -func (f *IntSlice) Value() []int { - return *f -} - -// Get returns the slice of ints set by this flag -func (f *IntSlice) Get() interface{} { - return *f -} - -// Apply populates the flag given the flag set and environment -// Ignores errors -func (f IntSliceFlag) Apply(set *flag.FlagSet) { - f.ApplyWithError(set) -} - -// ApplyWithError populates the flag given the flag set and environment -func (f IntSliceFlag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - newVal := &IntSlice{} - for _, s := range strings.Split(envVal, ",") { - s = strings.TrimSpace(s) - if err := newVal.Set(s); err != nil { - return fmt.Errorf("could not parse %s as int slice value for flag %s: %s", envVal, f.Name, err) - } - } - f.Value = newVal - break - } - } - } - - eachName(f.Name, func(name string) { - if f.Value == nil { - f.Value = &IntSlice{} - } - set.Var(f.Value, name, f.Usage) - }) - - return nil -} - -// Int64Slice is an opaque type for []int to satisfy flag.Value and flag.Getter -type Int64Slice []int64 - -// Set parses the value into an integer and appends it to the list of values -func (f *Int64Slice) Set(value string) error { - tmp, err := strconv.ParseInt(value, 10, 64) - if err != nil { - return err - } - *f = append(*f, tmp) - return nil -} - -// String returns a readable representation of this value (for usage defaults) -func (f *Int64Slice) String() string { - return fmt.Sprintf("%#v", *f) -} - -// Value returns the slice of ints set by this flag -func (f *Int64Slice) Value() []int64 { - return *f -} - -// Get returns the slice of ints set by this flag -func (f *Int64Slice) Get() interface{} { - return *f -} - -// Apply populates the flag given the flag set and environment -// Ignores errors -func (f Int64SliceFlag) Apply(set *flag.FlagSet) { - f.ApplyWithError(set) -} - -// ApplyWithError populates the flag given the flag set and environment -func (f Int64SliceFlag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - newVal := &Int64Slice{} - for _, s := range strings.Split(envVal, ",") { - s = strings.TrimSpace(s) - if err := newVal.Set(s); err != nil { - return fmt.Errorf("could not parse %s as int64 slice value for flag %s: %s", envVal, f.Name, err) - } - } - f.Value = newVal - break - } - } - } - - eachName(f.Name, func(name string) { - if f.Value == nil { - f.Value = &Int64Slice{} - } - set.Var(f.Value, name, f.Usage) - }) - return nil -} - -// Apply populates the flag given the flag set and environment -// Ignores errors -func (f BoolFlag) Apply(set *flag.FlagSet) { - f.ApplyWithError(set) -} - -// ApplyWithError populates the flag given the flag set and environment -func (f BoolFlag) ApplyWithError(set *flag.FlagSet) error { - val := false - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - if envVal == "" { - val = false - break - } - - envValBool, err := strconv.ParseBool(envVal) - if err != nil { - return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err) - } - - val = envValBool - break - } - } - } - - eachName(f.Name, func(name string) { - if f.Destination != nil { - set.BoolVar(f.Destination, name, val, f.Usage) - return - } - set.Bool(name, val, f.Usage) - }) - - return nil -} - -// Apply populates the flag given the flag set and environment -// Ignores errors -func (f BoolTFlag) Apply(set *flag.FlagSet) { - f.ApplyWithError(set) -} - -// ApplyWithError populates the flag given the flag set and environment -func (f BoolTFlag) ApplyWithError(set *flag.FlagSet) error { - val := true - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - if envVal == "" { - val = false - break - } - - envValBool, err := strconv.ParseBool(envVal) - if err != nil { - return fmt.Errorf("could not parse %s as bool value for flag %s: %s", envVal, f.Name, err) - } - - val = envValBool - break - } - } - } - - eachName(f.Name, func(name string) { - if f.Destination != nil { - set.BoolVar(f.Destination, name, val, f.Usage) - return - } - set.Bool(name, val, f.Usage) - }) - - return nil -} - -// Apply populates the flag given the flag set and environment -// Ignores errors -func (f StringFlag) Apply(set *flag.FlagSet) { - f.ApplyWithError(set) -} - -// ApplyWithError populates the flag given the flag set and environment -func (f StringFlag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - f.Value = envVal - break - } - } - } - - eachName(f.Name, func(name string) { - if f.Destination != nil { - set.StringVar(f.Destination, name, f.Value, f.Usage) - return - } - set.String(name, f.Value, f.Usage) - }) - - return nil -} - -// Apply populates the flag given the flag set and environment -// Ignores errors -func (f IntFlag) Apply(set *flag.FlagSet) { - f.ApplyWithError(set) -} - -// ApplyWithError populates the flag given the flag set and environment -func (f IntFlag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - envValInt, err := strconv.ParseInt(envVal, 0, 64) - if err != nil { - return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err) - } - f.Value = int(envValInt) - break - } - } - } - - eachName(f.Name, func(name string) { - if f.Destination != nil { - set.IntVar(f.Destination, name, f.Value, f.Usage) - return - } - set.Int(name, f.Value, f.Usage) - }) - - return nil -} - -// Apply populates the flag given the flag set and environment -// Ignores errors -func (f Int64Flag) Apply(set *flag.FlagSet) { - f.ApplyWithError(set) -} - -// ApplyWithError populates the flag given the flag set and environment -func (f Int64Flag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - envValInt, err := strconv.ParseInt(envVal, 0, 64) - if err != nil { - return fmt.Errorf("could not parse %s as int value for flag %s: %s", envVal, f.Name, err) - } - - f.Value = envValInt - break - } - } - } - - eachName(f.Name, func(name string) { - if f.Destination != nil { - set.Int64Var(f.Destination, name, f.Value, f.Usage) - return - } - set.Int64(name, f.Value, f.Usage) - }) - - return nil -} - -// Apply populates the flag given the flag set and environment -// Ignores errors -func (f UintFlag) Apply(set *flag.FlagSet) { - f.ApplyWithError(set) -} - -// ApplyWithError populates the flag given the flag set and environment -func (f UintFlag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - envValInt, err := strconv.ParseUint(envVal, 0, 64) - if err != nil { - return fmt.Errorf("could not parse %s as uint value for flag %s: %s", envVal, f.Name, err) - } - - f.Value = uint(envValInt) - break - } - } - } - - eachName(f.Name, func(name string) { - if f.Destination != nil { - set.UintVar(f.Destination, name, f.Value, f.Usage) - return - } - set.Uint(name, f.Value, f.Usage) - }) - - return nil -} - -// Apply populates the flag given the flag set and environment -// Ignores errors -func (f Uint64Flag) Apply(set *flag.FlagSet) { - f.ApplyWithError(set) -} - -// ApplyWithError populates the flag given the flag set and environment -func (f Uint64Flag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - envValInt, err := strconv.ParseUint(envVal, 0, 64) - if err != nil { - return fmt.Errorf("could not parse %s as uint64 value for flag %s: %s", envVal, f.Name, err) - } - - f.Value = uint64(envValInt) - break - } - } - } - - eachName(f.Name, func(name string) { - if f.Destination != nil { - set.Uint64Var(f.Destination, name, f.Value, f.Usage) - return - } - set.Uint64(name, f.Value, f.Usage) - }) - - return nil -} - -// Apply populates the flag given the flag set and environment -// Ignores errors -func (f DurationFlag) Apply(set *flag.FlagSet) { - f.ApplyWithError(set) -} - -// ApplyWithError populates the flag given the flag set and environment -func (f DurationFlag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - envValDuration, err := time.ParseDuration(envVal) - if err != nil { - return fmt.Errorf("could not parse %s as duration for flag %s: %s", envVal, f.Name, err) - } - - f.Value = envValDuration - break - } - } - } - - eachName(f.Name, func(name string) { - if f.Destination != nil { - set.DurationVar(f.Destination, name, f.Value, f.Usage) - return - } - set.Duration(name, f.Value, f.Usage) - }) - - return nil -} - -// Apply populates the flag given the flag set and environment -// Ignores errors -func (f Float64Flag) Apply(set *flag.FlagSet) { - f.ApplyWithError(set) -} - -// ApplyWithError populates the flag given the flag set and environment -func (f Float64Flag) ApplyWithError(set *flag.FlagSet) error { - if f.EnvVar != "" { - for _, envVar := range strings.Split(f.EnvVar, ",") { - envVar = strings.TrimSpace(envVar) - if envVal, ok := syscall.Getenv(envVar); ok { - envValFloat, err := strconv.ParseFloat(envVal, 10) - if err != nil { - return fmt.Errorf("could not parse %s as float64 value for flag %s: %s", envVal, f.Name, err) - } - - f.Value = float64(envValFloat) - break - } - } - } - - eachName(f.Name, func(name string) { - if f.Destination != nil { - set.Float64Var(f.Destination, name, f.Value, f.Usage) - return - } - set.Float64(name, f.Value, f.Usage) - }) - - return nil -} - -func visibleFlags(fl []Flag) []Flag { - visible := []Flag{} - for _, flag := range fl { - field := flagValue(flag).FieldByName("Hidden") - if !field.IsValid() || !field.Bool() { - visible = append(visible, flag) - } - } - return visible -} - -func prefixFor(name string) (prefix string) { - if len(name) == 1 { - prefix = "-" - } else { - prefix = "--" - } - - return -} - -// Returns the placeholder, if any, and the unquoted usage string. -func unquoteUsage(usage string) (string, string) { - for i := 0; i < len(usage); i++ { - if usage[i] == '`' { - for j := i + 1; j < len(usage); j++ { - if usage[j] == '`' { - name := usage[i+1 : j] - usage = usage[:i] + name + usage[j+1:] - return name, usage - } - } - break - } - } - return "", usage -} - -func prefixedNames(fullName, placeholder string) string { - var prefixed string - parts := strings.Split(fullName, ",") - for i, name := range parts { - name = strings.Trim(name, " ") - prefixed += prefixFor(name) + name - if placeholder != "" { - prefixed += " " + placeholder - } - if i < len(parts)-1 { - prefixed += ", " - } - } - return prefixed -} - -func withEnvHint(envVar, str string) string { - envText := "" - if envVar != "" { - prefix := "$" - suffix := "" - sep := ", $" - if runtime.GOOS == "windows" { - prefix = "%" - suffix = "%" - sep = "%, %" - } - envText = fmt.Sprintf(" [%s%s%s]", prefix, strings.Join(strings.Split(envVar, ","), sep), suffix) - } - return str + envText -} - -func flagValue(f Flag) reflect.Value { - fv := reflect.ValueOf(f) - for fv.Kind() == reflect.Ptr { - fv = reflect.Indirect(fv) - } - return fv -} - -func stringifyFlag(f Flag) string { - fv := flagValue(f) - - switch f.(type) { - case IntSliceFlag: - return withEnvHint(fv.FieldByName("EnvVar").String(), - stringifyIntSliceFlag(f.(IntSliceFlag))) - case Int64SliceFlag: - return withEnvHint(fv.FieldByName("EnvVar").String(), - stringifyInt64SliceFlag(f.(Int64SliceFlag))) - case StringSliceFlag: - return withEnvHint(fv.FieldByName("EnvVar").String(), - stringifyStringSliceFlag(f.(StringSliceFlag))) - } - - placeholder, usage := unquoteUsage(fv.FieldByName("Usage").String()) - - needsPlaceholder := false - defaultValueString := "" - - if val := fv.FieldByName("Value"); val.IsValid() { - needsPlaceholder = true - defaultValueString = fmt.Sprintf(" (default: %v)", val.Interface()) - - if val.Kind() == reflect.String && val.String() != "" { - defaultValueString = fmt.Sprintf(" (default: %q)", val.String()) - } - } - - if defaultValueString == " (default: )" { - defaultValueString = "" - } - - if needsPlaceholder && placeholder == "" { - placeholder = defaultPlaceholder - } - - usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultValueString)) - - return withEnvHint(fv.FieldByName("EnvVar").String(), - fmt.Sprintf("%s\t%s", prefixedNames(fv.FieldByName("Name").String(), placeholder), usageWithDefault)) -} - -func stringifyIntSliceFlag(f IntSliceFlag) string { - defaultVals := []string{} - if f.Value != nil && len(f.Value.Value()) > 0 { - for _, i := range f.Value.Value() { - defaultVals = append(defaultVals, fmt.Sprintf("%d", i)) - } - } - - return stringifySliceFlag(f.Usage, f.Name, defaultVals) -} - -func stringifyInt64SliceFlag(f Int64SliceFlag) string { - defaultVals := []string{} - if f.Value != nil && len(f.Value.Value()) > 0 { - for _, i := range f.Value.Value() { - defaultVals = append(defaultVals, fmt.Sprintf("%d", i)) - } - } - - return stringifySliceFlag(f.Usage, f.Name, defaultVals) -} - -func stringifyStringSliceFlag(f StringSliceFlag) string { - defaultVals := []string{} - if f.Value != nil && len(f.Value.Value()) > 0 { - for _, s := range f.Value.Value() { - if len(s) > 0 { - defaultVals = append(defaultVals, fmt.Sprintf("%q", s)) - } - } - } - - return stringifySliceFlag(f.Usage, f.Name, defaultVals) -} - -func stringifySliceFlag(usage, name string, defaultVals []string) string { - placeholder, usage := unquoteUsage(usage) - if placeholder == "" { - placeholder = defaultPlaceholder - } - - defaultVal := "" - if len(defaultVals) > 0 { - defaultVal = fmt.Sprintf(" (default: %s)", strings.Join(defaultVals, ", ")) - } - - usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultVal)) - return fmt.Sprintf("%s\t%s", prefixedNames(name, placeholder), usageWithDefault) -} diff --git a/vendor/github.com/urfave/cli/flag_generated.go b/vendor/github.com/urfave/cli/flag_generated.go deleted file mode 100644 index 491b61956..000000000 --- a/vendor/github.com/urfave/cli/flag_generated.go +++ /dev/null @@ -1,627 +0,0 @@ -package cli - -import ( - "flag" - "strconv" - "time" -) - -// WARNING: This file is generated! - -// BoolFlag is a flag with type bool -type BoolFlag struct { - Name string - Usage string - EnvVar string - Hidden bool - Destination *bool -} - -// String returns a readable representation of this value -// (for usage defaults) -func (f BoolFlag) String() string { - return FlagStringer(f) -} - -// GetName returns the name of the flag -func (f BoolFlag) GetName() string { - return f.Name -} - -// Bool looks up the value of a local BoolFlag, returns -// false if not found -func (c *Context) Bool(name string) bool { - return lookupBool(name, c.flagSet) -} - -// GlobalBool looks up the value of a global BoolFlag, returns -// false if not found -func (c *Context) GlobalBool(name string) bool { - if fs := lookupGlobalFlagSet(name, c); fs != nil { - return lookupBool(name, fs) - } - return false -} - -func lookupBool(name string, set *flag.FlagSet) bool { - f := set.Lookup(name) - if f != nil { - parsed, err := strconv.ParseBool(f.Value.String()) - if err != nil { - return false - } - return parsed - } - return false -} - -// BoolTFlag is a flag with type bool that is true by default -type BoolTFlag struct { - Name string - Usage string - EnvVar string - Hidden bool - Destination *bool -} - -// String returns a readable representation of this value -// (for usage defaults) -func (f BoolTFlag) String() string { - return FlagStringer(f) -} - -// GetName returns the name of the flag -func (f BoolTFlag) GetName() string { - return f.Name -} - -// BoolT looks up the value of a local BoolTFlag, returns -// false if not found -func (c *Context) BoolT(name string) bool { - return lookupBoolT(name, c.flagSet) -} - -// GlobalBoolT looks up the value of a global BoolTFlag, returns -// false if not found -func (c *Context) GlobalBoolT(name string) bool { - if fs := lookupGlobalFlagSet(name, c); fs != nil { - return lookupBoolT(name, fs) - } - return false -} - -func lookupBoolT(name string, set *flag.FlagSet) bool { - f := set.Lookup(name) - if f != nil { - parsed, err := strconv.ParseBool(f.Value.String()) - if err != nil { - return false - } - return parsed - } - return false -} - -// DurationFlag is a flag with type time.Duration (see https://golang.org/pkg/time/#ParseDuration) -type DurationFlag struct { - Name string - Usage string - EnvVar string - Hidden bool - Value time.Duration - Destination *time.Duration -} - -// String returns a readable representation of this value -// (for usage defaults) -func (f DurationFlag) String() string { - return FlagStringer(f) -} - -// GetName returns the name of the flag -func (f DurationFlag) GetName() string { - return f.Name -} - -// Duration looks up the value of a local DurationFlag, returns -// 0 if not found -func (c *Context) Duration(name string) time.Duration { - return lookupDuration(name, c.flagSet) -} - -// GlobalDuration looks up the value of a global DurationFlag, returns -// 0 if not found -func (c *Context) GlobalDuration(name string) time.Duration { - if fs := lookupGlobalFlagSet(name, c); fs != nil { - return lookupDuration(name, fs) - } - return 0 -} - -func lookupDuration(name string, set *flag.FlagSet) time.Duration { - f := set.Lookup(name) - if f != nil { - parsed, err := time.ParseDuration(f.Value.String()) - if err != nil { - return 0 - } - return parsed - } - return 0 -} - -// Float64Flag is a flag with type float64 -type Float64Flag struct { - Name string - Usage string - EnvVar string - Hidden bool - Value float64 - Destination *float64 -} - -// String returns a readable representation of this value -// (for usage defaults) -func (f Float64Flag) String() string { - return FlagStringer(f) -} - -// GetName returns the name of the flag -func (f Float64Flag) GetName() string { - return f.Name -} - -// Float64 looks up the value of a local Float64Flag, returns -// 0 if not found -func (c *Context) Float64(name string) float64 { - return lookupFloat64(name, c.flagSet) -} - -// GlobalFloat64 looks up the value of a global Float64Flag, returns -// 0 if not found -func (c *Context) GlobalFloat64(name string) float64 { - if fs := lookupGlobalFlagSet(name, c); fs != nil { - return lookupFloat64(name, fs) - } - return 0 -} - -func lookupFloat64(name string, set *flag.FlagSet) float64 { - f := set.Lookup(name) - if f != nil { - parsed, err := strconv.ParseFloat(f.Value.String(), 64) - if err != nil { - return 0 - } - return parsed - } - return 0 -} - -// GenericFlag is a flag with type Generic -type GenericFlag struct { - Name string - Usage string - EnvVar string - Hidden bool - Value Generic -} - -// String returns a readable representation of this value -// (for usage defaults) -func (f GenericFlag) String() string { - return FlagStringer(f) -} - -// GetName returns the name of the flag -func (f GenericFlag) GetName() string { - return f.Name -} - -// Generic looks up the value of a local GenericFlag, returns -// nil if not found -func (c *Context) Generic(name string) interface{} { - return lookupGeneric(name, c.flagSet) -} - -// GlobalGeneric looks up the value of a global GenericFlag, returns -// nil if not found -func (c *Context) GlobalGeneric(name string) interface{} { - if fs := lookupGlobalFlagSet(name, c); fs != nil { - return lookupGeneric(name, fs) - } - return nil -} - -func lookupGeneric(name string, set *flag.FlagSet) interface{} { - f := set.Lookup(name) - if f != nil { - parsed, err := f.Value, error(nil) - if err != nil { - return nil - } - return parsed - } - return nil -} - -// Int64Flag is a flag with type int64 -type Int64Flag struct { - Name string - Usage string - EnvVar string - Hidden bool - Value int64 - Destination *int64 -} - -// String returns a readable representation of this value -// (for usage defaults) -func (f Int64Flag) String() string { - return FlagStringer(f) -} - -// GetName returns the name of the flag -func (f Int64Flag) GetName() string { - return f.Name -} - -// Int64 looks up the value of a local Int64Flag, returns -// 0 if not found -func (c *Context) Int64(name string) int64 { - return lookupInt64(name, c.flagSet) -} - -// GlobalInt64 looks up the value of a global Int64Flag, returns -// 0 if not found -func (c *Context) GlobalInt64(name string) int64 { - if fs := lookupGlobalFlagSet(name, c); fs != nil { - return lookupInt64(name, fs) - } - return 0 -} - -func lookupInt64(name string, set *flag.FlagSet) int64 { - f := set.Lookup(name) - if f != nil { - parsed, err := strconv.ParseInt(f.Value.String(), 0, 64) - if err != nil { - return 0 - } - return parsed - } - return 0 -} - -// IntFlag is a flag with type int -type IntFlag struct { - Name string - Usage string - EnvVar string - Hidden bool - Value int - Destination *int -} - -// String returns a readable representation of this value -// (for usage defaults) -func (f IntFlag) String() string { - return FlagStringer(f) -} - -// GetName returns the name of the flag -func (f IntFlag) GetName() string { - return f.Name -} - -// Int looks up the value of a local IntFlag, returns -// 0 if not found -func (c *Context) Int(name string) int { - return lookupInt(name, c.flagSet) -} - -// GlobalInt looks up the value of a global IntFlag, returns -// 0 if not found -func (c *Context) GlobalInt(name string) int { - if fs := lookupGlobalFlagSet(name, c); fs != nil { - return lookupInt(name, fs) - } - return 0 -} - -func lookupInt(name string, set *flag.FlagSet) int { - f := set.Lookup(name) - if f != nil { - parsed, err := strconv.ParseInt(f.Value.String(), 0, 64) - if err != nil { - return 0 - } - return int(parsed) - } - return 0 -} - -// IntSliceFlag is a flag with type *IntSlice -type IntSliceFlag struct { - Name string - Usage string - EnvVar string - Hidden bool - Value *IntSlice -} - -// String returns a readable representation of this value -// (for usage defaults) -func (f IntSliceFlag) String() string { - return FlagStringer(f) -} - -// GetName returns the name of the flag -func (f IntSliceFlag) GetName() string { - return f.Name -} - -// IntSlice looks up the value of a local IntSliceFlag, returns -// nil if not found -func (c *Context) IntSlice(name string) []int { - return lookupIntSlice(name, c.flagSet) -} - -// GlobalIntSlice looks up the value of a global IntSliceFlag, returns -// nil if not found -func (c *Context) GlobalIntSlice(name string) []int { - if fs := lookupGlobalFlagSet(name, c); fs != nil { - return lookupIntSlice(name, fs) - } - return nil -} - -func lookupIntSlice(name string, set *flag.FlagSet) []int { - f := set.Lookup(name) - if f != nil { - parsed, err := (f.Value.(*IntSlice)).Value(), error(nil) - if err != nil { - return nil - } - return parsed - } - return nil -} - -// Int64SliceFlag is a flag with type *Int64Slice -type Int64SliceFlag struct { - Name string - Usage string - EnvVar string - Hidden bool - Value *Int64Slice -} - -// String returns a readable representation of this value -// (for usage defaults) -func (f Int64SliceFlag) String() string { - return FlagStringer(f) -} - -// GetName returns the name of the flag -func (f Int64SliceFlag) GetName() string { - return f.Name -} - -// Int64Slice looks up the value of a local Int64SliceFlag, returns -// nil if not found -func (c *Context) Int64Slice(name string) []int64 { - return lookupInt64Slice(name, c.flagSet) -} - -// GlobalInt64Slice looks up the value of a global Int64SliceFlag, returns -// nil if not found -func (c *Context) GlobalInt64Slice(name string) []int64 { - if fs := lookupGlobalFlagSet(name, c); fs != nil { - return lookupInt64Slice(name, fs) - } - return nil -} - -func lookupInt64Slice(name string, set *flag.FlagSet) []int64 { - f := set.Lookup(name) - if f != nil { - parsed, err := (f.Value.(*Int64Slice)).Value(), error(nil) - if err != nil { - return nil - } - return parsed - } - return nil -} - -// StringFlag is a flag with type string -type StringFlag struct { - Name string - Usage string - EnvVar string - Hidden bool - Value string - Destination *string -} - -// String returns a readable representation of this value -// (for usage defaults) -func (f StringFlag) String() string { - return FlagStringer(f) -} - -// GetName returns the name of the flag -func (f StringFlag) GetName() string { - return f.Name -} - -// String looks up the value of a local StringFlag, returns -// "" if not found -func (c *Context) String(name string) string { - return lookupString(name, c.flagSet) -} - -// GlobalString looks up the value of a global StringFlag, returns -// "" if not found -func (c *Context) GlobalString(name string) string { - if fs := lookupGlobalFlagSet(name, c); fs != nil { - return lookupString(name, fs) - } - return "" -} - -func lookupString(name string, set *flag.FlagSet) string { - f := set.Lookup(name) - if f != nil { - parsed, err := f.Value.String(), error(nil) - if err != nil { - return "" - } - return parsed - } - return "" -} - -// StringSliceFlag is a flag with type *StringSlice -type StringSliceFlag struct { - Name string - Usage string - EnvVar string - Hidden bool - Value *StringSlice -} - -// String returns a readable representation of this value -// (for usage defaults) -func (f StringSliceFlag) String() string { - return FlagStringer(f) -} - -// GetName returns the name of the flag -func (f StringSliceFlag) GetName() string { - return f.Name -} - -// StringSlice looks up the value of a local StringSliceFlag, returns -// nil if not found -func (c *Context) StringSlice(name string) []string { - return lookupStringSlice(name, c.flagSet) -} - -// GlobalStringSlice looks up the value of a global StringSliceFlag, returns -// nil if not found -func (c *Context) GlobalStringSlice(name string) []string { - if fs := lookupGlobalFlagSet(name, c); fs != nil { - return lookupStringSlice(name, fs) - } - return nil -} - -func lookupStringSlice(name string, set *flag.FlagSet) []string { - f := set.Lookup(name) - if f != nil { - parsed, err := (f.Value.(*StringSlice)).Value(), error(nil) - if err != nil { - return nil - } - return parsed - } - return nil -} - -// Uint64Flag is a flag with type uint64 -type Uint64Flag struct { - Name string - Usage string - EnvVar string - Hidden bool - Value uint64 - Destination *uint64 -} - -// String returns a readable representation of this value -// (for usage defaults) -func (f Uint64Flag) String() string { - return FlagStringer(f) -} - -// GetName returns the name of the flag -func (f Uint64Flag) GetName() string { - return f.Name -} - -// Uint64 looks up the value of a local Uint64Flag, returns -// 0 if not found -func (c *Context) Uint64(name string) uint64 { - return lookupUint64(name, c.flagSet) -} - -// GlobalUint64 looks up the value of a global Uint64Flag, returns -// 0 if not found -func (c *Context) GlobalUint64(name string) uint64 { - if fs := lookupGlobalFlagSet(name, c); fs != nil { - return lookupUint64(name, fs) - } - return 0 -} - -func lookupUint64(name string, set *flag.FlagSet) uint64 { - f := set.Lookup(name) - if f != nil { - parsed, err := strconv.ParseUint(f.Value.String(), 0, 64) - if err != nil { - return 0 - } - return parsed - } - return 0 -} - -// UintFlag is a flag with type uint -type UintFlag struct { - Name string - Usage string - EnvVar string - Hidden bool - Value uint - Destination *uint -} - -// String returns a readable representation of this value -// (for usage defaults) -func (f UintFlag) String() string { - return FlagStringer(f) -} - -// GetName returns the name of the flag -func (f UintFlag) GetName() string { - return f.Name -} - -// Uint looks up the value of a local UintFlag, returns -// 0 if not found -func (c *Context) Uint(name string) uint { - return lookupUint(name, c.flagSet) -} - -// GlobalUint looks up the value of a global UintFlag, returns -// 0 if not found -func (c *Context) GlobalUint(name string) uint { - if fs := lookupGlobalFlagSet(name, c); fs != nil { - return lookupUint(name, fs) - } - return 0 -} - -func lookupUint(name string, set *flag.FlagSet) uint { - f := set.Lookup(name) - if f != nil { - parsed, err := strconv.ParseUint(f.Value.String(), 0, 64) - if err != nil { - return 0 - } - return uint(parsed) - } - return 0 -} diff --git a/vendor/github.com/urfave/cli/generate-flag-types b/vendor/github.com/urfave/cli/generate-flag-types deleted file mode 100644 index 7147381ce..000000000 --- a/vendor/github.com/urfave/cli/generate-flag-types +++ /dev/null @@ -1,255 +0,0 @@ -#!/usr/bin/env python -""" -The flag types that ship with the cli library have many things in common, and -so we can take advantage of the `go generate` command to create much of the -source code from a list of definitions. These definitions attempt to cover -the parts that vary between flag types, and should evolve as needed. - -An example of the minimum definition needed is: - - { - "name": "SomeType", - "type": "sometype", - "context_default": "nil" - } - -In this example, the code generated for the `cli` package will include a type -named `SomeTypeFlag` that is expected to wrap a value of type `sometype`. -Fetching values by name via `*cli.Context` will default to a value of `nil`. - -A more complete, albeit somewhat redundant, example showing all available -definition keys is: - - { - "name": "VeryMuchType", - "type": "*VeryMuchType", - "value": true, - "dest": false, - "doctail": " which really only wraps a []float64, oh well!", - "context_type": "[]float64", - "context_default": "nil", - "parser": "parseVeryMuchType(f.Value.String())", - "parser_cast": "[]float64(parsed)" - } - -The meaning of each field is as follows: - - name (string) - The type "name", which will be suffixed with - `Flag` when generating the type definition - for `cli` and the wrapper type for `altsrc` - type (string) - The type that the generated `Flag` type for `cli` - is expected to "contain" as its `.Value` member - value (bool) - Should the generated `cli` type have a `Value` - member? - dest (bool) - Should the generated `cli` type support a - destination pointer? - doctail (string) - Additional docs for the `cli` flag type comment - context_type (string) - The literal type used in the `*cli.Context` - reader func signature - context_default (string) - The literal value used as the default by the - `*cli.Context` reader funcs when no value is - present - parser (string) - Literal code used to parse the flag `f`, - expected to have a return signature of - (value, error) - parser_cast (string) - Literal code used to cast the `parsed` value - returned from the `parser` code -""" - -from __future__ import print_function, unicode_literals - -import argparse -import json -import os -import subprocess -import sys -import tempfile -import textwrap - - -class _FancyFormatter(argparse.ArgumentDefaultsHelpFormatter, - argparse.RawDescriptionHelpFormatter): - pass - - -def main(sysargs=sys.argv[:]): - parser = argparse.ArgumentParser( - description='Generate flag type code!', - formatter_class=_FancyFormatter) - parser.add_argument( - 'package', - type=str, default='cli', choices=_WRITEFUNCS.keys(), - help='Package for which flag types will be generated' - ) - parser.add_argument( - '-i', '--in-json', - type=argparse.FileType('r'), - default=sys.stdin, - help='Input JSON file which defines each type to be generated' - ) - parser.add_argument( - '-o', '--out-go', - type=argparse.FileType('w'), - default=sys.stdout, - help='Output file/stream to which generated source will be written' - ) - parser.epilog = __doc__ - - args = parser.parse_args(sysargs[1:]) - _generate_flag_types(_WRITEFUNCS[args.package], args.out_go, args.in_json) - return 0 - - -def _generate_flag_types(writefunc, output_go, input_json): - types = json.load(input_json) - - tmp = tempfile.NamedTemporaryFile(suffix='.go', delete=False) - writefunc(tmp, types) - tmp.close() - - new_content = subprocess.check_output( - ['goimports', tmp.name] - ).decode('utf-8') - - print(new_content, file=output_go, end='') - output_go.flush() - os.remove(tmp.name) - - -def _set_typedef_defaults(typedef): - typedef.setdefault('doctail', '') - typedef.setdefault('context_type', typedef['type']) - typedef.setdefault('dest', True) - typedef.setdefault('value', True) - typedef.setdefault('parser', 'f.Value, error(nil)') - typedef.setdefault('parser_cast', 'parsed') - - -def _write_cli_flag_types(outfile, types): - _fwrite(outfile, """\ - package cli - - // WARNING: This file is generated! - - """) - - for typedef in types: - _set_typedef_defaults(typedef) - - _fwrite(outfile, """\ - // {name}Flag is a flag with type {type}{doctail} - type {name}Flag struct {{ - Name string - Usage string - EnvVar string - Hidden bool - """.format(**typedef)) - - if typedef['value']: - _fwrite(outfile, """\ - Value {type} - """.format(**typedef)) - - if typedef['dest']: - _fwrite(outfile, """\ - Destination *{type} - """.format(**typedef)) - - _fwrite(outfile, "\n}\n\n") - - _fwrite(outfile, """\ - // String returns a readable representation of this value - // (for usage defaults) - func (f {name}Flag) String() string {{ - return FlagStringer(f) - }} - - // GetName returns the name of the flag - func (f {name}Flag) GetName() string {{ - return f.Name - }} - - // {name} looks up the value of a local {name}Flag, returns - // {context_default} if not found - func (c *Context) {name}(name string) {context_type} {{ - return lookup{name}(name, c.flagSet) - }} - - // Global{name} looks up the value of a global {name}Flag, returns - // {context_default} if not found - func (c *Context) Global{name}(name string) {context_type} {{ - if fs := lookupGlobalFlagSet(name, c); fs != nil {{ - return lookup{name}(name, fs) - }} - return {context_default} - }} - - func lookup{name}(name string, set *flag.FlagSet) {context_type} {{ - f := set.Lookup(name) - if f != nil {{ - parsed, err := {parser} - if err != nil {{ - return {context_default} - }} - return {parser_cast} - }} - return {context_default} - }} - """.format(**typedef)) - - -def _write_altsrc_flag_types(outfile, types): - _fwrite(outfile, """\ - package altsrc - - import ( - "gopkg.in/urfave/cli.v1" - ) - - // WARNING: This file is generated! - - """) - - for typedef in types: - _set_typedef_defaults(typedef) - - _fwrite(outfile, """\ - // {name}Flag is the flag type that wraps cli.{name}Flag to allow - // for other values to be specified - type {name}Flag struct {{ - cli.{name}Flag - set *flag.FlagSet - }} - - // New{name}Flag creates a new {name}Flag - func New{name}Flag(fl cli.{name}Flag) *{name}Flag {{ - return &{name}Flag{{{name}Flag: fl, set: nil}} - }} - - // Apply saves the flagSet for later usage calls, then calls the - // wrapped {name}Flag.Apply - func (f *{name}Flag) Apply(set *flag.FlagSet) {{ - f.set = set - f.{name}Flag.Apply(set) - }} - - // ApplyWithError saves the flagSet for later usage calls, then calls the - // wrapped {name}Flag.ApplyWithError - func (f *{name}Flag) ApplyWithError(set *flag.FlagSet) error {{ - f.set = set - return f.{name}Flag.ApplyWithError(set) - }} - """.format(**typedef)) - - -def _fwrite(outfile, text): - print(textwrap.dedent(text), end='', file=outfile) - - -_WRITEFUNCS = { - 'cli': _write_cli_flag_types, - 'altsrc': _write_altsrc_flag_types -} - -if __name__ == '__main__': - sys.exit(main()) diff --git a/vendor/github.com/urfave/cli/runtests b/vendor/github.com/urfave/cli/runtests deleted file mode 100644 index ee22bdeed..000000000 --- a/vendor/github.com/urfave/cli/runtests +++ /dev/null @@ -1,122 +0,0 @@ -#!/usr/bin/env python -from __future__ import print_function - -import argparse -import os -import sys -import tempfile - -from subprocess import check_call, check_output - - -PACKAGE_NAME = os.environ.get( - 'CLI_PACKAGE_NAME', 'github.com/urfave/cli' -) - - -def main(sysargs=sys.argv[:]): - targets = { - 'vet': _vet, - 'test': _test, - 'gfmrun': _gfmrun, - 'toc': _toc, - 'gen': _gen, - } - - parser = argparse.ArgumentParser() - parser.add_argument( - 'target', nargs='?', choices=tuple(targets.keys()), default='test' - ) - args = parser.parse_args(sysargs[1:]) - - targets[args.target]() - return 0 - - -def _test(): - if check_output('go version'.split()).split()[2] < 'go1.2': - _run('go test -v .') - return - - coverprofiles = [] - for subpackage in ['', 'altsrc']: - coverprofile = 'cli.coverprofile' - if subpackage != '': - coverprofile = '{}.coverprofile'.format(subpackage) - - coverprofiles.append(coverprofile) - - _run('go test -v'.split() + [ - '-coverprofile={}'.format(coverprofile), - ('{}/{}'.format(PACKAGE_NAME, subpackage)).rstrip('/') - ]) - - combined_name = _combine_coverprofiles(coverprofiles) - _run('go tool cover -func={}'.format(combined_name)) - os.remove(combined_name) - - -def _gfmrun(): - go_version = check_output('go version'.split()).split()[2] - if go_version < 'go1.3': - print('runtests: skip on {}'.format(go_version), file=sys.stderr) - return - _run(['gfmrun', '-c', str(_gfmrun_count()), '-s', 'README.md']) - - -def _vet(): - _run('go vet ./...') - - -def _toc(): - _run('node_modules/.bin/markdown-toc -i README.md') - _run('git diff --exit-code') - - -def _gen(): - go_version = check_output('go version'.split()).split()[2] - if go_version < 'go1.5': - print('runtests: skip on {}'.format(go_version), file=sys.stderr) - return - - _run('go generate ./...') - _run('git diff --exit-code') - - -def _run(command): - if hasattr(command, 'split'): - command = command.split() - print('runtests: {}'.format(' '.join(command)), file=sys.stderr) - check_call(command) - - -def _gfmrun_count(): - with open('README.md') as infile: - lines = infile.read().splitlines() - return len(filter(_is_go_runnable, lines)) - - -def _is_go_runnable(line): - return line.startswith('package main') - - -def _combine_coverprofiles(coverprofiles): - combined = tempfile.NamedTemporaryFile( - suffix='.coverprofile', delete=False - ) - combined.write('mode: set\n') - - for coverprofile in coverprofiles: - with open(coverprofile, 'r') as infile: - for line in infile.readlines(): - if not line.startswith('mode: '): - combined.write(line) - - combined.flush() - name = combined.name - combined.close() - return name - - -if __name__ == '__main__': - sys.exit(main()) diff --git a/vendor/github.com/urfave/cli/.flake8 b/vendor/github.com/urfave/cli/v2/.flake8 similarity index 100% rename from vendor/github.com/urfave/cli/.flake8 rename to vendor/github.com/urfave/cli/v2/.flake8 diff --git a/vendor/github.com/urfave/cli/.gitignore b/vendor/github.com/urfave/cli/v2/.gitignore similarity index 59% rename from vendor/github.com/urfave/cli/.gitignore rename to vendor/github.com/urfave/cli/v2/.gitignore index faf70c4c2..b013e4ac6 100644 --- a/vendor/github.com/urfave/cli/.gitignore +++ b/vendor/github.com/urfave/cli/v2/.gitignore @@ -1,2 +1,5 @@ *.coverprofile +*.orig node_modules/ +vendor +.idea diff --git a/vendor/github.com/urfave/cli/v2/CODE_OF_CONDUCT.md b/vendor/github.com/urfave/cli/v2/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..41ba294f6 --- /dev/null +++ b/vendor/github.com/urfave/cli/v2/CODE_OF_CONDUCT.md @@ -0,0 +1,74 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, gender identity and expression, level of experience, +education, socio-economic status, nationality, personal appearance, race, +religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting Dan Buch at dan@meatballhat.com. All complaints will be +reviewed and investigated and will result in a response that is deemed necessary +and appropriate to the circumstances. The project team is obligated to maintain +confidentiality with regard to the reporter of an incident. Further details of +specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + diff --git a/vendor/github.com/urfave/cli/LICENSE b/vendor/github.com/urfave/cli/v2/LICENSE similarity index 100% rename from vendor/github.com/urfave/cli/LICENSE rename to vendor/github.com/urfave/cli/v2/LICENSE diff --git a/vendor/github.com/urfave/cli/v2/README.md b/vendor/github.com/urfave/cli/v2/README.md new file mode 100644 index 000000000..e7fb3d755 --- /dev/null +++ b/vendor/github.com/urfave/cli/v2/README.md @@ -0,0 +1,68 @@ +cli +=== + +[![Windows Build Status](https://ci.appveyor.com/api/projects/status/rtgk5xufi932pb2v?svg=true)](https://ci.appveyor.com/project/urfave/cli) + +[![GoDoc](https://godoc.org/github.com/urfave/cli?status.svg)](https://godoc.org/github.com/urfave/cli) +[![codebeat](https://codebeat.co/badges/0a8f30aa-f975-404b-b878-5fab3ae1cc5f)](https://codebeat.co/projects/github-com-urfave-cli) +[![Go Report Card](https://goreportcard.com/badge/urfave/cli)](https://goreportcard.com/report/urfave/cli) +[![codecov](https://codecov.io/gh/urfave/cli/branch/master/graph/badge.svg)](https://codecov.io/gh/urfave/cli) + +cli is a simple, fast, and fun package for building command line apps in Go. The +goal is to enable developers to write fast and distributable command line +applications in an expressive way. + +## Usage Documentation + +Usage documentation exists for each major version. Don't know what version you're on? You're probably using the version from the `master` branch, which is currently `v2`. + +- `v2` - [./docs/v2/manual.md](./docs/v2/manual.md) +- `v1` - [./docs/v1/manual.md](./docs/v1/manual.md) + +## Installation + +Make sure you have a working Go environment. Go version 1.11+ is supported. [See the install instructions for Go](http://golang.org/doc/install.html). + +Go Modules are strongly recommended when using this package. [See the go blog guide on using Go Modules](https://blog.golang.org/using-go-modules). + +### Using `v2` releases + +``` +$ GO111MODULE=on go get github.com/urfave/cli/v2 +``` + +```go +... +import ( + "github.com/urfave/cli/v2" // imports as package "cli" +) +... +``` + +### Using `v1` releases + +``` +$ GO111MODULE=on go get github.com/urfave/cli +``` + +```go +... +import ( + "github.com/urfave/cli" +) +... +``` + +### GOPATH + +Make sure your `PATH` includes the `$GOPATH/bin` directory so your commands can +be easily used: +``` +export PATH=$PATH:$GOPATH/bin +``` + +### Supported platforms + +cli is tested against multiple versions of Go on Linux, and against the latest +released version of Go on OS X and Windows. This project uses Github Actions for +builds. For more build info, please look at the [./.github/workflows/cli.yml](https://github.com/urfave/cli/blob/master/.github/workflows/cli.yml). diff --git a/vendor/github.com/urfave/cli/app.go b/vendor/github.com/urfave/cli/v2/app.go similarity index 65% rename from vendor/github.com/urfave/cli/app.go rename to vendor/github.com/urfave/cli/v2/app.go index 51fc45d87..c04e9afda 100644 --- a/vendor/github.com/urfave/cli/app.go +++ b/vendor/github.com/urfave/cli/v2/app.go @@ -1,23 +1,22 @@ package cli import ( + "context" + "flag" "fmt" "io" - "io/ioutil" "os" "path/filepath" + "reflect" "sort" "time" ) var ( - changeLogURL = "https://github.com/urfave/cli/blob/master/CHANGELOG.md" - appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL) - runAndExitOnErrorDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-runandexitonerror", changeLogURL) - - contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you." - - errInvalidActionType = NewExitError("ERROR invalid Action type. "+ + changeLogURL = "https://github.com/urfave/cli/blob/master/docs/CHANGELOG.md" + appActionDeprecationURL = fmt.Sprintf("%s#deprecated-cli-app-action-signature", changeLogURL) + contactSysadmin = "This is an error in the application. Please contact the distributor of this application if this is not you." + errInvalidActionType = NewExitError("ERROR invalid Action type. "+ fmt.Sprintf("Must be `func(*Context`)` or `func(*Context) error). %s", contactSysadmin)+ fmt.Sprintf("See %s", appActionDeprecationURL), 2) ) @@ -40,7 +39,7 @@ type App struct { // Description of the program Description string // List of commands to execute - Commands []Command + Commands []*Command // List of flags to parse Flags []Flag // Boolean to enable bash completion commands @@ -49,9 +48,9 @@ type App struct { HideHelp bool // Boolean to hide built-in version flag and the VERSION section of help HideVersion bool - // Populate on app startup, only gettable through method Categories() + // categories contains the categorized commands and is populated on app startup categories CommandCategories - // An action to execute when the bash-completion flag is set + // An action to execute when the shell completion flag is set BashComplete BashCompleteFunc // An action to execute before any subcommands are run, but after the context is ready // If a non-nil error is returned, no subcommands are run @@ -59,12 +58,8 @@ type App struct { // An action to execute after any subcommands are run, but after the subcommand has finished // It is run even if Action() panics After AfterFunc - // The action to execute when no subcommands are specified - // Expects a `cli.ActionFunc` but will accept the *deprecated* signature of `func(*cli.Context) {}` - // *Note*: support for the deprecated `Action` signature will be removed in a future version - Action interface{} - + Action ActionFunc // Execute this function if the proper command cannot be found CommandNotFound CommandNotFoundFunc // Execute this function if an usage error occurs @@ -72,17 +67,16 @@ type App struct { // Compilation date Compiled time.Time // List of all authors who contributed - Authors []Author + Authors []*Author // Copyright of the binary if any Copyright string - // Name of Author (Note: Use App.Authors, this is deprecated) - Author string - // Email of Author (Note: Use App.Authors, this is deprecated) - Email string // Writer writer to write output to Writer io.Writer // ErrWriter writes error output ErrWriter io.Writer + // Execute this function to handle ExitErrors. If not provided, HandleExitCoder is provided to + // function as a default, so this is optional. + ExitErrHandler ExitErrHandlerFunc // Other custom info Metadata map[string]interface{} // Carries a function which returns app specific info. @@ -91,6 +85,10 @@ type App struct { // cli.go uses text/template to render templates. You can // render custom help text by setting this variable. CustomAppHelpTemplate string + // Boolean to enable short-option handling so user can combine several + // single-character bool arguments into one + // i.e. foobar -o -v -> foobar -ov + UseShortOptionHandling bool didSetup bool } @@ -113,7 +111,6 @@ func NewApp() *App { HelpName: filepath.Base(os.Args[0]), Usage: "A new cli application", UsageText: "", - Version: "0.0.0", BashComplete: DefaultAppComplete, Action: helpCommand.Action, Compiled: compileTime(), @@ -131,22 +128,52 @@ func (a *App) Setup() { a.didSetup = true - if a.Author != "" || a.Email != "" { - a.Authors = append(a.Authors, Author{Name: a.Author, Email: a.Email}) + if a.Name == "" { + a.Name = filepath.Base(os.Args[0]) } - newCmds := []Command{} + if a.HelpName == "" { + a.HelpName = filepath.Base(os.Args[0]) + } + + if a.Usage == "" { + a.Usage = "A new cli application" + } + + if a.Version == "" { + a.HideVersion = true + } + + if a.BashComplete == nil { + a.BashComplete = DefaultAppComplete + } + + if a.Action == nil { + a.Action = helpCommand.Action + } + + if a.Compiled == (time.Time{}) { + a.Compiled = compileTime() + } + + if a.Writer == nil { + a.Writer = os.Stdout + } + + var newCommands []*Command + for _, c := range a.Commands { if c.HelpName == "" { c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) } - newCmds = append(newCmds, c) + newCommands = append(newCommands, c) } - a.Commands = newCmds + a.Commands = newCommands if a.Command(helpCommand.Name) == nil && !a.HideHelp { - a.Commands = append(a.Commands, helpCommand) - if (HelpFlag != BoolFlag{}) { + a.appendCommand(helpCommand) + + if HelpFlag != nil { a.appendFlag(HelpFlag) } } @@ -155,11 +182,11 @@ func (a *App) Setup() { a.appendFlag(VersionFlag) } - a.categories = CommandCategories{} + a.categories = newCommandCategories() for _, command := range a.Commands { - a.categories = a.categories.AddCommand(command.Category, command) + a.categories.AddCommand(command.Category, command) } - sort.Sort(a.categories) + sort.Sort(a.categories.(*commandCategories)) if a.Metadata == nil { a.Metadata = make(map[string]interface{}) @@ -170,9 +197,24 @@ func (a *App) Setup() { } } +func (a *App) newFlagSet() (*flag.FlagSet, error) { + return flagSet(a.Name, a.Flags) +} + +func (a *App) useShortOptionHandling() bool { + return a.UseShortOptionHandling +} + // Run is the entry point to the cli app. Parses the arguments slice and routes // to the proper flag/args combination func (a *App) Run(arguments []string) (err error) { + return a.RunContext(context.Background(), arguments) +} + +// RunContext is like Run except it takes a Context that will be +// passed to its commands and sub-commands. Through this, you can +// propagate timeouts and cancellation requests +func (a *App) RunContext(ctx context.Context, arguments []string) (err error) { a.Setup() // handle the completion flag separately from the flagset since @@ -183,19 +225,17 @@ func (a *App) Run(arguments []string) (err error) { // always appends the completion flag at the end of the command shellComplete, arguments := checkShellCompleteFlag(a, arguments) - // parse flags - set, err := flagSet(a.Name, a.Flags) + set, err := a.newFlagSet() if err != nil { return err } - set.SetOutput(ioutil.Discard) - err = set.Parse(arguments[1:]) + err = parseIter(set, a, arguments[1:], shellComplete) nerr := normalizeFlags(a.Flags, set) - context := NewContext(a, set, nil) + context := NewContext(a, set, &Context{Context: ctx}) if nerr != nil { - fmt.Fprintln(a.Writer, nerr) - ShowAppHelp(context) + _, _ = fmt.Fprintln(a.Writer, nerr) + _ = ShowAppHelp(context) return nerr } context.shellComplete = shellComplete @@ -207,16 +247,16 @@ func (a *App) Run(arguments []string) (err error) { if err != nil { if a.OnUsageError != nil { err := a.OnUsageError(context, err, false) - HandleExitCoder(err) + a.handleExitCoder(context, err) return err } - fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) - ShowAppHelp(context) + _, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) + _ = ShowAppHelp(context) return err } if !a.HideHelp && checkHelp(context) { - ShowAppHelp(context) + _ = ShowAppHelp(context) return nil } @@ -225,11 +265,17 @@ func (a *App) Run(arguments []string) (err error) { return nil } + cerr := checkRequiredFlags(a.Flags, context) + if cerr != nil { + _ = ShowAppHelp(context) + return cerr + } + if a.After != nil { defer func() { if afterErr := a.After(context); afterErr != nil { if err != nil { - err = NewMultiError(err, afterErr) + err = newMultiError(err, afterErr) } else { err = afterErr } @@ -240,8 +286,9 @@ func (a *App) Run(arguments []string) (err error) { if a.Before != nil { beforeErr := a.Before(context) if beforeErr != nil { - ShowAppHelp(context) - HandleExitCoder(beforeErr) + _, _ = fmt.Fprintf(a.Writer, "%v\n\n", beforeErr) + _ = ShowAppHelp(context) + a.handleExitCoder(context, beforeErr) err = beforeErr return err } @@ -261,9 +308,9 @@ func (a *App) Run(arguments []string) (err error) { } // Run default Action - err = HandleAction(a.Action, context) + err = a.Action(context) - HandleExitCoder(err) + a.handleExitCoder(context, err) return err } @@ -274,7 +321,7 @@ func (a *App) Run(arguments []string) (err error) { // code in the cli.ExitCoder func (a *App) RunAndExitOnError() { if err := a.Run(os.Args); err != nil { - fmt.Fprintln(a.errWriter(), err) + _, _ = fmt.Fprintln(a.errWriter(), err) OsExiter(1) } } @@ -282,17 +329,20 @@ func (a *App) RunAndExitOnError() { // RunAsSubcommand invokes the subcommand given the context, parses ctx.Args() to // generate command-specific flags func (a *App) RunAsSubcommand(ctx *Context) (err error) { + a.Setup() + // append help to commands if len(a.Commands) > 0 { if a.Command(helpCommand.Name) == nil && !a.HideHelp { - a.Commands = append(a.Commands, helpCommand) - if (HelpFlag != BoolFlag{}) { + a.appendCommand(helpCommand) + + if HelpFlag != nil { a.appendFlag(HelpFlag) } } } - newCmds := []Command{} + var newCmds []*Command for _, c := range a.Commands { if c.HelpName == "" { c.HelpName = fmt.Sprintf("%s %s", a.HelpName, c.Name) @@ -301,24 +351,22 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { } a.Commands = newCmds - // parse flags - set, err := flagSet(a.Name, a.Flags) + set, err := a.newFlagSet() if err != nil { return err } - set.SetOutput(ioutil.Discard) - err = set.Parse(ctx.Args().Tail()) + err = parseIter(set, a, ctx.Args().Tail(), ctx.shellComplete) nerr := normalizeFlags(a.Flags, set) context := NewContext(a, set, ctx) if nerr != nil { - fmt.Fprintln(a.Writer, nerr) - fmt.Fprintln(a.Writer) + _, _ = fmt.Fprintln(a.Writer, nerr) + _, _ = fmt.Fprintln(a.Writer) if len(a.Commands) > 0 { - ShowSubcommandHelp(context) + _ = ShowSubcommandHelp(context) } else { - ShowCommandHelp(ctx, context.Args().First()) + _ = ShowCommandHelp(ctx, context.Args().First()) } return nerr } @@ -330,11 +378,11 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { if err != nil { if a.OnUsageError != nil { err = a.OnUsageError(context, err, true) - HandleExitCoder(err) + a.handleExitCoder(context, err) return err } - fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) - ShowSubcommandHelp(context) + _, _ = fmt.Fprintf(a.Writer, "%s %s\n\n", "Incorrect Usage.", err.Error()) + _ = ShowSubcommandHelp(context) return err } @@ -348,13 +396,19 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { } } + cerr := checkRequiredFlags(a.Flags, context) + if cerr != nil { + _ = ShowSubcommandHelp(context) + return cerr + } + if a.After != nil { defer func() { afterErr := a.After(context) if afterErr != nil { - HandleExitCoder(err) + a.handleExitCoder(context, err) if err != nil { - err = NewMultiError(err, afterErr) + err = newMultiError(err, afterErr) } else { err = afterErr } @@ -365,7 +419,7 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { if a.Before != nil { beforeErr := a.Before(context) if beforeErr != nil { - HandleExitCoder(beforeErr) + a.handleExitCoder(context, beforeErr) err = beforeErr return err } @@ -381,9 +435,9 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { } // Run default Action - err = HandleAction(a.Action, context) + err = a.Action(context) - HandleExitCoder(err) + a.handleExitCoder(context, err) return err } @@ -391,28 +445,21 @@ func (a *App) RunAsSubcommand(ctx *Context) (err error) { func (a *App) Command(name string) *Command { for _, c := range a.Commands { if c.HasName(name) { - return &c + return c } } return nil } -// Categories returns a slice containing all the categories with the commands they contain -func (a *App) Categories() CommandCategories { - return a.categories -} - // VisibleCategories returns a slice of categories and commands that are // Hidden=false -func (a *App) VisibleCategories() []*CommandCategory { - ret := []*CommandCategory{} - for _, category := range a.categories { - if visible := func() *CommandCategory { - for _, command := range category.Commands { - if !command.Hidden { - return category - } +func (a *App) VisibleCategories() []CommandCategory { + ret := []CommandCategory{} + for _, category := range a.categories.Categories() { + if visible := func() CommandCategory { + if len(category.VisibleCommands()) > 0 { + return category } return nil }(); visible != nil { @@ -423,8 +470,8 @@ func (a *App) VisibleCategories() []*CommandCategory { } // VisibleCommands returns a slice of the Commands with Hidden=false -func (a *App) VisibleCommands() []Command { - ret := []Command{} +func (a *App) VisibleCommands() []*Command { + var ret []*Command for _, command := range a.Commands { if !command.Hidden { ret = append(ret, command) @@ -440,7 +487,7 @@ func (a *App) VisibleFlags() []Flag { func (a *App) hasFlag(flag Flag) bool { for _, f := range a.Flags { - if flag == f { + if reflect.DeepEqual(flag, f) { return true } } @@ -449,7 +496,6 @@ func (a *App) hasFlag(flag Flag) bool { } func (a *App) errWriter() io.Writer { - // When the app ErrWriter is nil use the package level one. if a.ErrWriter == nil { return ErrWriter @@ -458,9 +504,23 @@ func (a *App) errWriter() io.Writer { return a.ErrWriter } -func (a *App) appendFlag(flag Flag) { - if !a.hasFlag(flag) { - a.Flags = append(a.Flags, flag) +func (a *App) appendFlag(fl Flag) { + if !hasFlag(a.Flags, fl) { + a.Flags = append(a.Flags, fl) + } +} + +func (a *App) appendCommand(c *Command) { + if !hasCommand(a.Commands, c) { + a.Commands = append(a.Commands, c) + } +} + +func (a *App) handleExitCoder(context *Context, err error) { + if a.ExitErrHandler != nil { + a.ExitErrHandler(context, err) + } else { + HandleExitCoder(err) } } @@ -471,7 +531,7 @@ type Author struct { } // String makes Author comply to the Stringer interface, to allow an easy print in the templating process -func (a Author) String() string { +func (a *Author) String() string { e := "" if a.Email != "" { e = " <" + a.Email + ">" @@ -484,14 +544,15 @@ func (a Author) String() string { // it's an ActionFunc or a func with the legacy signature for Action, the func // is run! func HandleAction(action interface{}, context *Context) (err error) { - if a, ok := action.(ActionFunc); ok { + switch a := action.(type) { + case ActionFunc: return a(context) - } else if a, ok := action.(func(*Context) error); ok { + case func(*Context) error: return a(context) - } else if a, ok := action.(func(*Context)); ok { // deprecated function signature + case func(*Context): // deprecated function signature a(context) return nil - } else { - return errInvalidActionType } + + return errInvalidActionType } diff --git a/vendor/github.com/urfave/cli/v2/appveyor.yml b/vendor/github.com/urfave/cli/v2/appveyor.yml new file mode 100644 index 000000000..f1cae90b5 --- /dev/null +++ b/vendor/github.com/urfave/cli/v2/appveyor.yml @@ -0,0 +1,28 @@ +version: "{build}" + +os: Windows Server 2016 + +image: Visual Studio 2017 + +clone_folder: c:\gopath\src\github.com\urfave\cli + +cache: + - node_modules + +environment: + GOPATH: C:\gopath + GOVERSION: 1.11.x + GO111MODULE: on + GOPROXY: https://proxy.golang.org + +install: + - set PATH=%GOPATH%\bin;C:\go\bin;%PATH% + - go version + - go env + - go get github.com/urfave/gfmrun/cmd/gfmrun + - go mod tidy + +build_script: + - go run build.go vet + - go run build.go test + - go run build.go gfmrun docs/v1/manual.md diff --git a/vendor/github.com/urfave/cli/v2/args.go b/vendor/github.com/urfave/cli/v2/args.go new file mode 100644 index 000000000..bd65c17bd --- /dev/null +++ b/vendor/github.com/urfave/cli/v2/args.go @@ -0,0 +1,54 @@ +package cli + +type Args interface { + // Get returns the nth argument, or else a blank string + Get(n int) string + // First returns the first argument, or else a blank string + First() string + // Tail returns the rest of the arguments (not the first one) + // or else an empty string slice + Tail() []string + // Len returns the length of the wrapped slice + Len() int + // Present checks if there are any arguments present + Present() bool + // Slice returns a copy of the internal slice + Slice() []string +} + +type args []string + +func (a *args) Get(n int) string { + if len(*a) > n { + return (*a)[n] + } + return "" +} + +func (a *args) First() string { + return a.Get(0) +} + +func (a *args) Tail() []string { + if a.Len() >= 2 { + tail := []string((*a)[1:]) + ret := make([]string, len(tail)) + copy(ret, tail) + return ret + } + return []string{} +} + +func (a *args) Len() int { + return len(*a) +} + +func (a *args) Present() bool { + return a.Len() != 0 +} + +func (a *args) Slice() []string { + ret := make([]string, len(*a)) + copy(ret, *a) + return ret +} diff --git a/vendor/github.com/urfave/cli/v2/category.go b/vendor/github.com/urfave/cli/v2/category.go new file mode 100644 index 000000000..867e3908c --- /dev/null +++ b/vendor/github.com/urfave/cli/v2/category.go @@ -0,0 +1,79 @@ +package cli + +// CommandCategories interface allows for category manipulation +type CommandCategories interface { + // AddCommand adds a command to a category, creating a new category if necessary. + AddCommand(category string, command *Command) + // categories returns a copy of the category slice + Categories() []CommandCategory +} + +type commandCategories []*commandCategory + +func newCommandCategories() CommandCategories { + ret := commandCategories([]*commandCategory{}) + return &ret +} + +func (c *commandCategories) Less(i, j int) bool { + return lexicographicLess((*c)[i].Name(), (*c)[j].Name()) +} + +func (c *commandCategories) Len() int { + return len(*c) +} + +func (c *commandCategories) Swap(i, j int) { + (*c)[i], (*c)[j] = (*c)[j], (*c)[i] +} + +func (c *commandCategories) AddCommand(category string, command *Command) { + for _, commandCategory := range []*commandCategory(*c) { + if commandCategory.name == category { + commandCategory.commands = append(commandCategory.commands, command) + return + } + } + newVal := append(*c, + &commandCategory{name: category, commands: []*Command{command}}) + *c = newVal +} + +func (c *commandCategories) Categories() []CommandCategory { + ret := make([]CommandCategory, len(*c)) + for i, cat := range *c { + ret[i] = cat + } + return ret +} + +// CommandCategory is a category containing commands. +type CommandCategory interface { + // Name returns the category name string + Name() string + // VisibleCommands returns a slice of the Commands with Hidden=false + VisibleCommands() []*Command +} + +type commandCategory struct { + name string + commands []*Command +} + +func (c *commandCategory) Name() string { + return c.name +} + +func (c *commandCategory) VisibleCommands() []*Command { + if c.commands == nil { + c.commands = []*Command{} + } + + var ret []*Command + for _, command := range c.commands { + if !command.Hidden { + ret = append(ret, command) + } + } + return ret +} diff --git a/vendor/github.com/urfave/cli/cli.go b/vendor/github.com/urfave/cli/v2/cli.go similarity index 56% rename from vendor/github.com/urfave/cli/cli.go rename to vendor/github.com/urfave/cli/v2/cli.go index 90c07eb8e..62a5bc22d 100644 --- a/vendor/github.com/urfave/cli/cli.go +++ b/vendor/github.com/urfave/cli/v2/cli.go @@ -2,21 +2,22 @@ // Go applications. cli is designed to be easy to understand and write, the most simple // cli application can be written as follows: // func main() { -// cli.NewApp().Run(os.Args) +// (&cli.App{}).Run(os.Args) // } // // Of course this application does not do much, so let's make this an actual application: // func main() { -// app := cli.NewApp() -// app.Name = "greet" -// app.Usage = "say a greeting" -// app.Action = func(c *cli.Context) error { -// println("Greetings") -// return nil -// } +// app := &cli.App{ +// Name: "greet", +// Usage: "say a greeting", +// Action: func(c *cli.Context) error { +// fmt.Println("Greetings") +// return nil +// }, +// } // // app.Run(os.Args) // } package cli -//go:generate python ./generate-flag-types cli -i flag-types.json -o flag_generated.go +//go:generate go run flag-gen/main.go flag-gen/assets_vfsdata.go diff --git a/vendor/github.com/urfave/cli/command.go b/vendor/github.com/urfave/cli/v2/command.go similarity index 57% rename from vendor/github.com/urfave/cli/command.go rename to vendor/github.com/urfave/cli/v2/command.go index 23de2944b..db6c8024b 100644 --- a/vendor/github.com/urfave/cli/command.go +++ b/vendor/github.com/urfave/cli/v2/command.go @@ -1,8 +1,8 @@ package cli import ( + "flag" "fmt" - "io/ioutil" "sort" "strings" ) @@ -11,8 +11,6 @@ import ( type Command struct { // The name of the command Name string - // short name of the command. Typically one character (deprecated, use `Aliases`) - ShortName string // A list of aliases for the command Aliases []string // A short description of the usage of this command @@ -34,27 +32,23 @@ type Command struct { // It is run even if Action() panics After AfterFunc // The function to call when this command is invoked - Action interface{} - // TODO: replace `Action: interface{}` with `Action: ActionFunc` once some kind - // of deprecation period has passed, maybe? - + Action ActionFunc // Execute this function if a usage error occurs. OnUsageError OnUsageErrorFunc // List of child commands - Subcommands Commands + Subcommands []*Command // List of flags to parse Flags []Flag // Treat all flags as normal arguments if true SkipFlagParsing bool - // Skip argument reordering which attempts to move flags before arguments, - // but only works if all flags appear after all arguments. This behavior was - // removed n version 2 since it only works under specific conditions so we - // backport here by exposing it as an option for compatibility. - SkipArgReorder bool // Boolean to hide built-in help command HideHelp bool // Boolean to hide this command from help or completion Hidden bool + // Boolean to enable short-option handling so user can combine several + // single-character bool arguments into one + // i.e. foobar -o -v -> foobar -ov + UseShortOptionHandling bool // Full name of command for help, defaults to full command name, including parent commands. HelpName string @@ -66,14 +60,16 @@ type Command struct { CustomHelpTemplate string } -type CommandsByName []Command +type Commands []*Command + +type CommandsByName []*Command func (c CommandsByName) Len() int { return len(c) } func (c CommandsByName) Less(i, j int) bool { - return c[i].Name < c[j].Name + return lexicographicLess(c[i].Name, c[j].Name) } func (c CommandsByName) Swap(i, j int) { @@ -82,81 +78,29 @@ func (c CommandsByName) Swap(i, j int) { // FullName returns the full name of the command. // For subcommands this ensures that parent commands are part of the command path -func (c Command) FullName() string { +func (c *Command) FullName() string { if c.commandNamePath == nil { return c.Name } return strings.Join(c.commandNamePath, " ") } -// Commands is a slice of Command -type Commands []Command - // Run invokes the command given the context, parses ctx.Args() to generate command-specific flags -func (c Command) Run(ctx *Context) (err error) { +func (c *Command) Run(ctx *Context) (err error) { if len(c.Subcommands) > 0 { return c.startApp(ctx) } - if !c.HideHelp && (HelpFlag != BoolFlag{}) { + if !c.HideHelp && HelpFlag != nil { // append help to flags - c.Flags = append( - c.Flags, - HelpFlag, - ) + c.appendFlag(HelpFlag) } - set, err := flagSet(c.Name, c.Flags) - if err != nil { - return err - } - set.SetOutput(ioutil.Discard) - - if c.SkipFlagParsing { - err = set.Parse(append([]string{"--"}, ctx.Args().Tail()...)) - } else if !c.SkipArgReorder { - firstFlagIndex := -1 - terminatorIndex := -1 - for index, arg := range ctx.Args() { - if arg == "--" { - terminatorIndex = index - break - } else if arg == "-" { - // Do nothing. A dash alone is not really a flag. - continue - } else if strings.HasPrefix(arg, "-") && firstFlagIndex == -1 { - firstFlagIndex = index - } - } - - if firstFlagIndex > -1 { - args := ctx.Args() - regularArgs := make([]string, len(args[1:firstFlagIndex])) - copy(regularArgs, args[1:firstFlagIndex]) - - var flagArgs []string - if terminatorIndex > -1 { - flagArgs = args[firstFlagIndex:terminatorIndex] - regularArgs = append(regularArgs, args[terminatorIndex:]...) - } else { - flagArgs = args[firstFlagIndex:] - } - - err = set.Parse(append(flagArgs, regularArgs...)) - } else { - err = set.Parse(ctx.Args().Tail()) - } - } else { - err = set.Parse(ctx.Args().Tail()) + if ctx.App.UseShortOptionHandling { + c.UseShortOptionHandling = true } - nerr := normalizeFlags(c.Flags, set) - if nerr != nil { - fmt.Fprintln(ctx.App.Writer, nerr) - fmt.Fprintln(ctx.App.Writer) - ShowCommandHelp(ctx, c.Name) - return nerr - } + set, err := c.parseFlags(ctx.Args(), ctx.shellComplete) context := NewContext(ctx.App, set, ctx) context.Command = c @@ -166,13 +110,13 @@ func (c Command) Run(ctx *Context) (err error) { if err != nil { if c.OnUsageError != nil { - err := c.OnUsageError(context, err, false) - HandleExitCoder(err) + err = c.OnUsageError(context, err, false) + context.App.handleExitCoder(context, err) return err } - fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error()) - fmt.Fprintln(context.App.Writer) - ShowCommandHelp(context, c.Name) + _, _ = fmt.Fprintln(context.App.Writer, "Incorrect Usage:", err.Error()) + _, _ = fmt.Fprintln(context.App.Writer) + _ = ShowCommandHelp(context, c.Name) return err } @@ -180,13 +124,19 @@ func (c Command) Run(ctx *Context) (err error) { return nil } + cerr := checkRequiredFlags(c.Flags, context) + if cerr != nil { + _ = ShowCommandHelp(context, c.Name) + return cerr + } + if c.After != nil { defer func() { afterErr := c.After(context) if afterErr != nil { - HandleExitCoder(err) + context.App.handleExitCoder(context, err) if err != nil { - err = NewMultiError(err, afterErr) + err = newMultiError(err, afterErr) } else { err = afterErr } @@ -197,8 +147,8 @@ func (c Command) Run(ctx *Context) (err error) { if c.Before != nil { err = c.Before(context) if err != nil { - ShowCommandHelp(context, c.Name) - HandleExitCoder(err) + _ = ShowCommandHelp(context, c.Name) + context.App.handleExitCoder(context, err) return err } } @@ -207,27 +157,53 @@ func (c Command) Run(ctx *Context) (err error) { c.Action = helpSubcommand.Action } - err = HandleAction(c.Action, context) + context.Command = c + err = c.Action(context) if err != nil { - HandleExitCoder(err) + context.App.handleExitCoder(context, err) } return err } -// Names returns the names including short names and aliases. -func (c Command) Names() []string { - names := []string{c.Name} - - if c.ShortName != "" { - names = append(names, c.ShortName) - } - - return append(names, c.Aliases...) +func (c *Command) newFlagSet() (*flag.FlagSet, error) { + return flagSet(c.Name, c.Flags) } -// HasName returns true if Command.Name or Command.ShortName matches given name -func (c Command) HasName(name string) bool { +func (c *Command) useShortOptionHandling() bool { + return c.UseShortOptionHandling +} + +func (c *Command) parseFlags(args Args, shellComplete bool) (*flag.FlagSet, error) { + set, err := c.newFlagSet() + if err != nil { + return nil, err + } + + if c.SkipFlagParsing { + return set, set.Parse(append([]string{"--"}, args.Tail()...)) + } + + err = parseIter(set, c, args.Tail(), shellComplete) + if err != nil { + return nil, err + } + + err = normalizeFlags(c.Flags, set) + if err != nil { + return nil, err + } + + return set, nil +} + +// Names returns the names including short names and aliases. +func (c *Command) Names() []string { + return append([]string{c.Name}, c.Aliases...) +} + +// HasName returns true if Command.Name matches given name +func (c *Command) HasName(name string) bool { for _, n := range c.Names() { if n == name { return true @@ -236,11 +212,12 @@ func (c Command) HasName(name string) bool { return false } -func (c Command) startApp(ctx *Context) error { - app := NewApp() - app.Metadata = ctx.App.Metadata - // set the name and usage - app.Name = fmt.Sprintf("%s %s", ctx.App.Name, c.Name) +func (c *Command) startApp(ctx *Context) error { + app := &App{ + Metadata: ctx.App.Metadata, + Name: fmt.Sprintf("%s %s", ctx.App.Name, c.Name), + } + if c.HelpName == "" { app.HelpName = c.HelpName } else { @@ -263,17 +240,17 @@ func (c Command) startApp(ctx *Context) error { app.Version = ctx.App.Version app.HideVersion = ctx.App.HideVersion app.Compiled = ctx.App.Compiled - app.Author = ctx.App.Author - app.Email = ctx.App.Email app.Writer = ctx.App.Writer app.ErrWriter = ctx.App.ErrWriter + app.ExitErrHandler = ctx.App.ExitErrHandler + app.UseShortOptionHandling = ctx.App.UseShortOptionHandling - app.categories = CommandCategories{} + app.categories = newCommandCategories() for _, command := range c.Subcommands { - app.categories = app.categories.AddCommand(command.Category, command) + app.categories.AddCommand(command.Category, command) } - sort.Sort(app.categories) + sort.Sort(app.categories.(*commandCategories)) // bash completion app.EnableBashCompletion = ctx.App.EnableBashCompletion @@ -299,6 +276,22 @@ func (c Command) startApp(ctx *Context) error { } // VisibleFlags returns a slice of the Flags with Hidden=false -func (c Command) VisibleFlags() []Flag { +func (c *Command) VisibleFlags() []Flag { return visibleFlags(c.Flags) } + +func (c *Command) appendFlag(fl Flag) { + if !hasFlag(c.Flags, fl) { + c.Flags = append(c.Flags, fl) + } +} + +func hasCommand(commands []*Command, command *Command) bool { + for _, existing := range commands { + if command == existing { + return true + } + } + + return false +} diff --git a/vendor/github.com/urfave/cli/v2/context.go b/vendor/github.com/urfave/cli/v2/context.go new file mode 100644 index 000000000..c0c526f41 --- /dev/null +++ b/vendor/github.com/urfave/cli/v2/context.go @@ -0,0 +1,274 @@ +package cli + +import ( + "context" + "errors" + "flag" + "fmt" + "strings" +) + +// Context is a type that is passed through to +// each Handler action in a cli application. Context +// can be used to retrieve context-specific args and +// parsed command-line options. +type Context struct { + context.Context + App *App + Command *Command + shellComplete bool + setFlags map[string]bool + flagSet *flag.FlagSet + parentContext *Context +} + +// NewContext creates a new context. For use in when invoking an App or Command action. +func NewContext(app *App, set *flag.FlagSet, parentCtx *Context) *Context { + c := &Context{App: app, flagSet: set, parentContext: parentCtx} + if parentCtx != nil { + c.Context = parentCtx.Context + c.shellComplete = parentCtx.shellComplete + if parentCtx.flagSet == nil { + parentCtx.flagSet = &flag.FlagSet{} + } + } + + c.Command = &Command{} + + if c.Context == nil { + c.Context = context.Background() + } + + return c +} + +// NumFlags returns the number of flags set +func (c *Context) NumFlags() int { + return c.flagSet.NFlag() +} + +// Set sets a context flag to a value. +func (c *Context) Set(name, value string) error { + return c.flagSet.Set(name, value) +} + +// IsSet determines if the flag was actually set +func (c *Context) IsSet(name string) bool { + if fs := lookupFlagSet(name, c); fs != nil { + if fs := lookupFlagSet(name, c); fs != nil { + isSet := false + fs.Visit(func(f *flag.Flag) { + if f.Name == name { + isSet = true + } + }) + if isSet { + return true + } + } + + f := lookupFlag(name, c) + if f == nil { + return false + } + + return f.IsSet() + } + + return false +} + +// LocalFlagNames returns a slice of flag names used in this context. +func (c *Context) LocalFlagNames() []string { + var names []string + c.flagSet.Visit(makeFlagNameVisitor(&names)) + return names +} + +// FlagNames returns a slice of flag names used by the this context and all of +// its parent contexts. +func (c *Context) FlagNames() []string { + var names []string + for _, ctx := range c.Lineage() { + ctx.flagSet.Visit(makeFlagNameVisitor(&names)) + } + return names +} + +// Lineage returns *this* context and all of its ancestor contexts in order from +// child to parent +func (c *Context) Lineage() []*Context { + var lineage []*Context + + for cur := c; cur != nil; cur = cur.parentContext { + lineage = append(lineage, cur) + } + + return lineage +} + +// Value returns the value of the flag corresponding to `name` +func (c *Context) Value(name string) interface{} { + return c.flagSet.Lookup(name).Value.(flag.Getter).Get() +} + +// Args returns the command line arguments associated with the context. +func (c *Context) Args() Args { + ret := args(c.flagSet.Args()) + return &ret +} + +// NArg returns the number of the command line arguments. +func (c *Context) NArg() int { + return c.Args().Len() +} + +func lookupFlag(name string, ctx *Context) Flag { + for _, c := range ctx.Lineage() { + if c.Command == nil { + continue + } + + for _, f := range c.Command.Flags { + for _, n := range f.Names() { + if n == name { + return f + } + } + } + } + + if ctx.App != nil { + for _, f := range ctx.App.Flags { + for _, n := range f.Names() { + if n == name { + return f + } + } + } + } + + return nil +} + +func lookupFlagSet(name string, ctx *Context) *flag.FlagSet { + for _, c := range ctx.Lineage() { + if f := c.flagSet.Lookup(name); f != nil { + return c.flagSet + } + } + + return nil +} + +func copyFlag(name string, ff *flag.Flag, set *flag.FlagSet) { + switch ff.Value.(type) { + case Serializer: + _ = set.Set(name, ff.Value.(Serializer).Serialize()) + default: + _ = set.Set(name, ff.Value.String()) + } +} + +func normalizeFlags(flags []Flag, set *flag.FlagSet) error { + visited := make(map[string]bool) + set.Visit(func(f *flag.Flag) { + visited[f.Name] = true + }) + for _, f := range flags { + parts := f.Names() + if len(parts) == 1 { + continue + } + var ff *flag.Flag + for _, name := range parts { + name = strings.Trim(name, " ") + if visited[name] { + if ff != nil { + return errors.New("Cannot use two forms of the same flag: " + name + " " + ff.Name) + } + ff = set.Lookup(name) + } + } + if ff == nil { + continue + } + for _, name := range parts { + name = strings.Trim(name, " ") + if !visited[name] { + copyFlag(name, ff, set) + } + } + } + return nil +} + +func makeFlagNameVisitor(names *[]string) func(*flag.Flag) { + return func(f *flag.Flag) { + nameParts := strings.Split(f.Name, ",") + name := strings.TrimSpace(nameParts[0]) + + for _, part := range nameParts { + part = strings.TrimSpace(part) + if len(part) > len(name) { + name = part + } + } + + if name != "" { + *names = append(*names, name) + } + } +} + +type requiredFlagsErr interface { + error + getMissingFlags() []string +} + +type errRequiredFlags struct { + missingFlags []string +} + +func (e *errRequiredFlags) Error() string { + numberOfMissingFlags := len(e.missingFlags) + if numberOfMissingFlags == 1 { + return fmt.Sprintf("Required flag %q not set", e.missingFlags[0]) + } + joinedMissingFlags := strings.Join(e.missingFlags, ", ") + return fmt.Sprintf("Required flags %q not set", joinedMissingFlags) +} + +func (e *errRequiredFlags) getMissingFlags() []string { + return e.missingFlags +} + +func checkRequiredFlags(flags []Flag, context *Context) requiredFlagsErr { + var missingFlags []string + for _, f := range flags { + if rf, ok := f.(RequiredFlag); ok && rf.IsRequired() { + var flagPresent bool + var flagName string + + for _, key := range f.Names() { + if len(key) > 1 { + flagName = key + } + + if context.IsSet(strings.TrimSpace(key)) { + flagPresent = true + } + } + + if !flagPresent && flagName != "" { + missingFlags = append(missingFlags, flagName) + } + } + } + + if len(missingFlags) != 0 { + return &errRequiredFlags{missingFlags: missingFlags} + } + + return nil +} diff --git a/vendor/github.com/urfave/cli/v2/docs.go b/vendor/github.com/urfave/cli/v2/docs.go new file mode 100644 index 000000000..dc16fc82d --- /dev/null +++ b/vendor/github.com/urfave/cli/v2/docs.go @@ -0,0 +1,148 @@ +package cli + +import ( + "bytes" + "fmt" + "io" + "sort" + "strings" + "text/template" + + "github.com/cpuguy83/go-md2man/v2/md2man" +) + +// ToMarkdown creates a markdown string for the `*App` +// The function errors if either parsing or writing of the string fails. +func (a *App) ToMarkdown() (string, error) { + var w bytes.Buffer + if err := a.writeDocTemplate(&w); err != nil { + return "", err + } + return w.String(), nil +} + +// ToMan creates a man page string for the `*App` +// The function errors if either parsing or writing of the string fails. +func (a *App) ToMan() (string, error) { + var w bytes.Buffer + if err := a.writeDocTemplate(&w); err != nil { + return "", err + } + man := md2man.Render(w.Bytes()) + return string(man), nil +} + +type cliTemplate struct { + App *App + Commands []string + GlobalArgs []string + SynopsisArgs []string +} + +func (a *App) writeDocTemplate(w io.Writer) error { + const name = "cli" + t, err := template.New(name).Parse(MarkdownDocTemplate) + if err != nil { + return err + } + return t.ExecuteTemplate(w, name, &cliTemplate{ + App: a, + Commands: prepareCommands(a.Commands, 0), + GlobalArgs: prepareArgsWithValues(a.VisibleFlags()), + SynopsisArgs: prepareArgsSynopsis(a.VisibleFlags()), + }) +} + +func prepareCommands(commands []*Command, level int) []string { + var coms []string + for _, command := range commands { + if command.Hidden { + continue + } + usage := "" + if command.Usage != "" { + usage = command.Usage + } + + prepared := fmt.Sprintf("%s %s\n\n%s\n", + strings.Repeat("#", level+2), + strings.Join(command.Names(), ", "), + usage, + ) + + flags := prepareArgsWithValues(command.Flags) + if len(flags) > 0 { + prepared += fmt.Sprintf("\n%s", strings.Join(flags, "\n")) + } + + coms = append(coms, prepared) + + // recursevly iterate subcommands + if len(command.Subcommands) > 0 { + coms = append( + coms, + prepareCommands(command.Subcommands, level+1)..., + ) + } + } + + return coms +} + +func prepareArgsWithValues(flags []Flag) []string { + return prepareFlags(flags, ", ", "**", "**", `""`, true) +} + +func prepareArgsSynopsis(flags []Flag) []string { + return prepareFlags(flags, "|", "[", "]", "[value]", false) +} + +func prepareFlags( + flags []Flag, + sep, opener, closer, value string, + addDetails bool, +) []string { + args := []string{} + for _, f := range flags { + flag, ok := f.(DocGenerationFlag) + if !ok { + continue + } + modifiedArg := opener + + for _, s := range flag.Names() { + trimmed := strings.TrimSpace(s) + if len(modifiedArg) > len(opener) { + modifiedArg += sep + } + if len(trimmed) > 1 { + modifiedArg += fmt.Sprintf("--%s", trimmed) + } else { + modifiedArg += fmt.Sprintf("-%s", trimmed) + } + } + modifiedArg += closer + if flag.TakesValue() { + modifiedArg += fmt.Sprintf("=%s", value) + } + + if addDetails { + modifiedArg += flagDetails(flag) + } + + args = append(args, modifiedArg+"\n") + + } + sort.Strings(args) + return args +} + +// flagDetails returns a string containing the flags metadata +func flagDetails(flag DocGenerationFlag) string { + description := flag.GetUsage() + value := flag.GetValue() + if value != "" { + description += " (default: " + value + ")" + } + return ": " + description +} diff --git a/vendor/github.com/urfave/cli/errors.go b/vendor/github.com/urfave/cli/v2/errors.go similarity index 62% rename from vendor/github.com/urfave/cli/errors.go rename to vendor/github.com/urfave/cli/v2/errors.go index 562b2953c..344b4361e 100644 --- a/vendor/github.com/urfave/cli/errors.go +++ b/vendor/github.com/urfave/cli/v2/errors.go @@ -15,25 +15,40 @@ var OsExiter = os.Exit var ErrWriter io.Writer = os.Stderr // MultiError is an error that wraps multiple errors. -type MultiError struct { - Errors []error +type MultiError interface { + error + // Errors returns a copy of the errors slice + Errors() []error } // NewMultiError creates a new MultiError. Pass in one or more errors. -func NewMultiError(err ...error) MultiError { - return MultiError{Errors: err} +func newMultiError(err ...error) MultiError { + ret := multiError(err) + return &ret } +type multiError []error + // Error implements the error interface. -func (m MultiError) Error() string { - errs := make([]string, len(m.Errors)) - for i, err := range m.Errors { +func (m *multiError) Error() string { + errs := make([]string, len(*m)) + for i, err := range *m { errs[i] = err.Error() } return strings.Join(errs, "\n") } +// Errors returns a copy of the errors slice +func (m *multiError) Errors() []error { + errs := make([]error, len(*m)) + for _, err := range *m { + errs = append(errs, err) + } + return errs +} + +// ErrorFormatter is the interface that will suitably format the error output type ErrorFormatter interface { Format(s fmt.State, verb rune) } @@ -45,29 +60,30 @@ type ExitCoder interface { ExitCode() int } -// ExitError fulfills both the builtin `error` interface and `ExitCoder` -type ExitError struct { +type exitError struct { exitCode int message interface{} } -// NewExitError makes a new *ExitError -func NewExitError(message interface{}, exitCode int) *ExitError { - return &ExitError{ - exitCode: exitCode, +// NewExitError makes a new *exitError +func NewExitError(message interface{}, exitCode int) ExitCoder { + return Exit(message, exitCode) +} + +// Exit wraps a message and exit code into an ExitCoder suitable for handling by +// HandleExitCoder +func Exit(message interface{}, exitCode int) ExitCoder { + return &exitError{ message: message, + exitCode: exitCode, } } -// Error returns the string message, fulfilling the interface required by -// `error` -func (ee *ExitError) Error() string { +func (ee *exitError) Error() string { return fmt.Sprintf("%v", ee.message) } -// ExitCode returns the exit code, fulfilling the interface required by -// `ExitCoder` -func (ee *ExitError) ExitCode() int { +func (ee *exitError) ExitCode() int { return ee.exitCode } @@ -83,9 +99,9 @@ func HandleExitCoder(err error) { if exitErr, ok := err.(ExitCoder); ok { if err.Error() != "" { if _, ok := exitErr.(ErrorFormatter); ok { - fmt.Fprintf(ErrWriter, "%+v\n", err) + _, _ = fmt.Fprintf(ErrWriter, "%+v\n", err) } else { - fmt.Fprintln(ErrWriter, err) + _, _ = fmt.Fprintln(ErrWriter, err) } } OsExiter(exitErr.ExitCode()) @@ -101,10 +117,10 @@ func HandleExitCoder(err error) { func handleMultiError(multiErr MultiError) int { code := 1 - for _, merr := range multiErr.Errors { + for _, merr := range multiErr.Errors() { if multiErr2, ok := merr.(MultiError); ok { code = handleMultiError(multiErr2) - } else { + } else if merr != nil { fmt.Fprintln(ErrWriter, merr) if exitErr, ok := merr.(ExitCoder); ok { code = exitErr.ExitCode() diff --git a/vendor/github.com/urfave/cli/v2/fish.go b/vendor/github.com/urfave/cli/v2/fish.go new file mode 100644 index 000000000..67122c9fe --- /dev/null +++ b/vendor/github.com/urfave/cli/v2/fish.go @@ -0,0 +1,192 @@ +package cli + +import ( + "bytes" + "fmt" + "io" + "strings" + "text/template" +) + +// ToFishCompletion creates a fish completion string for the `*App` +// The function errors if either parsing or writing of the string fails. +func (a *App) ToFishCompletion() (string, error) { + var w bytes.Buffer + if err := a.writeFishCompletionTemplate(&w); err != nil { + return "", err + } + return w.String(), nil +} + +type fishCompletionTemplate struct { + App *App + Completions []string + AllCommands []string +} + +func (a *App) writeFishCompletionTemplate(w io.Writer) error { + const name = "cli" + t, err := template.New(name).Parse(FishCompletionTemplate) + if err != nil { + return err + } + allCommands := []string{} + + // Add global flags + completions := a.prepareFishFlags(a.VisibleFlags(), allCommands) + + // Add help flag + if !a.HideHelp { + completions = append( + completions, + a.prepareFishFlags([]Flag{HelpFlag}, allCommands)..., + ) + } + + // Add version flag + if !a.HideVersion { + completions = append( + completions, + a.prepareFishFlags([]Flag{VersionFlag}, allCommands)..., + ) + } + + // Add commands and their flags + completions = append( + completions, + a.prepareFishCommands(a.VisibleCommands(), &allCommands, []string{})..., + ) + + return t.ExecuteTemplate(w, name, &fishCompletionTemplate{ + App: a, + Completions: completions, + AllCommands: allCommands, + }) +} + +func (a *App) prepareFishCommands(commands []*Command, allCommands *[]string, previousCommands []string) []string { + completions := []string{} + for _, command := range commands { + if command.Hidden { + continue + } + + var completion strings.Builder + completion.WriteString(fmt.Sprintf( + "complete -r -c %s -n '%s' -a '%s'", + a.Name, + a.fishSubcommandHelper(previousCommands), + strings.Join(command.Names(), " "), + )) + + if command.Usage != "" { + completion.WriteString(fmt.Sprintf(" -d '%s'", + escapeSingleQuotes(command.Usage))) + } + + if !command.HideHelp { + completions = append( + completions, + a.prepareFishFlags([]Flag{HelpFlag}, command.Names())..., + ) + } + + *allCommands = append(*allCommands, command.Names()...) + completions = append(completions, completion.String()) + completions = append( + completions, + a.prepareFishFlags(command.Flags, command.Names())..., + ) + + // recursevly iterate subcommands + if len(command.Subcommands) > 0 { + completions = append( + completions, + a.prepareFishCommands( + command.Subcommands, allCommands, command.Names(), + )..., + ) + } + } + + return completions +} + +func (a *App) prepareFishFlags(flags []Flag, previousCommands []string) []string { + completions := []string{} + for _, f := range flags { + flag, ok := f.(DocGenerationFlag) + if !ok { + continue + } + + completion := &strings.Builder{} + completion.WriteString(fmt.Sprintf( + "complete -c %s -n '%s'", + a.Name, + a.fishSubcommandHelper(previousCommands), + )) + + fishAddFileFlag(f, completion) + + for idx, opt := range flag.Names() { + if idx == 0 { + completion.WriteString(fmt.Sprintf( + " -l %s", strings.TrimSpace(opt), + )) + } else { + completion.WriteString(fmt.Sprintf( + " -s %s", strings.TrimSpace(opt), + )) + + } + } + + if flag.TakesValue() { + completion.WriteString(" -r") + } + + if flag.GetUsage() != "" { + completion.WriteString(fmt.Sprintf(" -d '%s'", + escapeSingleQuotes(flag.GetUsage()))) + } + + completions = append(completions, completion.String()) + } + + return completions +} + +func fishAddFileFlag(flag Flag, completion *strings.Builder) { + switch f := flag.(type) { + case *GenericFlag: + if f.TakesFile { + return + } + case *StringFlag: + if f.TakesFile { + return + } + case *StringSliceFlag: + if f.TakesFile { + return + } + } + completion.WriteString(" -f") +} + +func (a *App) fishSubcommandHelper(allCommands []string) string { + fishHelper := fmt.Sprintf("__fish_%s_no_subcommand", a.Name) + if len(allCommands) > 0 { + fishHelper = fmt.Sprintf( + "__fish_seen_subcommand_from %s", + strings.Join(allCommands, " "), + ) + } + return fishHelper + +} + +func escapeSingleQuotes(input string) string { + return strings.Replace(input, `'`, `\'`, -1) +} diff --git a/vendor/github.com/urfave/cli/v2/flag.go b/vendor/github.com/urfave/cli/v2/flag.go new file mode 100644 index 000000000..ec128fd44 --- /dev/null +++ b/vendor/github.com/urfave/cli/v2/flag.go @@ -0,0 +1,398 @@ +package cli + +import ( + "flag" + "fmt" + "io/ioutil" + "reflect" + "regexp" + "runtime" + "strconv" + "strings" + "syscall" + "time" +) + +const defaultPlaceholder = "value" + +var ( + slPfx = fmt.Sprintf("sl:::%d:::", time.Now().UTC().UnixNano()) + + commaWhitespace = regexp.MustCompile("[, ]+.*") +) + +// BashCompletionFlag enables bash-completion for all commands and subcommands +var BashCompletionFlag Flag = &BoolFlag{ + Name: "generate-bash-completion", + Hidden: true, +} + +// VersionFlag prints the version for the application +var VersionFlag Flag = &BoolFlag{ + Name: "version", + Aliases: []string{"v"}, + Usage: "print the version", +} + +// HelpFlag prints the help for all commands and subcommands. +// Set to nil to disable the flag. The subcommand +// will still be added unless HideHelp is set to true. +var HelpFlag Flag = &BoolFlag{ + Name: "help", + Aliases: []string{"h"}, + Usage: "show help", +} + +// FlagStringer converts a flag definition to a string. This is used by help +// to display a flag. +var FlagStringer FlagStringFunc = stringifyFlag + +// Serializer is used to circumvent the limitations of flag.FlagSet.Set +type Serializer interface { + Serialize() string +} + +// FlagNamePrefixer converts a full flag name and its placeholder into the help +// message flag prefix. This is used by the default FlagStringer. +var FlagNamePrefixer FlagNamePrefixFunc = prefixedNames + +// FlagEnvHinter annotates flag help message with the environment variable +// details. This is used by the default FlagStringer. +var FlagEnvHinter FlagEnvHintFunc = withEnvHint + +// FlagFileHinter annotates flag help message with the environment variable +// details. This is used by the default FlagStringer. +var FlagFileHinter FlagFileHintFunc = withFileHint + +// FlagsByName is a slice of Flag. +type FlagsByName []Flag + +func (f FlagsByName) Len() int { + return len(f) +} + +func (f FlagsByName) Less(i, j int) bool { + if len(f[j].Names()) == 0 { + return false + } else if len(f[i].Names()) == 0 { + return true + } + return lexicographicLess(f[i].Names()[0], f[j].Names()[0]) +} + +func (f FlagsByName) Swap(i, j int) { + f[i], f[j] = f[j], f[i] +} + +// Flag is a common interface related to parsing flags in cli. +// For more advanced flag parsing techniques, it is recommended that +// this interface be implemented. +type Flag interface { + fmt.Stringer + // Apply Flag settings to the given flag set + Apply(*flag.FlagSet) error + Names() []string + IsSet() bool +} + +// RequiredFlag is an interface that allows us to mark flags as required +// it allows flags required flags to be backwards compatible with the Flag interface +type RequiredFlag interface { + Flag + + IsRequired() bool +} + +// DocGenerationFlag is an interface that allows documentation generation for the flag +type DocGenerationFlag interface { + Flag + + // TakesValue returns true if the flag takes a value, otherwise false + TakesValue() bool + + // GetUsage returns the usage string for the flag + GetUsage() string + + // GetValue returns the flags value as string representation and an empty + // string if the flag takes no value at all. + GetValue() string +} + +func flagSet(name string, flags []Flag) (*flag.FlagSet, error) { + set := flag.NewFlagSet(name, flag.ContinueOnError) + + for _, f := range flags { + if err := f.Apply(set); err != nil { + return nil, err + } + } + set.SetOutput(ioutil.Discard) + return set, nil +} + +func visibleFlags(fl []Flag) []Flag { + var visible []Flag + for _, f := range fl { + field := flagValue(f).FieldByName("Hidden") + if !field.IsValid() || !field.Bool() { + visible = append(visible, f) + } + } + return visible +} + +func prefixFor(name string) (prefix string) { + if len(name) == 1 { + prefix = "-" + } else { + prefix = "--" + } + + return +} + +// Returns the placeholder, if any, and the unquoted usage string. +func unquoteUsage(usage string) (string, string) { + for i := 0; i < len(usage); i++ { + if usage[i] == '`' { + for j := i + 1; j < len(usage); j++ { + if usage[j] == '`' { + name := usage[i+1 : j] + usage = usage[:i] + name + usage[j+1:] + return name, usage + } + } + break + } + } + return "", usage +} + +func prefixedNames(names []string, placeholder string) string { + var prefixed string + for i, name := range names { + if name == "" { + continue + } + + prefixed += prefixFor(name) + name + if placeholder != "" { + prefixed += " " + placeholder + } + if i < len(names)-1 { + prefixed += ", " + } + } + return prefixed +} + +func withEnvHint(envVars []string, str string) string { + envText := "" + if envVars != nil && len(envVars) > 0 { + prefix := "$" + suffix := "" + sep := ", $" + if runtime.GOOS == "windows" { + prefix = "%" + suffix = "%" + sep = "%, %" + } + + envText = fmt.Sprintf(" [%s%s%s]", prefix, strings.Join(envVars, sep), suffix) + } + return str + envText +} + +func flagNames(f Flag) []string { + var ret []string + + name := flagStringField(f, "Name") + aliases := flagStringSliceField(f, "Aliases") + + for _, part := range append([]string{name}, aliases...) { + // v1 -> v2 migration warning zone: + // Strip off anything after the first found comma or space, which + // *hopefully* makes it a tiny bit more obvious that unexpected behavior is + // caused by using the v1 form of stringly typed "Name". + ret = append(ret, commaWhitespace.ReplaceAllString(part, "")) + } + + return ret +} + +func flagStringSliceField(f Flag, name string) []string { + fv := flagValue(f) + field := fv.FieldByName(name) + + if field.IsValid() { + return field.Interface().([]string) + } + + return []string{} +} + +func flagStringField(f Flag, name string) string { + fv := flagValue(f) + field := fv.FieldByName(name) + + if field.IsValid() { + return field.String() + } + + return "" +} + +func withFileHint(filePath, str string) string { + fileText := "" + if filePath != "" { + fileText = fmt.Sprintf(" [%s]", filePath) + } + return str + fileText +} + +func flagValue(f Flag) reflect.Value { + fv := reflect.ValueOf(f) + for fv.Kind() == reflect.Ptr { + fv = reflect.Indirect(fv) + } + return fv +} + +func stringifyFlag(f Flag) string { + fv := flagValue(f) + + switch f.(type) { + case *IntSliceFlag: + return withEnvHint(flagStringSliceField(f, "EnvVars"), + stringifyIntSliceFlag(f.(*IntSliceFlag))) + case *Int64SliceFlag: + return withEnvHint(flagStringSliceField(f, "EnvVars"), + stringifyInt64SliceFlag(f.(*Int64SliceFlag))) + case *Float64SliceFlag: + return withEnvHint(flagStringSliceField(f, "EnvVars"), + stringifyFloat64SliceFlag(f.(*Float64SliceFlag))) + case *StringSliceFlag: + return withEnvHint(flagStringSliceField(f, "EnvVars"), + stringifyStringSliceFlag(f.(*StringSliceFlag))) + } + + placeholder, usage := unquoteUsage(fv.FieldByName("Usage").String()) + + needsPlaceholder := false + defaultValueString := "" + val := fv.FieldByName("Value") + if val.IsValid() { + needsPlaceholder = val.Kind() != reflect.Bool + defaultValueString = fmt.Sprintf(" (default: %v)", val.Interface()) + + if val.Kind() == reflect.String && val.String() != "" { + defaultValueString = fmt.Sprintf(" (default: %q)", val.String()) + } + } + + helpText := fv.FieldByName("DefaultText") + if helpText.IsValid() && helpText.String() != "" { + needsPlaceholder = val.Kind() != reflect.Bool + defaultValueString = fmt.Sprintf(" (default: %s)", helpText.String()) + } + + if defaultValueString == " (default: )" { + defaultValueString = "" + } + + if needsPlaceholder && placeholder == "" { + placeholder = defaultPlaceholder + } + + usageWithDefault := strings.TrimSpace(usage + defaultValueString) + + return withEnvHint(flagStringSliceField(f, "EnvVars"), + fmt.Sprintf("%s\t%s", prefixedNames(f.Names(), placeholder), usageWithDefault)) +} + +func stringifyIntSliceFlag(f *IntSliceFlag) string { + var defaultVals []string + if f.Value != nil && len(f.Value.Value()) > 0 { + for _, i := range f.Value.Value() { + defaultVals = append(defaultVals, strconv.Itoa(i)) + } + } + + return stringifySliceFlag(f.Usage, f.Names(), defaultVals) +} + +func stringifyInt64SliceFlag(f *Int64SliceFlag) string { + var defaultVals []string + if f.Value != nil && len(f.Value.Value()) > 0 { + for _, i := range f.Value.Value() { + defaultVals = append(defaultVals, strconv.FormatInt(i, 10)) + } + } + + return stringifySliceFlag(f.Usage, f.Names(), defaultVals) +} + +func stringifyFloat64SliceFlag(f *Float64SliceFlag) string { + var defaultVals []string + + if f.Value != nil && len(f.Value.Value()) > 0 { + for _, i := range f.Value.Value() { + defaultVals = append(defaultVals, strings.TrimRight(strings.TrimRight(fmt.Sprintf("%f", i), "0"), ".")) + } + } + + return stringifySliceFlag(f.Usage, f.Names(), defaultVals) +} + +func stringifyStringSliceFlag(f *StringSliceFlag) string { + var defaultVals []string + if f.Value != nil && len(f.Value.Value()) > 0 { + for _, s := range f.Value.Value() { + if len(s) > 0 { + defaultVals = append(defaultVals, strconv.Quote(s)) + } + } + } + + return stringifySliceFlag(f.Usage, f.Names(), defaultVals) +} + +func stringifySliceFlag(usage string, names, defaultVals []string) string { + placeholder, usage := unquoteUsage(usage) + if placeholder == "" { + placeholder = defaultPlaceholder + } + + defaultVal := "" + if len(defaultVals) > 0 { + defaultVal = fmt.Sprintf(" (default: %s)", strings.Join(defaultVals, ", ")) + } + + usageWithDefault := strings.TrimSpace(fmt.Sprintf("%s%s", usage, defaultVal)) + return fmt.Sprintf("%s\t%s", prefixedNames(names, placeholder), usageWithDefault) +} + +func hasFlag(flags []Flag, fl Flag) bool { + for _, existing := range flags { + if fl == existing { + return true + } + } + + return false +} + +func flagFromEnvOrFile(envVars []string, filePath string) (val string, ok bool) { + for _, envVar := range envVars { + envVar = strings.TrimSpace(envVar) + if val, ok := syscall.Getenv(envVar); ok { + return val, true + } + } + for _, fileVar := range strings.Split(filePath, ",") { + if data, err := ioutil.ReadFile(fileVar); err == nil { + return string(data), true + } + } + return "", false +} diff --git a/vendor/github.com/urfave/cli/v2/flag_bool.go b/vendor/github.com/urfave/cli/v2/flag_bool.go new file mode 100644 index 000000000..6a1da61ef --- /dev/null +++ b/vendor/github.com/urfave/cli/v2/flag_bool.go @@ -0,0 +1,106 @@ +package cli + +import ( + "flag" + "fmt" + "strconv" +) + +// BoolFlag is a flag with type bool +type BoolFlag struct { + Name string + Aliases []string + Usage string + EnvVars []string + FilePath string + Required bool + Hidden bool + Value bool + DefaultText string + Destination *bool + HasBeenSet bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *BoolFlag) IsSet() bool { + return f.HasBeenSet +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f *BoolFlag) String() string { + return FlagStringer(f) +} + +// Names returns the names of the flag +func (f *BoolFlag) Names() []string { + return flagNames(f) +} + +// IsRequired returns whether or not the flag is required +func (f *BoolFlag) IsRequired() bool { + return f.Required +} + +// TakesValue returns true of the flag takes a value, otherwise false +func (f *BoolFlag) TakesValue() bool { + return false +} + +// GetUsage returns the usage string for the flag +func (f *BoolFlag) GetUsage() string { + return f.Usage +} + +// GetValue returns the flags value as string representation and an empty +// string if the flag takes no value at all. +func (f *BoolFlag) GetValue() string { + return "" +} + +// Apply populates the flag given the flag set and environment +func (f *BoolFlag) Apply(set *flag.FlagSet) error { + if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val != "" { + valBool, err := strconv.ParseBool(val) + + if err != nil { + return fmt.Errorf("could not parse %q as bool value for flag %s: %s", val, f.Name, err) + } + + f.Value = valBool + f.HasBeenSet = true + } + } + + for _, name := range f.Names() { + if f.Destination != nil { + set.BoolVar(f.Destination, name, f.Value, f.Usage) + continue + } + set.Bool(name, f.Value, f.Usage) + } + + return nil +} + +// Bool looks up the value of a local BoolFlag, returns +// false if not found +func (c *Context) Bool(name string) bool { + if fs := lookupFlagSet(name, c); fs != nil { + return lookupBool(name, fs) + } + return false +} + +func lookupBool(name string, set *flag.FlagSet) bool { + f := set.Lookup(name) + if f != nil { + parsed, err := strconv.ParseBool(f.Value.String()) + if err != nil { + return false + } + return parsed + } + return false +} diff --git a/vendor/github.com/urfave/cli/v2/flag_duration.go b/vendor/github.com/urfave/cli/v2/flag_duration.go new file mode 100644 index 000000000..2c34944a4 --- /dev/null +++ b/vendor/github.com/urfave/cli/v2/flag_duration.go @@ -0,0 +1,105 @@ +package cli + +import ( + "flag" + "fmt" + "time" +) + +// DurationFlag is a flag with type time.Duration (see https://golang.org/pkg/time/#ParseDuration) +type DurationFlag struct { + Name string + Aliases []string + Usage string + EnvVars []string + FilePath string + Required bool + Hidden bool + Value time.Duration + DefaultText string + Destination *time.Duration + HasBeenSet bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *DurationFlag) IsSet() bool { + return f.HasBeenSet +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f *DurationFlag) String() string { + return FlagStringer(f) +} + +// Names returns the names of the flag +func (f *DurationFlag) Names() []string { + return flagNames(f) +} + +// IsRequired returns whether or not the flag is required +func (f *DurationFlag) IsRequired() bool { + return f.Required +} + +// TakesValue returns true of the flag takes a value, otherwise false +func (f *DurationFlag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (f *DurationFlag) GetUsage() string { + return f.Usage +} + +// GetValue returns the flags value as string representation and an empty +// string if the flag takes no value at all. +func (f *DurationFlag) GetValue() string { + return f.Value.String() +} + +// Apply populates the flag given the flag set and environment +func (f *DurationFlag) Apply(set *flag.FlagSet) error { + if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val != "" { + valDuration, err := time.ParseDuration(val) + + if err != nil { + return fmt.Errorf("could not parse %q as duration value for flag %s: %s", val, f.Name, err) + } + + f.Value = valDuration + f.HasBeenSet = true + } + } + + for _, name := range f.Names() { + if f.Destination != nil { + set.DurationVar(f.Destination, name, f.Value, f.Usage) + continue + } + set.Duration(name, f.Value, f.Usage) + } + return nil +} + +// Duration looks up the value of a local DurationFlag, returns +// 0 if not found +func (c *Context) Duration(name string) time.Duration { + if fs := lookupFlagSet(name, c); fs != nil { + return lookupDuration(name, fs) + } + return 0 +} + +func lookupDuration(name string, set *flag.FlagSet) time.Duration { + f := set.Lookup(name) + if f != nil { + parsed, err := time.ParseDuration(f.Value.String()) + if err != nil { + return 0 + } + return parsed + } + return 0 +} diff --git a/vendor/github.com/urfave/cli/v2/flag_float64.go b/vendor/github.com/urfave/cli/v2/flag_float64.go new file mode 100644 index 000000000..31f06f35e --- /dev/null +++ b/vendor/github.com/urfave/cli/v2/flag_float64.go @@ -0,0 +1,106 @@ +package cli + +import ( + "flag" + "fmt" + "strconv" +) + +// Float64Flag is a flag with type float64 +type Float64Flag struct { + Name string + Aliases []string + Usage string + EnvVars []string + FilePath string + Required bool + Hidden bool + Value float64 + DefaultText string + Destination *float64 + HasBeenSet bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *Float64Flag) IsSet() bool { + return f.HasBeenSet +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f *Float64Flag) String() string { + return FlagStringer(f) +} + +// Names returns the names of the flag +func (f *Float64Flag) Names() []string { + return flagNames(f) +} + +// IsRequired returns whether or not the flag is required +func (f *Float64Flag) IsRequired() bool { + return f.Required +} + +// TakesValue returns true of the flag takes a value, otherwise false +func (f *Float64Flag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (f *Float64Flag) GetUsage() string { + return f.Usage +} + +// GetValue returns the flags value as string representation and an empty +// string if the flag takes no value at all. +func (f *Float64Flag) GetValue() string { + return fmt.Sprintf("%f", f.Value) +} + +// Apply populates the flag given the flag set and environment +func (f *Float64Flag) Apply(set *flag.FlagSet) error { + if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val != "" { + valFloat, err := strconv.ParseFloat(val, 10) + + if err != nil { + return fmt.Errorf("could not parse %q as float64 value for flag %s: %s", val, f.Name, err) + } + + f.Value = valFloat + f.HasBeenSet = true + } + } + + for _, name := range f.Names() { + if f.Destination != nil { + set.Float64Var(f.Destination, name, f.Value, f.Usage) + continue + } + set.Float64(name, f.Value, f.Usage) + } + + return nil +} + +// Float64 looks up the value of a local Float64Flag, returns +// 0 if not found +func (c *Context) Float64(name string) float64 { + if fs := lookupFlagSet(name, c); fs != nil { + return lookupFloat64(name, fs) + } + return 0 +} + +func lookupFloat64(name string, set *flag.FlagSet) float64 { + f := set.Lookup(name) + if f != nil { + parsed, err := strconv.ParseFloat(f.Value.String(), 64) + if err != nil { + return 0 + } + return parsed + } + return 0 +} diff --git a/vendor/github.com/urfave/cli/v2/flag_float64_slice.go b/vendor/github.com/urfave/cli/v2/flag_float64_slice.go new file mode 100644 index 000000000..91d2e9d10 --- /dev/null +++ b/vendor/github.com/urfave/cli/v2/flag_float64_slice.go @@ -0,0 +1,165 @@ +package cli + +import ( + "encoding/json" + "flag" + "fmt" + "strconv" + "strings" +) + +// Float64Slice wraps []float64 to satisfy flag.Value +type Float64Slice struct { + slice []float64 + hasBeenSet bool +} + +// NewFloat64Slice makes a *Float64Slice with default values +func NewFloat64Slice(defaults ...float64) *Float64Slice { + return &Float64Slice{slice: append([]float64{}, defaults...)} +} + +// Set parses the value into a float64 and appends it to the list of values +func (f *Float64Slice) Set(value string) error { + if !f.hasBeenSet { + f.slice = []float64{} + f.hasBeenSet = true + } + + if strings.HasPrefix(value, slPfx) { + // Deserializing assumes overwrite + _ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), &f.slice) + f.hasBeenSet = true + return nil + } + + tmp, err := strconv.ParseFloat(value, 64) + if err != nil { + return err + } + + f.slice = append(f.slice, tmp) + return nil +} + +// String returns a readable representation of this value (for usage defaults) +func (f *Float64Slice) String() string { + return fmt.Sprintf("%#v", f.slice) +} + +// Serialize allows Float64Slice to fulfill Serializer +func (f *Float64Slice) Serialize() string { + jsonBytes, _ := json.Marshal(f.slice) + return fmt.Sprintf("%s%s", slPfx, string(jsonBytes)) +} + +// Value returns the slice of float64s set by this flag +func (f *Float64Slice) Value() []float64 { + return f.slice +} + +// Get returns the slice of float64s set by this flag +func (f *Float64Slice) Get() interface{} { + return *f +} + +// Float64SliceFlag is a flag with type *Float64Slice +type Float64SliceFlag struct { + Name string + Aliases []string + Usage string + EnvVars []string + FilePath string + Required bool + Hidden bool + Value *Float64Slice + DefaultText string + HasBeenSet bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *Float64SliceFlag) IsSet() bool { + return f.HasBeenSet +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f *Float64SliceFlag) String() string { + return FlagStringer(f) +} + +// Names returns the names of the flag +func (f *Float64SliceFlag) Names() []string { + return flagNames(f) +} + +// IsRequired returns whether or not the flag is required +func (f *Float64SliceFlag) IsRequired() bool { + return f.Required +} + +// TakesValue returns true if the flag takes a value, otherwise false +func (f *Float64SliceFlag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (f *Float64SliceFlag) GetUsage() string { + return f.Usage +} + +// GetValue returns the flags value as string representation and an empty +// string if the flag takes no value at all. +func (f *Float64SliceFlag) GetValue() string { + if f.Value != nil { + return f.Value.String() + } + return "" +} + +// Apply populates the flag given the flag set and environment +func (f *Float64SliceFlag) Apply(set *flag.FlagSet) error { + if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val != "" { + f.Value = &Float64Slice{} + + for _, s := range strings.Split(val, ",") { + if err := f.Value.Set(strings.TrimSpace(s)); err != nil { + return fmt.Errorf("could not parse %q as float64 slice value for flag %s: %s", f.Value, f.Name, err) + } + } + + f.HasBeenSet = true + } + } + + for _, name := range f.Names() { + if f.Value == nil { + f.Value = &Float64Slice{} + } + set.Var(f.Value, name, f.Usage) + } + + return nil +} + +// Float64Slice looks up the value of a local Float64SliceFlag, returns +// nil if not found +func (c *Context) Float64Slice(name string) []float64 { + if fs := lookupFlagSet(name, c); fs != nil { + return lookupFloat64Slice(name, fs) + } + return nil +} + +func lookupFloat64Slice(name string, set *flag.FlagSet) []float64 { + f := set.Lookup(name) + if f != nil { + parsed, err := (f.Value.(*Float64Slice)).Value(), error(nil) + if err != nil { + return nil + } + return parsed + } + return nil +} diff --git a/vendor/github.com/urfave/cli/v2/flag_generic.go b/vendor/github.com/urfave/cli/v2/flag_generic.go new file mode 100644 index 000000000..2d9baa78d --- /dev/null +++ b/vendor/github.com/urfave/cli/v2/flag_generic.go @@ -0,0 +1,108 @@ +package cli + +import ( + "flag" + "fmt" +) + +// Generic is a generic parseable type identified by a specific flag +type Generic interface { + Set(value string) error + String() string +} + +// GenericFlag is a flag with type Generic +type GenericFlag struct { + Name string + Aliases []string + Usage string + EnvVars []string + FilePath string + Required bool + Hidden bool + TakesFile bool + Value Generic + DefaultText string + HasBeenSet bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *GenericFlag) IsSet() bool { + return f.HasBeenSet +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f *GenericFlag) String() string { + return FlagStringer(f) +} + +// Names returns the names of the flag +func (f *GenericFlag) Names() []string { + return flagNames(f) +} + +// IsRequired returns whether or not the flag is required +func (f *GenericFlag) IsRequired() bool { + return f.Required +} + +// TakesValue returns true of the flag takes a value, otherwise false +func (f *GenericFlag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (f *GenericFlag) GetUsage() string { + return f.Usage +} + +// GetValue returns the flags value as string representation and an empty +// string if the flag takes no value at all. +func (f *GenericFlag) GetValue() string { + if f.Value != nil { + return f.Value.String() + } + return "" +} + +// Apply takes the flagset and calls Set on the generic flag with the value +// provided by the user for parsing by the flag +func (f GenericFlag) Apply(set *flag.FlagSet) error { + if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val != "" { + if err := f.Value.Set(val); err != nil { + return fmt.Errorf("could not parse %q as value for flag %s: %s", val, f.Name, err) + } + + f.HasBeenSet = true + } + } + + for _, name := range f.Names() { + set.Var(f.Value, name, f.Usage) + } + + return nil +} + +// Generic looks up the value of a local GenericFlag, returns +// nil if not found +func (c *Context) Generic(name string) interface{} { + if fs := lookupFlagSet(name, c); fs != nil { + return lookupGeneric(name, fs) + } + return nil +} + +func lookupGeneric(name string, set *flag.FlagSet) interface{} { + f := set.Lookup(name) + if f != nil { + parsed, err := f.Value, error(nil) + if err != nil { + return nil + } + return parsed + } + return nil +} diff --git a/vendor/github.com/urfave/cli/v2/flag_int.go b/vendor/github.com/urfave/cli/v2/flag_int.go new file mode 100644 index 000000000..be961bf5f --- /dev/null +++ b/vendor/github.com/urfave/cli/v2/flag_int.go @@ -0,0 +1,106 @@ +package cli + +import ( + "flag" + "fmt" + "strconv" +) + +// IntFlag is a flag with type int +type IntFlag struct { + Name string + Aliases []string + Usage string + EnvVars []string + FilePath string + Required bool + Hidden bool + Value int + DefaultText string + Destination *int + HasBeenSet bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *IntFlag) IsSet() bool { + return f.HasBeenSet +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f *IntFlag) String() string { + return FlagStringer(f) +} + +// Names returns the names of the flag +func (f *IntFlag) Names() []string { + return flagNames(f) +} + +// IsRequired returns whether or not the flag is required +func (f *IntFlag) IsRequired() bool { + return f.Required +} + +// TakesValue returns true of the flag takes a value, otherwise false +func (f *IntFlag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (f *IntFlag) GetUsage() string { + return f.Usage +} + +// GetValue returns the flags value as string representation and an empty +// string if the flag takes no value at all. +func (f *IntFlag) GetValue() string { + return fmt.Sprintf("%d", f.Value) +} + +// Apply populates the flag given the flag set and environment +func (f *IntFlag) Apply(set *flag.FlagSet) error { + if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val != "" { + valInt, err := strconv.ParseInt(val, 0, 64) + + if err != nil { + return fmt.Errorf("could not parse %q as int value for flag %s: %s", val, f.Name, err) + } + + f.Value = int(valInt) + f.HasBeenSet = true + } + } + + for _, name := range f.Names() { + if f.Destination != nil { + set.IntVar(f.Destination, name, f.Value, f.Usage) + continue + } + set.Int(name, f.Value, f.Usage) + } + + return nil +} + +// Int looks up the value of a local IntFlag, returns +// 0 if not found +func (c *Context) Int(name string) int { + if fs := lookupFlagSet(name, c); fs != nil { + return lookupInt(name, fs) + } + return 0 +} + +func lookupInt(name string, set *flag.FlagSet) int { + f := set.Lookup(name) + if f != nil { + parsed, err := strconv.ParseInt(f.Value.String(), 0, 64) + if err != nil { + return 0 + } + return int(parsed) + } + return 0 +} diff --git a/vendor/github.com/urfave/cli/v2/flag_int64.go b/vendor/github.com/urfave/cli/v2/flag_int64.go new file mode 100644 index 000000000..c979119f8 --- /dev/null +++ b/vendor/github.com/urfave/cli/v2/flag_int64.go @@ -0,0 +1,105 @@ +package cli + +import ( + "flag" + "fmt" + "strconv" +) + +// Int64Flag is a flag with type int64 +type Int64Flag struct { + Name string + Aliases []string + Usage string + EnvVars []string + FilePath string + Required bool + Hidden bool + Value int64 + DefaultText string + Destination *int64 + HasBeenSet bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *Int64Flag) IsSet() bool { + return f.HasBeenSet +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f *Int64Flag) String() string { + return FlagStringer(f) +} + +// Names returns the names of the flag +func (f *Int64Flag) Names() []string { + return flagNames(f) +} + +// IsRequired returns whether or not the flag is required +func (f *Int64Flag) IsRequired() bool { + return f.Required +} + +// TakesValue returns true of the flag takes a value, otherwise false +func (f *Int64Flag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (f *Int64Flag) GetUsage() string { + return f.Usage +} + +// GetValue returns the flags value as string representation and an empty +// string if the flag takes no value at all. +func (f *Int64Flag) GetValue() string { + return fmt.Sprintf("%d", f.Value) +} + +// Apply populates the flag given the flag set and environment +func (f *Int64Flag) Apply(set *flag.FlagSet) error { + if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val != "" { + valInt, err := strconv.ParseInt(val, 0, 64) + + if err != nil { + return fmt.Errorf("could not parse %q as int value for flag %s: %s", val, f.Name, err) + } + + f.Value = valInt + f.HasBeenSet = true + } + } + + for _, name := range f.Names() { + if f.Destination != nil { + set.Int64Var(f.Destination, name, f.Value, f.Usage) + continue + } + set.Int64(name, f.Value, f.Usage) + } + return nil +} + +// Int64 looks up the value of a local Int64Flag, returns +// 0 if not found +func (c *Context) Int64(name string) int64 { + if fs := lookupFlagSet(name, c); fs != nil { + return lookupInt64(name, fs) + } + return 0 +} + +func lookupInt64(name string, set *flag.FlagSet) int64 { + f := set.Lookup(name) + if f != nil { + parsed, err := strconv.ParseInt(f.Value.String(), 0, 64) + if err != nil { + return 0 + } + return parsed + } + return 0 +} diff --git a/vendor/github.com/urfave/cli/v2/flag_int64_slice.go b/vendor/github.com/urfave/cli/v2/flag_int64_slice.go new file mode 100644 index 000000000..41aa0667e --- /dev/null +++ b/vendor/github.com/urfave/cli/v2/flag_int64_slice.go @@ -0,0 +1,161 @@ +package cli + +import ( + "encoding/json" + "flag" + "fmt" + "strconv" + "strings" +) + +// Int64Slice wraps []int64 to satisfy flag.Value +type Int64Slice struct { + slice []int64 + hasBeenSet bool +} + +// NewInt64Slice makes an *Int64Slice with default values +func NewInt64Slice(defaults ...int64) *Int64Slice { + return &Int64Slice{slice: append([]int64{}, defaults...)} +} + +// Set parses the value into an integer and appends it to the list of values +func (i *Int64Slice) Set(value string) error { + if !i.hasBeenSet { + i.slice = []int64{} + i.hasBeenSet = true + } + + if strings.HasPrefix(value, slPfx) { + // Deserializing assumes overwrite + _ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), &i.slice) + i.hasBeenSet = true + return nil + } + + tmp, err := strconv.ParseInt(value, 0, 64) + if err != nil { + return err + } + + i.slice = append(i.slice, tmp) + + return nil +} + +// String returns a readable representation of this value (for usage defaults) +func (i *Int64Slice) String() string { + return fmt.Sprintf("%#v", i.slice) +} + +// Serialize allows Int64Slice to fulfill Serializer +func (i *Int64Slice) Serialize() string { + jsonBytes, _ := json.Marshal(i.slice) + return fmt.Sprintf("%s%s", slPfx, string(jsonBytes)) +} + +// Value returns the slice of ints set by this flag +func (i *Int64Slice) Value() []int64 { + return i.slice +} + +// Get returns the slice of ints set by this flag +func (i *Int64Slice) Get() interface{} { + return *i +} + +// Int64SliceFlag is a flag with type *Int64Slice +type Int64SliceFlag struct { + Name string + Aliases []string + Usage string + EnvVars []string + FilePath string + Required bool + Hidden bool + Value *Int64Slice + DefaultText string + HasBeenSet bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *Int64SliceFlag) IsSet() bool { + return f.HasBeenSet +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f *Int64SliceFlag) String() string { + return FlagStringer(f) +} + +// Names returns the names of the flag +func (f *Int64SliceFlag) Names() []string { + return flagNames(f) +} + +// IsRequired returns whether or not the flag is required +func (f *Int64SliceFlag) IsRequired() bool { + return f.Required +} + +// TakesValue returns true of the flag takes a value, otherwise false +func (f *Int64SliceFlag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (f Int64SliceFlag) GetUsage() string { + return f.Usage +} + +// GetValue returns the flags value as string representation and an empty +// string if the flag takes no value at all. +func (f *Int64SliceFlag) GetValue() string { + if f.Value != nil { + return f.Value.String() + } + return "" +} + +// Apply populates the flag given the flag set and environment +func (f *Int64SliceFlag) Apply(set *flag.FlagSet) error { + if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + f.Value = &Int64Slice{} + + for _, s := range strings.Split(val, ",") { + if err := f.Value.Set(strings.TrimSpace(s)); err != nil { + return fmt.Errorf("could not parse %q as int64 slice value for flag %s: %s", val, f.Name, err) + } + } + + f.HasBeenSet = true + } + + for _, name := range f.Names() { + if f.Value == nil { + f.Value = &Int64Slice{} + } + set.Var(f.Value, name, f.Usage) + } + + return nil +} + +// Int64Slice looks up the value of a local Int64SliceFlag, returns +// nil if not found +func (c *Context) Int64Slice(name string) []int64 { + return lookupInt64Slice(name, c.flagSet) +} + +func lookupInt64Slice(name string, set *flag.FlagSet) []int64 { + f := set.Lookup(name) + if f != nil { + parsed, err := (f.Value.(*Int64Slice)).Value(), error(nil) + if err != nil { + return nil + } + return parsed + } + return nil +} diff --git a/vendor/github.com/urfave/cli/v2/flag_int_slice.go b/vendor/github.com/urfave/cli/v2/flag_int_slice.go new file mode 100644 index 000000000..938897842 --- /dev/null +++ b/vendor/github.com/urfave/cli/v2/flag_int_slice.go @@ -0,0 +1,175 @@ +package cli + +import ( + "encoding/json" + "flag" + "fmt" + "strconv" + "strings" +) + +// IntSlice wraps []int to satisfy flag.Value +type IntSlice struct { + slice []int + hasBeenSet bool +} + +// NewIntSlice makes an *IntSlice with default values +func NewIntSlice(defaults ...int) *IntSlice { + return &IntSlice{slice: append([]int{}, defaults...)} +} + +// TODO: Consistently have specific Set function for Int64 and Float64 ? +// SetInt directly adds an integer to the list of values +func (i *IntSlice) SetInt(value int) { + if !i.hasBeenSet { + i.slice = []int{} + i.hasBeenSet = true + } + + i.slice = append(i.slice, value) +} + +// Set parses the value into an integer and appends it to the list of values +func (i *IntSlice) Set(value string) error { + if !i.hasBeenSet { + i.slice = []int{} + i.hasBeenSet = true + } + + if strings.HasPrefix(value, slPfx) { + // Deserializing assumes overwrite + _ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), &i.slice) + i.hasBeenSet = true + return nil + } + + tmp, err := strconv.ParseInt(value, 0, 64) + if err != nil { + return err + } + + i.slice = append(i.slice, int(tmp)) + + return nil +} + +// String returns a readable representation of this value (for usage defaults) +func (i *IntSlice) String() string { + return fmt.Sprintf("%#v", i.slice) +} + +// Serialize allows IntSlice to fulfill Serializer +func (i *IntSlice) Serialize() string { + jsonBytes, _ := json.Marshal(i.slice) + return fmt.Sprintf("%s%s", slPfx, string(jsonBytes)) +} + +// Value returns the slice of ints set by this flag +func (i *IntSlice) Value() []int { + return i.slice +} + +// Get returns the slice of ints set by this flag +func (i *IntSlice) Get() interface{} { + return *i +} + +// IntSliceFlag is a flag with type *IntSlice +type IntSliceFlag struct { + Name string + Aliases []string + Usage string + EnvVars []string + FilePath string + Required bool + Hidden bool + Value *IntSlice + DefaultText string + HasBeenSet bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *IntSliceFlag) IsSet() bool { + return f.HasBeenSet +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f *IntSliceFlag) String() string { + return FlagStringer(f) +} + +// Names returns the names of the flag +func (f *IntSliceFlag) Names() []string { + return flagNames(f) +} + +// IsRequired returns whether or not the flag is required +func (f *IntSliceFlag) IsRequired() bool { + return f.Required +} + +// TakesValue returns true of the flag takes a value, otherwise false +func (f *IntSliceFlag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (f IntSliceFlag) GetUsage() string { + return f.Usage +} + +// GetValue returns the flags value as string representation and an empty +// string if the flag takes no value at all. +func (f *IntSliceFlag) GetValue() string { + if f.Value != nil { + return f.Value.String() + } + return "" +} + +// Apply populates the flag given the flag set and environment +func (f *IntSliceFlag) Apply(set *flag.FlagSet) error { + if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + f.Value = &IntSlice{} + + for _, s := range strings.Split(val, ",") { + if err := f.Value.Set(strings.TrimSpace(s)); err != nil { + return fmt.Errorf("could not parse %q as int slice value for flag %s: %s", val, f.Name, err) + } + } + + f.HasBeenSet = true + } + + for _, name := range f.Names() { + if f.Value == nil { + f.Value = &IntSlice{} + } + set.Var(f.Value, name, f.Usage) + } + + return nil +} + +// IntSlice looks up the value of a local IntSliceFlag, returns +// nil if not found +func (c *Context) IntSlice(name string) []int { + if fs := lookupFlagSet(name, c); fs != nil { + return lookupIntSlice(name, c.flagSet) + } + return nil +} + +func lookupIntSlice(name string, set *flag.FlagSet) []int { + f := set.Lookup(name) + if f != nil { + parsed, err := (f.Value.(*IntSlice)).Value(), error(nil) + if err != nil { + return nil + } + return parsed + } + return nil +} diff --git a/vendor/github.com/urfave/cli/v2/flag_path.go b/vendor/github.com/urfave/cli/v2/flag_path.go new file mode 100644 index 000000000..a32285739 --- /dev/null +++ b/vendor/github.com/urfave/cli/v2/flag_path.go @@ -0,0 +1,95 @@ +package cli + +import "flag" + +type PathFlag struct { + Name string + Aliases []string + Usage string + EnvVars []string + FilePath string + Required bool + Hidden bool + TakesFile bool + Value string + DefaultText string + Destination *string + HasBeenSet bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *PathFlag) IsSet() bool { + return f.HasBeenSet +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f *PathFlag) String() string { + return FlagStringer(f) +} + +// Names returns the names of the flag +func (f *PathFlag) Names() []string { + return flagNames(f) +} + +// IsRequired returns whether or not the flag is required +func (f *PathFlag) IsRequired() bool { + return f.Required +} + +// TakesValue returns true of the flag takes a value, otherwise false +func (f *PathFlag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (f *PathFlag) GetUsage() string { + return f.Usage +} + +// GetValue returns the flags value as string representation and an empty +// string if the flag takes no value at all. +func (f *PathFlag) GetValue() string { + return f.Value +} + +// Apply populates the flag given the flag set and environment +func (f *PathFlag) Apply(set *flag.FlagSet) error { + if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + f.Value = val + f.HasBeenSet = true + } + + for _, name := range f.Names() { + if f.Destination != nil { + set.StringVar(f.Destination, name, f.Value, f.Usage) + continue + } + set.String(name, f.Value, f.Usage) + } + + return nil +} + +// Path looks up the value of a local PathFlag, returns +// "" if not found +func (c *Context) Path(name string) string { + if fs := lookupFlagSet(name, c); fs != nil { + return lookupPath(name, fs) + } + + return "" +} + +func lookupPath(name string, set *flag.FlagSet) string { + f := set.Lookup(name) + if f != nil { + parsed, err := f.Value.String(), error(nil) + if err != nil { + return "" + } + return parsed + } + return "" +} diff --git a/vendor/github.com/urfave/cli/v2/flag_string.go b/vendor/github.com/urfave/cli/v2/flag_string.go new file mode 100644 index 000000000..bcd82530c --- /dev/null +++ b/vendor/github.com/urfave/cli/v2/flag_string.go @@ -0,0 +1,95 @@ +package cli + +import "flag" + +// StringFlag is a flag with type string +type StringFlag struct { + Name string + Aliases []string + Usage string + EnvVars []string + FilePath string + Required bool + Hidden bool + TakesFile bool + Value string + DefaultText string + Destination *string + HasBeenSet bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *StringFlag) IsSet() bool { + return f.HasBeenSet +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f *StringFlag) String() string { + return FlagStringer(f) +} + +// Names returns the names of the flag +func (f *StringFlag) Names() []string { + return flagNames(f) +} + +// IsRequired returns whether or not the flag is required +func (f *StringFlag) IsRequired() bool { + return f.Required +} + +// TakesValue returns true of the flag takes a value, otherwise false +func (f *StringFlag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (f *StringFlag) GetUsage() string { + return f.Usage +} + +// GetValue returns the flags value as string representation and an empty +// string if the flag takes no value at all. +func (f *StringFlag) GetValue() string { + return f.Value +} + +// Apply populates the flag given the flag set and environment +func (f *StringFlag) Apply(set *flag.FlagSet) error { + if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + f.Value = val + f.HasBeenSet = true + } + + for _, name := range f.Names() { + if f.Destination != nil { + set.StringVar(f.Destination, name, f.Value, f.Usage) + continue + } + set.String(name, f.Value, f.Usage) + } + + return nil +} + +// String looks up the value of a local StringFlag, returns +// "" if not found +func (c *Context) String(name string) string { + if fs := lookupFlagSet(name, c); fs != nil { + return lookupString(name, fs) + } + return "" +} + +func lookupString(name string, set *flag.FlagSet) string { + f := set.Lookup(name) + if f != nil { + parsed, err := f.Value.String(), error(nil) + if err != nil { + return "" + } + return parsed + } + return "" +} diff --git a/vendor/github.com/urfave/cli/v2/flag_string_slice.go b/vendor/github.com/urfave/cli/v2/flag_string_slice.go new file mode 100644 index 000000000..a114a495e --- /dev/null +++ b/vendor/github.com/urfave/cli/v2/flag_string_slice.go @@ -0,0 +1,159 @@ +package cli + +import ( + "encoding/json" + "flag" + "fmt" + "strings" +) + +// StringSlice wraps a []string to satisfy flag.Value +type StringSlice struct { + slice []string + hasBeenSet bool +} + +// NewStringSlice creates a *StringSlice with default values +func NewStringSlice(defaults ...string) *StringSlice { + return &StringSlice{slice: append([]string{}, defaults...)} +} + +// Set appends the string value to the list of values +func (s *StringSlice) Set(value string) error { + if !s.hasBeenSet { + s.slice = []string{} + s.hasBeenSet = true + } + + if strings.HasPrefix(value, slPfx) { + // Deserializing assumes overwrite + _ = json.Unmarshal([]byte(strings.Replace(value, slPfx, "", 1)), &s.slice) + s.hasBeenSet = true + return nil + } + + s.slice = append(s.slice, value) + + return nil +} + +// String returns a readable representation of this value (for usage defaults) +func (s *StringSlice) String() string { + return fmt.Sprintf("%s", s.slice) +} + +// Serialize allows StringSlice to fulfill Serializer +func (s *StringSlice) Serialize() string { + jsonBytes, _ := json.Marshal(s.slice) + return fmt.Sprintf("%s%s", slPfx, string(jsonBytes)) +} + +// Value returns the slice of strings set by this flag +func (s *StringSlice) Value() []string { + return s.slice +} + +// Get returns the slice of strings set by this flag +func (s *StringSlice) Get() interface{} { + return *s +} + +// StringSliceFlag is a flag with type *StringSlice +type StringSliceFlag struct { + Name string + Aliases []string + Usage string + EnvVars []string + FilePath string + Required bool + Hidden bool + TakesFile bool + Value *StringSlice + DefaultText string + HasBeenSet bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *StringSliceFlag) IsSet() bool { + return f.HasBeenSet +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f *StringSliceFlag) String() string { + return FlagStringer(f) +} + +// Names returns the names of the flag +func (f *StringSliceFlag) Names() []string { + return flagNames(f) +} + +// IsRequired returns whether or not the flag is required +func (f *StringSliceFlag) IsRequired() bool { + return f.Required +} + +// TakesValue returns true of the flag takes a value, otherwise false +func (f *StringSliceFlag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (f *StringSliceFlag) GetUsage() string { + return f.Usage +} + +// GetValue returns the flags value as string representation and an empty +// string if the flag takes no value at all. +func (f *StringSliceFlag) GetValue() string { + if f.Value != nil { + return f.Value.String() + } + return "" +} + +// Apply populates the flag given the flag set and environment +func (f *StringSliceFlag) Apply(set *flag.FlagSet) error { + if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + f.Value = &StringSlice{} + + for _, s := range strings.Split(val, ",") { + if err := f.Value.Set(strings.TrimSpace(s)); err != nil { + return fmt.Errorf("could not parse %q as string value for flag %s: %s", val, f.Name, err) + } + } + + f.HasBeenSet = true + } + + for _, name := range f.Names() { + if f.Value == nil { + f.Value = &StringSlice{} + } + set.Var(f.Value, name, f.Usage) + } + + return nil +} + +// StringSlice looks up the value of a local StringSliceFlag, returns +// nil if not found +func (c *Context) StringSlice(name string) []string { + if fs := lookupFlagSet(name, c); fs != nil { + return lookupStringSlice(name, fs) + } + return nil +} + +func lookupStringSlice(name string, set *flag.FlagSet) []string { + f := set.Lookup(name) + if f != nil { + parsed, err := (f.Value.(*StringSlice)).Value(), error(nil) + if err != nil { + return nil + } + return parsed + } + return nil +} diff --git a/vendor/github.com/urfave/cli/v2/flag_timestamp.go b/vendor/github.com/urfave/cli/v2/flag_timestamp.go new file mode 100644 index 000000000..d24edcd7f --- /dev/null +++ b/vendor/github.com/urfave/cli/v2/flag_timestamp.go @@ -0,0 +1,152 @@ +package cli + +import ( + "flag" + "fmt" + "time" +) + +// Timestamp wrap to satisfy golang's flag interface. +type Timestamp struct { + timestamp *time.Time + hasBeenSet bool + layout string +} + +// Timestamp constructor +func NewTimestamp(timestamp time.Time) *Timestamp { + return &Timestamp{timestamp: ×tamp} +} + +// Set the timestamp value directly +func (t *Timestamp) SetTimestamp(value time.Time) { + if !t.hasBeenSet { + t.timestamp = &value + t.hasBeenSet = true + } +} + +// Set the timestamp string layout for future parsing +func (t *Timestamp) SetLayout(layout string) { + t.layout = layout +} + +// Parses the string value to timestamp +func (t *Timestamp) Set(value string) error { + timestamp, err := time.Parse(t.layout, value) + if err != nil { + return err + } + + t.timestamp = ×tamp + t.hasBeenSet = true + return nil +} + +// String returns a readable representation of this value (for usage defaults) +func (t *Timestamp) String() string { + return fmt.Sprintf("%#v", t.timestamp) +} + +// Value returns the timestamp value stored in the flag +func (t *Timestamp) Value() *time.Time { + return t.timestamp +} + +// Get returns the flag structure +func (t *Timestamp) Get() interface{} { + return *t +} + +// TimestampFlag is a flag with type time +type TimestampFlag struct { + Name string + Aliases []string + Usage string + EnvVars []string + FilePath string + Required bool + Hidden bool + Layout string + Value *Timestamp + DefaultText string + HasBeenSet bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *TimestampFlag) IsSet() bool { + return f.HasBeenSet +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f *TimestampFlag) String() string { + return FlagStringer(f) +} + +// Names returns the names of the flag +func (f *TimestampFlag) Names() []string { + return flagNames(f) +} + +// IsRequired returns whether or not the flag is required +func (f *TimestampFlag) IsRequired() bool { + return f.Required +} + +// TakesValue returns true of the flag takes a value, otherwise false +func (f *TimestampFlag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (f *TimestampFlag) GetUsage() string { + return f.Usage +} + +// GetValue returns the flags value as string representation and an empty +// string if the flag takes no value at all. +func (f *TimestampFlag) GetValue() string { + if f.Value != nil { + return f.Value.timestamp.String() + } + return "" +} + +// Apply populates the flag given the flag set and environment +func (f *TimestampFlag) Apply(set *flag.FlagSet) error { + if f.Layout == "" { + return fmt.Errorf("timestamp Layout is required") + } + f.Value = &Timestamp{} + f.Value.SetLayout(f.Layout) + + if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if err := f.Value.Set(val); err != nil { + return fmt.Errorf("could not parse %q as timestamp value for flag %s: %s", val, f.Name, err) + } + f.HasBeenSet = true + } + + for _, name := range f.Names() { + set.Var(f.Value, name, f.Usage) + } + return nil +} + +// Timestamp gets the timestamp from a flag name +func (c *Context) Timestamp(name string) *time.Time { + if fs := lookupFlagSet(name, c); fs != nil { + return lookupTimestamp(name, fs) + } + return nil +} + +// Fetches the timestamp value from the local timestampWrap +func lookupTimestamp(name string, set *flag.FlagSet) *time.Time { + f := set.Lookup(name) + if f != nil { + return (f.Value.(*Timestamp)).Value() + } + return nil +} diff --git a/vendor/github.com/urfave/cli/v2/flag_uint.go b/vendor/github.com/urfave/cli/v2/flag_uint.go new file mode 100644 index 000000000..9f592388f --- /dev/null +++ b/vendor/github.com/urfave/cli/v2/flag_uint.go @@ -0,0 +1,105 @@ +package cli + +import ( + "flag" + "fmt" + "strconv" +) + +// UintFlag is a flag with type uint +type UintFlag struct { + Name string + Aliases []string + Usage string + EnvVars []string + FilePath string + Required bool + Hidden bool + Value uint + DefaultText string + Destination *uint + HasBeenSet bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *UintFlag) IsSet() bool { + return f.HasBeenSet +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f *UintFlag) String() string { + return FlagStringer(f) +} + +// Names returns the names of the flag +func (f *UintFlag) Names() []string { + return flagNames(f) +} + +// IsRequired returns whether or not the flag is required +func (f *UintFlag) IsRequired() bool { + return f.Required +} + +// TakesValue returns true of the flag takes a value, otherwise false +func (f *UintFlag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (f *UintFlag) GetUsage() string { + return f.Usage +} + +// Apply populates the flag given the flag set and environment +func (f *UintFlag) Apply(set *flag.FlagSet) error { + if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val != "" { + valInt, err := strconv.ParseUint(val, 0, 64) + if err != nil { + return fmt.Errorf("could not parse %q as uint value for flag %s: %s", val, f.Name, err) + } + + f.Value = uint(valInt) + f.HasBeenSet = true + } + } + + for _, name := range f.Names() { + if f.Destination != nil { + set.UintVar(f.Destination, name, f.Value, f.Usage) + continue + } + set.Uint(name, f.Value, f.Usage) + } + + return nil +} + +// GetValue returns the flags value as string representation and an empty +// string if the flag takes no value at all. +func (f *UintFlag) GetValue() string { + return fmt.Sprintf("%d", f.Value) +} + +// Uint looks up the value of a local UintFlag, returns +// 0 if not found +func (c *Context) Uint(name string) uint { + if fs := lookupFlagSet(name, c); fs != nil { + return lookupUint(name, fs) + } + return 0 +} + +func lookupUint(name string, set *flag.FlagSet) uint { + f := set.Lookup(name) + if f != nil { + parsed, err := strconv.ParseUint(f.Value.String(), 0, 64) + if err != nil { + return 0 + } + return uint(parsed) + } + return 0 +} diff --git a/vendor/github.com/urfave/cli/v2/flag_uint64.go b/vendor/github.com/urfave/cli/v2/flag_uint64.go new file mode 100644 index 000000000..5bbd1fa4b --- /dev/null +++ b/vendor/github.com/urfave/cli/v2/flag_uint64.go @@ -0,0 +1,105 @@ +package cli + +import ( + "flag" + "fmt" + "strconv" +) + +// Uint64Flag is a flag with type uint64 +type Uint64Flag struct { + Name string + Aliases []string + Usage string + EnvVars []string + FilePath string + Required bool + Hidden bool + Value uint64 + DefaultText string + Destination *uint64 + HasBeenSet bool +} + +// IsSet returns whether or not the flag has been set through env or file +func (f *Uint64Flag) IsSet() bool { + return f.HasBeenSet +} + +// String returns a readable representation of this value +// (for usage defaults) +func (f *Uint64Flag) String() string { + return FlagStringer(f) +} + +// Names returns the names of the flag +func (f *Uint64Flag) Names() []string { + return flagNames(f) +} + +// IsRequired returns whether or not the flag is required +func (f *Uint64Flag) IsRequired() bool { + return f.Required +} + +// TakesValue returns true of the flag takes a value, otherwise false +func (f *Uint64Flag) TakesValue() bool { + return true +} + +// GetUsage returns the usage string for the flag +func (f *Uint64Flag) GetUsage() string { + return f.Usage +} + +// Apply populates the flag given the flag set and environment +func (f *Uint64Flag) Apply(set *flag.FlagSet) error { + if val, ok := flagFromEnvOrFile(f.EnvVars, f.FilePath); ok { + if val != "" { + valInt, err := strconv.ParseUint(val, 0, 64) + if err != nil { + return fmt.Errorf("could not parse %q as uint64 value for flag %s: %s", val, f.Name, err) + } + + f.Value = valInt + f.HasBeenSet = true + } + } + + for _, name := range f.Names() { + if f.Destination != nil { + set.Uint64Var(f.Destination, name, f.Value, f.Usage) + continue + } + set.Uint64(name, f.Value, f.Usage) + } + + return nil +} + +// GetValue returns the flags value as string representation and an empty +// string if the flag takes no value at all. +func (f *Uint64Flag) GetValue() string { + return fmt.Sprintf("%d", f.Value) +} + +// Uint64 looks up the value of a local Uint64Flag, returns +// 0 if not found +func (c *Context) Uint64(name string) uint64 { + if fs := lookupFlagSet(name, c); fs != nil { + return lookupUint64(name, fs) + } + return 0 +} + +func lookupUint64(name string, set *flag.FlagSet) uint64 { + f := set.Lookup(name) + if f != nil { + parsed, err := strconv.ParseUint(f.Value.String(), 0, 64) + if err != nil { + return 0 + } + return parsed + } + return 0 +} diff --git a/vendor/github.com/urfave/cli/funcs.go b/vendor/github.com/urfave/cli/v2/funcs.go similarity index 59% rename from vendor/github.com/urfave/cli/funcs.go rename to vendor/github.com/urfave/cli/v2/funcs.go index cba5e6cb0..474c48faf 100644 --- a/vendor/github.com/urfave/cli/funcs.go +++ b/vendor/github.com/urfave/cli/v2/funcs.go @@ -1,6 +1,6 @@ package cli -// BashCompleteFunc is an action to execute when the bash-completion flag is set +// BashCompleteFunc is an action to execute when the shell completion flag is set type BashCompleteFunc func(*Context) // BeforeFunc is an action to execute before any subcommands are run, but after @@ -23,6 +23,22 @@ type CommandNotFoundFunc func(*Context, string) // is displayed and the execution is interrupted. type OnUsageErrorFunc func(context *Context, err error, isSubcommand bool) error +// ExitErrHandlerFunc is executed if provided in order to handle exitError values +// returned by Actions and Before/After functions. +type ExitErrHandlerFunc func(context *Context, err error) + // FlagStringFunc is used by the help generation to display a flag, which is // expected to be a single line. type FlagStringFunc func(Flag) string + +// FlagNamePrefixFunc is used by the default FlagStringFunc to create prefix +// text for a flag's full name. +type FlagNamePrefixFunc func(fullName []string, placeholder string) string + +// FlagEnvHintFunc is used by the default FlagStringFunc to annotate flag help +// with the environment variable details. +type FlagEnvHintFunc func(envVars []string, str string) string + +// FlagFileHintFunc is used by the default FlagStringFunc to annotate flag help +// with the file path details. +type FlagFileHintFunc func(filePath, str string) string diff --git a/vendor/github.com/urfave/cli/v2/go.mod b/vendor/github.com/urfave/cli/v2/go.mod new file mode 100644 index 000000000..c38d41c14 --- /dev/null +++ b/vendor/github.com/urfave/cli/v2/go.mod @@ -0,0 +1,9 @@ +module github.com/urfave/cli/v2 + +go 1.11 + +require ( + github.com/BurntSushi/toml v0.3.1 + github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d + gopkg.in/yaml.v2 v2.2.2 +) diff --git a/vendor/github.com/urfave/cli/v2/go.sum b/vendor/github.com/urfave/cli/v2/go.sum new file mode 100644 index 000000000..ef121ff5d --- /dev/null +++ b/vendor/github.com/urfave/cli/v2/go.sum @@ -0,0 +1,14 @@ +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +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/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/vendor/github.com/urfave/cli/help.go b/vendor/github.com/urfave/cli/v2/help.go similarity index 50% rename from vendor/github.com/urfave/cli/help.go rename to vendor/github.com/urfave/cli/v2/help.go index 57ec98d58..c1e974a48 100644 --- a/vendor/github.com/urfave/cli/help.go +++ b/vendor/github.com/urfave/cli/v2/help.go @@ -7,78 +7,10 @@ import ( "strings" "text/tabwriter" "text/template" + "unicode/utf8" ) -// AppHelpTemplate is the text template for the Default help topic. -// cli.go uses text/template to render templates. You can -// render custom help text by setting this variable. -var AppHelpTemplate = `NAME: - {{.Name}}{{if .Usage}} - {{.Usage}}{{end}} - -USAGE: - {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}} - -VERSION: - {{.Version}}{{end}}{{end}}{{if .Description}} - -DESCRIPTION: - {{.Description}}{{end}}{{if len .Authors}} - -AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}: - {{range $index, $author := .Authors}}{{if $index}} - {{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}} - -COMMANDS:{{range .VisibleCategories}}{{if .Name}} - {{.Name}}:{{end}}{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} - -GLOBAL OPTIONS: - {{range $index, $option := .VisibleFlags}}{{if $index}} - {{end}}{{$option}}{{end}}{{end}}{{if .Copyright}} - -COPYRIGHT: - {{.Copyright}}{{end}} -` - -// CommandHelpTemplate is the text template for the command help topic. -// cli.go uses text/template to render templates. You can -// render custom help text by setting this variable. -var CommandHelpTemplate = `NAME: - {{.HelpName}} - {{.Usage}} - -USAGE: - {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}} - -CATEGORY: - {{.Category}}{{end}}{{if .Description}} - -DESCRIPTION: - {{.Description}}{{end}}{{if .VisibleFlags}} - -OPTIONS: - {{range .VisibleFlags}}{{.}} - {{end}}{{end}} -` - -// SubcommandHelpTemplate is the text template for the subcommand help topic. -// cli.go uses text/template to render templates. You can -// render custom help text by setting this variable. -var SubcommandHelpTemplate = `NAME: - {{.HelpName}} - {{if .Description}}{{.Description}}{{else}}{{.Usage}}{{end}} - -USAGE: - {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}} - -COMMANDS:{{range .VisibleCategories}}{{if .Name}} - {{.Name}}:{{end}}{{range .VisibleCommands}} - {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}} -{{end}}{{if .VisibleFlags}} -OPTIONS: - {{range .VisibleFlags}}{{.}} - {{end}}{{end}} -` - -var helpCommand = Command{ +var helpCommand = &Command{ Name: "help", Aliases: []string{"h"}, Usage: "Shows a list of commands or help for one command", @@ -89,12 +21,12 @@ var helpCommand = Command{ return ShowCommandHelp(c, args.First()) } - ShowAppHelp(c) + _ = ShowAppHelp(c) return nil }, } -var helpSubcommand = Command{ +var helpSubcommand = &Command{ Name: "help", Aliases: []string{"h"}, Usage: "Shows a list of commands or help for one command", @@ -115,13 +47,18 @@ type helpPrinter func(w io.Writer, templ string, data interface{}) // Prints help for the App or Command with custom template function. type helpPrinterCustom func(w io.Writer, templ string, data interface{}, customFunc map[string]interface{}) -// HelpPrinter is a function that writes the help output. If not set a default -// is used. The function signature is: -// func(w io.Writer, templ string, data interface{}) +// HelpPrinter is a function that writes the help output. If not set explicitly, +// this calls HelpPrinterCustom using only the default template functions. +// +// If custom logic for printing help is required, this function can be +// overridden. If the ExtraInfo field is defined on an App, this function +// should not be modified, as HelpPrinterCustom will be used directly in order +// to capture the extra information. var HelpPrinter helpPrinter = printHelp -// HelpPrinterCustom is same as HelpPrinter but -// takes a custom function for template function map. +// HelpPrinterCustom is a function that writes the help output. It is used as +// the default implementation of HelpPrinter, and may be called directly if +// the ExtraInfo field is set on an App. var HelpPrinterCustom helpPrinterCustom = printHelpCustom // VersionPrinter prints the version for the App @@ -129,43 +66,122 @@ var VersionPrinter = printVersion // ShowAppHelpAndExit - Prints the list of subcommands for the app and exits with exit code. func ShowAppHelpAndExit(c *Context, exitCode int) { - ShowAppHelp(c) + _ = ShowAppHelp(c) os.Exit(exitCode) } // ShowAppHelp is an action that displays the help. -func ShowAppHelp(c *Context) (err error) { - if c.App.CustomAppHelpTemplate == "" { - HelpPrinter(c.App.Writer, AppHelpTemplate, c.App) - return +func ShowAppHelp(c *Context) error { + template := c.App.CustomAppHelpTemplate + if template == "" { + template = AppHelpTemplate } + + if c.App.ExtraInfo == nil { + HelpPrinter(c.App.Writer, template, c.App) + return nil + } + customAppData := func() map[string]interface{} { - if c.App.ExtraInfo == nil { - return nil - } return map[string]interface{}{ "ExtraInfo": c.App.ExtraInfo, } } - HelpPrinterCustom(c.App.Writer, c.App.CustomAppHelpTemplate, c.App, customAppData()) + HelpPrinterCustom(c.App.Writer, template, c.App, customAppData()) + return nil } // DefaultAppComplete prints the list of subcommands as the default app completion method func DefaultAppComplete(c *Context) { - for _, command := range c.App.Commands { + DefaultCompleteWithFlags(nil)(c) +} + +func printCommandSuggestions(commands []*Command, writer io.Writer) { + for _, command := range commands { if command.Hidden { continue } - for _, name := range command.Names() { - fmt.Fprintln(c.App.Writer, name) + if os.Getenv("_CLI_ZSH_AUTOCOMPLETE_HACK") == "1" { + for _, name := range command.Names() { + _, _ = fmt.Fprintf(writer, "%s:%s\n", name, command.Usage) + } + } else { + for _, name := range command.Names() { + _, _ = fmt.Fprintf(writer, "%s\n", name) + } + } + } +} + +func cliArgContains(flagName string) bool { + for _, name := range strings.Split(flagName, ",") { + name = strings.TrimSpace(name) + count := utf8.RuneCountInString(name) + if count > 2 { + count = 2 + } + flag := fmt.Sprintf("%s%s", strings.Repeat("-", count), name) + for _, a := range os.Args { + if a == flag { + return true + } + } + } + return false +} + +func printFlagSuggestions(lastArg string, flags []Flag, writer io.Writer) { + cur := strings.TrimPrefix(lastArg, "-") + cur = strings.TrimPrefix(cur, "-") + for _, flag := range flags { + if bflag, ok := flag.(*BoolFlag); ok && bflag.Hidden { + continue + } + for _, name := range flag.Names() { + name = strings.TrimSpace(name) + // this will get total count utf8 letters in flag name + count := utf8.RuneCountInString(name) + if count > 2 { + count = 2 // resuse this count to generate single - or -- in flag completion + } + // if flag name has more than one utf8 letter and last argument in cli has -- prefix then + // skip flag completion for short flags example -v or -x + if strings.HasPrefix(lastArg, "--") && count == 1 { + continue + } + // match if last argument matches this flag and it is not repeated + if strings.HasPrefix(name, cur) && cur != name && !cliArgContains(name) { + flagCompletion := fmt.Sprintf("%s%s", strings.Repeat("-", count), name) + _, _ = fmt.Fprintln(writer, flagCompletion) + } + } + } +} + +func DefaultCompleteWithFlags(cmd *Command) func(c *Context) { + return func(c *Context) { + if len(os.Args) > 2 { + lastArg := os.Args[len(os.Args)-2] + if strings.HasPrefix(lastArg, "-") { + printFlagSuggestions(lastArg, c.App.Flags, c.App.Writer) + if cmd != nil { + printFlagSuggestions(lastArg, cmd.Flags, c.App.Writer) + } + return + } + } + if cmd != nil { + printCommandSuggestions(cmd.Subcommands, c.App.Writer) + } else { + printCommandSuggestions(c.App.Commands, c.App.Writer) } } } // ShowCommandHelpAndExit - exits with code after showing help func ShowCommandHelpAndExit(c *Context, command string, code int) { - ShowCommandHelp(c, command) + _ = ShowCommandHelp(c, command) os.Exit(code) } @@ -179,17 +195,19 @@ func ShowCommandHelp(ctx *Context, command string) error { for _, c := range ctx.App.Commands { if c.HasName(command) { - if c.CustomHelpTemplate != "" { - HelpPrinterCustom(ctx.App.Writer, c.CustomHelpTemplate, c, nil) - } else { - HelpPrinter(ctx.App.Writer, CommandHelpTemplate, c) + templ := c.CustomHelpTemplate + if templ == "" { + templ = CommandHelpTemplate } + + HelpPrinter(ctx.App.Writer, templ, c) + return nil } } if ctx.App.CommandNotFound == nil { - return NewExitError(fmt.Sprintf("No help topic for '%v'", command), 3) + return Exit(fmt.Sprintf("No help topic for '%v'", command), 3) } ctx.App.CommandNotFound(ctx, command) @@ -198,7 +216,15 @@ func ShowCommandHelp(ctx *Context, command string) error { // ShowSubcommandHelp prints help for the given subcommand func ShowSubcommandHelp(c *Context) error { - return ShowCommandHelp(c, c.Command.Name) + if c == nil { + return nil + } + + if c.Command != nil { + return ShowCommandHelp(c, c.Command.Name) + } + + return ShowCommandHelp(c, "") } // ShowVersion prints the version number of the App @@ -207,7 +233,7 @@ func ShowVersion(c *Context) { } func printVersion(c *Context) { - fmt.Fprintf(c.App.Writer, "%v version %v\n", c.App.Name, c.App.Version) + _, _ = fmt.Fprintf(c.App.Writer, "%v version %v\n", c.App.Name, c.App.Version) } // ShowCompletions prints the lists of commands within a given context @@ -221,66 +247,70 @@ func ShowCompletions(c *Context) { // ShowCommandCompletions prints the custom completions for a given command func ShowCommandCompletions(ctx *Context, command string) { c := ctx.App.Command(command) - if c != nil && c.BashComplete != nil { - c.BashComplete(ctx) + if c != nil { + if c.BashComplete != nil { + c.BashComplete(ctx) + } else { + DefaultCompleteWithFlags(c)(ctx) + } } + } -func printHelpCustom(out io.Writer, templ string, data interface{}, customFunc map[string]interface{}) { +// printHelpCustom is the default implementation of HelpPrinterCustom. +// +// The customFuncs map will be combined with a default template.FuncMap to +// allow using arbitrary functions in template rendering. +func printHelpCustom(out io.Writer, templ string, data interface{}, customFuncs map[string]interface{}) { funcMap := template.FuncMap{ "join": strings.Join, } - if customFunc != nil { - for key, value := range customFunc { - funcMap[key] = value - } + for key, value := range customFuncs { + funcMap[key] = value } w := tabwriter.NewWriter(out, 1, 8, 2, ' ', 0) t := template.Must(template.New("help").Funcs(funcMap).Parse(templ)) + err := t.Execute(w, data) if err != nil { // If the writer is closed, t.Execute will fail, and there's nothing // we can do to recover. if os.Getenv("CLI_TEMPLATE_ERROR_DEBUG") != "" { - fmt.Fprintf(ErrWriter, "CLI TEMPLATE ERROR: %#v\n", err) + _, _ = fmt.Fprintf(ErrWriter, "CLI TEMPLATE ERROR: %#v\n", err) } return } - w.Flush() + _ = w.Flush() } func printHelp(out io.Writer, templ string, data interface{}) { - printHelpCustom(out, templ, data, nil) + HelpPrinterCustom(out, templ, data, nil) } func checkVersion(c *Context) bool { found := false - if VersionFlag.GetName() != "" { - eachName(VersionFlag.GetName(), func(name string) { - if c.GlobalBool(name) || c.Bool(name) { - found = true - } - }) + for _, name := range VersionFlag.Names() { + if c.Bool(name) { + found = true + } } return found } func checkHelp(c *Context) bool { found := false - if HelpFlag.GetName() != "" { - eachName(HelpFlag.GetName(), func(name string) { - if c.GlobalBool(name) || c.Bool(name) { - found = true - } - }) + for _, name := range HelpFlag.Names() { + if c.Bool(name) { + found = true + } } return found } func checkCommandHelp(c *Context, name string) bool { if c.Bool("h") || c.Bool("help") { - ShowCommandHelp(c, name) + _ = ShowCommandHelp(c, name) return true } @@ -289,7 +319,7 @@ func checkCommandHelp(c *Context, name string) bool { func checkSubcommandHelp(c *Context) bool { if c.Bool("h") || c.Bool("help") { - ShowSubcommandHelp(c) + _ = ShowSubcommandHelp(c) return true } @@ -304,7 +334,7 @@ func checkShellCompleteFlag(a *App, arguments []string) (bool, []string) { pos := len(arguments) - 1 lastArg := arguments[pos] - if lastArg != "--"+BashCompletionFlag.GetName() { + if lastArg != "--generate-bash-completion" { return false, arguments } diff --git a/vendor/github.com/urfave/cli/v2/parse.go b/vendor/github.com/urfave/cli/v2/parse.go new file mode 100644 index 000000000..7df17296a --- /dev/null +++ b/vendor/github.com/urfave/cli/v2/parse.go @@ -0,0 +1,94 @@ +package cli + +import ( + "flag" + "strings" +) + +type iterativeParser interface { + newFlagSet() (*flag.FlagSet, error) + useShortOptionHandling() bool +} + +// To enable short-option handling (e.g., "-it" vs "-i -t") we have to +// iteratively catch parsing errors. This way we achieve LR parsing without +// transforming any arguments. Otherwise, there is no way we can discriminate +// combined short options from common arguments that should be left untouched. +// Pass `shellComplete` to continue parsing options on failure during shell +// completion when, the user-supplied options may be incomplete. +func parseIter(set *flag.FlagSet, ip iterativeParser, args []string, shellComplete bool) error { + for { + err := set.Parse(args) + if !ip.useShortOptionHandling() || err == nil { + if shellComplete { + return nil + } + return err + } + + errStr := err.Error() + trimmed := strings.TrimPrefix(errStr, "flag provided but not defined: -") + if errStr == trimmed { + return err + } + + // regenerate the initial args with the split short opts + argsWereSplit := false + for i, arg := range args { + // skip args that are not part of the error message + if name := strings.TrimLeft(arg, "-"); name != trimmed { + continue + } + + // if we can't split, the error was accurate + shortOpts := splitShortOptions(set, arg) + if len(shortOpts) == 1 { + return err + } + + // swap current argument with the split version + args = append(args[:i], append(shortOpts, args[i+1:]...)...) + argsWereSplit = true + break + } + + // This should be an impossible to reach code path, but in case the arg + // splitting failed to happen, this will prevent infinite loops + if !argsWereSplit { + return err + } + + // Since custom parsing failed, replace the flag set before retrying + newSet, err := ip.newFlagSet() + if err != nil { + return err + } + *set = *newSet + } +} + +func splitShortOptions(set *flag.FlagSet, arg string) []string { + shortFlagsExist := func(s string) bool { + for _, c := range s[1:] { + if f := set.Lookup(string(c)); f == nil { + return false + } + } + return true + } + + if !isSplittable(arg) || !shortFlagsExist(arg) { + return []string{arg} + } + + separated := make([]string, 0, len(arg)-1) + for _, flagChar := range arg[1:] { + separated = append(separated, "-"+string(flagChar)) + } + + return separated +} + +func isSplittable(flagArg string) bool { + return strings.HasPrefix(flagArg, "-") && !strings.HasPrefix(flagArg, "--") && len(flagArg) > 2 +} diff --git a/vendor/github.com/urfave/cli/v2/sort.go b/vendor/github.com/urfave/cli/v2/sort.go new file mode 100644 index 000000000..23d1c2f77 --- /dev/null +++ b/vendor/github.com/urfave/cli/v2/sort.go @@ -0,0 +1,29 @@ +package cli + +import "unicode" + +// lexicographicLess compares strings alphabetically considering case. +func lexicographicLess(i, j string) bool { + iRunes := []rune(i) + jRunes := []rune(j) + + lenShared := len(iRunes) + if lenShared > len(jRunes) { + lenShared = len(jRunes) + } + + for index := 0; index < lenShared; index++ { + ir := iRunes[index] + jr := jRunes[index] + + if lir, ljr := unicode.ToLower(ir), unicode.ToLower(jr); lir != ljr { + return lir < ljr + } + + if ir != jr { + return ir < jr + } + } + + return i < j +} diff --git a/vendor/github.com/urfave/cli/v2/template.go b/vendor/github.com/urfave/cli/v2/template.go new file mode 100644 index 000000000..1cc4bd624 --- /dev/null +++ b/vendor/github.com/urfave/cli/v2/template.go @@ -0,0 +1,119 @@ +package cli + +// AppHelpTemplate is the text template for the Default help topic. +// cli.go uses text/template to render templates. You can +// render custom help text by setting this variable. +var AppHelpTemplate = `NAME: + {{.Name}}{{if .Usage}} - {{.Usage}}{{end}} + +USAGE: + {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} {{if .VisibleFlags}}[global options]{{end}}{{if .Commands}} command [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Version}}{{if not .HideVersion}} + +VERSION: + {{.Version}}{{end}}{{end}}{{if .Description}} + +DESCRIPTION: + {{.Description}}{{end}}{{if len .Authors}} + +AUTHOR{{with $length := len .Authors}}{{if ne 1 $length}}S{{end}}{{end}}: + {{range $index, $author := .Authors}}{{if $index}} + {{end}}{{$author}}{{end}}{{end}}{{if .VisibleCommands}} + +COMMANDS:{{range .VisibleCategories}}{{if .Name}} + {{.Name}}:{{range .VisibleCommands}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} + +GLOBAL OPTIONS: + {{range $index, $option := .VisibleFlags}}{{if $index}} + {{end}}{{$option}}{{end}}{{end}}{{if .Copyright}} + +COPYRIGHT: + {{.Copyright}}{{end}} +` + +// CommandHelpTemplate is the text template for the command help topic. +// cli.go uses text/template to render templates. You can +// render custom help text by setting this variable. +var CommandHelpTemplate = `NAME: + {{.HelpName}} - {{.Usage}} + +USAGE: + {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}}{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}}{{if .Category}} + +CATEGORY: + {{.Category}}{{end}}{{if .Description}} + +DESCRIPTION: + {{.Description}}{{end}}{{if .VisibleFlags}} + +OPTIONS: + {{range .VisibleFlags}}{{.}} + {{end}}{{end}} +` + +// SubcommandHelpTemplate is the text template for the subcommand help topic. +// cli.go uses text/template to render templates. You can +// render custom help text by setting this variable. +var SubcommandHelpTemplate = `NAME: + {{.HelpName}} - {{if .Description}}{{.Description}}{{else}}{{.Usage}}{{end}} + +USAGE: + {{if .UsageText}}{{.UsageText}}{{else}}{{.HelpName}} command{{if .VisibleFlags}} [command options]{{end}} {{if .ArgsUsage}}{{.ArgsUsage}}{{else}}[arguments...]{{end}}{{end}} + +COMMANDS:{{range .VisibleCategories}}{{if .Name}} + {{.Name}}:{{range .VisibleCommands}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{else}}{{range .VisibleCommands}} + {{join .Names ", "}}{{"\t"}}{{.Usage}}{{end}}{{end}}{{end}}{{if .VisibleFlags}} + +OPTIONS: + {{range .VisibleFlags}}{{.}} + {{end}}{{end}} +` + +var MarkdownDocTemplate = `% {{ .App.Name }}(8){{ if .App.Description }} {{ .App.Description }}{{ end }} +{{ range $a := .App.Authors }} +% {{ $a }}{{ end }} + +# NAME + +{{ .App.Name }}{{ if .App.Usage }} - {{ .App.Usage }}{{ end }} + +# SYNOPSIS + +{{ .App.Name }} +{{ if .SynopsisArgs }} +` + "```" + ` +{{ range $v := .SynopsisArgs }}{{ $v }}{{ end }}` + "```" + ` +{{ end }}{{ if .App.UsageText }} +# DESCRIPTION + +{{ .App.UsageText }} +{{ end }} +**Usage**: + +` + "```" + ` +{{ .App.Name }} [GLOBAL OPTIONS] command [COMMAND OPTIONS] [ARGUMENTS...] +` + "```" + ` +{{ if .GlobalArgs }} +# GLOBAL OPTIONS +{{ range $v := .GlobalArgs }} +{{ $v }}{{ end }} +{{ end }}{{ if .Commands }} +# COMMANDS +{{ range $v := .Commands }} +{{ $v }}{{ end }}{{ end }}` + +var FishCompletionTemplate = `# {{ .App.Name }} fish shell completion + +function __fish_{{ .App.Name }}_no_subcommand --description 'Test if there has been any subcommand yet' + for i in (commandline -opc) + if contains -- $i{{ range $v := .AllCommands }} {{ $v }}{{ end }} + return 1 + end + end + return 0 +end + +{{ range $v := .Completions }}{{ $v }} +{{ end }}` diff --git a/vendor/github.com/vektah/gqlparser/.gitignore b/vendor/github.com/vektah/gqlparser/v2/.gitignore similarity index 100% rename from vendor/github.com/vektah/gqlparser/.gitignore rename to vendor/github.com/vektah/gqlparser/v2/.gitignore diff --git a/vendor/github.com/vektah/gqlparser/LICENSE b/vendor/github.com/vektah/gqlparser/v2/LICENSE similarity index 100% rename from vendor/github.com/vektah/gqlparser/LICENSE rename to vendor/github.com/vektah/gqlparser/v2/LICENSE diff --git a/vendor/github.com/vektah/gqlparser/ast/argmap.go b/vendor/github.com/vektah/gqlparser/v2/ast/argmap.go similarity index 100% rename from vendor/github.com/vektah/gqlparser/ast/argmap.go rename to vendor/github.com/vektah/gqlparser/v2/ast/argmap.go diff --git a/vendor/github.com/vektah/gqlparser/ast/collections.go b/vendor/github.com/vektah/gqlparser/v2/ast/collections.go similarity index 93% rename from vendor/github.com/vektah/gqlparser/ast/collections.go rename to vendor/github.com/vektah/gqlparser/v2/ast/collections.go index 6bf672976..94b800ee2 100644 --- a/vendor/github.com/vektah/gqlparser/ast/collections.go +++ b/vendor/github.com/vektah/gqlparser/v2/ast/collections.go @@ -33,6 +33,16 @@ func (l DirectiveList) ForName(name string) *Directive { return nil } +func (l DirectiveList) ForNames(name string) []*Directive { + resp := []*Directive{} + for _, it := range l { + if it.Name == name { + resp = append(resp, it) + } + } + return resp +} + type OperationList []*OperationDefinition func (l OperationList) ForName(name string) *OperationDefinition { diff --git a/vendor/github.com/vektah/gqlparser/ast/definition.go b/vendor/github.com/vektah/gqlparser/v2/ast/definition.go similarity index 100% rename from vendor/github.com/vektah/gqlparser/ast/definition.go rename to vendor/github.com/vektah/gqlparser/v2/ast/definition.go diff --git a/vendor/github.com/vektah/gqlparser/ast/directive.go b/vendor/github.com/vektah/gqlparser/v2/ast/directive.go similarity index 100% rename from vendor/github.com/vektah/gqlparser/ast/directive.go rename to vendor/github.com/vektah/gqlparser/v2/ast/directive.go diff --git a/vendor/github.com/vektah/gqlparser/ast/document.go b/vendor/github.com/vektah/gqlparser/v2/ast/document.go similarity index 100% rename from vendor/github.com/vektah/gqlparser/ast/document.go rename to vendor/github.com/vektah/gqlparser/v2/ast/document.go diff --git a/vendor/github.com/vektah/gqlparser/ast/dumper.go b/vendor/github.com/vektah/gqlparser/v2/ast/dumper.go similarity index 100% rename from vendor/github.com/vektah/gqlparser/ast/dumper.go rename to vendor/github.com/vektah/gqlparser/v2/ast/dumper.go diff --git a/vendor/github.com/vektah/gqlparser/ast/fragment.go b/vendor/github.com/vektah/gqlparser/v2/ast/fragment.go similarity index 100% rename from vendor/github.com/vektah/gqlparser/ast/fragment.go rename to vendor/github.com/vektah/gqlparser/v2/ast/fragment.go diff --git a/vendor/github.com/vektah/gqlparser/ast/operation.go b/vendor/github.com/vektah/gqlparser/v2/ast/operation.go similarity index 100% rename from vendor/github.com/vektah/gqlparser/ast/operation.go rename to vendor/github.com/vektah/gqlparser/v2/ast/operation.go diff --git a/vendor/github.com/vektah/gqlparser/v2/ast/path.go b/vendor/github.com/vektah/gqlparser/v2/ast/path.go new file mode 100644 index 000000000..9af168438 --- /dev/null +++ b/vendor/github.com/vektah/gqlparser/v2/ast/path.go @@ -0,0 +1,67 @@ +package ast + +import ( + "bytes" + "encoding/json" + "fmt" +) + +var _ json.Unmarshaler = (*Path)(nil) + +type Path []PathElement + +type PathElement interface { + isPathElement() +} + +var _ PathElement = PathIndex(0) +var _ PathElement = PathName("") + +func (path Path) String() string { + var str bytes.Buffer + for i, v := range path { + switch v := v.(type) { + case PathIndex: + str.WriteString(fmt.Sprintf("[%d]", v)) + case PathName: + if i != 0 { + str.WriteByte('.') + } + str.WriteString(string(v)) + default: + panic(fmt.Sprintf("unknown type: %T", v)) + } + } + return str.String() +} + +func (path *Path) UnmarshalJSON(b []byte) error { + var vs []interface{} + err := json.Unmarshal(b, &vs) + if err != nil { + return err + } + + *path = make([]PathElement, 0, len(vs)) + for _, v := range vs { + switch v := v.(type) { + case string: + *path = append(*path, PathName(v)) + case int: + *path = append(*path, PathIndex(v)) + case float64: + *path = append(*path, PathIndex(int(v))) + default: + return fmt.Errorf("unknown path element type: %T", v) + } + } + return nil +} + +type PathIndex int + +func (_ PathIndex) isPathElement() {} + +type PathName string + +func (_ PathName) isPathElement() {} diff --git a/vendor/github.com/vektah/gqlparser/ast/selection.go b/vendor/github.com/vektah/gqlparser/v2/ast/selection.go similarity index 100% rename from vendor/github.com/vektah/gqlparser/ast/selection.go rename to vendor/github.com/vektah/gqlparser/v2/ast/selection.go diff --git a/vendor/github.com/vektah/gqlparser/ast/source.go b/vendor/github.com/vektah/gqlparser/v2/ast/source.go similarity index 94% rename from vendor/github.com/vektah/gqlparser/ast/source.go rename to vendor/github.com/vektah/gqlparser/v2/ast/source.go index 08c66689a..2949f83f7 100644 --- a/vendor/github.com/vektah/gqlparser/ast/source.go +++ b/vendor/github.com/vektah/gqlparser/v2/ast/source.go @@ -3,13 +3,13 @@ package ast // Source covers a single *.graphql file type Source struct { // Name is the filename of the source - Name string + Name string // Input is the actual contents of the source file - Input string + Input string // BuiltIn indicate whether the source is a part of the specification BuiltIn bool } - + type Position struct { Start int // The starting position, in runes, of this token in the input. End int // The end position, in runes, of this token in the input. diff --git a/vendor/github.com/vektah/gqlparser/ast/type.go b/vendor/github.com/vektah/gqlparser/v2/ast/type.go similarity index 100% rename from vendor/github.com/vektah/gqlparser/ast/type.go rename to vendor/github.com/vektah/gqlparser/v2/ast/type.go diff --git a/vendor/github.com/vektah/gqlparser/ast/value.go b/vendor/github.com/vektah/gqlparser/v2/ast/value.go similarity index 100% rename from vendor/github.com/vektah/gqlparser/ast/value.go rename to vendor/github.com/vektah/gqlparser/v2/ast/value.go diff --git a/vendor/github.com/vektah/gqlparser/v2/formatter/formatter.go b/vendor/github.com/vektah/gqlparser/v2/formatter/formatter.go new file mode 100644 index 000000000..194911a1e --- /dev/null +++ b/vendor/github.com/vektah/gqlparser/v2/formatter/formatter.go @@ -0,0 +1,605 @@ +package formatter + +import ( + "fmt" + "io" + "sort" + "strings" + + "github.com/vektah/gqlparser/v2/ast" +) + +type Formatter interface { + FormatSchema(schema *ast.Schema) + FormatSchemaDocument(doc *ast.SchemaDocument) + FormatQueryDocument(doc *ast.QueryDocument) +} + +func NewFormatter(w io.Writer) Formatter { + return &formatter{writer: w} +} + +type formatter struct { + writer io.Writer + + indent int + emitBuiltin bool + + padNext bool + lineHead bool +} + +func (f *formatter) writeString(s string) { + _, _ = f.writer.Write([]byte(s)) +} + +func (f *formatter) writeIndent() *formatter { + if f.lineHead { + f.writeString(strings.Repeat("\t", f.indent)) + } + f.lineHead = false + f.padNext = false + + return f +} + +func (f *formatter) WriteNewline() *formatter { + f.writeString("\n") + f.lineHead = true + f.padNext = false + + return f +} + +func (f *formatter) WriteWord(word string) *formatter { + if f.lineHead { + f.writeIndent() + } + if f.padNext { + f.writeString(" ") + } + f.writeString(strings.TrimSpace(word)) + f.padNext = true + + return f +} + +func (f *formatter) WriteString(s string) *formatter { + if f.lineHead { + f.writeIndent() + } + if f.padNext { + f.writeString(" ") + } + f.writeString(s) + f.padNext = false + + return f +} + +func (f *formatter) WriteDescription(s string) *formatter { + if s == "" { + return f + } + + f.WriteString(`"""`).WriteNewline() + + ss := strings.Split(s, "\n") + for _, s := range ss { + f.WriteString(s).WriteNewline() + } + + f.WriteString(`"""`).WriteNewline() + + return f +} + +func (f *formatter) IncrementIndent() { + f.indent++ +} + +func (f *formatter) DecrementIndent() { + f.indent-- +} + +func (f *formatter) NoPadding() *formatter { + f.padNext = false + + return f +} + +func (f *formatter) NeedPadding() *formatter { + f.padNext = true + + return f +} + +func (f *formatter) FormatSchema(schema *ast.Schema) { + if schema == nil { + return + } + + var inSchema bool + startSchema := func() { + if !inSchema { + inSchema = true + + f.WriteWord("schema").WriteString("{").WriteNewline() + f.IncrementIndent() + } + } + if schema.Query != nil && schema.Query.Name != "Query" { + startSchema() + f.WriteWord("query").NoPadding().WriteString(":").NeedPadding() + f.WriteWord(schema.Query.Name).WriteNewline() + } + if schema.Mutation != nil && schema.Mutation.Name != "Mutation" { + startSchema() + f.WriteWord("mutation").NoPadding().WriteString(":").NeedPadding() + f.WriteWord(schema.Mutation.Name).WriteNewline() + } + if schema.Subscription != nil && schema.Subscription.Name != "Subscription" { + startSchema() + f.WriteWord("subscription").NoPadding().WriteString(":").NeedPadding() + f.WriteWord(schema.Subscription.Name).WriteNewline() + } + if inSchema { + f.DecrementIndent() + f.WriteString("}").WriteNewline() + } + + directiveNames := make([]string, 0, len(schema.Directives)) + for name := range schema.Directives { + directiveNames = append(directiveNames, name) + } + sort.Strings(directiveNames) + for _, name := range directiveNames { + f.FormatDirectiveDefinition(schema.Directives[name]) + } + + typeNames := make([]string, 0, len(schema.Types)) + for name := range schema.Types { + typeNames = append(typeNames, name) + } + sort.Strings(typeNames) + for _, name := range typeNames { + f.FormatDefinition(schema.Types[name], false) + } +} + +func (f *formatter) FormatSchemaDocument(doc *ast.SchemaDocument) { + // TODO emit by position based order + + if doc == nil { + return + } + + f.FormatSchemaDefinitionList(doc.Schema, false) + f.FormatSchemaDefinitionList(doc.SchemaExtension, true) + + f.FormatDirectiveDefinitionList(doc.Directives) + + f.FormatDefinitionList(doc.Definitions, false) + f.FormatDefinitionList(doc.Extensions, true) +} + +func (f *formatter) FormatQueryDocument(doc *ast.QueryDocument) { + // TODO emit by position based order + + if doc == nil { + return + } + + f.FormatOperationList(doc.Operations) + f.FormatFragmentDefinitionList(doc.Fragments) +} + +func (f *formatter) FormatSchemaDefinitionList(lists ast.SchemaDefinitionList, extension bool) { + if len(lists) == 0 { + return + } + + if extension { + f.WriteWord("extend") + } + f.WriteWord("schema").WriteString("{").WriteNewline() + f.IncrementIndent() + + for _, def := range lists { + f.FormatSchemaDefinition(def) + } + + f.DecrementIndent() + f.WriteString("}").WriteNewline() +} + +func (f *formatter) FormatSchemaDefinition(def *ast.SchemaDefinition) { + f.WriteDescription(def.Description) + + f.FormatDirectiveList(def.Directives) + + f.FormatOperationTypeDefinitionList(def.OperationTypes) +} + +func (f *formatter) FormatOperationTypeDefinitionList(lists ast.OperationTypeDefinitionList) { + for _, def := range lists { + f.FormatOperationTypeDefinition(def) + } +} + +func (f *formatter) FormatOperationTypeDefinition(def *ast.OperationTypeDefinition) { + f.WriteWord(string(def.Operation)).NoPadding().WriteString(":").NeedPadding() + f.WriteWord(def.Type) + f.WriteNewline() +} + +func (f *formatter) FormatFieldList(fieldList ast.FieldList) { + if len(fieldList) == 0 { + return + } + + f.WriteString("{").WriteNewline() + f.IncrementIndent() + + for _, field := range fieldList { + f.FormatFieldDefinition(field) + } + + f.DecrementIndent() + f.WriteString("}") +} + +func (f *formatter) FormatFieldDefinition(field *ast.FieldDefinition) { + if !f.emitBuiltin && strings.HasPrefix(field.Name, "__") { + return + } + + f.WriteDescription(field.Description) + + f.WriteWord(field.Name).NoPadding() + f.FormatArgumentDefinitionList(field.Arguments) + f.NoPadding().WriteString(":").NeedPadding() + f.FormatType(field.Type) + + if field.DefaultValue != nil { + f.WriteWord("=") + f.FormatValue(field.DefaultValue) + } + + f.FormatDirectiveList(field.Directives) + + f.WriteNewline() +} + +func (f *formatter) FormatArgumentDefinitionList(lists ast.ArgumentDefinitionList) { + if len(lists) == 0 { + return + } + + f.WriteString("(") + for idx, arg := range lists { + f.FormatArgumentDefinition(arg) + + if idx != len(lists)-1 { + f.NoPadding().WriteWord(",") + } + } + f.NoPadding().WriteString(")").NeedPadding() +} + +func (f *formatter) FormatArgumentDefinition(def *ast.ArgumentDefinition) { + f.WriteDescription(def.Description) + + f.WriteWord(def.Name).NoPadding().WriteString(":").NeedPadding() + f.FormatType(def.Type) + + if def.DefaultValue != nil { + f.WriteWord("=") + f.FormatValue(def.DefaultValue) + } +} + +func (f *formatter) FormatDirectiveLocation(location ast.DirectiveLocation) { + f.WriteWord(string(location)) +} + +func (f *formatter) FormatDirectiveDefinitionList(lists ast.DirectiveDefinitionList) { + if len(lists) == 0 { + return + } + + for _, dec := range lists { + f.FormatDirectiveDefinition(dec) + } +} + +func (f *formatter) FormatDirectiveDefinition(def *ast.DirectiveDefinition) { + if !f.emitBuiltin { + if def.Position.Src.BuiltIn { + return + } + } + + f.WriteDescription(def.Description) + f.WriteWord("directive").WriteString("@").WriteWord(def.Name) + + if len(def.Arguments) != 0 { + f.NoPadding() + f.FormatArgumentDefinitionList(def.Arguments) + } + + if len(def.Locations) != 0 { + f.WriteWord("on") + + for idx, dirLoc := range def.Locations { + f.FormatDirectiveLocation(dirLoc) + + if idx != len(def.Locations)-1 { + f.WriteWord("|") + } + } + } + + f.WriteNewline() +} + +func (f *formatter) FormatDefinitionList(lists ast.DefinitionList, extend bool) { + if len(lists) == 0 { + return + } + + for _, dec := range lists { + f.FormatDefinition(dec, extend) + } +} + +func (f *formatter) FormatDefinition(def *ast.Definition, extend bool) { + if !f.emitBuiltin && def.BuiltIn { + return + } + + f.WriteDescription(def.Description) + + if extend { + f.WriteWord("extend") + } + + switch def.Kind { + case ast.Scalar: + f.WriteWord("scalar").WriteWord(def.Name) + + case ast.Object: + f.WriteWord("type").WriteWord(def.Name) + + case ast.Interface: + f.WriteWord("interface").WriteWord(def.Name) + + case ast.Union: + f.WriteWord("union").WriteWord(def.Name) + + case ast.Enum: + f.WriteWord("enum").WriteWord(def.Name) + + case ast.InputObject: + f.WriteWord("input").WriteWord(def.Name) + } + + if len(def.Interfaces) != 0 { + f.WriteWord("implements").WriteWord(strings.Join(def.Interfaces, " & ")) + } + + f.FormatDirectiveList(def.Directives) + + if len(def.Types) != 0 { + f.WriteWord("=").WriteWord(strings.Join(def.Types, " | ")) + } + + f.FormatFieldList(def.Fields) + + f.FormatEnumValueList(def.EnumValues) + + f.WriteNewline() +} + +func (f *formatter) FormatEnumValueList(lists ast.EnumValueList) { + if len(lists) == 0 { + return + } + + f.WriteString("{").WriteNewline() + f.IncrementIndent() + + for _, v := range lists { + f.FormatEnumValueDefinition(v) + } + + f.DecrementIndent() + f.WriteString("}") +} + +func (f *formatter) FormatEnumValueDefinition(def *ast.EnumValueDefinition) { + f.WriteDescription(def.Description) + + f.WriteWord(def.Name) + f.FormatDirectiveList(def.Directives) + + f.WriteNewline() +} + +func (f *formatter) FormatOperationList(lists ast.OperationList) { + for _, def := range lists { + f.FormatOperationDefinition(def) + } +} + +func (f *formatter) FormatOperationDefinition(def *ast.OperationDefinition) { + f.WriteWord(string(def.Operation)) + if def.Name != "" { + f.WriteWord(def.Name) + } + f.FormatVariableDefinitionList(def.VariableDefinitions) + f.FormatDirectiveList(def.Directives) + + if len(def.SelectionSet) != 0 { + f.FormatSelectionSet(def.SelectionSet) + f.WriteNewline() + } +} + +func (f *formatter) FormatDirectiveList(lists ast.DirectiveList) { + if len(lists) == 0 { + return + } + + for _, dir := range lists { + f.FormatDirective(dir) + } +} + +func (f *formatter) FormatDirective(dir *ast.Directive) { + f.WriteString("@").WriteWord(dir.Name) + f.FormatArgumentList(dir.Arguments) +} + +func (f *formatter) FormatArgumentList(lists ast.ArgumentList) { + if len(lists) == 0 { + return + } + f.NoPadding().WriteString("(") + for idx, arg := range lists { + f.FormatArgument(arg) + + if idx != len(lists)-1 { + f.NoPadding().WriteWord(",") + } + } + f.WriteString(")").NeedPadding() +} + +func (f *formatter) FormatArgument(arg *ast.Argument) { + f.WriteWord(arg.Name).NoPadding().WriteString(":").NeedPadding() + f.WriteString(arg.Value.String()) +} + +func (f *formatter) FormatFragmentDefinitionList(lists ast.FragmentDefinitionList) { + for _, def := range lists { + f.FormatFragmentDefinition(def) + } +} + +func (f *formatter) FormatFragmentDefinition(def *ast.FragmentDefinition) { + f.WriteWord("fragment").WriteWord(def.Name) + f.FormatVariableDefinitionList(def.VariableDefinition) + f.WriteWord("on").WriteWord(def.TypeCondition) + f.FormatDirectiveList(def.Directives) + + if len(def.SelectionSet) != 0 { + f.FormatSelectionSet(def.SelectionSet) + f.WriteNewline() + } +} + +func (f *formatter) FormatVariableDefinitionList(lists ast.VariableDefinitionList) { + if len(lists) == 0 { + return + } + + f.WriteString("(") + for idx, def := range lists { + f.FormatVariableDefinition(def) + + if idx != len(lists)-1 { + f.NoPadding().WriteWord(",") + } + } + f.NoPadding().WriteString(")").NeedPadding() +} + +func (f *formatter) FormatVariableDefinition(def *ast.VariableDefinition) { + f.WriteString("$").WriteWord(def.Variable).NoPadding().WriteString(":").NeedPadding() + f.FormatType(def.Type) + + if def.DefaultValue != nil { + f.WriteWord("=") + f.FormatValue(def.DefaultValue) + } + + // TODO https://github.com/vektah/gqlparser/v2/issues/102 + // VariableDefinition : Variable : Type DefaultValue? Directives[Const]? +} + +func (f *formatter) FormatSelectionSet(sets ast.SelectionSet) { + if len(sets) == 0 { + return + } + + f.WriteString("{").WriteNewline() + f.IncrementIndent() + + for _, sel := range sets { + f.FormatSelection(sel) + } + + f.DecrementIndent() + f.WriteString("}") +} + +func (f *formatter) FormatSelection(selection ast.Selection) { + switch v := selection.(type) { + case *ast.Field: + f.FormatField(v) + + case *ast.FragmentSpread: + f.FormatFragmentSpread(v) + + case *ast.InlineFragment: + f.FormatInlineFragment(v) + + default: + panic(fmt.Errorf("unknown Selection type: %T", selection)) + } + + f.WriteNewline() +} + +func (f *formatter) FormatField(field *ast.Field) { + if field.Alias != "" && field.Alias != field.Name { + f.WriteWord(field.Alias).NoPadding().WriteString(":").NeedPadding() + } + f.WriteWord(field.Name) + + if len(field.Arguments) != 0 { + f.NoPadding() + f.FormatArgumentList(field.Arguments) + f.NeedPadding() + } + + f.FormatDirectiveList(field.Directives) + + f.FormatSelectionSet(field.SelectionSet) +} + +func (f *formatter) FormatFragmentSpread(spread *ast.FragmentSpread) { + f.WriteWord("...").WriteWord(spread.Name) + + f.FormatDirectiveList(spread.Directives) +} + +func (f *formatter) FormatInlineFragment(inline *ast.InlineFragment) { + f.WriteWord("...") + if inline.TypeCondition != "" { + f.WriteWord("on").WriteWord(inline.TypeCondition) + } + + f.FormatDirectiveList(inline.Directives) + + f.FormatSelectionSet(inline.SelectionSet) +} + +func (f *formatter) FormatType(t *ast.Type) { + f.WriteWord(t.String()) +} + +func (f *formatter) FormatValue(value *ast.Value) { + f.WriteString(value.String()) +} diff --git a/vendor/github.com/vektah/gqlparser/go.mod b/vendor/github.com/vektah/gqlparser/v2/go.mod similarity index 52% rename from vendor/github.com/vektah/gqlparser/go.mod rename to vendor/github.com/vektah/gqlparser/v2/go.mod index dff172f6f..afdb99d8c 100644 --- a/vendor/github.com/vektah/gqlparser/go.mod +++ b/vendor/github.com/vektah/gqlparser/v2/go.mod @@ -1,10 +1,12 @@ -module github.com/vektah/gqlparser +module github.com/vektah/gqlparser/v2 + +go 1.12 require ( github.com/agnivade/levenshtein v1.0.1 github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 - github.com/sergi/go-diff v1.0.0 // indirect - github.com/stretchr/testify v1.3.0 + github.com/sergi/go-diff v1.1.0 // indirect + github.com/stretchr/testify v1.4.0 golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6 - gopkg.in/yaml.v2 v2.2.2 + gopkg.in/yaml.v2 v2.2.4 ) diff --git a/vendor/github.com/vektah/gqlparser/go.sum b/vendor/github.com/vektah/gqlparser/v2/go.sum similarity index 52% rename from vendor/github.com/vektah/gqlparser/go.sum rename to vendor/github.com/vektah/gqlparser/v2/go.sum index 794906fa2..38f4ee3d6 100644 --- a/vendor/github.com/vektah/gqlparser/go.sum +++ b/vendor/github.com/vektah/gqlparser/v2/go.sum @@ -4,16 +4,27 @@ github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNg github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= -github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6 h1:iZgcI2DDp6zW5v9Z/5+f0NuqoxNdmzg4hivjk2WLXpY= golang.org/x/tools v0.0.0-20190125232054-d66bd3c5d5a6/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/vendor/github.com/vektah/gqlparser/gqlerror/error.go b/vendor/github.com/vektah/gqlparser/v2/gqlerror/error.go similarity index 81% rename from vendor/github.com/vektah/gqlparser/gqlerror/error.go rename to vendor/github.com/vektah/gqlparser/v2/gqlerror/error.go index c4c0847ad..390a7ab3f 100644 --- a/vendor/github.com/vektah/gqlparser/gqlerror/error.go +++ b/vendor/github.com/vektah/gqlparser/v2/gqlerror/error.go @@ -5,13 +5,13 @@ import ( "fmt" "strconv" - "github.com/vektah/gqlparser/ast" + "github.com/vektah/gqlparser/v2/ast" ) // Error is the standard graphql error type described in https://facebook.github.io/graphql/draft/#sec-Errors type Error struct { Message string `json:"message"` - Path []interface{} `json:"path,omitempty"` + Path ast.Path `json:"path,omitempty"` Locations []Location `json:"locations,omitempty"` Extensions map[string]interface{} `json:"extensions,omitempty"` Rule string `json:"-"` @@ -63,20 +63,7 @@ func (err *Error) Error() string { } func (err Error) pathString() string { - var str bytes.Buffer - for i, v := range err.Path { - - switch v := v.(type) { - case int, int64: - str.WriteString(fmt.Sprintf("[%d]", v)) - default: - if i != 0 { - str.WriteByte('.') - } - str.WriteString(fmt.Sprint(v)) - } - } - return str.String() + return err.Path.String() } func (errs List) Error() string { @@ -88,7 +75,7 @@ func (errs List) Error() string { return buf.String() } -func WrapPath(path []interface{}, err error) *Error { +func WrapPath(path ast.Path, err error) *Error { return &Error{ Message: err.Error(), Path: path, @@ -101,7 +88,7 @@ func Errorf(message string, args ...interface{}) *Error { } } -func ErrorPathf(path []interface{}, message string, args ...interface{}) *Error { +func ErrorPathf(path ast.Path, message string, args ...interface{}) *Error { return &Error{ Message: fmt.Sprintf(message, args...), Path: path, diff --git a/vendor/github.com/vektah/gqlparser/gqlparser.go b/vendor/github.com/vektah/gqlparser/v2/gqlparser.go similarity index 79% rename from vendor/github.com/vektah/gqlparser/gqlparser.go rename to vendor/github.com/vektah/gqlparser/v2/gqlparser.go index 71e464072..ace63e14a 100644 --- a/vendor/github.com/vektah/gqlparser/gqlparser.go +++ b/vendor/github.com/vektah/gqlparser/v2/gqlparser.go @@ -1,11 +1,11 @@ package gqlparser import ( - "github.com/vektah/gqlparser/ast" - "github.com/vektah/gqlparser/gqlerror" - "github.com/vektah/gqlparser/parser" - "github.com/vektah/gqlparser/validator" - _ "github.com/vektah/gqlparser/validator/rules" + "github.com/vektah/gqlparser/v2/ast" + "github.com/vektah/gqlparser/v2/gqlerror" + "github.com/vektah/gqlparser/v2/parser" + "github.com/vektah/gqlparser/v2/validator" + _ "github.com/vektah/gqlparser/v2/validator/rules" ) func LoadSchema(str ...*ast.Source) (*ast.Schema, *gqlerror.Error) { diff --git a/vendor/github.com/vektah/gqlparser/lexer/blockstring.go b/vendor/github.com/vektah/gqlparser/v2/lexer/blockstring.go similarity index 100% rename from vendor/github.com/vektah/gqlparser/lexer/blockstring.go rename to vendor/github.com/vektah/gqlparser/v2/lexer/blockstring.go diff --git a/vendor/github.com/vektah/gqlparser/lexer/lexer.go b/vendor/github.com/vektah/gqlparser/v2/lexer/lexer.go similarity index 99% rename from vendor/github.com/vektah/gqlparser/lexer/lexer.go rename to vendor/github.com/vektah/gqlparser/v2/lexer/lexer.go index 89687857b..720dd5b48 100644 --- a/vendor/github.com/vektah/gqlparser/lexer/lexer.go +++ b/vendor/github.com/vektah/gqlparser/v2/lexer/lexer.go @@ -4,8 +4,8 @@ import ( "bytes" "unicode/utf8" - "github.com/vektah/gqlparser/ast" - "github.com/vektah/gqlparser/gqlerror" + "github.com/vektah/gqlparser/v2/ast" + "github.com/vektah/gqlparser/v2/gqlerror" ) // Lexer turns graphql request and schema strings into tokens diff --git a/vendor/github.com/vektah/gqlparser/lexer/lexer_test.yml b/vendor/github.com/vektah/gqlparser/v2/lexer/lexer_test.yml similarity index 100% rename from vendor/github.com/vektah/gqlparser/lexer/lexer_test.yml rename to vendor/github.com/vektah/gqlparser/v2/lexer/lexer_test.yml diff --git a/vendor/github.com/vektah/gqlparser/lexer/token.go b/vendor/github.com/vektah/gqlparser/v2/lexer/token.go similarity index 98% rename from vendor/github.com/vektah/gqlparser/lexer/token.go rename to vendor/github.com/vektah/gqlparser/v2/lexer/token.go index aef8b7298..8985a7efb 100644 --- a/vendor/github.com/vektah/gqlparser/lexer/token.go +++ b/vendor/github.com/vektah/gqlparser/v2/lexer/token.go @@ -3,7 +3,7 @@ package lexer import ( "strconv" - "github.com/vektah/gqlparser/ast" + "github.com/vektah/gqlparser/v2/ast" ) const ( diff --git a/vendor/github.com/vektah/gqlparser/parser/parser.go b/vendor/github.com/vektah/gqlparser/v2/parser/parser.go similarity index 94% rename from vendor/github.com/vektah/gqlparser/parser/parser.go rename to vendor/github.com/vektah/gqlparser/v2/parser/parser.go index 96e984022..52b8e6840 100644 --- a/vendor/github.com/vektah/gqlparser/parser/parser.go +++ b/vendor/github.com/vektah/gqlparser/v2/parser/parser.go @@ -3,9 +3,9 @@ package parser import ( "strconv" - "github.com/vektah/gqlparser/ast" - "github.com/vektah/gqlparser/gqlerror" - "github.com/vektah/gqlparser/lexer" + "github.com/vektah/gqlparser/v2/ast" + "github.com/vektah/gqlparser/v2/gqlerror" + "github.com/vektah/gqlparser/v2/lexer" ) type parser struct { diff --git a/vendor/github.com/vektah/gqlparser/parser/query.go b/vendor/github.com/vektah/gqlparser/v2/parser/query.go similarity index 98% rename from vendor/github.com/vektah/gqlparser/parser/query.go rename to vendor/github.com/vektah/gqlparser/v2/parser/query.go index 89e1e2e33..87deeeeb3 100644 --- a/vendor/github.com/vektah/gqlparser/parser/query.go +++ b/vendor/github.com/vektah/gqlparser/v2/parser/query.go @@ -1,10 +1,10 @@ package parser import ( - "github.com/vektah/gqlparser/gqlerror" - "github.com/vektah/gqlparser/lexer" + "github.com/vektah/gqlparser/v2/gqlerror" + "github.com/vektah/gqlparser/v2/lexer" - . "github.com/vektah/gqlparser/ast" + . "github.com/vektah/gqlparser/v2/ast" ) func ParseQuery(source *Source) (*QueryDocument, *gqlerror.Error) { diff --git a/vendor/github.com/vektah/gqlparser/parser/query_test.yml b/vendor/github.com/vektah/gqlparser/v2/parser/query_test.yml similarity index 100% rename from vendor/github.com/vektah/gqlparser/parser/query_test.yml rename to vendor/github.com/vektah/gqlparser/v2/parser/query_test.yml diff --git a/vendor/github.com/vektah/gqlparser/parser/schema.go b/vendor/github.com/vektah/gqlparser/v2/parser/schema.go similarity index 99% rename from vendor/github.com/vektah/gqlparser/parser/schema.go rename to vendor/github.com/vektah/gqlparser/v2/parser/schema.go index 5689e4334..7cfdd147e 100644 --- a/vendor/github.com/vektah/gqlparser/parser/schema.go +++ b/vendor/github.com/vektah/gqlparser/v2/parser/schema.go @@ -1,9 +1,9 @@ package parser import ( - . "github.com/vektah/gqlparser/ast" - "github.com/vektah/gqlparser/gqlerror" - "github.com/vektah/gqlparser/lexer" + . "github.com/vektah/gqlparser/v2/ast" + "github.com/vektah/gqlparser/v2/gqlerror" + "github.com/vektah/gqlparser/v2/lexer" ) func ParseSchema(source *Source) (*SchemaDocument, *gqlerror.Error) { diff --git a/vendor/github.com/vektah/gqlparser/parser/schema_test.yml b/vendor/github.com/vektah/gqlparser/v2/parser/schema_test.yml similarity index 100% rename from vendor/github.com/vektah/gqlparser/parser/schema_test.yml rename to vendor/github.com/vektah/gqlparser/v2/parser/schema_test.yml diff --git a/vendor/github.com/vektah/gqlparser/readme.md b/vendor/github.com/vektah/gqlparser/v2/readme.md similarity index 85% rename from vendor/github.com/vektah/gqlparser/readme.md rename to vendor/github.com/vektah/gqlparser/v2/readme.md index 8d1362bbb..a7cb346b6 100644 --- a/vendor/github.com/vektah/gqlparser/readme.md +++ b/vendor/github.com/vektah/gqlparser/v2/readme.md @@ -1,4 +1,4 @@ -gqlparser [![CircleCI](https://badgen.net/circleci/github/vektah/gqlparser/master)](https://circleci.com/gh/vektah/gqlparser) [![Go Report Card](https://goreportcard.com/badge/github.com/vektah/gqlparser)](https://goreportcard.com/report/github.com/vektah/gqlparser) [![Coverage Status](https://badgen.net/coveralls/c/github/vektah/gqlparser)](https://coveralls.io/github/vektah/gqlparser?branch=master) +gqlparser [![CircleCI](https://badgen.net/circleci/github/vektah/gqlparser/master)](https://circleci.com/gh/vektah/gqlparser) [![Go Report Card](https://goreportcard.com/badge/github.com/vektah/gqlparser/v2)](https://goreportcard.com/report/github.com/vektah/gqlparser/v2) [![Coverage Status](https://badgen.net/coveralls/c/github/vektah/gqlparser)](https://coveralls.io/github/vektah/gqlparser?branch=master) === This is a parser for graphql, written to mirror the graphql-js reference implementation as closely while remaining idiomatic and easy to use. diff --git a/vendor/github.com/vektah/gqlparser/validator/error.go b/vendor/github.com/vektah/gqlparser/v2/validator/error.go similarity index 94% rename from vendor/github.com/vektah/gqlparser/validator/error.go rename to vendor/github.com/vektah/gqlparser/v2/validator/error.go index f354dee59..f8f76055a 100644 --- a/vendor/github.com/vektah/gqlparser/validator/error.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/error.go @@ -3,8 +3,8 @@ package validator import ( "fmt" - "github.com/vektah/gqlparser/ast" - "github.com/vektah/gqlparser/gqlerror" + "github.com/vektah/gqlparser/v2/ast" + "github.com/vektah/gqlparser/v2/gqlerror" ) type ErrorOption func(err *gqlerror.Error) diff --git a/vendor/github.com/vektah/gqlparser/validator/messaging.go b/vendor/github.com/vektah/gqlparser/v2/validator/messaging.go similarity index 100% rename from vendor/github.com/vektah/gqlparser/validator/messaging.go rename to vendor/github.com/vektah/gqlparser/v2/validator/messaging.go diff --git a/vendor/github.com/vektah/gqlparser/validator/prelude.go b/vendor/github.com/vektah/gqlparser/v2/validator/prelude.go similarity index 98% rename from vendor/github.com/vektah/gqlparser/validator/prelude.go rename to vendor/github.com/vektah/gqlparser/v2/validator/prelude.go index 00ae78f74..0c7e9b2d5 100644 --- a/vendor/github.com/vektah/gqlparser/validator/prelude.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/prelude.go @@ -1,6 +1,6 @@ package validator -import "github.com/vektah/gqlparser/ast" +import "github.com/vektah/gqlparser/v2/ast" var Prelude = &ast.Source{ Name: "prelude.graphql", diff --git a/vendor/github.com/vektah/gqlparser/validator/prelude.graphql b/vendor/github.com/vektah/gqlparser/v2/validator/prelude.graphql similarity index 100% rename from vendor/github.com/vektah/gqlparser/validator/prelude.graphql rename to vendor/github.com/vektah/gqlparser/v2/validator/prelude.graphql diff --git a/vendor/github.com/vektah/gqlparser/validator/rules/fields_on_correct_type.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/fields_on_correct_type.go similarity index 97% rename from vendor/github.com/vektah/gqlparser/validator/rules/fields_on_correct_type.go rename to vendor/github.com/vektah/gqlparser/v2/validator/rules/fields_on_correct_type.go index 69148d526..8bfb8cc3d 100644 --- a/vendor/github.com/vektah/gqlparser/validator/rules/fields_on_correct_type.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/fields_on_correct_type.go @@ -4,8 +4,8 @@ import ( "fmt" "sort" - "github.com/vektah/gqlparser/ast" - . "github.com/vektah/gqlparser/validator" + "github.com/vektah/gqlparser/v2/ast" + . "github.com/vektah/gqlparser/v2/validator" ) func init() { diff --git a/vendor/github.com/vektah/gqlparser/validator/rules/fragments_on_composite_types.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/fragments_on_composite_types.go similarity index 92% rename from vendor/github.com/vektah/gqlparser/validator/rules/fragments_on_composite_types.go rename to vendor/github.com/vektah/gqlparser/v2/validator/rules/fragments_on_composite_types.go index a4a482469..5215f697e 100644 --- a/vendor/github.com/vektah/gqlparser/validator/rules/fragments_on_composite_types.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/fragments_on_composite_types.go @@ -3,8 +3,8 @@ package validator import ( "fmt" - "github.com/vektah/gqlparser/ast" - . "github.com/vektah/gqlparser/validator" + "github.com/vektah/gqlparser/v2/ast" + . "github.com/vektah/gqlparser/v2/validator" ) func init() { diff --git a/vendor/github.com/vektah/gqlparser/validator/rules/known_argument_names.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/known_argument_names.go similarity index 94% rename from vendor/github.com/vektah/gqlparser/validator/rules/known_argument_names.go rename to vendor/github.com/vektah/gqlparser/v2/validator/rules/known_argument_names.go index 1a46431d8..9a53df172 100644 --- a/vendor/github.com/vektah/gqlparser/validator/rules/known_argument_names.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/known_argument_names.go @@ -1,8 +1,8 @@ package validator import ( - "github.com/vektah/gqlparser/ast" - . "github.com/vektah/gqlparser/validator" + "github.com/vektah/gqlparser/v2/ast" + . "github.com/vektah/gqlparser/v2/validator" ) func init() { diff --git a/vendor/github.com/vektah/gqlparser/validator/rules/known_directives.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/known_directives.go similarity index 87% rename from vendor/github.com/vektah/gqlparser/validator/rules/known_directives.go rename to vendor/github.com/vektah/gqlparser/v2/validator/rules/known_directives.go index dc4353efc..f3c0f80e4 100644 --- a/vendor/github.com/vektah/gqlparser/validator/rules/known_directives.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/known_directives.go @@ -1,8 +1,8 @@ package validator import ( - "github.com/vektah/gqlparser/ast" - . "github.com/vektah/gqlparser/validator" + "github.com/vektah/gqlparser/v2/ast" + . "github.com/vektah/gqlparser/v2/validator" ) func init() { diff --git a/vendor/github.com/vektah/gqlparser/validator/rules/known_fragment_names.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/known_fragment_names.go similarity index 82% rename from vendor/github.com/vektah/gqlparser/validator/rules/known_fragment_names.go rename to vendor/github.com/vektah/gqlparser/v2/validator/rules/known_fragment_names.go index ec91588c3..b7427d0d0 100644 --- a/vendor/github.com/vektah/gqlparser/validator/rules/known_fragment_names.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/known_fragment_names.go @@ -1,8 +1,8 @@ package validator import ( - "github.com/vektah/gqlparser/ast" - . "github.com/vektah/gqlparser/validator" + "github.com/vektah/gqlparser/v2/ast" + . "github.com/vektah/gqlparser/v2/validator" ) func init() { diff --git a/vendor/github.com/vektah/gqlparser/validator/rules/known_type_names.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/known_type_names.go similarity index 94% rename from vendor/github.com/vektah/gqlparser/validator/rules/known_type_names.go rename to vendor/github.com/vektah/gqlparser/v2/validator/rules/known_type_names.go index 223086b38..c228af704 100644 --- a/vendor/github.com/vektah/gqlparser/validator/rules/known_type_names.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/known_type_names.go @@ -1,8 +1,8 @@ package validator import ( - "github.com/vektah/gqlparser/ast" - . "github.com/vektah/gqlparser/validator" + "github.com/vektah/gqlparser/v2/ast" + . "github.com/vektah/gqlparser/v2/validator" ) func init() { diff --git a/vendor/github.com/vektah/gqlparser/validator/rules/lone_anonymous_operation.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/lone_anonymous_operation.go similarity index 83% rename from vendor/github.com/vektah/gqlparser/validator/rules/lone_anonymous_operation.go rename to vendor/github.com/vektah/gqlparser/v2/validator/rules/lone_anonymous_operation.go index dd232142b..d27528542 100644 --- a/vendor/github.com/vektah/gqlparser/validator/rules/lone_anonymous_operation.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/lone_anonymous_operation.go @@ -1,8 +1,8 @@ package validator import ( - "github.com/vektah/gqlparser/ast" - . "github.com/vektah/gqlparser/validator" + "github.com/vektah/gqlparser/v2/ast" + . "github.com/vektah/gqlparser/v2/validator" ) func init() { diff --git a/vendor/github.com/vektah/gqlparser/validator/rules/no_fragment_cycles.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/no_fragment_cycles.go similarity index 96% rename from vendor/github.com/vektah/gqlparser/validator/rules/no_fragment_cycles.go rename to vendor/github.com/vektah/gqlparser/v2/validator/rules/no_fragment_cycles.go index 7511529b7..81fffa578 100644 --- a/vendor/github.com/vektah/gqlparser/validator/rules/no_fragment_cycles.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/no_fragment_cycles.go @@ -4,8 +4,8 @@ import ( "fmt" "strings" - "github.com/vektah/gqlparser/ast" - . "github.com/vektah/gqlparser/validator" + "github.com/vektah/gqlparser/v2/ast" + . "github.com/vektah/gqlparser/v2/validator" ) func init() { diff --git a/vendor/github.com/vektah/gqlparser/validator/rules/no_undefined_variables.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/no_undefined_variables.go similarity index 88% rename from vendor/github.com/vektah/gqlparser/validator/rules/no_undefined_variables.go rename to vendor/github.com/vektah/gqlparser/v2/validator/rules/no_undefined_variables.go index 505206bed..4f05f00a2 100644 --- a/vendor/github.com/vektah/gqlparser/validator/rules/no_undefined_variables.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/no_undefined_variables.go @@ -1,8 +1,8 @@ package validator import ( - "github.com/vektah/gqlparser/ast" - . "github.com/vektah/gqlparser/validator" + "github.com/vektah/gqlparser/v2/ast" + . "github.com/vektah/gqlparser/v2/validator" ) func init() { diff --git a/vendor/github.com/vektah/gqlparser/validator/rules/no_unused_fragments.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/no_unused_fragments.go similarity index 88% rename from vendor/github.com/vektah/gqlparser/validator/rules/no_unused_fragments.go rename to vendor/github.com/vektah/gqlparser/v2/validator/rules/no_unused_fragments.go index 4aa835f5c..dfc896725 100644 --- a/vendor/github.com/vektah/gqlparser/validator/rules/no_unused_fragments.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/no_unused_fragments.go @@ -1,8 +1,8 @@ package validator import ( - "github.com/vektah/gqlparser/ast" - . "github.com/vektah/gqlparser/validator" + "github.com/vektah/gqlparser/v2/ast" + . "github.com/vektah/gqlparser/v2/validator" ) func init() { diff --git a/vendor/github.com/vektah/gqlparser/validator/rules/no_unused_variables.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/no_unused_variables.go similarity index 88% rename from vendor/github.com/vektah/gqlparser/validator/rules/no_unused_variables.go rename to vendor/github.com/vektah/gqlparser/v2/validator/rules/no_unused_variables.go index 28cf77365..df2e5f4b7 100644 --- a/vendor/github.com/vektah/gqlparser/validator/rules/no_unused_variables.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/no_unused_variables.go @@ -1,8 +1,8 @@ package validator import ( - "github.com/vektah/gqlparser/ast" - . "github.com/vektah/gqlparser/validator" + "github.com/vektah/gqlparser/v2/ast" + . "github.com/vektah/gqlparser/v2/validator" ) func init() { diff --git a/vendor/github.com/vektah/gqlparser/validator/rules/overlapping_fields_can_be_merged.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/overlapping_fields_can_be_merged.go similarity index 99% rename from vendor/github.com/vektah/gqlparser/validator/rules/overlapping_fields_can_be_merged.go rename to vendor/github.com/vektah/gqlparser/v2/validator/rules/overlapping_fields_can_be_merged.go index bb2f18319..5cd028df8 100644 --- a/vendor/github.com/vektah/gqlparser/validator/rules/overlapping_fields_can_be_merged.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/overlapping_fields_can_be_merged.go @@ -5,8 +5,8 @@ import ( "fmt" "reflect" - "github.com/vektah/gqlparser/ast" - . "github.com/vektah/gqlparser/validator" + "github.com/vektah/gqlparser/v2/ast" + . "github.com/vektah/gqlparser/v2/validator" ) func init() { diff --git a/vendor/github.com/vektah/gqlparser/validator/rules/possible_fragment_spreads.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/possible_fragment_spreads.go similarity index 95% rename from vendor/github.com/vektah/gqlparser/validator/rules/possible_fragment_spreads.go rename to vendor/github.com/vektah/gqlparser/v2/validator/rules/possible_fragment_spreads.go index 046118348..a3f795c97 100644 --- a/vendor/github.com/vektah/gqlparser/validator/rules/possible_fragment_spreads.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/possible_fragment_spreads.go @@ -1,8 +1,8 @@ package validator import ( - "github.com/vektah/gqlparser/ast" - . "github.com/vektah/gqlparser/validator" + "github.com/vektah/gqlparser/v2/ast" + . "github.com/vektah/gqlparser/v2/validator" ) func init() { diff --git a/vendor/github.com/vektah/gqlparser/validator/rules/provided_required_arguments.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/provided_required_arguments.go similarity index 94% rename from vendor/github.com/vektah/gqlparser/validator/rules/provided_required_arguments.go rename to vendor/github.com/vektah/gqlparser/v2/validator/rules/provided_required_arguments.go index 55791a6bf..4410288a8 100644 --- a/vendor/github.com/vektah/gqlparser/validator/rules/provided_required_arguments.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/provided_required_arguments.go @@ -1,8 +1,8 @@ package validator import ( - "github.com/vektah/gqlparser/ast" - . "github.com/vektah/gqlparser/validator" + "github.com/vektah/gqlparser/v2/ast" + . "github.com/vektah/gqlparser/v2/validator" ) func init() { diff --git a/vendor/github.com/vektah/gqlparser/validator/rules/scalar_leafs.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/scalar_leafs.go similarity index 91% rename from vendor/github.com/vektah/gqlparser/validator/rules/scalar_leafs.go rename to vendor/github.com/vektah/gqlparser/v2/validator/rules/scalar_leafs.go index bb961f448..718bc6834 100644 --- a/vendor/github.com/vektah/gqlparser/validator/rules/scalar_leafs.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/scalar_leafs.go @@ -1,8 +1,8 @@ package validator import ( - "github.com/vektah/gqlparser/ast" - . "github.com/vektah/gqlparser/validator" + "github.com/vektah/gqlparser/v2/ast" + . "github.com/vektah/gqlparser/v2/validator" ) func init() { diff --git a/vendor/github.com/vektah/gqlparser/validator/rules/single_field_subscriptions.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/single_field_subscriptions.go similarity index 87% rename from vendor/github.com/vektah/gqlparser/validator/rules/single_field_subscriptions.go rename to vendor/github.com/vektah/gqlparser/v2/validator/rules/single_field_subscriptions.go index 53003c112..206e3a6e2 100644 --- a/vendor/github.com/vektah/gqlparser/validator/rules/single_field_subscriptions.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/single_field_subscriptions.go @@ -3,8 +3,8 @@ package validator import ( "strconv" - "github.com/vektah/gqlparser/ast" - . "github.com/vektah/gqlparser/validator" + "github.com/vektah/gqlparser/v2/ast" + . "github.com/vektah/gqlparser/v2/validator" ) func init() { diff --git a/vendor/github.com/vektah/gqlparser/validator/rules/unique_argument_names.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/unique_argument_names.go similarity index 89% rename from vendor/github.com/vektah/gqlparser/validator/rules/unique_argument_names.go rename to vendor/github.com/vektah/gqlparser/v2/validator/rules/unique_argument_names.go index 0ddcde724..fc2b639df 100644 --- a/vendor/github.com/vektah/gqlparser/validator/rules/unique_argument_names.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/unique_argument_names.go @@ -1,8 +1,8 @@ package validator import ( - "github.com/vektah/gqlparser/ast" - . "github.com/vektah/gqlparser/validator" + "github.com/vektah/gqlparser/v2/ast" + . "github.com/vektah/gqlparser/v2/validator" ) func init() { diff --git a/vendor/github.com/vektah/gqlparser/validator/rules/unique_directives_per_location.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/unique_directives_per_location.go similarity index 85% rename from vendor/github.com/vektah/gqlparser/validator/rules/unique_directives_per_location.go rename to vendor/github.com/vektah/gqlparser/v2/validator/rules/unique_directives_per_location.go index 077c4687e..2ea4f13d1 100644 --- a/vendor/github.com/vektah/gqlparser/validator/rules/unique_directives_per_location.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/unique_directives_per_location.go @@ -1,8 +1,8 @@ package validator import ( - "github.com/vektah/gqlparser/ast" - . "github.com/vektah/gqlparser/validator" + "github.com/vektah/gqlparser/v2/ast" + . "github.com/vektah/gqlparser/v2/validator" ) func init() { diff --git a/vendor/github.com/vektah/gqlparser/validator/rules/unique_fragment_names.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/unique_fragment_names.go similarity index 84% rename from vendor/github.com/vektah/gqlparser/validator/rules/unique_fragment_names.go rename to vendor/github.com/vektah/gqlparser/v2/validator/rules/unique_fragment_names.go index 46a8b7c7b..8c348aea0 100644 --- a/vendor/github.com/vektah/gqlparser/validator/rules/unique_fragment_names.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/unique_fragment_names.go @@ -1,8 +1,8 @@ package validator import ( - "github.com/vektah/gqlparser/ast" - . "github.com/vektah/gqlparser/validator" + "github.com/vektah/gqlparser/v2/ast" + . "github.com/vektah/gqlparser/v2/validator" ) func init() { diff --git a/vendor/github.com/vektah/gqlparser/validator/rules/unique_input_field_names.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/unique_input_field_names.go similarity index 85% rename from vendor/github.com/vektah/gqlparser/validator/rules/unique_input_field_names.go rename to vendor/github.com/vektah/gqlparser/v2/validator/rules/unique_input_field_names.go index f254d5888..092be671c 100644 --- a/vendor/github.com/vektah/gqlparser/validator/rules/unique_input_field_names.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/unique_input_field_names.go @@ -1,8 +1,8 @@ package validator import ( - "github.com/vektah/gqlparser/ast" - . "github.com/vektah/gqlparser/validator" + "github.com/vektah/gqlparser/v2/ast" + . "github.com/vektah/gqlparser/v2/validator" ) func init() { diff --git a/vendor/github.com/vektah/gqlparser/validator/rules/unique_operation_names.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/unique_operation_names.go similarity index 83% rename from vendor/github.com/vektah/gqlparser/validator/rules/unique_operation_names.go rename to vendor/github.com/vektah/gqlparser/v2/validator/rules/unique_operation_names.go index c1ab56be0..4d41b60ae 100644 --- a/vendor/github.com/vektah/gqlparser/validator/rules/unique_operation_names.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/unique_operation_names.go @@ -1,8 +1,8 @@ package validator import ( - "github.com/vektah/gqlparser/ast" - . "github.com/vektah/gqlparser/validator" + "github.com/vektah/gqlparser/v2/ast" + . "github.com/vektah/gqlparser/v2/validator" ) func init() { diff --git a/vendor/github.com/vektah/gqlparser/validator/rules/unique_variable_names.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/unique_variable_names.go similarity index 85% rename from vendor/github.com/vektah/gqlparser/validator/rules/unique_variable_names.go rename to vendor/github.com/vektah/gqlparser/v2/validator/rules/unique_variable_names.go index 70590a886..64ac5072d 100644 --- a/vendor/github.com/vektah/gqlparser/validator/rules/unique_variable_names.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/unique_variable_names.go @@ -1,8 +1,8 @@ package validator import ( - "github.com/vektah/gqlparser/ast" - . "github.com/vektah/gqlparser/validator" + "github.com/vektah/gqlparser/v2/ast" + . "github.com/vektah/gqlparser/v2/validator" ) func init() { diff --git a/vendor/github.com/vektah/gqlparser/validator/rules/values_of_correct_type.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/values_of_correct_type.go similarity index 97% rename from vendor/github.com/vektah/gqlparser/validator/rules/values_of_correct_type.go rename to vendor/github.com/vektah/gqlparser/v2/validator/rules/values_of_correct_type.go index d64cc666d..40dc2194a 100644 --- a/vendor/github.com/vektah/gqlparser/validator/rules/values_of_correct_type.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/values_of_correct_type.go @@ -3,8 +3,8 @@ package validator import ( "fmt" - "github.com/vektah/gqlparser/ast" - . "github.com/vektah/gqlparser/validator" + "github.com/vektah/gqlparser/v2/ast" + . "github.com/vektah/gqlparser/v2/validator" ) func init() { diff --git a/vendor/github.com/vektah/gqlparser/validator/rules/variables_are_input_types.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/variables_are_input_types.go similarity index 86% rename from vendor/github.com/vektah/gqlparser/validator/rules/variables_are_input_types.go rename to vendor/github.com/vektah/gqlparser/v2/validator/rules/variables_are_input_types.go index 9d58ae1c2..4ea94e5a8 100644 --- a/vendor/github.com/vektah/gqlparser/validator/rules/variables_are_input_types.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/variables_are_input_types.go @@ -1,8 +1,8 @@ package validator import ( - "github.com/vektah/gqlparser/ast" - . "github.com/vektah/gqlparser/validator" + "github.com/vektah/gqlparser/v2/ast" + . "github.com/vektah/gqlparser/v2/validator" ) func init() { diff --git a/vendor/github.com/vektah/gqlparser/validator/rules/variables_in_allowed_position.go b/vendor/github.com/vektah/gqlparser/v2/validator/rules/variables_in_allowed_position.go similarity index 91% rename from vendor/github.com/vektah/gqlparser/validator/rules/variables_in_allowed_position.go rename to vendor/github.com/vektah/gqlparser/v2/validator/rules/variables_in_allowed_position.go index e6d97c9f6..aba6eb621 100644 --- a/vendor/github.com/vektah/gqlparser/validator/rules/variables_in_allowed_position.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/rules/variables_in_allowed_position.go @@ -1,8 +1,8 @@ package validator import ( - "github.com/vektah/gqlparser/ast" - . "github.com/vektah/gqlparser/validator" + "github.com/vektah/gqlparser/v2/ast" + . "github.com/vektah/gqlparser/v2/validator" ) func init() { diff --git a/vendor/github.com/vektah/gqlparser/validator/schema.go b/vendor/github.com/vektah/gqlparser/v2/validator/schema.go similarity index 95% rename from vendor/github.com/vektah/gqlparser/validator/schema.go rename to vendor/github.com/vektah/gqlparser/v2/validator/schema.go index 3242822fa..4c96c31a7 100644 --- a/vendor/github.com/vektah/gqlparser/validator/schema.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/schema.go @@ -6,9 +6,9 @@ import ( "strconv" "strings" - . "github.com/vektah/gqlparser/ast" - "github.com/vektah/gqlparser/gqlerror" - "github.com/vektah/gqlparser/parser" + . "github.com/vektah/gqlparser/v2/ast" + "github.com/vektah/gqlparser/v2/gqlerror" + "github.com/vektah/gqlparser/v2/parser" ) func LoadSchema(inputs ...*Source) (*Schema, *gqlerror.Error) { @@ -34,10 +34,18 @@ func ValidateSchemaDocument(ast *SchemaDocument) (*Schema, *gqlerror.Error) { schema.Types[def.Name] = ast.Definitions[i] } + defs := append(DefinitionList{}, ast.Definitions...) + for _, ext := range ast.Extensions { def := schema.Types[ext.Name] if def == nil { - return nil, gqlerror.ErrorPosf(ext.Position, "Cannot extend type %s because it does not exist.", ext.Name) + schema.Types[ext.Name] = &Definition{ + Kind: ext.Kind, + Name: ext.Name, + Position: ext.Position, + } + def = schema.Types[ext.Name] + defs = append(defs, def) } if def.Kind != ext.Kind { @@ -51,7 +59,7 @@ func ValidateSchemaDocument(ast *SchemaDocument) (*Schema, *gqlerror.Error) { def.EnumValues = append(def.EnumValues, ext.EnumValues...) } - for _, def := range ast.Definitions { + for _, def := range defs { switch def.Kind { case Union: for _, t := range def.Types { @@ -147,9 +155,9 @@ func ValidateSchemaDocument(ast *SchemaDocument) (*Schema, *gqlerror.Error) { }, &FieldDefinition{ Name: "__type", - Type: NonNullNamedType("__Type", nil), + Type: NamedType("__Type", nil), Arguments: ArgumentDefinitionList{ - {Name: "name", Type: NamedType("String", nil)}, + {Name: "name", Type: NonNullNamedType("String", nil)}, }, }, ) diff --git a/vendor/github.com/vektah/gqlparser/validator/schema_test.yml b/vendor/github.com/vektah/gqlparser/v2/validator/schema_test.yml similarity index 94% rename from vendor/github.com/vektah/gqlparser/validator/schema_test.yml rename to vendor/github.com/vektah/gqlparser/v2/validator/schema_test.yml index b1d85c448..a5d8bb491 100644 --- a/vendor/github.com/vektah/gqlparser/validator/schema_test.yml +++ b/vendor/github.com/vektah/gqlparser/v2/validator/schema_test.yml @@ -418,15 +418,43 @@ unions: message: "UNION type \"Baz\" must be OBJECT." locations: [{line: 1, column: 7}] + - name: unions of pure type extensions are valid + input: | + + type Review { + body: String! + author: User! @provides(fields: "username") + product: Product! + } + + extend type User @key(fields: "id") { + id: ID! @external + reviews: [Review] + } + + extend type Product @key(fields: "upc") { + upc: String! @external + reviews: [Review] + } + + union Foo = User | Product + scalar _Any + scalar _FieldSet + directive @external on FIELD_DEFINITION + directive @requires(fields: _FieldSet!) on FIELD_DEFINITION + directive @provides(fields: _FieldSet!) on FIELD_DEFINITION + directive @key(fields: _FieldSet!) on OBJECT | INTERFACE + directive @extends on OBJECT + + + type extensions: - - name: cannot extend non existant types + - name: can extend non existant types input: | extend type A { name: String } - error: - message: "Cannot extend type A because it does not exist." - locations: [{line: 1, column: 13}] + - name: cannot extend incorret type existant types input: | diff --git a/vendor/github.com/vektah/gqlparser/validator/suggestionList.go b/vendor/github.com/vektah/gqlparser/v2/validator/suggestionList.go similarity index 100% rename from vendor/github.com/vektah/gqlparser/validator/suggestionList.go rename to vendor/github.com/vektah/gqlparser/v2/validator/suggestionList.go diff --git a/vendor/github.com/vektah/gqlparser/validator/validator.go b/vendor/github.com/vektah/gqlparser/v2/validator/validator.go similarity index 90% rename from vendor/github.com/vektah/gqlparser/validator/validator.go rename to vendor/github.com/vektah/gqlparser/v2/validator/validator.go index bbacec6f2..34bf93db3 100644 --- a/vendor/github.com/vektah/gqlparser/validator/validator.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/validator.go @@ -1,8 +1,8 @@ package validator import ( - . "github.com/vektah/gqlparser/ast" - "github.com/vektah/gqlparser/gqlerror" + . "github.com/vektah/gqlparser/v2/ast" + "github.com/vektah/gqlparser/v2/gqlerror" ) type AddErrFunc func(options ...ErrorOption) diff --git a/vendor/github.com/vektah/gqlparser/validator/vars.go b/vendor/github.com/vektah/gqlparser/v2/validator/vars.go similarity index 81% rename from vendor/github.com/vektah/gqlparser/validator/vars.go rename to vendor/github.com/vektah/gqlparser/v2/validator/vars.go index aaf3a0d1a..9151fa8e9 100644 --- a/vendor/github.com/vektah/gqlparser/validator/vars.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/vars.go @@ -2,11 +2,12 @@ package validator import ( "reflect" + "strings" "fmt" - "github.com/vektah/gqlparser/ast" - "github.com/vektah/gqlparser/gqlerror" + "github.com/vektah/gqlparser/v2/ast" + "github.com/vektah/gqlparser/v2/gqlerror" ) var UnexpectedType = fmt.Errorf("Unexpected Type") @@ -16,12 +17,12 @@ func VariableValues(schema *ast.Schema, op *ast.OperationDefinition, variables m coercedVars := map[string]interface{}{} validator := varValidator{ - path: []interface{}{"variable"}, + path: ast.Path{ast.PathName("variable")}, schema: schema, } for _, v := range op.VariableDefinitions { - validator.path = append(validator.path, v.Variable) + validator.path = append(validator.path, ast.PathName(v.Variable)) if !v.Definition.IsInputType() { return nil, gqlerror.ErrorPathf(validator.path, "must an input type") @@ -68,7 +69,7 @@ func VariableValues(schema *ast.Schema, op *ast.OperationDefinition, variables m } type varValidator struct { - path []interface{} + path ast.Path schema *ast.Schema } @@ -86,7 +87,7 @@ func (v *varValidator) validateVarType(typ *ast.Type, val reflect.Value) *gqlerr for i := 0; i < val.Len(); i++ { resetPath() - v.path = append(v.path, i) + v.path = append(v.path, ast.PathIndex(i)) field := val.Index(i) if field.Kind() == reflect.Ptr || field.Kind() == reflect.Interface { @@ -109,13 +110,27 @@ func (v *varValidator) validateVarType(typ *ast.Type, val reflect.Value) *gqlerr panic(fmt.Errorf("missing def for %s", typ.NamedType)) } + if !typ.NonNull && !val.IsValid() { + // If the type is not null and we got a invalid value namely null/nil, then it's valid + return nil + } + switch def.Kind { case ast.Enum: kind := val.Type().Kind() - if kind == reflect.Int || kind == reflect.Int32 || kind == reflect.Int64 || kind == reflect.String { - return nil + if kind != reflect.Int && kind != reflect.Int32 && kind != reflect.Int64 && kind != reflect.String { + return gqlerror.ErrorPathf(v.path, "enums must be ints or strings") } - return gqlerror.ErrorPathf(v.path, "enums must be ints or strings") + isValidEnum := false + for _, enumVal := range def.EnumValues { + if strings.EqualFold(val.String(), enumVal.Name) { + isValidEnum = true + } + } + if !isValidEnum { + return gqlerror.ErrorPathf(v.path, "%s is not a valid %s", val.String(), def.Name) + } + return nil case ast.Scalar: kind := val.Type().Kind() switch typ.NamedType { @@ -156,7 +171,7 @@ func (v *varValidator) validateVarType(typ *ast.Type, val reflect.Value) *gqlerr val.MapIndex(name) fieldDef := def.Fields.ForName(name.String()) resetPath() - v.path = append(v.path, name.String()) + v.path = append(v.path, ast.PathName(name.String())) if fieldDef == nil { return gqlerror.ErrorPathf(v.path, "unknown field") @@ -165,7 +180,7 @@ func (v *varValidator) validateVarType(typ *ast.Type, val reflect.Value) *gqlerr for _, fieldDef := range def.Fields { resetPath() - v.path = append(v.path, fieldDef.Name) + v.path = append(v.path, ast.PathName(fieldDef.Name)) field := val.MapIndex(reflect.ValueOf(fieldDef.Name)) if !field.IsValid() { diff --git a/vendor/github.com/vektah/gqlparser/validator/walk.go b/vendor/github.com/vektah/gqlparser/v2/validator/walk.go similarity index 99% rename from vendor/github.com/vektah/gqlparser/validator/walk.go rename to vendor/github.com/vektah/gqlparser/v2/validator/walk.go index 751ba1f11..8f8abf108 100644 --- a/vendor/github.com/vektah/gqlparser/validator/walk.go +++ b/vendor/github.com/vektah/gqlparser/v2/validator/walk.go @@ -4,7 +4,7 @@ import ( "context" "fmt" - "github.com/vektah/gqlparser/ast" + "github.com/vektah/gqlparser/v2/ast" ) type Events struct { diff --git a/vendor/github.com/vektra/mockery/v2/.gitignore b/vendor/github.com/vektra/mockery/v2/.gitignore new file mode 100644 index 000000000..69e75fdbc --- /dev/null +++ b/vendor/github.com/vektra/mockery/v2/.gitignore @@ -0,0 +1,3 @@ +mockery.prof +dist +.idea diff --git a/vendor/github.com/vektra/mockery/v2/.goreleaser.yml b/vendor/github.com/vektra/mockery/v2/.goreleaser.yml new file mode 100644 index 000000000..6ae0728ff --- /dev/null +++ b/vendor/github.com/vektra/mockery/v2/.goreleaser.yml @@ -0,0 +1,59 @@ +--- +project_name: mockery +before: + hooks: + - go mod download +builds: + - main: ./main.go + ldflags: + - -s -w -X github.com/vektra/mockery/v2/pkg/config.SemVer={{.Version}} + env: + - CGO_ENABLED=0 + goos: + - darwin + - linux + - windows + goarch: + - amd64 +archives: + - replacements: + darwin: Darwin + linux: Linux + windows: Windows + 386: i386 + amd64: x86_64 + files: + - README.md + - LICENSE +checksum: + name_template: "checksum.txt" +snapshot: + name_template: "{{ .Tag }}-next" +changelog: + sort: asc +dockers: + - goos: linux + goarch: amd64 + binaries: + - mockery + image_templates: + - 'vektra/mockery:{{ .Tag }}' + - 'vektra/mockery:v{{ .Major }}' + - 'vektra/mockery:v{{ .Major }}.{{ .Minor }}' + - 'vektra/mockery:latest' + build_flag_templates: + - "--pull" + - "--label=org.opencontainers.image.created={{.Date}}" + - "--label=org.opencontainers.image.name={{.ProjectName}}" + - "--label=org.opencontainers.image.revision={{.FullCommit}}" + - "--label=org.opencontainers.image.version={{.Version}}" + - "--label=org.opencontainers.image.source={{.GitURL}}" +brews: + - homepage: https://github.com/vektra/mockery + description: "A mock code autogenerator for Go" + github: + owner: vektra + name: homebrew-tap + folder: Formula + test: | + system "#{bin}mockery --version" diff --git a/vendor/github.com/vektra/mockery/v2/.mockery.yaml b/vendor/github.com/vektra/mockery/v2/.mockery.yaml new file mode 100644 index 000000000..31e893de8 --- /dev/null +++ b/vendor/github.com/vektra/mockery/v2/.mockery.yaml @@ -0,0 +1,3 @@ +quiet: False +all: True +keeptree: True diff --git a/vendor/github.com/vektra/mockery/v2/.travis.yml b/vendor/github.com/vektra/mockery/v2/.travis.yml new file mode 100644 index 000000000..c07f9a728 --- /dev/null +++ b/vendor/github.com/vektra/mockery/v2/.travis.yml @@ -0,0 +1,33 @@ +language: go + +os: + - linux + - osx + +env: + - GO111MODULE=on CGO_ENABLED=0 + +go: + - 1.14.x + - tip + +git: + depth: 1 + +script: + - go test -v -coverprofile=coverage.txt ./... + +services: + - docker + +after_success: + - test -n "$TRAVIS_TAG" && docker login -u="$DOCKER_USERNAME" -p="$DOCKER_PASSWORD" + - bash <(curl -s https://codecov.io/bash) + +deploy: + - provider: script + script: curl -sL https://git.io/goreleaser | bash + on: + tags: true + condition: $TRAVIS_OS_NAME = linux + go: 1.14.x diff --git a/vendor/github.com/vektra/mockery/v2/Dockerfile b/vendor/github.com/vektra/mockery/v2/Dockerfile new file mode 100644 index 000000000..14dba6067 --- /dev/null +++ b/vendor/github.com/vektra/mockery/v2/Dockerfile @@ -0,0 +1,9 @@ +FROM golang:1.14-alpine as builder + +COPY mockery /usr/local/bin + +# Explicitly set a writable cache path when running --user=$(id -u):$(id -g) +# see: https://github.com/golang/go/issues/26280#issuecomment-445294378 +ENV GOCACHE /tmp/.cache + +ENTRYPOINT ["/usr/local/bin/mockery"] diff --git a/vendor/github.com/vektra/mockery/v2/LICENSE b/vendor/github.com/vektra/mockery/v2/LICENSE new file mode 100644 index 000000000..873b122fe --- /dev/null +++ b/vendor/github.com/vektra/mockery/v2/LICENSE @@ -0,0 +1,28 @@ +Copyright (c) 2014, Opinionated Architecture +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +* Neither the name of the {organization} nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/vendor/github.com/vektra/mockery/v2/Makefile b/vendor/github.com/vektra/mockery/v2/Makefile new file mode 100644 index 000000000..2090b8c51 --- /dev/null +++ b/vendor/github.com/vektra/mockery/v2/Makefile @@ -0,0 +1,24 @@ +SHELL=bash + +all: clean fmt test fixture install docker integration + +clean: + rm -rf mocks + +fmt: + go fmt ./... + +test: + go test ./... + +fixture: + mockery --print --dir pkg/fixtures --name RequesterVariadic > pkg/fixtures/mocks/requester_variadic.go + +install: + go install ./... + +docker: + docker build -t vektra/mockery . + +integration: docker install + ./hack/run-e2e.sh diff --git a/vendor/github.com/vektra/mockery/v2/README.md b/vendor/github.com/vektra/mockery/v2/README.md new file mode 100644 index 000000000..61e878d06 --- /dev/null +++ b/vendor/github.com/vektra/mockery/v2/README.md @@ -0,0 +1,307 @@ + +mockery +======= +[![Linux Build Status](https://travis-ci.org/vektra/mockery.svg?branch=master)](https://travis-ci.org/vektra/mockery) [![go.dev reference](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white&style=flat-square)](https://pkg.go.dev/github.com/vektra/mockery/v2?tab=overview) ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/vektra/mockery) ![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/vektra/mockery) [![Go Report Card](https://goreportcard.com/badge/github.com/vektra/mockery)](https://goreportcard.com/report/github.com/vektra/mockery) [![codecov](https://codecov.io/gh/vektra/mockery/branch/master/graph/badge.svg)](https://codecov.io/gh/vektra/mockery) + + + + +mockery provides the ability to easily generate mocks for golang interfaces using the [stretchr/testify/mock](https://pkg.go.dev/github.com/stretchr/testify/mock?tab=doc) package. It removes +the boilerplate coding required to use mocks. + +Table of Contents +----------------- + +- [Installation](#installation) + * [Github Release](#github-release) + * [Docker](#docker) + * [Homebrew](#homebrew) + * [go get](#go-get) +- [Examples](#examples) + + [Simplest case](#simplest-case) + + [Next level case](#next-level-case) +- [Return Value Provider Functions](#return-value-provider-functions) + + [Requirements](#requirements) + + [Notes](#notes) +- [Extended Flag Descriptions](#extended-flag-descriptions) +- [Mocking interfaces in `main`](#mocking-interfaces-in-main) +- [Configuration](#configuration) + * [Example](#example) +- [Semantic Versioning](#semantic-versioning) +- [Stargazers](#stargazers) + + +Installation +------------ + +### Github Release + +Visit the [releases page](https://github.com/vektra/mockery/releases) to download one of the pre-built binaries for your platform. + +### Docker + +Use the [Docker image](https://hub.docker.com/r/vektra/mockery) + + docker pull vektra/mockery + +### Homebrew + +Install through homebrew + + brew install vektra/tap/mockery + brew upgrade mockery + +### go get + +Alternatively, you can use the DEPRECATED method of: + + go get github.com/vektra/mockery/v2/.../ + +to get a development version of the software. + +Examples +-------- + +![](https://raw.githubusercontent.com/vektra/mockery/master/docs/Peek%202020-06-28%2000-08.gif) + +#### Simplest case + +Given this is in `string.go` + +```go +package test + +type Stringer interface { + String() string +} +``` + +Run: `mockery --name=Stringer` and the following will be output to `mocks/Stringer.go`: + +```go +package mocks + +import "github.com/stretchr/testify/mock" + +type Stringer struct { + mock.Mock +} + +func (m *Stringer) String() string { + ret := m.Called() + + var r0 string + if rf, ok := ret.Get(0).(func() string); ok { + r0 = rf() + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} +``` + +#### Function type case + +Given this is in `send.go` + +```go +package test + +type SendFunc func(data string) (int, error) +``` + +Run: `mockery --name=SendFunc` and the following will be output to `mocks/SendFunc.go`: + +```go +package mocks + +import "github.com/stretchr/testify/mock" + +type SendFunc struct { + mock.Mock +} + +func (_m *SendFunc) Execute(data string) (int, error) { + ret := _m.Called(data) + + var r0 int + if rf, ok := ret.Get(0).(func(string) int); ok { + r0 = rf(data) + } else { + r0 = ret.Get(0).(int) + } + + var r1 error + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(data) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} +``` + +#### Next level case + +See [github.com/jaytaylor/mockery-example](https://github.com/jaytaylor/mockery-example) +for the fully runnable version of the outline below. + +```go +package main + +import ( + "fmt" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/s3" + "github.com/jaytaylor/mockery-example/mocks" + "github.com/stretchr/testify/mock" +) + +func main() { + mockS3 := &mocks.S3API{} + + mockResultFn := func(input *s3.ListObjectsInput) *s3.ListObjectsOutput { + output := &s3.ListObjectsOutput{} + output.SetCommonPrefixes([]*s3.CommonPrefix{ + &s3.CommonPrefix{ + Prefix: aws.String("2017-01-01"), + }, + }) + return output + } + + // NB: .Return(...) must return the same signature as the method being mocked. + // In this case it's (*s3.ListObjectsOutput, error). + mockS3.On("ListObjects", mock.MatchedBy(func(input *s3.ListObjectsInput) bool { + return input.Delimiter != nil && *input.Delimiter == "/" && input.Prefix == nil + })).Return(mockResultFn, nil) + + listingInput := &s3.ListObjectsInput{ + Bucket: aws.String("foo"), + Delimiter: aws.String("/"), + } + listingOutput, err := mockS3.ListObjects(listingInput) + if err != nil { + panic(err) + } + + for _, x := range listingOutput.CommonPrefixes { + fmt.Printf("common prefix: %+v\n", *x) + } +} +``` + + +Return Value Provider Functions +-------------------------------- + +If your tests need access to the arguments to calculate the return values, +set the return value to a function that takes the method's arguments as its own +arguments and returns the return value. For example, given this interface: + +```go +package test + +type Proxy interface { + passthrough(ctx context.Context, s string) string +} +``` + +The argument can be passed through as the return value: + +```go +import . "github.com/stretchr/testify/mock" + +Mock.On("passthrough", mock.AnythingOfType("context.Context"), mock.AnythingOfType("string")).Return(func(ctx context.Context, s string) string { + return s +}) +``` + +#### Requirements + +`Return` must be passed the same argument count and types as expected by the interface. If the return argument signature of `passthrough` in the above example was instead `(string, error)` in the interface, `Return` would also need a second argument to define the error value. + +If any return argument is missing, `github.com/stretchr/testify/mock.Arguments.Get` will emit a panic. + +For example, `panic: assert: arguments: Cannot call Get(0) because there are 0 argument(s). [recovered]` indicates that `Return` was not provided any arguments but (at least one) was expected based on the interface. `Get(1)` would indicate that the `Return` call is missing a second argument, and so on. + +#### Notes + +This approach should be used judiciously, as return values should generally +not depend on arguments in mocks; however, this approach can be helpful for +situations like passthroughs or other test-only calculations. + + +Extended Flag Descriptions +-------------------------- + +The following descriptions provide additional elaboration on a few common parameters. + +| flag name | description | +|---|---| +| `--name` | The `--name` option takes either the name or matching regular expression of interface to generate mock(s) for. | +| `--all` | It's common for a big package to have a lot of interfaces, so mockery provides `--all`. This option will tell mockery to scan all files under the directory named by `--dir` ("." by default) and generates mocks for any interfaces it finds. This option implies `--recursive=true`. | +| `--recursive` | Use the `--recursive` option to search subdirectories for the interface(s). This option is only compatible with `--name`. The `--all` option implies `--recursive=true`. | +| `--output` | mockery always generates files with the package `mocks` to keep things clean and simple. You can control which mocks directory is used by using `--output`, which defaults to `./mocks`. | +| `--inpackage` and `--keeptree` | For some complex repositories, there could be multiple interfaces with the same name but in different packages. In that case, `--inpackage` allows generating the mocked interfaces directly in the package that it mocks. In the case you don't want to generate the mocks into the package but want to keep a similar structure, use the option `--keeptree`. | +| `--filename` | Use the `--filename` and `--structname` to override the default generated file and struct name. These options are only compatible with non-regular expressions in `--name`, where only one mock is generated. | +| `--case` | mockery generates files using the casing of the original interface name. This can be modified by specifying `--case underscore` to format the generated file name using underscore casing. | +| `--print` | Use `mockery --print` to have the resulting code printed out instead of written to disk. | + +Mocking interfaces in `main` +---------------------------- + +When your interfaces are in the main package you should supply the `--inpackage` flag. +This will generate mocks in the same package as the target code avoiding import issues. + +Configuration +-------------- + +mockery uses [spf13/viper](https://github.com/spf13/viper) under the hood for its configuration parsing. It is bound to three different configuration sources, in order of decreasing precedence: + +1. Command line +2. Environment variables +3. Configuration file + +### Example + + $ export MOCKERY_STRUCTNAME=config_from_env + $ echo $MOCKERY_STRUCTNAME + config_from_env + $ grep structname .mockery.yaml + structname: config_from_file + $ ./mockery showconfig --structname config_from_cli | grep structname + Using config file: /home/ltclipp/git/vektra/mockery/.mockery.yaml + structname: config_from_cli + $ ./mockery showconfig | grep structname + Using config file: /home/ltclipp/git/vektra/mockery/.mockery.yaml + structname: config_from_env + $ unset MOCKERY_STRUCTNAME + $ ./mockery showconfig | grep structname + Using config file: /home/ltclipp/git/vektra/mockery/.mockery.yaml + structname: config_from_file + +By default it searches the current working directory for a file named `.mockery.[extension]` where [extension] is any of the [recognized extensions](https://pkg.go.dev/github.com/spf13/viper@v1.7.0?tab=doc#pkg-variables). + +Semantic Versioning +------------------- + +The versioning in this project applies only to the behavior of the mockery binary itself. This project explicitly does not promise a stable internal API, but rather a stable executable. The versioning applies to the following: + +1. CLI arguments. +2. Parsing of Golang code. New features in the Golang language will be supported in a backwards-compatible manner, except during major version bumps. +3. Behavior of mock objects. Mock objects can be considered to be part of the public API. +4. Behavior of mockery given a set of arguments. + +What the version does _not_ track: +1. The interfaces, objects, methods etc. in the vektra/mockery package. +2. Compatibility of `go get`-ing mockery with new or old versions of Golang. + + +Stargazers +---------- + +[![Stargazers over time](https://starchart.cc/vektra/mockery.svg)](https://starchart.cc/vektra/mockery) diff --git a/vendor/github.com/99designs/gqlgen/appveyor.yml b/vendor/github.com/vektra/mockery/v2/appveyor.yml similarity index 55% rename from vendor/github.com/99designs/gqlgen/appveyor.yml rename to vendor/github.com/vektra/mockery/v2/appveyor.yml index db4751114..3516ab675 100644 --- a/vendor/github.com/99designs/gqlgen/appveyor.yml +++ b/vendor/github.com/vektra/mockery/v2/appveyor.yml @@ -1,32 +1,29 @@ version: "{build}" -# Source Config - -skip_branch_with_pr: true -clone_folder: c:\projects\gqlgen - -# Build host +clone_folder: c:\gopath\src\github.com\vektra\mockery environment: GOPATH: c:\gopath - GOVERSION: 1.11.5 - PATH: '%PATH%;c:\gopath\bin' + matrix: + - environment: + GOVERSION: 1.7.5 + - environment: + GOVERSION: 1.8 init: - git config --global core.autocrlf input -# Build - install: - # Install the specific Go version. - rmdir c:\go /s /q - appveyor DownloadFile https://storage.googleapis.com/golang/go%GOVERSION%.windows-amd64.msi - msiexec /i go%GOVERSION%.windows-amd64.msi /q + - set Path=c:\go\bin;c:\gopath\bin;%Path% - go version + - go env build: false deploy: false test_script: - - go generate ./... - - go test -timeout 20m ./... + - go get -v -t ./... + - go test -v github.com/vektra/mockery/mockery diff --git a/vendor/github.com/vektra/mockery/v2/cmd/mockery.go b/vendor/github.com/vektra/mockery/v2/cmd/mockery.go new file mode 100644 index 000000000..daee27687 --- /dev/null +++ b/vendor/github.com/vektra/mockery/v2/cmd/mockery.go @@ -0,0 +1,290 @@ +package cmd + +import ( + "context" + "fmt" + "os" + "path/filepath" + "regexp" + "runtime/pprof" + "strings" + "time" + + homedir "github.com/mitchellh/go-homedir" + "github.com/pkg/errors" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/vektra/mockery/v2/pkg" + "github.com/vektra/mockery/v2/pkg/config" + "github.com/vektra/mockery/v2/pkg/logging" + "golang.org/x/crypto/ssh/terminal" + "golang.org/x/tools/go/packages" +) + +var ( + cfgFile = "" + rootCmd = &cobra.Command{ + Use: "mockery", + Short: "Generate mock objects for your Golang interfaces", + RunE: func(cmd *cobra.Command, args []string) error { + r, err := GetRootAppFromViper(viper.GetViper()) + if err != nil { + printStackTrace(err) + return err + } + return r.Run() + }, + } +) + +type stackTracer interface { + StackTrace() errors.StackTrace +} + +func printStackTrace(e error) { + fmt.Printf("%v\n", e) + if err, ok := e.(stackTracer); ok { + for _, f := range err.StackTrace() { + fmt.Printf("%+s:%d\n", f, f) + } + } + +} + +// Execute executes the cobra CLI workflow +func Execute() { + if err := rootCmd.Execute(); err != nil { + //printStackTrace(err) + os.Exit(1) + } +} + +func init() { + cobra.OnInitialize(initConfig) + + pFlags := rootCmd.PersistentFlags() + pFlags.StringVar(&cfgFile, "config", "", "config file to use") + pFlags.String("name", "", "name or matching regular expression of interface to generate mock for") + pFlags.Bool("print", false, "print the generated mock to stdout") + pFlags.String("output", "./mocks", "directory to write mocks to") + pFlags.String("outpkg", "mocks", "name of generated package") + pFlags.String("packageprefix", "", "prefix for the generated package name, it is ignored if outpkg is also specified.") + pFlags.String("dir", ".", "directory to search for interfaces") + pFlags.BoolP("recursive", "r", false, "recurse search into sub-directories") + pFlags.Bool("all", false, "generates mocks for all found interfaces in all sub-directories") + pFlags.Bool("inpackage", false, "generate a mock that goes inside the original package") + pFlags.Bool("testonly", false, "generate a mock in a _test.go file") + pFlags.String("case", "camel", "name the mocked file using casing convention [camel, snake, underscore]") + pFlags.String("note", "", "comment to insert into prologue of each generated file") + pFlags.String("cpuprofile", "", "write cpu profile to file") + pFlags.Bool("version", false, "prints the installed version of mockery") + pFlags.Bool("quiet", false, `suppresses logger output (equivalent to --log-level="")`) + pFlags.Bool("keeptree", false, "keep the tree structure of the original interface files into a different repository. Must be used with XX") + pFlags.String("tags", "", "space-separated list of additional build tags to use") + pFlags.String("filename", "", "name of generated file (only works with -name and no regex)") + pFlags.String("structname", "", "name of generated struct (only works with -name and no regex)") + pFlags.String("log-level", "info", "Level of logging") + pFlags.String("srcpkg", "", "source pkg to search for interfaces") + pFlags.BoolP("dry-run", "d", false, "Do a dry run, don't modify any files") + + viper.BindPFlags(pFlags) +} + +func initConfig() { + if cfgFile != "" { + // Use config file from the flag. + viper.SetConfigFile(cfgFile) + } else { + // Find home directory. + home, err := homedir.Dir() + if err != nil { + log.Fatal().Err(err).Msgf("Failed to find homedir") + } + + // Search config in home directory with name ".cobra" (without extension). + viper.AddConfigPath(".") + viper.AddConfigPath(home) + viper.SetConfigName(".mockery") + } + + viper.SetEnvPrefix("mockery") + viper.AutomaticEnv() + + // Note we purposely ignore the error. Don't care if we can't find a config file. + if err := viper.ReadInConfig(); err == nil { + fmt.Fprintf(os.Stderr, "Using config file: %s\n", viper.ConfigFileUsed()) + } +} + +const regexMetadataChars = "\\.+*?()|[]{}^$" + +type RootApp struct { + config.Config +} + +func GetRootAppFromViper(v *viper.Viper) (*RootApp, error) { + r := &RootApp{} + if err := v.UnmarshalExact(&r.Config); err != nil { + return nil, errors.Wrapf(err, "failed to get config") + } + return r, nil +} + +func (r *RootApp) Run() error { + var recursive bool + var filter *regexp.Regexp + var err error + var limitOne bool + + if r.Quiet { + // if "quiet" flag is set, disable logging + r.Config.LogLevel = "" + } + + log, err := getLogger(r.Config.LogLevel) + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to initialize logger: %v\n", err) + return err + } + log = log.With().Bool(logging.LogKeyDryRun, r.Config.DryRun).Logger() + log.Info().Msgf("Starting mockery") + ctx := log.WithContext(context.Background()) + + if r.Config.Version { + fmt.Println(config.SemVer) + return nil + } else if r.Config.Name != "" && r.Config.All { + log.Fatal().Msgf("Specify --name or --all, but not both") + } else if (r.Config.FileName != "" || r.Config.StructName != "") && r.Config.All { + log.Fatal().Msgf("Cannot specify --filename or --structname with --all") + } else if r.Config.Dir != "" && r.Config.Dir != "." && r.Config.SrcPkg != "" { + log.Fatal().Msgf("Specify -dir or -srcpkg, but not both") + } else if r.Config.Name != "" { + recursive = r.Config.Recursive + if strings.ContainsAny(r.Config.Name, regexMetadataChars) { + if filter, err = regexp.Compile(r.Config.Name); err != nil { + log.Fatal().Err(err).Msgf("Invalid regular expression provided to -name") + } else if r.Config.FileName != "" || r.Config.StructName != "" { + log.Fatal().Msgf("Cannot specify --filename or --structname with regex in --name") + } + } else { + filter = regexp.MustCompile(fmt.Sprintf("^%s$", r.Config.Name)) + limitOne = true + } + } else if r.Config.All { + recursive = true + filter = regexp.MustCompile(".*") + } else { + log.Fatal().Msgf("Use --name to specify the name of the interface or --all for all interfaces found") + } + if r.Config.KeepTree && r.Config.InPackage { + log.Fatal().Msgf("--keeptree and --inpackage are mutually exclusive") + } + + if r.Config.Profile != "" { + f, err := os.Create(r.Config.Profile) + if err != nil { + return errors.Wrapf(err, "Failed to create profile file") + } + + pprof.StartCPUProfile(f) + defer pprof.StopCPUProfile() + } + + var osp pkg.OutputStreamProvider + if r.Config.Print { + osp = &pkg.StdoutStreamProvider{} + } else { + osp = &pkg.FileOutputStreamProvider{ + Config: r.Config, + BaseDir: r.Config.Output, + InPackage: r.Config.InPackage, + TestOnly: r.Config.TestOnly, + Case: r.Config.Case, + KeepTree: r.Config.KeepTree, + KeepTreeOriginalDirectory: r.Config.Dir, + FileName: r.Config.FileName, + } + } + + baseDir := r.Config.Dir + + if r.Config.SrcPkg != "" { + pkgs, err := packages.Load(&packages.Config{ + Mode: packages.NeedFiles, + }, r.Config.SrcPkg) + if err != nil || len(pkgs) == 0 { + log.Fatal().Err(err).Msgf("Failed to load package %s", r.Config.SrcPkg) + } + + // NOTE: we only pass one package name (config.SrcPkg) to packages.Load + // it should return one package at most + pkg := pkgs[0] + + if pkg.Errors != nil { + log.Fatal().Err(pkg.Errors[0]).Msgf("Failed to load package %s", r.Config.SrcPkg) + } + + if len(pkg.GoFiles) == 0 { + log.Fatal().Msgf("No go files in package %s", r.Config.SrcPkg) + } + baseDir = filepath.Dir(pkg.GoFiles[0]) + } + + visitor := &pkg.GeneratorVisitor{ + Config: r.Config, + InPackage: r.Config.InPackage, + Note: r.Config.Note, + Osp: osp, + PackageName: r.Config.Outpkg, + PackageNamePrefix: r.Config.Packageprefix, + StructName: r.Config.StructName, + } + + walker := pkg.Walker{ + Config: r.Config, + BaseDir: baseDir, + Recursive: recursive, + Filter: filter, + LimitOne: limitOne, + BuildTags: strings.Split(r.Config.BuildTags, " "), + } + + generated := walker.Walk(ctx, visitor) + + if r.Config.Name != "" && !generated { + log.Fatal().Msgf("Unable to find '%s' in any go files under this path", r.Config.Name) + } + return nil +} + +type timeHook struct{} + +func (t timeHook) Run(e *zerolog.Event, level zerolog.Level, msg string) { + e.Time("time", time.Now()) +} + +func getLogger(levelStr string) (zerolog.Logger, error) { + level, err := zerolog.ParseLevel(levelStr) + if err != nil { + return zerolog.Logger{}, errors.Wrapf(err, "Couldn't parse log level") + } + out := os.Stderr + writer := zerolog.ConsoleWriter{ + Out: out, + TimeFormat: time.RFC822, + } + if !terminal.IsTerminal(int(out.Fd())) { + writer.NoColor = true + } + log := zerolog.New(writer). + Hook(timeHook{}). + Level(level). + With(). + Str("version", config.SemVer). + Logger() + + return log, nil +} diff --git a/vendor/github.com/vektra/mockery/v2/cmd/showconfig.go b/vendor/github.com/vektra/mockery/v2/cmd/showconfig.go new file mode 100644 index 000000000..e13555fd7 --- /dev/null +++ b/vendor/github.com/vektra/mockery/v2/cmd/showconfig.go @@ -0,0 +1,36 @@ +package cmd + +import ( + "fmt" + + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/viper" + "github.com/vektra/mockery/v2/pkg/config" + "gopkg.in/yaml.v2" +) + +// showconfigCmd represents the showconfig command +var showconfigCmd = &cobra.Command{ + Use: "showconfig", + Short: "Show the merged config", + Long: `Print out a yaml representation of the merged config. +This initializes viper and prints out the merged configuration between +config files, environment variables, and CLI flags.`, + RunE: func(cmd *cobra.Command, args []string) error { + config := &config.Config{} + if err := viper.UnmarshalExact(config); err != nil { + return errors.Wrapf(err, "failed to unmarshal config") + } + out, err := yaml.Marshal(config) + if err != nil { + return errors.Wrapf(err, "Failed to marsrhal yaml") + } + fmt.Printf("%s", string(out)) + return nil + }, +} + +func init() { + rootCmd.AddCommand(showconfigCmd) +} diff --git a/vendor/github.com/vektra/mockery/v2/go.mod b/vendor/github.com/vektra/mockery/v2/go.mod new file mode 100644 index 000000000..fd55e7e1c --- /dev/null +++ b/vendor/github.com/vektra/mockery/v2/go.mod @@ -0,0 +1,15 @@ +module github.com/vektra/mockery/v2 + +go 1.14 + +require ( + github.com/mitchellh/go-homedir v1.1.0 + github.com/pkg/errors v0.8.1 + github.com/rs/zerolog v1.18.0 + github.com/spf13/cobra v1.0.0 + github.com/spf13/viper v1.7.0 + github.com/stretchr/testify v1.3.0 + golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 + golang.org/x/tools v0.0.0-20200323144430-8dcfad9e016e + gopkg.in/yaml.v2 v2.2.4 +) diff --git a/vendor/github.com/vektra/mockery/v2/go.sum b/vendor/github.com/vektra/mockery/v2/go.sum new file mode 100644 index 000000000..369441b6a --- /dev/null +++ b/vendor/github.com/vektra/mockery/v2/go.sum @@ -0,0 +1,343 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +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/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +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/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= +github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= +github.com/pkg/errors v0.8.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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= +github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.18.0 h1:CbAm3kP2Tptby1i9sYy2MGRg0uxIN9cyDb59Ys7W8z8= +github.com/rs/zerolog v1.18.0/go.mod h1:9nvC1axdVrAHcu/s9taAVfBuIdTZLVQmKQyvrUjF5+I= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8= +github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= +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.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/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-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/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-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/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-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/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-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/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-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/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-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190828213141-aed303cbaa74/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200323144430-8dcfad9e016e h1:ssd5ulOvVWlh4kDSUF2SqzmMeWfjmwDXM+uGw/aQjRE= +golang.org/x/tools v0.0.0-20200323144430-8dcfad9e016e/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +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 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +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.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/vendor/github.com/vektra/mockery/v2/main.go b/vendor/github.com/vektra/mockery/v2/main.go new file mode 100644 index 000000000..62d9f745f --- /dev/null +++ b/vendor/github.com/vektra/mockery/v2/main.go @@ -0,0 +1,9 @@ +package main + +import ( + "github.com/vektra/mockery/v2/cmd" +) + +func main() { + cmd.Execute() +} diff --git a/vendor/github.com/vektra/mockery/v2/pkg/config/config.go b/vendor/github.com/vektra/mockery/v2/pkg/config/config.go new file mode 100644 index 000000000..d5ea8529c --- /dev/null +++ b/vendor/github.com/vektra/mockery/v2/pkg/config/config.go @@ -0,0 +1,34 @@ +package config + +// SemVer is the version of mockery at build time. +var SemVer = "0.0.0-dev" + +type Config struct { + All bool + BuildTags string + Case string + Config string + Cpuprofile string + Dir string + DryRun bool `mapstructure:"dry-run"` + FileName string + InPackage bool + KeepTree bool + LogLevel string `mapstructure:"log-level"` + Name string + Note string + Outpkg string + Packageprefix string + Output string + Print bool + Profile string + Quiet bool + Recursive bool + SrcPkg string + // StructName overrides the name given to the mock struct and should only be nonempty + // when generating for an exact match (non regex expression in -name). + StructName string + Tags string + TestOnly bool + Version bool +} diff --git a/vendor/github.com/vektra/mockery/v2/pkg/generator.go b/vendor/github.com/vektra/mockery/v2/pkg/generator.go new file mode 100644 index 000000000..4a53e1931 --- /dev/null +++ b/vendor/github.com/vektra/mockery/v2/pkg/generator.go @@ -0,0 +1,631 @@ +package pkg + +import ( + "bytes" + "context" + "errors" + "fmt" + "go/ast" + "go/build" + "go/types" + "io" + "os" + "path/filepath" + "regexp" + "sort" + "strings" + "unicode" + + "github.com/rs/zerolog" + "github.com/vektra/mockery/v2/pkg/config" + "github.com/vektra/mockery/v2/pkg/logging" + "golang.org/x/tools/imports" +) + +var invalidIdentifierChar = regexp.MustCompile("[^[:digit:][:alpha:]_]") + +// Generator is responsible for generating the string containing +// imports and the mock struct that will later be written out as file. +type Generator struct { + config.Config + buf bytes.Buffer + + iface *Interface + pkg string + localPackageName *string + + localizationCache map[string]string + packagePathToName map[string]string + nameToPackagePath map[string]string + + packageRoots []string +} + +// NewGenerator builds a Generator. +func NewGenerator(ctx context.Context, c config.Config, iface *Interface, pkg string) *Generator { + + var roots []string + + for _, root := range filepath.SplitList(build.Default.GOPATH) { + roots = append(roots, filepath.Join(root, "src")) + } + + g := &Generator{ + Config: c, + iface: iface, + pkg: pkg, + localizationCache: make(map[string]string), + packagePathToName: make(map[string]string), + nameToPackagePath: make(map[string]string), + packageRoots: roots, + } + + g.addPackageImportWithName(ctx, "github.com/stretchr/testify/mock", "mock") + return g +} + +func (g *Generator) populateImports(ctx context.Context) { + log := zerolog.Ctx(ctx) + + log.Debug().Msgf("populating imports") + + for _, method := range g.iface.Methods() { + ftype := method.Signature + g.addImportsFromTuple(ctx, ftype.Params()) + g.addImportsFromTuple(ctx, ftype.Results()) + g.renderType(ctx, g.iface.NamedType) + } +} + +func (g *Generator) addImportsFromTuple(ctx context.Context, list *types.Tuple) { + for i := 0; i < list.Len(); i++ { + // We use renderType here because we need to recursively + // resolve any types to make sure that all named types that + // will appear in the interface file are known + g.renderType(ctx, list.At(i).Type()) + } +} + +func (g *Generator) addPackageImport(ctx context.Context, pkg *types.Package) string { + return g.addPackageImportWithName(ctx, pkg.Path(), pkg.Name()) +} + +func (g *Generator) addPackageImportWithName(ctx context.Context, path, name string) string { + path = g.getLocalizedPath(ctx, path) + if existingName, pathExists := g.packagePathToName[path]; pathExists { + return existingName + } + + nonConflictingName := g.getNonConflictingName(path, name) + g.packagePathToName[path] = nonConflictingName + g.nameToPackagePath[nonConflictingName] = path + return nonConflictingName +} + +func (g *Generator) getNonConflictingName(path, name string) string { + if !g.importNameExists(name) { + return name + } + + // The path will always contain '/' because it is enforced in getLocalizedPath + // regardless of OS. + directories := strings.Split(path, "/") + + cleanedDirectories := make([]string, 0, len(directories)) + for _, directory := range directories { + cleaned := invalidIdentifierChar.ReplaceAllString(directory, "_") + cleanedDirectories = append(cleanedDirectories, cleaned) + } + numDirectories := len(cleanedDirectories) + var prospectiveName string + for i := 1; i <= numDirectories; i++ { + prospectiveName = strings.Join(cleanedDirectories[numDirectories-i:], "") + if !g.importNameExists(prospectiveName) { + return prospectiveName + } + } + // Try adding numbers to the given name + i := 2 + for { + prospectiveName = fmt.Sprintf("%v%d", name, i) + if !g.importNameExists(prospectiveName) { + return prospectiveName + } + i++ + } +} + +func (g *Generator) importNameExists(name string) bool { + _, nameExists := g.nameToPackagePath[name] + return nameExists +} + +func (g *Generator) getLocalizedPathFromPackage(ctx context.Context, pkg *types.Package) string { + return g.getLocalizedPath(ctx, pkg.Path()) +} + +func calculateImport(ctx context.Context, set []string, path string) string { + log := zerolog.Ctx(ctx).With().Str(logging.LogKeyPath, path).Logger() + ctx = log.WithContext(ctx) + + for _, root := range set { + if strings.HasPrefix(path, root) { + packagePath, err := filepath.Rel(root, path) + if err == nil { + return packagePath + } else { + log.Err(err).Msgf("Unable to localize path") + } + } + } + return path +} + +// TODO(@IvanMalison): Is there not a better way to get the actual +// import path of a package? +func (g *Generator) getLocalizedPath(ctx context.Context, path string) string { + log := zerolog.Ctx(ctx).With().Str(logging.LogKeyPath, path).Logger() + ctx = log.WithContext(ctx) + + if strings.HasSuffix(path, ".go") { + path, _ = filepath.Split(path) + } + if localized, ok := g.localizationCache[path]; ok { + return localized + } + directories := strings.Split(path, string(filepath.Separator)) + numDirectories := len(directories) + vendorIndex := -1 + for i := 1; i <= numDirectories; i++ { + dir := directories[numDirectories-i] + if dir == "vendor" { + vendorIndex = numDirectories - i + break + } + } + + toReturn := path + if vendorIndex >= 0 { + toReturn = filepath.Join(directories[vendorIndex+1:]...) + } else if filepath.IsAbs(path) { + toReturn = calculateImport(ctx, g.packageRoots, path) + } + + // Enforce '/' slashes for import paths in every OS. + toReturn = filepath.ToSlash(toReturn) + + g.localizationCache[path] = toReturn + return toReturn +} + +func (g *Generator) mockName() string { + if g.StructName != "" { + return g.StructName + } + + if g.InPackage { + if ast.IsExported(g.iface.Name) { + return "Mock" + g.iface.Name + } + first := true + return "mock" + strings.Map(func(r rune) rune { + if first { + first = false + return unicode.ToUpper(r) + } + return r + }, g.iface.Name) + } + + return g.iface.Name +} + +func (g *Generator) unescapedImportPath(imp *ast.ImportSpec) string { + return strings.Replace(imp.Path.Value, "\"", "", -1) +} + +func (g *Generator) getImportStringFromSpec(imp *ast.ImportSpec) string { + if name, ok := g.packagePathToName[g.unescapedImportPath(imp)]; ok { + return fmt.Sprintf("import %s %s\n", name, imp.Path.Value) + } + return fmt.Sprintf("import %s\n", imp.Path.Value) +} + +func (g *Generator) sortedImportNames() (importNames []string) { + for name := range g.nameToPackagePath { + importNames = append(importNames, name) + } + sort.Strings(importNames) + return +} + +func (g *Generator) generateImports(ctx context.Context) { + log := zerolog.Ctx(ctx) + + log.Debug().Msgf("generating imports") + log.Debug().Msgf("%v", g.nameToPackagePath) + + pkgPath := g.nameToPackagePath[g.iface.Pkg.Name()] + // Sort by import name so that we get a deterministic order + for _, name := range g.sortedImportNames() { + logImport := log.With().Str(logging.LogKeyImport, g.nameToPackagePath[name]).Logger() + logImport.Debug().Msgf("found import") + + path := g.nameToPackagePath[name] + if g.InPackage && path == pkgPath { + logImport.Debug().Msgf("import (%s) equals interface's package path (%s), skipping", path, pkgPath) + continue + } + g.printf("import %s \"%s\"\n", name, path) + } +} + +// GeneratePrologue generates the prologue of the mock. +func (g *Generator) GeneratePrologue(ctx context.Context, pkg string) { + g.populateImports(ctx) + if g.InPackage { + g.printf("package %s\n\n", g.iface.Pkg.Name()) + } else { + g.printf("package %v\n\n", pkg) + } + + g.generateImports(ctx) + g.printf("\n") +} + +// GeneratePrologueNote adds a note after the prologue to the output +// string. +func (g *Generator) GeneratePrologueNote(note string) { + g.printf("// Code generated by mockery v%s. DO NOT EDIT.\n", config.SemVer) + if note != "" { + g.printf("\n") + for _, n := range strings.Split(note, "\\n") { + g.printf("// %s\n", n) + } + } + g.printf("\n") +} + +// ErrNotInterface is returned when the given type is not an interface +// type. +var ErrNotInterface = errors.New("expression not an interface") + +func (g *Generator) printf(s string, vals ...interface{}) { + fmt.Fprintf(&g.buf, s, vals...) +} + +type namer interface { + Name() string +} + +func (g *Generator) renderType(ctx context.Context, typ types.Type) string { + switch t := typ.(type) { + case *types.Named: + o := t.Obj() + if o.Pkg() == nil || o.Pkg().Name() == "main" || (g.InPackage && o.Pkg() == g.iface.Pkg) { + return o.Name() + } + return g.addPackageImport(ctx, o.Pkg()) + "." + o.Name() + case *types.Basic: + return t.Name() + case *types.Pointer: + return "*" + g.renderType(ctx, t.Elem()) + case *types.Slice: + return "[]" + g.renderType(ctx, t.Elem()) + case *types.Array: + return fmt.Sprintf("[%d]%s", t.Len(), g.renderType(ctx, t.Elem())) + case *types.Signature: + switch t.Results().Len() { + case 0: + return fmt.Sprintf( + "func(%s)", + g.renderTypeTuple(ctx, t.Params()), + ) + case 1: + return fmt.Sprintf( + "func(%s) %s", + g.renderTypeTuple(ctx, t.Params()), + g.renderType(ctx, t.Results().At(0).Type()), + ) + default: + return fmt.Sprintf( + "func(%s)(%s)", + g.renderTypeTuple(ctx, t.Params()), + g.renderTypeTuple(ctx, t.Results()), + ) + } + case *types.Map: + kt := g.renderType(ctx, t.Key()) + vt := g.renderType(ctx, t.Elem()) + + return fmt.Sprintf("map[%s]%s", kt, vt) + case *types.Chan: + switch t.Dir() { + case types.SendRecv: + return "chan " + g.renderType(ctx, t.Elem()) + case types.RecvOnly: + return "<-chan " + g.renderType(ctx, t.Elem()) + default: + return "chan<- " + g.renderType(ctx, t.Elem()) + } + case *types.Struct: + var fields []string + + for i := 0; i < t.NumFields(); i++ { + f := t.Field(i) + + if f.Anonymous() { + fields = append(fields, g.renderType(ctx, f.Type())) + } else { + fields = append(fields, fmt.Sprintf("%s %s", f.Name(), g.renderType(ctx, f.Type()))) + } + } + + return fmt.Sprintf("struct{%s}", strings.Join(fields, ";")) + case *types.Interface: + if t.NumMethods() != 0 { + panic("Unable to mock inline interfaces with methods") + } + + return "interface{}" + case namer: + return t.Name() + default: + panic(fmt.Sprintf("un-namable type: %#v (%T)", t, t)) + } +} + +func (g *Generator) renderTypeTuple(ctx context.Context, tup *types.Tuple) string { + var parts []string + + for i := 0; i < tup.Len(); i++ { + v := tup.At(i) + + parts = append(parts, g.renderType(ctx, v.Type())) + } + + return strings.Join(parts, " , ") +} + +func isNillable(typ types.Type) bool { + switch t := typ.(type) { + case *types.Pointer, *types.Array, *types.Map, *types.Interface, *types.Signature, *types.Chan, *types.Slice: + return true + case *types.Named: + return isNillable(t.Underlying()) + } + return false +} + +type paramList struct { + Names []string + Types []string + Params []string + Nilable []bool + Variadic bool +} + +func (g *Generator) genList(ctx context.Context, list *types.Tuple, variadic bool) *paramList { + var params paramList + + if list == nil { + return ¶ms + } + + for i := 0; i < list.Len(); i++ { + v := list.At(i) + + ts := g.renderType(ctx, v.Type()) + + if variadic && i == list.Len()-1 { + t := v.Type() + switch t := t.(type) { + case *types.Slice: + params.Variadic = true + ts = "..." + g.renderType(ctx, t.Elem()) + default: + panic("bad variadic type!") + } + } + + pname := v.Name() + + if g.nameCollides(pname) || pname == "" { + pname = fmt.Sprintf("_a%d", i) + } + + params.Names = append(params.Names, pname) + params.Types = append(params.Types, ts) + + params.Params = append(params.Params, fmt.Sprintf("%s %s", pname, ts)) + params.Nilable = append(params.Nilable, isNillable(v.Type())) + } + + return ¶ms +} + +func (g *Generator) nameCollides(pname string) bool { + if pname == g.pkg { + return true + } + return g.importNameExists(pname) +} + +// ErrNotSetup is returned when the generator is not configured. +var ErrNotSetup = errors.New("not setup") + +// Generate builds a string that constitutes a valid go source file +// containing the mock of the relevant interface. +func (g *Generator) Generate(ctx context.Context) error { + g.populateImports(ctx) + if g.iface == nil { + return ErrNotSetup + } + + g.printf( + "// %s is an autogenerated mock type for the %s type\n", g.mockName(), + g.iface.Name, + ) + + g.printf( + "type %s struct {\n\tmock.Mock\n}\n\n", g.mockName(), + ) + + for _, method := range g.iface.Methods() { + + ftype := method.Signature + fname := method.Name + + params := g.genList(ctx, ftype.Params(), ftype.Variadic()) + returns := g.genList(ctx, ftype.Results(), false) + + if len(params.Names) == 0 { + g.printf("// %s provides a mock function with given fields:\n", fname) + } else { + g.printf( + "// %s provides a mock function with given fields: %s\n", fname, + strings.Join(params.Names, ", "), + ) + } + g.printf( + "func (_m *%s) %s(%s) ", g.mockName(), fname, + strings.Join(params.Params, ", "), + ) + + switch len(returns.Types) { + case 0: + g.printf("{\n") + case 1: + g.printf("%s {\n", returns.Types[0]) + default: + g.printf("(%s) {\n", strings.Join(returns.Types, ", ")) + } + + var formattedParamNames string + for i, name := range params.Names { + if i > 0 { + formattedParamNames += ", " + } + + paramType := params.Types[i] + // for variable args, move the ... to the end. + if strings.Index(paramType, "...") == 0 { + name += "..." + } + formattedParamNames += name + } + + called := g.generateCalled(params, formattedParamNames) // _m.Called invocation string + + if len(returns.Types) > 0 { + g.printf("\tret := %s\n\n", called) + + var ( + ret []string + ) + + for idx, typ := range returns.Types { + g.printf("\tvar r%d %s\n", idx, typ) + g.printf("\tif rf, ok := ret.Get(%d).(func(%s) %s); ok {\n", + idx, strings.Join(params.Types, ", "), typ) + g.printf("\t\tr%d = rf(%s)\n", idx, formattedParamNames) + g.printf("\t} else {\n") + if typ == "error" { + g.printf("\t\tr%d = ret.Error(%d)\n", idx, idx) + } else if returns.Nilable[idx] { + g.printf("\t\tif ret.Get(%d) != nil {\n", idx) + g.printf("\t\t\tr%d = ret.Get(%d).(%s)\n", idx, idx, typ) + g.printf("\t\t}\n") + } else { + g.printf("\t\tr%d = ret.Get(%d).(%s)\n", idx, idx, typ) + } + g.printf("\t}\n\n") + + ret = append(ret, fmt.Sprintf("r%d", idx)) + } + + g.printf("\treturn %s\n", strings.Join(ret, ", ")) + } else { + g.printf("\t%s\n", called) + } + + g.printf("}\n") + } + + return nil +} + +// generateCalled returns the Mock.Called invocation string and, if necessary, prints the +// steps to prepare its argument list. +// +// It is separate from Generate to avoid cyclomatic complexity through early return statements. +func (g *Generator) generateCalled(list *paramList, formattedParamNames string) string { + namesLen := len(list.Names) + if namesLen == 0 { + return "_m.Called()" + } + + if !list.Variadic { + return "_m.Called(" + formattedParamNames + ")" + } + + var variadicArgsName string + variadicName := list.Names[namesLen-1] + + // list.Types[] will contain a leading '...'. Strip this from the string to + // do easier comparison. + strippedIfaceType := strings.Trim(list.Types[namesLen-1], "...") + variadicIface := strippedIfaceType == "interface{}" + + if variadicIface { + // Variadic is already of the interface{} type, so we don't need special handling. + variadicArgsName = variadicName + } else { + // Define _va to avoid "cannot use t (type T) as type []interface {} in append" error + // whenever the variadic type is non-interface{}. + g.printf("\t_va := make([]interface{}, len(%s))\n", variadicName) + g.printf("\tfor _i := range %s {\n\t\t_va[_i] = %s[_i]\n\t}\n", variadicName, variadicName) + variadicArgsName = "_va" + } + + // _ca will hold all arguments we'll mirror into Called, one argument per distinct value + // passed to the method. + // + // For example, if the second argument is variadic and consists of three values, + // a total of 4 arguments will be passed to Called. The alternative is to + // pass a total of 2 arguments where the second is a slice with those 3 values from + // the variadic argument. But the alternative is less accessible because it requires + // building a []interface{} before calling Mock methods like On and AssertCalled for + // the variadic argument, and creates incompatibility issues with the diff algorithm + // in github.com/stretchr/testify/mock. + // + // This mirroring will allow argument lists for methods like On and AssertCalled to + // always resemble the expected calls they describe and retain compatibility. + // + // It's okay for us to use the interface{} type, regardless of the actual types, because + // Called receives only interface{} anyway. + g.printf("\tvar _ca []interface{}\n") + + if namesLen > 1 { + nonVariadicParamNames := formattedParamNames[0:strings.LastIndex(formattedParamNames, ",")] + g.printf("\t_ca = append(_ca, %s)\n", nonVariadicParamNames) + } + g.printf("\t_ca = append(_ca, %s...)\n", variadicArgsName) + + return "_m.Called(_ca...)" +} + +func (g *Generator) Write(w io.Writer) error { + opt := &imports.Options{Comments: true} + theBytes := g.buf.Bytes() + + res, err := imports.Process("mock.go", theBytes, opt) + if err != nil { + line := "--------------------------------------------------------------------------------------------" + fmt.Fprintf(os.Stderr, "Between the lines is the file (mock.go) mockery generated in-memory but detected as invalid:\n%s\n%s\n%s\n", line, g.buf.String(), line) + return err + } + + w.Write(res) + return nil +} diff --git a/vendor/github.com/vektra/mockery/v2/pkg/logging/logging.go b/vendor/github.com/vektra/mockery/v2/pkg/logging/logging.go new file mode 100644 index 000000000..6fc1f97f5 --- /dev/null +++ b/vendor/github.com/vektra/mockery/v2/pkg/logging/logging.go @@ -0,0 +1,12 @@ +package logging + +const ( + LogKeyBaseDir = "base-dir" + LogKeyDir = "dir" + LogKeyDryRun = "dry-run" + LogKeyFile = "file" + LogKeyInterface = "interface" + LogKeyImport = "import" + LogKeyPath = "path" + LogKeyQualifiedName = "qualified-name" +) diff --git a/vendor/github.com/vektra/mockery/v2/pkg/mockery.go b/vendor/github.com/vektra/mockery/v2/pkg/mockery.go new file mode 100644 index 000000000..c1caffeb1 --- /dev/null +++ b/vendor/github.com/vektra/mockery/v2/pkg/mockery.go @@ -0,0 +1 @@ +package pkg diff --git a/vendor/github.com/vektra/mockery/v2/pkg/outputter.go b/vendor/github.com/vektra/mockery/v2/pkg/outputter.go new file mode 100644 index 000000000..6f19309e7 --- /dev/null +++ b/vendor/github.com/vektra/mockery/v2/pkg/outputter.go @@ -0,0 +1,105 @@ +package pkg + +import ( + "context" + "io" + "os" + "path/filepath" + "regexp" + "strings" + + "github.com/rs/zerolog" + "github.com/vektra/mockery/v2/pkg/config" + "github.com/vektra/mockery/v2/pkg/logging" +) + +type Cleanup func() error + +type OutputStreamProvider interface { + GetWriter(context.Context, *Interface) (io.Writer, error, Cleanup) +} + +type StdoutStreamProvider struct { +} + +func (this *StdoutStreamProvider) GetWriter(ctx context.Context, iface *Interface) (io.Writer, error, Cleanup) { + return os.Stdout, nil, func() error { return nil } +} + +type FileOutputStreamProvider struct { + Config config.Config + BaseDir string + InPackage bool + TestOnly bool + Case string + KeepTree bool + KeepTreeOriginalDirectory string + FileName string +} + +func (this *FileOutputStreamProvider) GetWriter(ctx context.Context, iface *Interface) (io.Writer, error, Cleanup) { + log := zerolog.Ctx(ctx).With().Str(logging.LogKeyInterface, iface.Name).Logger() + ctx = log.WithContext(ctx) + + var path string + + caseName := iface.Name + if this.Case == "underscore" || this.Case == "snake" { + caseName = this.underscoreCaseName(caseName) + } + + if this.KeepTree { + absOriginalDir, err := filepath.Abs(this.KeepTreeOriginalDirectory) + if err != nil { + return nil, err, func() error { return nil } + } + relativePath := strings.TrimPrefix( + filepath.Join(filepath.Dir(iface.FileName), this.filename(caseName)), + absOriginalDir) + path = filepath.Join(this.BaseDir, relativePath) + if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { + return nil, err, func() error { return nil } + } + } else if this.InPackage { + path = filepath.Join(filepath.Dir(iface.FileName), this.filename(caseName)) + } else { + path = filepath.Join(this.BaseDir, this.filename(caseName)) + if err := os.MkdirAll(filepath.Dir(path), 0755); err != nil { + return nil, err, func() error { return nil } + } + } + + log = log.With().Str(logging.LogKeyPath, path).Logger() + ctx = log.WithContext(ctx) + + log.Debug().Msgf("creating writer to file") + f, err := os.Create(path) + if err != nil { + return nil, err, func() error { return nil } + } + + return f, nil, func() error { + return f.Close() + } +} + +func (this *FileOutputStreamProvider) filename(name string) string { + if this.FileName != "" { + return this.FileName + } else if this.InPackage && this.TestOnly { + return "mock_" + name + "_test.go" + } else if this.InPackage { + return "mock_" + name + ".go" + } else if this.TestOnly { + return name + "_test.go" + } + return name + ".go" +} + +// shamelessly taken from http://stackoverflow.com/questions/1175208/elegant-python-function-to-convert-camelcase-to-camel-caseo +func (this *FileOutputStreamProvider) underscoreCaseName(caseName string) string { + rxp1 := regexp.MustCompile("(.)([A-Z][a-z]+)") + s1 := rxp1.ReplaceAllString(caseName, "${1}_${2}") + rxp2 := regexp.MustCompile("([a-z0-9])([A-Z])") + return strings.ToLower(rxp2.ReplaceAllString(s1, "${1}_${2}")) +} diff --git a/vendor/github.com/vektra/mockery/v2/pkg/parse.go b/vendor/github.com/vektra/mockery/v2/pkg/parse.go new file mode 100644 index 000000000..6373443e3 --- /dev/null +++ b/vendor/github.com/vektra/mockery/v2/pkg/parse.go @@ -0,0 +1,278 @@ +package pkg + +import ( + "context" + "fmt" + "go/ast" + "go/types" + "io/ioutil" + "path/filepath" + "sort" + "strings" + + "github.com/rs/zerolog" + "github.com/vektra/mockery/v2/pkg/logging" + "golang.org/x/tools/go/packages" +) + +type parserEntry struct { + fileName string + pkg *packages.Package + syntax *ast.File + interfaces []string +} + +type Parser struct { + entries []*parserEntry + entriesByFileName map[string]*parserEntry + parserPackages []*types.Package + conf packages.Config +} + +func NewParser(buildTags []string) *Parser { + var conf packages.Config + conf.Mode = packages.LoadSyntax + if len(buildTags) > 0 { + conf.BuildFlags = []string{"-tags", strings.Join(buildTags, ",")} + } + return &Parser{ + parserPackages: make([]*types.Package, 0), + entriesByFileName: map[string]*parserEntry{}, + conf: conf, + } +} + +func (p *Parser) Parse(ctx context.Context, path string) error { + // To support relative paths to mock targets w/ vendor deps, we need to provide eventual + // calls to build.Context.Import with an absolute path. It needs to be absolute because + // Import will only find the vendor directory if our target path for parsing is under + // a "root" (GOROOT or a GOPATH). Only absolute paths will pass the prefix-based validation. + // + // For example, if our parse target is "./ifaces", Import will check if any "roots" are a + // prefix of "ifaces" and decide to skip the vendor search. + path, err := filepath.Abs(path) + if err != nil { + return err + } + + dir := filepath.Dir(path) + + files, err := ioutil.ReadDir(dir) + if err != nil { + return err + } + + for _, fi := range files { + log := zerolog.Ctx(ctx).With(). + Str(logging.LogKeyDir, dir). + Str(logging.LogKeyFile, fi.Name()). + Logger() + ctx = log.WithContext(ctx) + + if filepath.Ext(fi.Name()) != ".go" || strings.HasSuffix(fi.Name(), "_test.go") { + continue + } + + log.Debug().Msgf("parsing") + + fname := fi.Name() + fpath := filepath.Join(dir, fname) + if _, ok := p.entriesByFileName[fpath]; ok { + continue + } + + pkgs, err := packages.Load(&p.conf, "file="+fpath) + if err != nil { + return err + } + if len(pkgs) == 0 { + continue + } + if len(pkgs) > 1 { + names := make([]string, len(pkgs)) + for i, p := range pkgs { + names[i] = p.Name + } + panic(fmt.Sprintf("file %s resolves to multiple packages: %s", fpath, strings.Join(names, ", "))) + } + + pkg := pkgs[0] + if len(pkg.Errors) > 0 { + return pkg.Errors[0] + } + if len(pkg.GoFiles) == 0 { + continue + } + + for idx, f := range pkg.GoFiles { + if _, ok := p.entriesByFileName[f]; ok { + continue + } + entry := parserEntry{ + fileName: f, + pkg: pkg, + syntax: pkg.Syntax[idx], + } + p.entries = append(p.entries, &entry) + p.entriesByFileName[f] = &entry + } + } + + return nil +} + +type NodeVisitor struct { + declaredInterfaces []string +} + +func NewNodeVisitor() *NodeVisitor { + return &NodeVisitor{ + declaredInterfaces: make([]string, 0), + } +} + +func (n *NodeVisitor) DeclaredInterfaces() []string { + return n.declaredInterfaces +} + +func (nv *NodeVisitor) Visit(node ast.Node) ast.Visitor { + switch n := node.(type) { + case *ast.TypeSpec: + switch n.Type.(type) { + case *ast.InterfaceType, *ast.FuncType: + nv.declaredInterfaces = append(nv.declaredInterfaces, n.Name.Name) + } + } + return nv +} + +func (p *Parser) Load() error { + for _, entry := range p.entries { + nv := NewNodeVisitor() + ast.Walk(nv, entry.syntax) + entry.interfaces = nv.DeclaredInterfaces() + } + return nil +} + +func (p *Parser) Find(name string) (*Interface, error) { + for _, entry := range p.entries { + for _, iface := range entry.interfaces { + if iface == name { + list := p.packageInterfaces(entry.pkg.Types, entry.fileName, []string{name}, nil) + if len(list) > 0 { + return list[0], nil + } + } + } + } + return nil, ErrNotInterface +} + +type Method struct { + Name string + Signature *types.Signature +} + +// Interface type represents the target type that we will generate a mock for. +// It could be an interface, or a function type. +// Function type emulates: an interface it has 1 method with the function signature +// and a general name, e.g. "Execute". +type Interface struct { + Name string // Name of the type to be mocked. + QualifiedName string // Path to the package of the target type. + FileName string + File *ast.File + Pkg *types.Package + NamedType *types.Named + IsFunction bool // If true, this instance represents a function, otherwise it's an interface. + ActualInterface *types.Interface // Holds the actual interface type, in case it's an interface. + SingleFunction *Method // Holds the function type information, in case it's a function type. +} + +func (iface *Interface) Methods() []*Method { + if iface.IsFunction { + return []*Method{iface.SingleFunction} + } + methods := make([]*Method, iface.ActualInterface.NumMethods()) + for i := 0; i < iface.ActualInterface.NumMethods(); i++ { + fn := iface.ActualInterface.Method(i) + methods[i] = &Method{Name: fn.Name(), Signature: fn.Type().(*types.Signature)} + } + return methods +} + +type sortableIFaceList []*Interface + +func (s sortableIFaceList) Len() int { + return len(s) +} + +func (s sortableIFaceList) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} + +func (s sortableIFaceList) Less(i, j int) bool { + return strings.Compare(s[i].Name, s[j].Name) == -1 +} + +func (p *Parser) Interfaces() []*Interface { + ifaces := make(sortableIFaceList, 0) + for _, entry := range p.entries { + declaredIfaces := entry.interfaces + ifaces = p.packageInterfaces(entry.pkg.Types, entry.fileName, declaredIfaces, ifaces) + } + + sort.Sort(ifaces) + return ifaces +} + +func (p *Parser) packageInterfaces( + pkg *types.Package, + fileName string, + declaredInterfaces []string, + ifaces []*Interface) []*Interface { + scope := pkg.Scope() + for _, name := range declaredInterfaces { + obj := scope.Lookup(name) + if obj == nil { + continue + } + + typ, ok := obj.Type().(*types.Named) + if !ok { + continue + } + + name = typ.Obj().Name() + + if typ.Obj().Pkg() == nil { + continue + } + + elem := &Interface{ + Name: name, + Pkg: pkg, + QualifiedName: pkg.Path(), + FileName: fileName, + NamedType: typ, + } + + iface, ok := typ.Underlying().(*types.Interface) + if ok { + elem.IsFunction = false + elem.ActualInterface = iface + } else { + sig, ok := typ.Underlying().(*types.Signature) + if !ok { + continue + } + elem.IsFunction = true + elem.SingleFunction = &Method{Name: "Execute", Signature: sig} + } + + ifaces = append(ifaces, elem) + } + + return ifaces +} diff --git a/vendor/github.com/vektra/mockery/v2/pkg/walker.go b/vendor/github.com/vektra/mockery/v2/pkg/walker.go new file mode 100644 index 000000000..6854b1db9 --- /dev/null +++ b/vendor/github.com/vektra/mockery/v2/pkg/walker.go @@ -0,0 +1,167 @@ +package pkg + +import ( + "context" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "regexp" + "strings" + + "github.com/vektra/mockery/v2/pkg/config" + "github.com/vektra/mockery/v2/pkg/logging" + + "github.com/rs/zerolog" +) + +type Walker struct { + config.Config + BaseDir string + Recursive bool + Filter *regexp.Regexp + LimitOne bool + BuildTags []string +} + +type WalkerVisitor interface { + VisitWalk(context.Context, *Interface) error +} + +func (this *Walker) Walk(ctx context.Context, visitor WalkerVisitor) (generated bool) { + log := zerolog.Ctx(ctx) + ctx = log.WithContext(ctx) + + log.Info().Msgf("Walking") + + parser := NewParser(this.BuildTags) + this.doWalk(ctx, parser, this.BaseDir, visitor) + + err := parser.Load() + if err != nil { + fmt.Fprintf(os.Stderr, "Error walking: %v\n", err) + os.Exit(1) + } + + for _, iface := range parser.Interfaces() { + if !this.Filter.MatchString(iface.Name) { + continue + } + err := visitor.VisitWalk(ctx, iface) + if err != nil { + fmt.Fprintf(os.Stderr, "Error walking %s: %s\n", iface.Name, err) + os.Exit(1) + } + generated = true + if this.LimitOne { + return + } + } + + return +} + +func (this *Walker) doWalk(ctx context.Context, p *Parser, dir string, visitor WalkerVisitor) (generated bool) { + log := zerolog.Ctx(ctx) + ctx = log.WithContext(ctx) + + files, err := ioutil.ReadDir(dir) + if err != nil { + return + } + + for _, file := range files { + if strings.HasPrefix(file.Name(), ".") || strings.HasPrefix(file.Name(), "_") { + continue + } + + path := filepath.Join(dir, file.Name()) + + if file.IsDir() { + if this.Recursive { + generated = this.doWalk(ctx, p, path, visitor) || generated + if generated && this.LimitOne { + return + } + } + continue + } + + if !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") { + continue + } + + err = p.Parse(ctx, path) + if err != nil { + log.Err(err).Msgf("Error parsing file") + continue + } + } + + return +} + +type GeneratorVisitor struct { + config.Config + InPackage bool + Note string + Osp OutputStreamProvider + // The name of the output package, if InPackage is false (defaults to "mocks") + PackageName string + PackageNamePrefix string + StructName string +} + +func (this *GeneratorVisitor) VisitWalk(ctx context.Context, iface *Interface) error { + log := zerolog.Ctx(ctx).With(). + Str(logging.LogKeyInterface, iface.Name). + Str(logging.LogKeyQualifiedName, iface.QualifiedName). + Logger() + ctx = log.WithContext(ctx) + + defer func() { + if r := recover(); r != nil { + log.Error().Msgf("Unable to generate mock: %s", r) + return + } + }() + + var out io.Writer + var pkg string + + if this.InPackage { + pkg = filepath.Dir(iface.FileName) + } else if (this.PackageName == "" || this.PackageName == "mocks") && this.PackageNamePrefix != "" { + // go with package name prefix only when package name is empty or default and package name prefix is specified + pkg = fmt.Sprintf("%s%s", this.PackageNamePrefix, iface.Pkg.Name()) + } else { + pkg = this.PackageName + } + + out, err, closer := this.Osp.GetWriter(ctx, iface) + if err != nil { + log.Err(err).Msgf("Unable to get writer") + os.Exit(1) + } + defer closer() + + gen := NewGenerator(ctx, this.Config, iface, pkg) + gen.GeneratePrologueNote(this.Note) + gen.GeneratePrologue(ctx, pkg) + + err = gen.Generate(ctx) + if err != nil { + return err + } + + log.Info().Msgf("Generating mock") + if !this.Config.DryRun { + err = gen.Write(out) + if err != nil { + return err + } + } + + return nil +} diff --git a/vendor/golang.org/x/crypto/ssh/terminal/terminal.go b/vendor/golang.org/x/crypto/ssh/terminal/terminal.go index 2f04ee5b5..2ffb97bfb 100644 --- a/vendor/golang.org/x/crypto/ssh/terminal/terminal.go +++ b/vendor/golang.org/x/crypto/ssh/terminal/terminal.go @@ -7,6 +7,7 @@ package terminal import ( "bytes" "io" + "runtime" "strconv" "sync" "unicode/utf8" @@ -112,6 +113,7 @@ func NewTerminal(c io.ReadWriter, prompt string) *Terminal { } const ( + keyCtrlC = 3 keyCtrlD = 4 keyCtrlU = 21 keyEnter = '\r' @@ -150,8 +152,12 @@ func bytesToKey(b []byte, pasteActive bool) (rune, []byte) { switch b[0] { case 1: // ^A return keyHome, b[1:] + case 2: // ^B + return keyLeft, b[1:] case 5: // ^E return keyEnd, b[1:] + case 6: // ^F + return keyRight, b[1:] case 8: // ^H return keyBackspace, b[1:] case 11: // ^K @@ -737,6 +743,9 @@ func (t *Terminal) readLine() (line string, err error) { return "", io.EOF } } + if key == keyCtrlC { + return "", io.EOF + } if key == keyPasteStart { t.pasteActive = true if len(t.line) == 0 { @@ -939,6 +948,8 @@ func (s *stRingBuffer) NthPreviousEntry(n int) (value string, ok bool) { // readPasswordLine reads from reader until it finds \n or io.EOF. // The slice returned does not include the \n. // readPasswordLine also ignores any \r it finds. +// Windows uses \r as end of line. So, on Windows, readPasswordLine +// reads until it finds \r and ignores any \n it finds during processing. func readPasswordLine(reader io.Reader) ([]byte, error) { var buf [1]byte var ret []byte @@ -947,10 +958,20 @@ func readPasswordLine(reader io.Reader) ([]byte, error) { n, err := reader.Read(buf[:]) if n > 0 { switch buf[0] { + case '\b': + if len(ret) > 0 { + ret = ret[:len(ret)-1] + } case '\n': - return ret, nil + if runtime.GOOS != "windows" { + return ret, nil + } + // otherwise ignore \n case '\r': - // remove \r from passwords on Windows + if runtime.GOOS == "windows" { + return ret, nil + } + // otherwise ignore \r default: ret = append(ret, buf[0]) } diff --git a/vendor/golang.org/x/crypto/ssh/terminal/util_windows.go b/vendor/golang.org/x/crypto/ssh/terminal/util_windows.go index 5cfdf8f3f..f614e9cb6 100644 --- a/vendor/golang.org/x/crypto/ssh/terminal/util_windows.go +++ b/vendor/golang.org/x/crypto/ssh/terminal/util_windows.go @@ -85,8 +85,8 @@ func ReadPassword(fd int) ([]byte, error) { } old := st - st &^= (windows.ENABLE_ECHO_INPUT) - st |= (windows.ENABLE_PROCESSED_INPUT | windows.ENABLE_LINE_INPUT | windows.ENABLE_PROCESSED_OUTPUT) + st &^= (windows.ENABLE_ECHO_INPUT | windows.ENABLE_LINE_INPUT) + st |= (windows.ENABLE_PROCESSED_OUTPUT | windows.ENABLE_PROCESSED_INPUT) if err := windows.SetConsoleMode(windows.Handle(fd), st); err != nil { return nil, err } diff --git a/vendor/golang.org/x/image/ccitt/reader.go b/vendor/golang.org/x/image/ccitt/reader.go new file mode 100644 index 000000000..62986f977 --- /dev/null +++ b/vendor/golang.org/x/image/ccitt/reader.go @@ -0,0 +1,663 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:generate go run gen.go + +// Package ccitt implements a CCITT (fax) image decoder. +package ccitt + +import ( + "encoding/binary" + "errors" + "image" + "io" + "math/bits" +) + +var ( + errInvalidBounds = errors.New("ccitt: invalid bounds") + errInvalidCode = errors.New("ccitt: invalid code") + errInvalidMode = errors.New("ccitt: invalid mode") + errInvalidOffset = errors.New("ccitt: invalid offset") + errMissingEOL = errors.New("ccitt: missing End-of-Line") + errRunLengthOverflowsWidth = errors.New("ccitt: run length overflows width") + errRunLengthTooLong = errors.New("ccitt: run length too long") + errUnsupportedMode = errors.New("ccitt: unsupported mode") + errUnsupportedSubFormat = errors.New("ccitt: unsupported sub-format") + errUnsupportedWidth = errors.New("ccitt: unsupported width") +) + +// Order specifies the bit ordering in a CCITT data stream. +type Order uint32 + +const ( + // LSB means Least Significant Bits first. + LSB Order = iota + // MSB means Most Significant Bits first. + MSB +) + +// SubFormat represents that the CCITT format consists of a number of +// sub-formats. Decoding or encoding a CCITT data stream requires knowing the +// sub-format context. It is not represented in the data stream per se. +type SubFormat uint32 + +const ( + Group3 SubFormat = iota + Group4 +) + +// Options are optional parameters. +type Options struct { + // Align means that some variable-bit-width codes are byte-aligned. + Align bool + // Invert means that black is the 1 bit or 0xFF byte, and white is 0. + Invert bool +} + +// maxWidth is the maximum (inclusive) supported width. This is a limitation of +// this implementation, to guard against integer overflow, and not anything +// inherent to the CCITT format. +const maxWidth = 1 << 20 + +func invertBytes(b []byte) { + for i, c := range b { + b[i] = ^c + } +} + +func reverseBitsWithinBytes(b []byte) { + for i, c := range b { + b[i] = bits.Reverse8(c) + } +} + +type bitReader struct { + r io.Reader + + // readErr is the error returned from the most recent r.Read call. As the + // io.Reader documentation says, when r.Read returns (n, err), "always + // process the n > 0 bytes returned before considering the error err". + readErr error + + // order is whether to process r's bytes LSB first or MSB first. + order Order + + // The low nBits bits of the bits field hold upcoming bits in LSB order. + bits uint64 + nBits uint32 + + // bytes[br:bw] holds bytes read from r but not yet loaded into bits. + br uint32 + bw uint32 + bytes [1024]uint8 +} + +func (b *bitReader) alignToByteBoundary() { + n := b.nBits & 7 + b.bits >>= n + b.nBits -= n +} + +// nextBitMaxNBits is the maximum possible value of bitReader.nBits after a +// bitReader.nextBit call, provided that bitReader.nBits was not more than this +// value before that call. +// +// Note that the decode function can unread bits, which can temporarily set the +// bitReader.nBits value above nextBitMaxNBits. +const nextBitMaxNBits = 31 + +func (b *bitReader) nextBit() (uint32, error) { + for { + if b.nBits > 0 { + bit := uint32(b.bits) & 1 + b.bits >>= 1 + b.nBits-- + return bit, nil + } + + if available := b.bw - b.br; available >= 4 { + // Read 32 bits, even though b.bits is a uint64, since the decode + // function may need to unread up to maxCodeLength bits, putting + // them back in the remaining (64 - 32) bits. TestMaxCodeLength + // checks that the generated maxCodeLength constant fits. + // + // If changing the Uint32 call, also change nextBitMaxNBits. + b.bits = uint64(binary.LittleEndian.Uint32(b.bytes[b.br:])) + b.br += 4 + b.nBits = 32 + continue + } else if available > 0 { + b.bits = uint64(b.bytes[b.br]) + b.br++ + b.nBits = 8 + continue + } + + if b.readErr != nil { + return 0, b.readErr + } + + n, err := b.r.Read(b.bytes[:]) + b.br = 0 + b.bw = uint32(n) + b.readErr = err + + if b.order != LSB { + reverseBitsWithinBytes(b.bytes[:b.bw]) + } + } +} + +func decode(b *bitReader, decodeTable [][2]int16) (uint32, error) { + nBitsRead, bitsRead, state := uint32(0), uint32(0), int32(1) + for { + bit, err := b.nextBit() + if err != nil { + return 0, err + } + bitsRead |= bit << nBitsRead + nBitsRead++ + // The "&1" is redundant, but can eliminate a bounds check. + state = int32(decodeTable[state][bit&1]) + if state < 0 { + return uint32(^state), nil + } else if state == 0 { + // Unread the bits we've read, then return errInvalidCode. + b.bits = (b.bits << nBitsRead) | uint64(bitsRead) + b.nBits += nBitsRead + return 0, errInvalidCode + } + } +} + +type reader struct { + br bitReader + subFormat SubFormat + + // width is the image width in pixels. + width int + + // rowsRemaining starts at the image height in pixels, when the reader is + // driven through the io.Reader interface, and decrements to zero as rows + // are decoded. When driven through DecodeIntoGray, this field is unused. + rowsRemaining int + + // curr and prev hold the current and previous rows. Each element is either + // 0x00 (black) or 0xFF (white). + // + // prev may be nil, when processing the first row. + curr []byte + prev []byte + + // ri is the read index. curr[:ri] are those bytes of curr that have been + // passed along via the Read method. + // + // When the reader is driven through DecodeIntoGray, instead of through the + // io.Reader interface, this field is unused. + ri int + + // wi is the write index. curr[:wi] are those bytes of curr that have + // already been decoded via the decodeRow method. + // + // What this implementation calls wi is roughly equivalent to what the spec + // calls the a0 index. + wi int + + // These fields are copied from the *Options (which may be nil). + align bool + invert bool + + // atStartOfRow is whether we have just started the row. Some parts of the + // spec say to treat this situation as if "wi = -1". + atStartOfRow bool + + // penColorIsWhite is whether the next run is black or white. + penColorIsWhite bool + + // seenStartOfImage is whether we've called the startDecode method. + seenStartOfImage bool + + // readErr is a sticky error for the Read method. + readErr error +} + +func (z *reader) Read(p []byte) (int, error) { + if z.readErr != nil { + return 0, z.readErr + } + originalP := p + + for len(p) > 0 { + // Allocate buffers (and decode any start-of-image codes), if + // processing the first or second row. + if z.curr == nil { + if !z.seenStartOfImage { + if z.readErr = z.startDecode(); z.readErr != nil { + break + } + z.atStartOfRow = true + } + z.curr = make([]byte, z.width) + } + + // Decode the next row, if necessary. + if z.atStartOfRow { + if z.rowsRemaining <= 0 { + if z.readErr = z.finishDecode(); z.readErr != nil { + break + } + z.readErr = io.EOF + break + } + if z.readErr = z.decodeRow(); z.readErr != nil { + break + } + z.rowsRemaining-- + } + + // Pack from z.curr (1 byte per pixel) to p (1 bit per pixel), up to 8 + // elements per iteration. + i := 0 + for ; i < len(p); i++ { + numToPack := len(z.curr) - z.ri + if numToPack <= 0 { + break + } else if numToPack > 8 { + numToPack = 8 + } + + byteValue := byte(0) + for j := 0; j < numToPack; j++ { + byteValue |= (z.curr[z.ri] & 0x80) >> uint(j) + z.ri++ + } + p[i] = byteValue + } + p = p[i:] + + // Prepare to decode the next row, if necessary. + if z.ri == len(z.curr) { + z.ri, z.curr, z.prev = 0, z.prev, z.curr + z.atStartOfRow = true + } + } + + n := len(originalP) - len(p) + // TODO: when invert is true, should the end-of-row padding bits be 0 or 1? + if z.invert { + invertBytes(originalP[:n]) + } + return n, z.readErr +} + +func (z *reader) penColor() byte { + if z.penColorIsWhite { + return 0xFF + } + return 0x00 +} + +func (z *reader) startDecode() error { + switch z.subFormat { + case Group3: + if err := z.decodeEOL(); err != nil { + return err + } + + case Group4: + // No-op. + + default: + return errUnsupportedSubFormat + } + + z.seenStartOfImage = true + return nil +} + +func (z *reader) finishDecode() error { + numberOfEOLs := 0 + switch z.subFormat { + case Group3: + // The stream ends with a RTC (Return To Control) of 6 consecutive + // EOL's, but we should have already just seen an EOL, either in + // z.startDecode (for a zero-height image) or in z.decodeRow. + numberOfEOLs = 5 + + case Group4: + // The stream ends with two EOL's, the first of which is possibly + // byte-aligned. + numberOfEOLs = 2 + if err := z.decodeEOL(); err == nil { + numberOfEOLs-- + } else if err == errInvalidCode { + // Try again, this time starting from a byte boundary. + z.br.alignToByteBoundary() + } else { + return err + } + + default: + return errUnsupportedSubFormat + } + + for ; numberOfEOLs > 0; numberOfEOLs-- { + if err := z.decodeEOL(); err != nil { + return err + } + } + return nil +} + +func (z *reader) decodeEOL() error { + // TODO: EOL doesn't have to be in the modeDecodeTable. It could be in its + // own table, or we could just hard-code it, especially if we might need to + // cater for optional byte-alignment, or an arbitrary number (potentially + // more than 8) of 0-valued padding bits. + if mode, err := decode(&z.br, modeDecodeTable[:]); err != nil { + return err + } else if mode != modeEOL { + return errMissingEOL + } + return nil +} + +func (z *reader) decodeRow() error { + z.wi = 0 + z.atStartOfRow = true + z.penColorIsWhite = true + + switch z.subFormat { + case Group3: + for ; z.wi < len(z.curr); z.atStartOfRow = false { + if err := z.decodeRun(); err != nil { + return err + } + } + return z.decodeEOL() + + case Group4: + if z.align { + z.br.alignToByteBoundary() + } + + for ; z.wi < len(z.curr); z.atStartOfRow = false { + mode, err := decode(&z.br, modeDecodeTable[:]) + if err != nil { + return err + } + rm := readerMode{} + if mode < uint32(len(readerModes)) { + rm = readerModes[mode] + } + if rm.function == nil { + return errInvalidMode + } + if err := rm.function(z, rm.arg); err != nil { + return err + } + } + return nil + } + + return errUnsupportedSubFormat +} + +func (z *reader) decodeRun() error { + table := blackDecodeTable[:] + if z.penColorIsWhite { + table = whiteDecodeTable[:] + } + + total := 0 + for { + n, err := decode(&z.br, table) + if err != nil { + return err + } + if n > maxWidth { + panic("unreachable") + } + total += int(n) + if total > maxWidth { + return errRunLengthTooLong + } + // Anything 0x3F or below is a terminal code. + if n <= 0x3F { + break + } + } + + if total > (len(z.curr) - z.wi) { + return errRunLengthOverflowsWidth + } + dst := z.curr[z.wi : z.wi+total] + penColor := z.penColor() + for i := range dst { + dst[i] = penColor + } + z.wi += total + z.penColorIsWhite = !z.penColorIsWhite + + return nil +} + +// The various modes' semantics are based on determining a row of pixels' +// "changing elements": those pixels whose color differs from the one on its +// immediate left. +// +// The row above the first row is implicitly all white. Similarly, the column +// to the left of the first column is implicitly all white. +// +// For example, here's Figure 1 in "ITU-T Recommendation T.6", where the +// current and previous rows contain black (B) and white (w) pixels. The a? +// indexes point into curr, the b? indexes point into prev. +// +// b1 b2 +// v v +// prev: BBBBBwwwwwBBBwwwww +// curr: BBBwwwwwBBBBBBwwww +// ^ ^ ^ +// a0 a1 a2 +// +// a0 is the "reference element" or current decoder position, roughly +// equivalent to what this implementation calls reader.wi. +// +// a1 is the next changing element to the right of a0, on the "coding line" +// (the current row). +// +// a2 is the next changing element to the right of a1, again on curr. +// +// b1 is the first changing element on the "reference line" (the previous row) +// to the right of a0 and of opposite color to a0. +// +// b2 is the next changing element to the right of b1, again on prev. +// +// The various modes calculate a1 (and a2, for modeH): +// - modePass calculates that a1 is at or to the right of b2. +// - modeH calculates a1 and a2 without considering b1 or b2. +// - modeV* calculates a1 to be b1 plus an adjustment (between -3 and +3). + +const ( + findB1 = false + findB2 = true +) + +// findB finds either the b1 or b2 value. +func (z *reader) findB(whichB bool) int { + // The initial row is a special case. The previous row is implicitly all + // white, so that there are no changing pixel elements. We return b1 or b2 + // to be at the end of the row. + if len(z.prev) != len(z.curr) { + return len(z.curr) + } + + i := z.wi + + if z.atStartOfRow { + // a0 is implicitly at -1, on a white pixel. b1 is the first black + // pixel in the previous row. b2 is the first white pixel after that. + for ; (i < len(z.prev)) && (z.prev[i] == 0xFF); i++ { + } + if whichB == findB2 { + for ; (i < len(z.prev)) && (z.prev[i] == 0x00); i++ { + } + } + return i + } + + // As per figure 1 above, assume that the current pen color is white. + // First, walk past every contiguous black pixel in prev, starting at a0. + oppositeColor := ^z.penColor() + for ; (i < len(z.prev)) && (z.prev[i] == oppositeColor); i++ { + } + + // Then walk past every contiguous white pixel. + penColor := ^oppositeColor + for ; (i < len(z.prev)) && (z.prev[i] == penColor); i++ { + } + + // We're now at a black pixel (or at the end of the row). That's b1. + if whichB == findB2 { + // If we're looking for b2, walk past every contiguous black pixel + // again. + oppositeColor := ^penColor + for ; (i < len(z.prev)) && (z.prev[i] == oppositeColor); i++ { + } + } + + return i +} + +type readerMode struct { + function func(z *reader, arg int) error + arg int +} + +var readerModes = [...]readerMode{ + modePass: {function: readerModePass}, + modeH: {function: readerModeH}, + modeV0: {function: readerModeV, arg: +0}, + modeVR1: {function: readerModeV, arg: +1}, + modeVR2: {function: readerModeV, arg: +2}, + modeVR3: {function: readerModeV, arg: +3}, + modeVL1: {function: readerModeV, arg: -1}, + modeVL2: {function: readerModeV, arg: -2}, + modeVL3: {function: readerModeV, arg: -3}, + modeExt: {function: readerModeExt}, +} + +func readerModePass(z *reader, arg int) error { + b2 := z.findB(findB2) + if (b2 < z.wi) || (len(z.curr) < b2) { + return errInvalidOffset + } + dst := z.curr[z.wi:b2] + penColor := z.penColor() + for i := range dst { + dst[i] = penColor + } + z.wi = b2 + return nil +} + +func readerModeH(z *reader, arg int) error { + // The first iteration finds a1. The second finds a2. + for i := 0; i < 2; i++ { + if err := z.decodeRun(); err != nil { + return err + } + } + return nil +} + +func readerModeV(z *reader, arg int) error { + a1 := z.findB(findB1) + arg + if (a1 < z.wi) || (len(z.curr) < a1) { + return errInvalidOffset + } + dst := z.curr[z.wi:a1] + penColor := z.penColor() + for i := range dst { + dst[i] = penColor + } + z.wi = a1 + z.penColorIsWhite = !z.penColorIsWhite + return nil +} + +func readerModeExt(z *reader, arg int) error { + return errUnsupportedMode +} + +// DecodeIntoGray decodes the CCITT-formatted data in r into dst. +// +// It returns an error if dst's width and height don't match the implied width +// and height of CCITT-formatted data. +func DecodeIntoGray(dst *image.Gray, r io.Reader, order Order, sf SubFormat, opts *Options) error { + bounds := dst.Bounds() + if (bounds.Dx() < 0) || (bounds.Dy() < 0) { + return errInvalidBounds + } + if bounds.Dx() > maxWidth { + return errUnsupportedWidth + } + + z := reader{ + br: bitReader{r: r, order: order}, + subFormat: sf, + align: (opts != nil) && opts.Align, + invert: (opts != nil) && opts.Invert, + width: bounds.Dx(), + } + if err := z.startDecode(); err != nil { + return err + } + + width := bounds.Dx() + for y := bounds.Min.Y; y < bounds.Max.Y; y++ { + p := (y - bounds.Min.Y) * dst.Stride + z.curr = dst.Pix[p : p+width] + if err := z.decodeRow(); err != nil { + return err + } + z.curr, z.prev = nil, z.curr + } + + if err := z.finishDecode(); err != nil { + return err + } + + if z.invert { + for y := bounds.Min.Y; y < bounds.Max.Y; y++ { + p := (y - bounds.Min.Y) * dst.Stride + invertBytes(dst.Pix[p : p+width]) + } + } + + return nil +} + +// NewReader returns an io.Reader that decodes the CCITT-formatted data in r. +// The resultant byte stream is one bit per pixel (MSB first), with 1 meaning +// white and 0 meaning black. Each row in the result is byte-aligned. +func NewReader(r io.Reader, order Order, sf SubFormat, width int, height int, opts *Options) io.Reader { + readErr := error(nil) + if (width < 0) || (height < 0) { + readErr = errInvalidBounds + } else if width > maxWidth { + readErr = errUnsupportedWidth + } + + return &reader{ + br: bitReader{r: r, order: order}, + subFormat: sf, + align: (opts != nil) && opts.Align, + invert: (opts != nil) && opts.Invert, + width: width, + rowsRemaining: height, + readErr: readErr, + } +} diff --git a/vendor/golang.org/x/image/ccitt/table.go b/vendor/golang.org/x/image/ccitt/table.go new file mode 100644 index 000000000..f01cc12b5 --- /dev/null +++ b/vendor/golang.org/x/image/ccitt/table.go @@ -0,0 +1,989 @@ +// generated by "go run gen.go". DO NOT EDIT. + +package ccitt + +// Each decodeTable is represented by an array of [2]int16's: a binary tree. +// Each array element (other than element 0, which means invalid) is a branch +// node in that tree. The root node is always element 1 (the second element). +// +// To walk the tree, look at the next bit in the bit stream, using it to select +// the first or second element of the [2]int16. If that int16 is 0, we have an +// invalid code. If it is positive, go to that branch node. If it is negative, +// then we have a leaf node, whose value is the bitwise complement (the ^ +// operator) of that int16. +// +// Comments above each decodeTable also show the same structure visually. The +// "b123" lines show the 123'rd branch node. The "=XXXXX" lines show an invalid +// code. The "=v1234" lines show a leaf node with value 1234. When reading the +// bit stream, a 0 or 1 bit means to go up or down, as you move left to right. +// +// For example, in modeDecodeTable, branch node b005 is three steps up from the +// root node, meaning that we have already seen "000". If the next bit is "0" +// then we move to branch node b006. Otherwise, the next bit is "1", and we +// move to the leaf node v0000 (also known as the modePass constant). Indeed, +// the bits that encode modePass are "0001". +// +// Tables 1, 2 and 3 come from the "ITU-T Recommendation T.6: FACSIMILE CODING +// SCHEMES AND CODING CONTROL FUNCTIONS FOR GROUP 4 FACSIMILE APPARATUS" +// specification: +// +// https://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-T.6-198811-I!!PDF-E&type=items + +// modeDecodeTable represents Table 1 and the End-of-Line code. +// +// +=XXXXX +// b015 +-+ +// | +=v0010 +// b014 +-+ +// | +=XXXXX +// b013 +-+ +// | +=XXXXX +// b012 +-+ +// | +=XXXXX +// b011 +-+ +// | +=XXXXX +// b009 +-+ +// | +=v0009 +// b007 +-+ +// | | +=v0008 +// b010 | +-+ +// | +=v0005 +// b006 +-+ +// | | +=v0007 +// b008 | +-+ +// | +=v0004 +// b005 +-+ +// | +=v0000 +// b003 +-+ +// | +=v0001 +// b002 +-+ +// | | +=v0006 +// b004 | +-+ +// | +=v0003 +// b001 +-+ +// +=v0002 +var modeDecodeTable = [...][2]int16{ + 0: {0, 0}, + 1: {2, ^2}, + 2: {3, 4}, + 3: {5, ^1}, + 4: {^6, ^3}, + 5: {6, ^0}, + 6: {7, 8}, + 7: {9, 10}, + 8: {^7, ^4}, + 9: {11, ^9}, + 10: {^8, ^5}, + 11: {12, 0}, + 12: {13, 0}, + 13: {14, 0}, + 14: {15, 0}, + 15: {0, ^10}, +} + +// whiteDecodeTable represents Tables 2 and 3 for a white run. +// +// +=XXXXX +// b059 +-+ +// | | +=v1792 +// b096 | | +-+ +// | | | | +=v1984 +// b100 | | | +-+ +// | | | +=v2048 +// b094 | | +-+ +// | | | | +=v2112 +// b101 | | | | +-+ +// | | | | | +=v2176 +// b097 | | | +-+ +// | | | | +=v2240 +// b102 | | | +-+ +// | | | +=v2304 +// b085 | +-+ +// | | +=v1856 +// b098 | | +-+ +// | | | +=v1920 +// b095 | +-+ +// | | +=v2368 +// b103 | | +-+ +// | | | +=v2432 +// b099 | +-+ +// | | +=v2496 +// b104 | +-+ +// | +=v2560 +// b040 +-+ +// | | +=v0029 +// b060 | +-+ +// | +=v0030 +// b026 +-+ +// | | +=v0045 +// b061 | | +-+ +// | | | +=v0046 +// b041 | +-+ +// | +=v0022 +// b016 +-+ +// | | +=v0023 +// b042 | | +-+ +// | | | | +=v0047 +// b062 | | | +-+ +// | | | +=v0048 +// b027 | +-+ +// | +=v0013 +// b008 +-+ +// | | +=v0020 +// b043 | | +-+ +// | | | | +=v0033 +// b063 | | | +-+ +// | | | +=v0034 +// b028 | | +-+ +// | | | | +=v0035 +// b064 | | | | +-+ +// | | | | | +=v0036 +// b044 | | | +-+ +// | | | | +=v0037 +// b065 | | | +-+ +// | | | +=v0038 +// b017 | +-+ +// | | +=v0019 +// b045 | | +-+ +// | | | | +=v0031 +// b066 | | | +-+ +// | | | +=v0032 +// b029 | +-+ +// | +=v0001 +// b004 +-+ +// | | +=v0012 +// b030 | | +-+ +// | | | | +=v0053 +// b067 | | | | +-+ +// | | | | | +=v0054 +// b046 | | | +-+ +// | | | +=v0026 +// b018 | | +-+ +// | | | | +=v0039 +// b068 | | | | +-+ +// | | | | | +=v0040 +// b047 | | | | +-+ +// | | | | | | +=v0041 +// b069 | | | | | +-+ +// | | | | | +=v0042 +// b031 | | | +-+ +// | | | | +=v0043 +// b070 | | | | +-+ +// | | | | | +=v0044 +// b048 | | | +-+ +// | | | +=v0021 +// b009 | +-+ +// | | +=v0028 +// b049 | | +-+ +// | | | | +=v0061 +// b071 | | | +-+ +// | | | +=v0062 +// b032 | | +-+ +// | | | | +=v0063 +// b072 | | | | +-+ +// | | | | | +=v0000 +// b050 | | | +-+ +// | | | | +=v0320 +// b073 | | | +-+ +// | | | +=v0384 +// b019 | +-+ +// | +=v0010 +// b002 +-+ +// | | +=v0011 +// b020 | | +-+ +// | | | | +=v0027 +// b051 | | | | +-+ +// | | | | | | +=v0059 +// b074 | | | | | +-+ +// | | | | | +=v0060 +// b033 | | | +-+ +// | | | | +=v1472 +// b086 | | | | +-+ +// | | | | | +=v1536 +// b075 | | | | +-+ +// | | | | | | +=v1600 +// b087 | | | | | +-+ +// | | | | | +=v1728 +// b052 | | | +-+ +// | | | +=v0018 +// b010 | | +-+ +// | | | | +=v0024 +// b053 | | | | +-+ +// | | | | | | +=v0049 +// b076 | | | | | +-+ +// | | | | | +=v0050 +// b034 | | | | +-+ +// | | | | | | +=v0051 +// b077 | | | | | | +-+ +// | | | | | | | +=v0052 +// b054 | | | | | +-+ +// | | | | | +=v0025 +// b021 | | | +-+ +// | | | | +=v0055 +// b078 | | | | +-+ +// | | | | | +=v0056 +// b055 | | | | +-+ +// | | | | | | +=v0057 +// b079 | | | | | +-+ +// | | | | | +=v0058 +// b035 | | | +-+ +// | | | +=v0192 +// b005 | +-+ +// | | +=v1664 +// b036 | | +-+ +// | | | | +=v0448 +// b080 | | | | +-+ +// | | | | | +=v0512 +// b056 | | | +-+ +// | | | | +=v0704 +// b088 | | | | +-+ +// | | | | | +=v0768 +// b081 | | | +-+ +// | | | +=v0640 +// b022 | | +-+ +// | | | | +=v0576 +// b082 | | | | +-+ +// | | | | | | +=v0832 +// b089 | | | | | +-+ +// | | | | | +=v0896 +// b057 | | | | +-+ +// | | | | | | +=v0960 +// b090 | | | | | | +-+ +// | | | | | | | +=v1024 +// b083 | | | | | +-+ +// | | | | | | +=v1088 +// b091 | | | | | +-+ +// | | | | | +=v1152 +// b037 | | | +-+ +// | | | | +=v1216 +// b092 | | | | +-+ +// | | | | | +=v1280 +// b084 | | | | +-+ +// | | | | | | +=v1344 +// b093 | | | | | +-+ +// | | | | | +=v1408 +// b058 | | | +-+ +// | | | +=v0256 +// b011 | +-+ +// | +=v0002 +// b001 +-+ +// | +=v0003 +// b012 | +-+ +// | | | +=v0128 +// b023 | | +-+ +// | | +=v0008 +// b006 | +-+ +// | | | +=v0009 +// b024 | | | +-+ +// | | | | | +=v0016 +// b038 | | | | +-+ +// | | | | +=v0017 +// b013 | | +-+ +// | | +=v0004 +// b003 +-+ +// | +=v0005 +// b014 | +-+ +// | | | +=v0014 +// b039 | | | +-+ +// | | | | +=v0015 +// b025 | | +-+ +// | | +=v0064 +// b007 +-+ +// | +=v0006 +// b015 +-+ +// +=v0007 +var whiteDecodeTable = [...][2]int16{ + 0: {0, 0}, + 1: {2, 3}, + 2: {4, 5}, + 3: {6, 7}, + 4: {8, 9}, + 5: {10, 11}, + 6: {12, 13}, + 7: {14, 15}, + 8: {16, 17}, + 9: {18, 19}, + 10: {20, 21}, + 11: {22, ^2}, + 12: {^3, 23}, + 13: {24, ^4}, + 14: {^5, 25}, + 15: {^6, ^7}, + 16: {26, 27}, + 17: {28, 29}, + 18: {30, 31}, + 19: {32, ^10}, + 20: {^11, 33}, + 21: {34, 35}, + 22: {36, 37}, + 23: {^128, ^8}, + 24: {^9, 38}, + 25: {39, ^64}, + 26: {40, 41}, + 27: {42, ^13}, + 28: {43, 44}, + 29: {45, ^1}, + 30: {^12, 46}, + 31: {47, 48}, + 32: {49, 50}, + 33: {51, 52}, + 34: {53, 54}, + 35: {55, ^192}, + 36: {^1664, 56}, + 37: {57, 58}, + 38: {^16, ^17}, + 39: {^14, ^15}, + 40: {59, 60}, + 41: {61, ^22}, + 42: {^23, 62}, + 43: {^20, 63}, + 44: {64, 65}, + 45: {^19, 66}, + 46: {67, ^26}, + 47: {68, 69}, + 48: {70, ^21}, + 49: {^28, 71}, + 50: {72, 73}, + 51: {^27, 74}, + 52: {75, ^18}, + 53: {^24, 76}, + 54: {77, ^25}, + 55: {78, 79}, + 56: {80, 81}, + 57: {82, 83}, + 58: {84, ^256}, + 59: {0, 85}, + 60: {^29, ^30}, + 61: {^45, ^46}, + 62: {^47, ^48}, + 63: {^33, ^34}, + 64: {^35, ^36}, + 65: {^37, ^38}, + 66: {^31, ^32}, + 67: {^53, ^54}, + 68: {^39, ^40}, + 69: {^41, ^42}, + 70: {^43, ^44}, + 71: {^61, ^62}, + 72: {^63, ^0}, + 73: {^320, ^384}, + 74: {^59, ^60}, + 75: {86, 87}, + 76: {^49, ^50}, + 77: {^51, ^52}, + 78: {^55, ^56}, + 79: {^57, ^58}, + 80: {^448, ^512}, + 81: {88, ^640}, + 82: {^576, 89}, + 83: {90, 91}, + 84: {92, 93}, + 85: {94, 95}, + 86: {^1472, ^1536}, + 87: {^1600, ^1728}, + 88: {^704, ^768}, + 89: {^832, ^896}, + 90: {^960, ^1024}, + 91: {^1088, ^1152}, + 92: {^1216, ^1280}, + 93: {^1344, ^1408}, + 94: {96, 97}, + 95: {98, 99}, + 96: {^1792, 100}, + 97: {101, 102}, + 98: {^1856, ^1920}, + 99: {103, 104}, + 100: {^1984, ^2048}, + 101: {^2112, ^2176}, + 102: {^2240, ^2304}, + 103: {^2368, ^2432}, + 104: {^2496, ^2560}, +} + +// blackDecodeTable represents Tables 2 and 3 for a black run. +// +// +=XXXXX +// b017 +-+ +// | | +=v1792 +// b042 | | +-+ +// | | | | +=v1984 +// b063 | | | +-+ +// | | | +=v2048 +// b029 | | +-+ +// | | | | +=v2112 +// b064 | | | | +-+ +// | | | | | +=v2176 +// b043 | | | +-+ +// | | | | +=v2240 +// b065 | | | +-+ +// | | | +=v2304 +// b022 | +-+ +// | | +=v1856 +// b044 | | +-+ +// | | | +=v1920 +// b030 | +-+ +// | | +=v2368 +// b066 | | +-+ +// | | | +=v2432 +// b045 | +-+ +// | | +=v2496 +// b067 | +-+ +// | +=v2560 +// b013 +-+ +// | | +=v0018 +// b031 | | +-+ +// | | | | +=v0052 +// b068 | | | | +-+ +// | | | | | | +=v0640 +// b095 | | | | | +-+ +// | | | | | +=v0704 +// b046 | | | +-+ +// | | | | +=v0768 +// b096 | | | | +-+ +// | | | | | +=v0832 +// b069 | | | +-+ +// | | | +=v0055 +// b023 | | +-+ +// | | | | +=v0056 +// b070 | | | | +-+ +// | | | | | | +=v1280 +// b097 | | | | | +-+ +// | | | | | +=v1344 +// b047 | | | | +-+ +// | | | | | | +=v1408 +// b098 | | | | | | +-+ +// | | | | | | | +=v1472 +// b071 | | | | | +-+ +// | | | | | +=v0059 +// b032 | | | +-+ +// | | | | +=v0060 +// b072 | | | | +-+ +// | | | | | | +=v1536 +// b099 | | | | | +-+ +// | | | | | +=v1600 +// b048 | | | +-+ +// | | | +=v0024 +// b018 | +-+ +// | | +=v0025 +// b049 | | +-+ +// | | | | +=v1664 +// b100 | | | | +-+ +// | | | | | +=v1728 +// b073 | | | +-+ +// | | | +=v0320 +// b033 | | +-+ +// | | | | +=v0384 +// b074 | | | | +-+ +// | | | | | +=v0448 +// b050 | | | +-+ +// | | | | +=v0512 +// b101 | | | | +-+ +// | | | | | +=v0576 +// b075 | | | +-+ +// | | | +=v0053 +// b024 | +-+ +// | | +=v0054 +// b076 | | +-+ +// | | | | +=v0896 +// b102 | | | +-+ +// | | | +=v0960 +// b051 | | +-+ +// | | | | +=v1024 +// b103 | | | | +-+ +// | | | | | +=v1088 +// b077 | | | +-+ +// | | | | +=v1152 +// b104 | | | +-+ +// | | | +=v1216 +// b034 | +-+ +// | +=v0064 +// b010 +-+ +// | | +=v0013 +// b019 | | +-+ +// | | | | +=v0023 +// b052 | | | | +-+ +// | | | | | | +=v0050 +// b078 | | | | | +-+ +// | | | | | +=v0051 +// b035 | | | | +-+ +// | | | | | | +=v0044 +// b079 | | | | | | +-+ +// | | | | | | | +=v0045 +// b053 | | | | | +-+ +// | | | | | | +=v0046 +// b080 | | | | | +-+ +// | | | | | +=v0047 +// b025 | | | +-+ +// | | | | +=v0057 +// b081 | | | | +-+ +// | | | | | +=v0058 +// b054 | | | | +-+ +// | | | | | | +=v0061 +// b082 | | | | | +-+ +// | | | | | +=v0256 +// b036 | | | +-+ +// | | | +=v0016 +// b014 | +-+ +// | | +=v0017 +// b037 | | +-+ +// | | | | +=v0048 +// b083 | | | | +-+ +// | | | | | +=v0049 +// b055 | | | +-+ +// | | | | +=v0062 +// b084 | | | +-+ +// | | | +=v0063 +// b026 | | +-+ +// | | | | +=v0030 +// b085 | | | | +-+ +// | | | | | +=v0031 +// b056 | | | | +-+ +// | | | | | | +=v0032 +// b086 | | | | | +-+ +// | | | | | +=v0033 +// b038 | | | +-+ +// | | | | +=v0040 +// b087 | | | | +-+ +// | | | | | +=v0041 +// b057 | | | +-+ +// | | | +=v0022 +// b020 | +-+ +// | +=v0014 +// b008 +-+ +// | | +=v0010 +// b015 | | +-+ +// | | | +=v0011 +// b011 | +-+ +// | | +=v0015 +// b027 | | +-+ +// | | | | +=v0128 +// b088 | | | | +-+ +// | | | | | +=v0192 +// b058 | | | | +-+ +// | | | | | | +=v0026 +// b089 | | | | | +-+ +// | | | | | +=v0027 +// b039 | | | +-+ +// | | | | +=v0028 +// b090 | | | | +-+ +// | | | | | +=v0029 +// b059 | | | +-+ +// | | | +=v0019 +// b021 | | +-+ +// | | | | +=v0020 +// b060 | | | | +-+ +// | | | | | | +=v0034 +// b091 | | | | | +-+ +// | | | | | +=v0035 +// b040 | | | | +-+ +// | | | | | | +=v0036 +// b092 | | | | | | +-+ +// | | | | | | | +=v0037 +// b061 | | | | | +-+ +// | | | | | | +=v0038 +// b093 | | | | | +-+ +// | | | | | +=v0039 +// b028 | | | +-+ +// | | | | +=v0021 +// b062 | | | | +-+ +// | | | | | | +=v0042 +// b094 | | | | | +-+ +// | | | | | +=v0043 +// b041 | | | +-+ +// | | | +=v0000 +// b016 | +-+ +// | +=v0012 +// b006 +-+ +// | | +=v0009 +// b012 | | +-+ +// | | | +=v0008 +// b009 | +-+ +// | +=v0007 +// b004 +-+ +// | | +=v0006 +// b007 | +-+ +// | +=v0005 +// b002 +-+ +// | | +=v0001 +// b005 | +-+ +// | +=v0004 +// b001 +-+ +// | +=v0003 +// b003 +-+ +// +=v0002 +var blackDecodeTable = [...][2]int16{ + 0: {0, 0}, + 1: {2, 3}, + 2: {4, 5}, + 3: {^3, ^2}, + 4: {6, 7}, + 5: {^1, ^4}, + 6: {8, 9}, + 7: {^6, ^5}, + 8: {10, 11}, + 9: {12, ^7}, + 10: {13, 14}, + 11: {15, 16}, + 12: {^9, ^8}, + 13: {17, 18}, + 14: {19, 20}, + 15: {^10, ^11}, + 16: {21, ^12}, + 17: {0, 22}, + 18: {23, 24}, + 19: {^13, 25}, + 20: {26, ^14}, + 21: {27, 28}, + 22: {29, 30}, + 23: {31, 32}, + 24: {33, 34}, + 25: {35, 36}, + 26: {37, 38}, + 27: {^15, 39}, + 28: {40, 41}, + 29: {42, 43}, + 30: {44, 45}, + 31: {^18, 46}, + 32: {47, 48}, + 33: {49, 50}, + 34: {51, ^64}, + 35: {52, 53}, + 36: {54, ^16}, + 37: {^17, 55}, + 38: {56, 57}, + 39: {58, 59}, + 40: {60, 61}, + 41: {62, ^0}, + 42: {^1792, 63}, + 43: {64, 65}, + 44: {^1856, ^1920}, + 45: {66, 67}, + 46: {68, 69}, + 47: {70, 71}, + 48: {72, ^24}, + 49: {^25, 73}, + 50: {74, 75}, + 51: {76, 77}, + 52: {^23, 78}, + 53: {79, 80}, + 54: {81, 82}, + 55: {83, 84}, + 56: {85, 86}, + 57: {87, ^22}, + 58: {88, 89}, + 59: {90, ^19}, + 60: {^20, 91}, + 61: {92, 93}, + 62: {^21, 94}, + 63: {^1984, ^2048}, + 64: {^2112, ^2176}, + 65: {^2240, ^2304}, + 66: {^2368, ^2432}, + 67: {^2496, ^2560}, + 68: {^52, 95}, + 69: {96, ^55}, + 70: {^56, 97}, + 71: {98, ^59}, + 72: {^60, 99}, + 73: {100, ^320}, + 74: {^384, ^448}, + 75: {101, ^53}, + 76: {^54, 102}, + 77: {103, 104}, + 78: {^50, ^51}, + 79: {^44, ^45}, + 80: {^46, ^47}, + 81: {^57, ^58}, + 82: {^61, ^256}, + 83: {^48, ^49}, + 84: {^62, ^63}, + 85: {^30, ^31}, + 86: {^32, ^33}, + 87: {^40, ^41}, + 88: {^128, ^192}, + 89: {^26, ^27}, + 90: {^28, ^29}, + 91: {^34, ^35}, + 92: {^36, ^37}, + 93: {^38, ^39}, + 94: {^42, ^43}, + 95: {^640, ^704}, + 96: {^768, ^832}, + 97: {^1280, ^1344}, + 98: {^1408, ^1472}, + 99: {^1536, ^1600}, + 100: {^1664, ^1728}, + 101: {^512, ^576}, + 102: {^896, ^960}, + 103: {^1024, ^1088}, + 104: {^1152, ^1216}, +} + +const maxCodeLength = 13 + +// Each encodeTable is represented by an array of bitStrings. + +// bitString is a pair of uint32 values representing a bit code. +// The nBits low bits of bits make up the actual bit code. +// Eg. bitString{0x0004, 8} represents the bitcode "00000100". +type bitString struct { + bits uint32 + nBits uint32 +} + +// modeEncodeTable represents Table 1 and the End-of-Line code. +var modeEncodeTable = [...]bitString{ + 0: {0x0001, 4}, // "0001" + 1: {0x0001, 3}, // "001" + 2: {0x0001, 1}, // "1" + 3: {0x0003, 3}, // "011" + 4: {0x0003, 6}, // "000011" + 5: {0x0003, 7}, // "0000011" + 6: {0x0002, 3}, // "010" + 7: {0x0002, 6}, // "000010" + 8: {0x0002, 7}, // "0000010" + 9: {0x0001, 7}, // "0000001" + 10: {0x0001, 12}, // "000000000001" +} + +// whiteEncodeTable2 represents Table 2 for a white run. +var whiteEncodeTable2 = [...]bitString{ + 0: {0x0035, 8}, // "00110101" + 1: {0x0007, 6}, // "000111" + 2: {0x0007, 4}, // "0111" + 3: {0x0008, 4}, // "1000" + 4: {0x000b, 4}, // "1011" + 5: {0x000c, 4}, // "1100" + 6: {0x000e, 4}, // "1110" + 7: {0x000f, 4}, // "1111" + 8: {0x0013, 5}, // "10011" + 9: {0x0014, 5}, // "10100" + 10: {0x0007, 5}, // "00111" + 11: {0x0008, 5}, // "01000" + 12: {0x0008, 6}, // "001000" + 13: {0x0003, 6}, // "000011" + 14: {0x0034, 6}, // "110100" + 15: {0x0035, 6}, // "110101" + 16: {0x002a, 6}, // "101010" + 17: {0x002b, 6}, // "101011" + 18: {0x0027, 7}, // "0100111" + 19: {0x000c, 7}, // "0001100" + 20: {0x0008, 7}, // "0001000" + 21: {0x0017, 7}, // "0010111" + 22: {0x0003, 7}, // "0000011" + 23: {0x0004, 7}, // "0000100" + 24: {0x0028, 7}, // "0101000" + 25: {0x002b, 7}, // "0101011" + 26: {0x0013, 7}, // "0010011" + 27: {0x0024, 7}, // "0100100" + 28: {0x0018, 7}, // "0011000" + 29: {0x0002, 8}, // "00000010" + 30: {0x0003, 8}, // "00000011" + 31: {0x001a, 8}, // "00011010" + 32: {0x001b, 8}, // "00011011" + 33: {0x0012, 8}, // "00010010" + 34: {0x0013, 8}, // "00010011" + 35: {0x0014, 8}, // "00010100" + 36: {0x0015, 8}, // "00010101" + 37: {0x0016, 8}, // "00010110" + 38: {0x0017, 8}, // "00010111" + 39: {0x0028, 8}, // "00101000" + 40: {0x0029, 8}, // "00101001" + 41: {0x002a, 8}, // "00101010" + 42: {0x002b, 8}, // "00101011" + 43: {0x002c, 8}, // "00101100" + 44: {0x002d, 8}, // "00101101" + 45: {0x0004, 8}, // "00000100" + 46: {0x0005, 8}, // "00000101" + 47: {0x000a, 8}, // "00001010" + 48: {0x000b, 8}, // "00001011" + 49: {0x0052, 8}, // "01010010" + 50: {0x0053, 8}, // "01010011" + 51: {0x0054, 8}, // "01010100" + 52: {0x0055, 8}, // "01010101" + 53: {0x0024, 8}, // "00100100" + 54: {0x0025, 8}, // "00100101" + 55: {0x0058, 8}, // "01011000" + 56: {0x0059, 8}, // "01011001" + 57: {0x005a, 8}, // "01011010" + 58: {0x005b, 8}, // "01011011" + 59: {0x004a, 8}, // "01001010" + 60: {0x004b, 8}, // "01001011" + 61: {0x0032, 8}, // "00110010" + 62: {0x0033, 8}, // "00110011" + 63: {0x0034, 8}, // "00110100" +} + +// whiteEncodeTable3 represents Table 3 for a white run. +var whiteEncodeTable3 = [...]bitString{ + 0: {0x001b, 5}, // "11011" + 1: {0x0012, 5}, // "10010" + 2: {0x0017, 6}, // "010111" + 3: {0x0037, 7}, // "0110111" + 4: {0x0036, 8}, // "00110110" + 5: {0x0037, 8}, // "00110111" + 6: {0x0064, 8}, // "01100100" + 7: {0x0065, 8}, // "01100101" + 8: {0x0068, 8}, // "01101000" + 9: {0x0067, 8}, // "01100111" + 10: {0x00cc, 9}, // "011001100" + 11: {0x00cd, 9}, // "011001101" + 12: {0x00d2, 9}, // "011010010" + 13: {0x00d3, 9}, // "011010011" + 14: {0x00d4, 9}, // "011010100" + 15: {0x00d5, 9}, // "011010101" + 16: {0x00d6, 9}, // "011010110" + 17: {0x00d7, 9}, // "011010111" + 18: {0x00d8, 9}, // "011011000" + 19: {0x00d9, 9}, // "011011001" + 20: {0x00da, 9}, // "011011010" + 21: {0x00db, 9}, // "011011011" + 22: {0x0098, 9}, // "010011000" + 23: {0x0099, 9}, // "010011001" + 24: {0x009a, 9}, // "010011010" + 25: {0x0018, 6}, // "011000" + 26: {0x009b, 9}, // "010011011" + 27: {0x0008, 11}, // "00000001000" + 28: {0x000c, 11}, // "00000001100" + 29: {0x000d, 11}, // "00000001101" + 30: {0x0012, 12}, // "000000010010" + 31: {0x0013, 12}, // "000000010011" + 32: {0x0014, 12}, // "000000010100" + 33: {0x0015, 12}, // "000000010101" + 34: {0x0016, 12}, // "000000010110" + 35: {0x0017, 12}, // "000000010111" + 36: {0x001c, 12}, // "000000011100" + 37: {0x001d, 12}, // "000000011101" + 38: {0x001e, 12}, // "000000011110" + 39: {0x001f, 12}, // "000000011111" +} + +// blackEncodeTable2 represents Table 2 for a black run. +var blackEncodeTable2 = [...]bitString{ + 0: {0x0037, 10}, // "0000110111" + 1: {0x0002, 3}, // "010" + 2: {0x0003, 2}, // "11" + 3: {0x0002, 2}, // "10" + 4: {0x0003, 3}, // "011" + 5: {0x0003, 4}, // "0011" + 6: {0x0002, 4}, // "0010" + 7: {0x0003, 5}, // "00011" + 8: {0x0005, 6}, // "000101" + 9: {0x0004, 6}, // "000100" + 10: {0x0004, 7}, // "0000100" + 11: {0x0005, 7}, // "0000101" + 12: {0x0007, 7}, // "0000111" + 13: {0x0004, 8}, // "00000100" + 14: {0x0007, 8}, // "00000111" + 15: {0x0018, 9}, // "000011000" + 16: {0x0017, 10}, // "0000010111" + 17: {0x0018, 10}, // "0000011000" + 18: {0x0008, 10}, // "0000001000" + 19: {0x0067, 11}, // "00001100111" + 20: {0x0068, 11}, // "00001101000" + 21: {0x006c, 11}, // "00001101100" + 22: {0x0037, 11}, // "00000110111" + 23: {0x0028, 11}, // "00000101000" + 24: {0x0017, 11}, // "00000010111" + 25: {0x0018, 11}, // "00000011000" + 26: {0x00ca, 12}, // "000011001010" + 27: {0x00cb, 12}, // "000011001011" + 28: {0x00cc, 12}, // "000011001100" + 29: {0x00cd, 12}, // "000011001101" + 30: {0x0068, 12}, // "000001101000" + 31: {0x0069, 12}, // "000001101001" + 32: {0x006a, 12}, // "000001101010" + 33: {0x006b, 12}, // "000001101011" + 34: {0x00d2, 12}, // "000011010010" + 35: {0x00d3, 12}, // "000011010011" + 36: {0x00d4, 12}, // "000011010100" + 37: {0x00d5, 12}, // "000011010101" + 38: {0x00d6, 12}, // "000011010110" + 39: {0x00d7, 12}, // "000011010111" + 40: {0x006c, 12}, // "000001101100" + 41: {0x006d, 12}, // "000001101101" + 42: {0x00da, 12}, // "000011011010" + 43: {0x00db, 12}, // "000011011011" + 44: {0x0054, 12}, // "000001010100" + 45: {0x0055, 12}, // "000001010101" + 46: {0x0056, 12}, // "000001010110" + 47: {0x0057, 12}, // "000001010111" + 48: {0x0064, 12}, // "000001100100" + 49: {0x0065, 12}, // "000001100101" + 50: {0x0052, 12}, // "000001010010" + 51: {0x0053, 12}, // "000001010011" + 52: {0x0024, 12}, // "000000100100" + 53: {0x0037, 12}, // "000000110111" + 54: {0x0038, 12}, // "000000111000" + 55: {0x0027, 12}, // "000000100111" + 56: {0x0028, 12}, // "000000101000" + 57: {0x0058, 12}, // "000001011000" + 58: {0x0059, 12}, // "000001011001" + 59: {0x002b, 12}, // "000000101011" + 60: {0x002c, 12}, // "000000101100" + 61: {0x005a, 12}, // "000001011010" + 62: {0x0066, 12}, // "000001100110" + 63: {0x0067, 12}, // "000001100111" +} + +// blackEncodeTable3 represents Table 3 for a black run. +var blackEncodeTable3 = [...]bitString{ + 0: {0x000f, 10}, // "0000001111" + 1: {0x00c8, 12}, // "000011001000" + 2: {0x00c9, 12}, // "000011001001" + 3: {0x005b, 12}, // "000001011011" + 4: {0x0033, 12}, // "000000110011" + 5: {0x0034, 12}, // "000000110100" + 6: {0x0035, 12}, // "000000110101" + 7: {0x006c, 13}, // "0000001101100" + 8: {0x006d, 13}, // "0000001101101" + 9: {0x004a, 13}, // "0000001001010" + 10: {0x004b, 13}, // "0000001001011" + 11: {0x004c, 13}, // "0000001001100" + 12: {0x004d, 13}, // "0000001001101" + 13: {0x0072, 13}, // "0000001110010" + 14: {0x0073, 13}, // "0000001110011" + 15: {0x0074, 13}, // "0000001110100" + 16: {0x0075, 13}, // "0000001110101" + 17: {0x0076, 13}, // "0000001110110" + 18: {0x0077, 13}, // "0000001110111" + 19: {0x0052, 13}, // "0000001010010" + 20: {0x0053, 13}, // "0000001010011" + 21: {0x0054, 13}, // "0000001010100" + 22: {0x0055, 13}, // "0000001010101" + 23: {0x005a, 13}, // "0000001011010" + 24: {0x005b, 13}, // "0000001011011" + 25: {0x0064, 13}, // "0000001100100" + 26: {0x0065, 13}, // "0000001100101" + 27: {0x0008, 11}, // "00000001000" + 28: {0x000c, 11}, // "00000001100" + 29: {0x000d, 11}, // "00000001101" + 30: {0x0012, 12}, // "000000010010" + 31: {0x0013, 12}, // "000000010011" + 32: {0x0014, 12}, // "000000010100" + 33: {0x0015, 12}, // "000000010101" + 34: {0x0016, 12}, // "000000010110" + 35: {0x0017, 12}, // "000000010111" + 36: {0x001c, 12}, // "000000011100" + 37: {0x001d, 12}, // "000000011101" + 38: {0x001e, 12}, // "000000011110" + 39: {0x001f, 12}, // "000000011111" +} + +// COPY PASTE table.go BEGIN + +const ( + modePass = iota // Pass + modeH // Horizontal + modeV0 // Vertical-0 + modeVR1 // Vertical-Right-1 + modeVR2 // Vertical-Right-2 + modeVR3 // Vertical-Right-3 + modeVL1 // Vertical-Left-1 + modeVL2 // Vertical-Left-2 + modeVL3 // Vertical-Left-3 + modeExt // Extension + modeEOL // End-of-Line +) + +// COPY PASTE table.go END diff --git a/vendor/golang.org/x/image/ccitt/writer.go b/vendor/golang.org/x/image/ccitt/writer.go new file mode 100644 index 000000000..87130ab04 --- /dev/null +++ b/vendor/golang.org/x/image/ccitt/writer.go @@ -0,0 +1,102 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package ccitt + +import ( + "encoding/binary" + "io" +) + +type bitWriter struct { + w io.Writer + + // order is whether to process w's bytes LSB first or MSB first. + order Order + + // The high nBits bits of the bits field hold encoded bits to be written to w. + bits uint64 + nBits uint32 + + // bytes[:bw] holds encoded bytes not yet written to w. + // Overflow protection is ensured by using a multiple of 8 as bytes length. + bw uint32 + bytes [1024]uint8 +} + +// flushBits copies 64 bits from b.bits to b.bytes. If b.bytes is then full, it +// is written to b.w. +func (b *bitWriter) flushBits() error { + binary.BigEndian.PutUint64(b.bytes[b.bw:], b.bits) + b.bits = 0 + b.nBits = 0 + b.bw += 8 + if b.bw < uint32(len(b.bytes)) { + return nil + } + b.bw = 0 + if b.order != MSB { + reverseBitsWithinBytes(b.bytes[:]) + } + _, err := b.w.Write(b.bytes[:]) + return err +} + +// close finalizes a bitcode stream by writing any +// pending bits to bitWriter's underlying io.Writer. +func (b *bitWriter) close() error { + // Write any encoded bits to bytes. + if b.nBits > 0 { + binary.BigEndian.PutUint64(b.bytes[b.bw:], b.bits) + b.bw += (b.nBits + 7) >> 3 + } + + if b.order != MSB { + reverseBitsWithinBytes(b.bytes[:b.bw]) + } + + // Write b.bw bytes to b.w. + _, err := b.w.Write(b.bytes[:b.bw]) + return err +} + +// alignToByteBoundary rounds b.nBits up to a multiple of 8. +// If all 64 bits are used, flush them to bitWriter's bytes. +func (b *bitWriter) alignToByteBoundary() error { + if b.nBits = (b.nBits + 7) &^ 7; b.nBits == 64 { + return b.flushBits() + } + return nil +} + +// writeCode writes a variable length bitcode to b's underlying io.Writer. +func (b *bitWriter) writeCode(bs bitString) error { + bits := bs.bits + nBits := bs.nBits + if 64-b.nBits >= nBits { + // b.bits has sufficient room for storing nBits bits. + b.bits |= uint64(bits) << (64 - nBits - b.nBits) + b.nBits += nBits + if b.nBits == 64 { + return b.flushBits() + } + return nil + } + + // Number of leading bits that fill b.bits. + i := 64 - b.nBits + + // Fill b.bits then flush and write remaining bits. + b.bits |= uint64(bits) >> (nBits - i) + b.nBits = 64 + + if err := b.flushBits(); err != nil { + return err + } + + nBits -= i + b.bits = uint64(bits) << (64 - nBits) + b.nBits = nBits + return nil +} diff --git a/vendor/golang.org/x/image/tiff/consts.go b/vendor/golang.org/x/image/tiff/consts.go index 3c51a70be..3e5f7f14d 100644 --- a/vendor/golang.org/x/image/tiff/consts.go +++ b/vendor/golang.org/x/image/tiff/consts.go @@ -42,11 +42,16 @@ const ( tCompression = 259 tPhotometricInterpretation = 262 + tFillOrder = 266 + tStripOffsets = 273 tSamplesPerPixel = 277 tRowsPerStrip = 278 tStripByteCounts = 279 + tT4Options = 292 // CCITT Group 3 options, a set of 32 flag bits. + tT6Options = 293 // CCITT Group 4 options, a set of 32 flag bits. + tTileWidth = 322 tTileLength = 323 tTileOffsets = 324 @@ -112,22 +117,33 @@ const ( mRGB mRGBA mNRGBA + mCMYK ) // CompressionType describes the type of compression used in Options. type CompressionType int +// Constants for supported compression types. const ( Uncompressed CompressionType = iota Deflate + LZW + CCITTGroup3 + CCITTGroup4 ) // specValue returns the compression type constant from the TIFF spec that // is equivalent to c. func (c CompressionType) specValue() uint32 { switch c { + case LZW: + return cLZW case Deflate: return cDeflate + case CCITTGroup3: + return cG3 + case CCITTGroup4: + return cG4 } return cNone } diff --git a/vendor/golang.org/x/image/tiff/fuzz.go b/vendor/golang.org/x/image/tiff/fuzz.go new file mode 100644 index 000000000..ec52c7882 --- /dev/null +++ b/vendor/golang.org/x/image/tiff/fuzz.go @@ -0,0 +1,29 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build gofuzz + +package tiff + +import "bytes" + +func Fuzz(data []byte) int { + cfg, err := DecodeConfig(bytes.NewReader(data)) + if err != nil { + return 0 + } + if cfg.Width*cfg.Height > 1e6 { + return 0 + } + img, err := Decode(bytes.NewReader(data)) + if err != nil { + return 0 + } + var w bytes.Buffer + err = Encode(&w, img, nil) + if err != nil { + panic(err) + } + return 1 +} diff --git a/vendor/golang.org/x/image/tiff/reader.go b/vendor/golang.org/x/image/tiff/reader.go index ce2ef718c..c26ec36bb 100644 --- a/vendor/golang.org/x/image/tiff/reader.go +++ b/vendor/golang.org/x/image/tiff/reader.go @@ -17,6 +17,7 @@ import ( "io/ioutil" "math" + "golang.org/x/image/ccitt" "golang.org/x/image/tiff/lzw" ) @@ -129,7 +130,10 @@ func (d *decoder) parseIFD(p []byte) (int, error) { tTileOffsets, tTileByteCounts, tImageLength, - tImageWidth: + tImageWidth, + tFillOrder, + tT4Options, + tT6Options: val, err := d.ifdUint(p) if err != nil { return 0, err @@ -441,7 +445,8 @@ func newDecoder(r io.Reader) (*decoder, error) { d.config.Height = int(d.firstVal(tImageLength)) if _, ok := d.features[tBitsPerSample]; !ok { - return nil, FormatError("BitsPerSample tag missing") + // Default is 1 per specification. + d.features[tBitsPerSample] = []uint{1} } d.bpp = d.firstVal(tBitsPerSample) switch d.bpp { @@ -539,6 +544,13 @@ func DecodeConfig(r io.Reader) (image.Config, error) { return d.config, nil } +func ccittFillOrder(tiffFillOrder uint) ccitt.Order { + if tiffFillOrder == 2 { + return ccitt.LSB + } + return ccitt.MSB +} + // Decode reads a TIFF image from r and returns it as an image.Image. // The type of Image returned depends on the contents of the TIFF. func Decode(r io.Reader) (img image.Image, err error) { @@ -644,6 +656,16 @@ func Decode(r io.Reader) (img image.Image, err error) { d.buf = make([]byte, n) _, err = d.r.ReadAt(d.buf, offset) } + case cG3: + inv := d.firstVal(tPhotometricInterpretation) == pWhiteIsZero + order := ccittFillOrder(d.firstVal(tFillOrder)) + r := ccitt.NewReader(io.NewSectionReader(d.r, offset, n), order, ccitt.Group3, blkW, blkH, &ccitt.Options{Invert: inv, Align: false}) + d.buf, err = ioutil.ReadAll(r) + case cG4: + inv := d.firstVal(tPhotometricInterpretation) == pWhiteIsZero + order := ccittFillOrder(d.firstVal(tFillOrder)) + r := ccitt.NewReader(io.NewSectionReader(d.r, offset, n), order, ccitt.Group4, blkW, blkH, &ccitt.Options{Invert: inv, Align: false}) + d.buf, err = ioutil.ReadAll(r) case cLZW: r := lzw.NewReader(io.NewSectionReader(d.r, offset, n), lzw.MSB, 8) d.buf, err = ioutil.ReadAll(r) diff --git a/vendor/golang.org/x/mod/LICENSE b/vendor/golang.org/x/mod/LICENSE new file mode 100644 index 000000000..6a66aea5e --- /dev/null +++ b/vendor/golang.org/x/mod/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/golang.org/x/mod/PATENTS b/vendor/golang.org/x/mod/PATENTS new file mode 100644 index 000000000..733099041 --- /dev/null +++ b/vendor/golang.org/x/mod/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/vendor/golang.org/x/tools/internal/module/module.go b/vendor/golang.org/x/mod/module/module.go similarity index 57% rename from vendor/golang.org/x/tools/internal/module/module.go rename to vendor/golang.org/x/mod/module/module.go index 9a4edb9de..6cd37280a 100644 --- a/vendor/golang.org/x/tools/internal/module/module.go +++ b/vendor/golang.org/x/mod/module/module.go @@ -2,8 +2,86 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// Package module defines the module.Version type -// along with support code. +// Package module defines the module.Version type along with support code. +// +// The module.Version type is a simple Path, Version pair: +// +// type Version struct { +// Path string +// Version string +// } +// +// There are no restrictions imposed directly by use of this structure, +// but additional checking functions, most notably Check, verify that +// a particular path, version pair is valid. +// +// Escaped Paths +// +// Module paths appear as substrings of file system paths +// (in the download cache) and of web server URLs in the proxy protocol. +// In general we cannot rely on file systems to be case-sensitive, +// nor can we rely on web servers, since they read from file systems. +// That is, we cannot rely on the file system to keep rsc.io/QUOTE +// and rsc.io/quote separate. Windows and macOS don't. +// Instead, we must never require two different casings of a file path. +// Because we want the download cache to match the proxy protocol, +// and because we want the proxy protocol to be possible to serve +// from a tree of static files (which might be stored on a case-insensitive +// file system), the proxy protocol must never require two different casings +// of a URL path either. +// +// One possibility would be to make the escaped form be the lowercase +// hexadecimal encoding of the actual path bytes. This would avoid ever +// needing different casings of a file path, but it would be fairly illegible +// to most programmers when those paths appeared in the file system +// (including in file paths in compiler errors and stack traces) +// in web server logs, and so on. Instead, we want a safe escaped form that +// leaves most paths unaltered. +// +// The safe escaped form is to replace every uppercase letter +// with an exclamation mark followed by the letter's lowercase equivalent. +// +// For example, +// +// github.com/Azure/azure-sdk-for-go -> github.com/!azure/azure-sdk-for-go. +// github.com/GoogleCloudPlatform/cloudsql-proxy -> github.com/!google!cloud!platform/cloudsql-proxy +// github.com/Sirupsen/logrus -> github.com/!sirupsen/logrus. +// +// Import paths that avoid upper-case letters are left unchanged. +// Note that because import paths are ASCII-only and avoid various +// problematic punctuation (like : < and >), the escaped form is also ASCII-only +// and avoids the same problematic punctuation. +// +// Import paths have never allowed exclamation marks, so there is no +// need to define how to escape a literal !. +// +// Unicode Restrictions +// +// Today, paths are disallowed from using Unicode. +// +// Although paths are currently disallowed from using Unicode, +// we would like at some point to allow Unicode letters as well, to assume that +// file systems and URLs are Unicode-safe (storing UTF-8), and apply +// the !-for-uppercase convention for escaping them in the file system. +// But there are at least two subtle considerations. +// +// First, note that not all case-fold equivalent distinct runes +// form an upper/lower pair. +// For example, U+004B ('K'), U+006B ('k'), and U+212A ('K' for Kelvin) +// are three distinct runes that case-fold to each other. +// When we do add Unicode letters, we must not assume that upper/lower +// are the only case-equivalent pairs. +// Perhaps the Kelvin symbol would be disallowed entirely, for example. +// Or perhaps it would escape as "!!k", or perhaps as "(212A)". +// +// Second, it would be nice to allow Unicode marks as well as letters, +// but marks include combining marks, and then we must deal not +// only with case folding but also normalization: both U+00E9 ('é') +// and U+0065 U+0301 ('e' followed by combining acute accent) +// look the same on the page and are treated by some file systems +// as the same path. If we do allow Unicode marks in paths, there +// must be some kind of normalization to allow only one canonical +// encoding of any character used in an import path. package module // IMPORTANT NOTE @@ -24,22 +102,95 @@ import ( "unicode" "unicode/utf8" - "golang.org/x/tools/internal/semver" + "golang.org/x/mod/semver" + errors "golang.org/x/xerrors" ) -// A Version is defined by a module path and version pair. +// A Version (for clients, a module.Version) is defined by a module path and version pair. +// These are stored in their plain (unescaped) form. type Version struct { + // Path is a module path, like "golang.org/x/text" or "rsc.io/quote/v2". Path string // Version is usually a semantic version in canonical form. - // There are two exceptions to this general rule. + // There are three exceptions to this general rule. // First, the top-level target of a build has no specific version // and uses Version = "". // Second, during MVS calculations the version "none" is used // to represent the decision to take no version of a given module. + // Third, filesystem paths found in "replace" directives are + // represented by a path with an empty version. Version string `json:",omitempty"` } +// String returns a representation of the Version suitable for logging +// (Path@Version, or just Path if Version is empty). +func (m Version) String() string { + if m.Version == "" { + return m.Path + } + return m.Path + "@" + m.Version +} + +// A ModuleError indicates an error specific to a module. +type ModuleError struct { + Path string + Version string + Err error +} + +// VersionError returns a ModuleError derived from a Version and error, +// or err itself if it is already such an error. +func VersionError(v Version, err error) error { + var mErr *ModuleError + if errors.As(err, &mErr) && mErr.Path == v.Path && mErr.Version == v.Version { + return err + } + return &ModuleError{ + Path: v.Path, + Version: v.Version, + Err: err, + } +} + +func (e *ModuleError) Error() string { + if v, ok := e.Err.(*InvalidVersionError); ok { + return fmt.Sprintf("%s@%s: invalid %s: %v", e.Path, v.Version, v.noun(), v.Err) + } + if e.Version != "" { + return fmt.Sprintf("%s@%s: %v", e.Path, e.Version, e.Err) + } + return fmt.Sprintf("module %s: %v", e.Path, e.Err) +} + +func (e *ModuleError) Unwrap() error { return e.Err } + +// An InvalidVersionError indicates an error specific to a version, with the +// module path unknown or specified externally. +// +// A ModuleError may wrap an InvalidVersionError, but an InvalidVersionError +// must not wrap a ModuleError. +type InvalidVersionError struct { + Version string + Pseudo bool + Err error +} + +// noun returns either "version" or "pseudo-version", depending on whether +// e.Version is a pseudo-version. +func (e *InvalidVersionError) noun() string { + if e.Pseudo { + return "pseudo-version" + } + return "version" +} + +func (e *InvalidVersionError) Error() string { + return fmt.Sprintf("%s %q invalid: %s", e.noun(), e.Version, e.Err) +} + +func (e *InvalidVersionError) Unwrap() error { return e.Err } + // Check checks that a given module path, version pair is valid. // In addition to the path being a valid module path // and the version being a valid semantic version, @@ -51,17 +202,14 @@ func Check(path, version string) error { return err } if !semver.IsValid(version) { - return fmt.Errorf("malformed semantic version %v", version) + return &ModuleError{ + Path: path, + Err: &InvalidVersionError{Version: version, Err: errors.New("not a semantic version")}, + } } _, pathMajor, _ := SplitPathVersion(path) - if !MatchPathMajor(version, pathMajor) { - if pathMajor == "" { - pathMajor = "v0 or v1" - } - if pathMajor[0] == '.' { // .v1 - pathMajor = pathMajor[1:] - } - return fmt.Errorf("mismatched module path %v and version %v (want %v)", path, version, pathMajor) + if err := CheckPathMajor(version, pathMajor); err != nil { + return &ModuleError{Path: path, Err: err} } return nil } @@ -79,7 +227,7 @@ func firstPathOK(r rune) bool { // Paths can be ASCII letters, ASCII digits, and limited ASCII punctuation: + - . _ and ~. // This matches what "go get" has historically recognized in import paths. // TODO(rsc): We would like to allow Unicode letters, but that requires additional -// care in the safe encoding (see note below). +// care in the safe encoding (see "escaped paths" above). func pathOK(r rune) bool { if r < utf8.RuneSelf { return r == '+' || r == '-' || r == '.' || r == '_' || r == '~' || @@ -94,7 +242,7 @@ func pathOK(r rune) bool { // For now we allow all Unicode letters but otherwise limit to pathOK plus a few more punctuation characters. // If we expand the set of allowed characters here, we have to // work harder at detecting potential case-folding and normalization collisions. -// See note about "safe encoding" below. +// See note about "escaped paths" above. func fileNameOK(r rune) bool { if r < utf8.RuneSelf { // Entire set of ASCII punctuation, from which we remove characters: @@ -120,6 +268,17 @@ func fileNameOK(r rune) bool { } // CheckPath checks that a module path is valid. +// A valid module path is a valid import path, as checked by CheckImportPath, +// with two additional constraints. +// First, the leading path element (up to the first slash, if any), +// by convention a domain name, must contain only lower-case ASCII letters, +// ASCII digits, dots (U+002E), and dashes (U+002D); +// it must contain at least one dot and cannot start with a dash. +// Second, for a final path element of the form /vN, where N looks numeric +// (ASCII digits and dots) must not begin with a leading zero, must not be /v1, +// and must not contain any dots. For paths beginning with "gopkg.in/", +// this second requirement is replaced by a requirement that the path +// follow the gopkg.in server's conventions. func CheckPath(path string) error { if err := checkPath(path, false); err != nil { return fmt.Errorf("malformed module path %q: %v", path, err) @@ -149,6 +308,20 @@ func CheckPath(path string) error { } // CheckImportPath checks that an import path is valid. +// +// A valid import path consists of one or more valid path elements +// separated by slashes (U+002F). (It must not begin with nor end in a slash.) +// +// A valid path element is a non-empty string made up of +// ASCII letters, ASCII digits, and limited ASCII punctuation: + - . _ and ~. +// It must not begin or end with a dot (U+002E), nor contain two dots in a row. +// +// The element prefix up to the first dot must not be a reserved file name +// on Windows, regardless of case (CON, com1, NuL, and so on). +// +// CheckImportPath may be less restrictive in the future, but see the +// top-level package documentation for additional information about +// subtleties of Unicode. func CheckImportPath(path string) error { if err := checkPath(path, false); err != nil { return fmt.Errorf("malformed import path %q: %v", path, err) @@ -169,8 +342,8 @@ func checkPath(path string, fileName bool) error { if path == "" { return fmt.Errorf("empty string") } - if strings.Contains(path, "..") { - return fmt.Errorf("double dot") + if path[0] == '-' { + return fmt.Errorf("leading dash") } if strings.Contains(path, "//") { return fmt.Errorf("double slash") @@ -226,13 +399,24 @@ func checkElem(elem string, fileName bool) error { } for _, bad := range badWindowsNames { if strings.EqualFold(bad, short) { - return fmt.Errorf("disallowed path element %q", elem) + return fmt.Errorf("%q disallowed as path element component on Windows", short) } } return nil } -// CheckFilePath checks whether a slash-separated file path is valid. +// CheckFilePath checks that a slash-separated file path is valid. +// The definition of a valid file path is the same as the definition +// of a valid import path except that the set of allowed characters is larger: +// all Unicode letters, ASCII digits, the ASCII space character (U+0020), +// and the ASCII punctuation characters +// “!#$%&()+,-.=@[]^_{}~”. +// (The excluded punctuation characters, " * < > ? ` ' | / \ and :, +// have special meanings in certain shells or operating systems.) +// +// CheckFilePath may be less restrictive in the future, but see the +// top-level package documentation for additional information about +// subtleties of Unicode. func CheckFilePath(path string) error { if err := checkPath(path, true); err != nil { return fmt.Errorf("malformed file path %q: %v", path, err) @@ -271,6 +455,9 @@ var badWindowsNames = []string{ // and version is either empty or "/vN" for N >= 2. // As a special case, gopkg.in paths are recognized directly; // they require ".vN" instead of "/vN", and for all N, not just N >= 2. +// SplitPathVersion returns with ok = false when presented with +// a path whose last path element does not satisfy the constraints +// applied by CheckPath, such as "example.com/pkg/v1" or "example.com/pkg/v1.2". func SplitPathVersion(path string) (prefix, pathMajor string, ok bool) { if strings.HasPrefix(path, "gopkg.in/") { return splitGopkgIn(path) @@ -319,20 +506,65 @@ func splitGopkgIn(path string) (prefix, pathMajor string, ok bool) { // MatchPathMajor reports whether the semantic version v // matches the path major version pathMajor. +// +// MatchPathMajor returns true if and only if CheckPathMajor returns nil. func MatchPathMajor(v, pathMajor string) bool { + return CheckPathMajor(v, pathMajor) == nil +} + +// CheckPathMajor returns a non-nil error if the semantic version v +// does not match the path major version pathMajor. +func CheckPathMajor(v, pathMajor string) error { + // TODO(jayconrod): return errors or panic for invalid inputs. This function + // (and others) was covered by integration tests for cmd/go, and surrounding + // code protected against invalid inputs like non-canonical versions. if strings.HasPrefix(pathMajor, ".v") && strings.HasSuffix(pathMajor, "-unstable") { pathMajor = strings.TrimSuffix(pathMajor, "-unstable") } if strings.HasPrefix(v, "v0.0.0-") && pathMajor == ".v1" { // Allow old bug in pseudo-versions that generated v0.0.0- pseudoversion for gopkg .v1. // For example, gopkg.in/yaml.v2@v2.2.1's go.mod requires gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405. - return true + return nil } m := semver.Major(v) if pathMajor == "" { - return m == "v0" || m == "v1" || semver.Build(v) == "+incompatible" + if m == "v0" || m == "v1" || semver.Build(v) == "+incompatible" { + return nil + } + pathMajor = "v0 or v1" + } else if pathMajor[0] == '/' || pathMajor[0] == '.' { + if m == pathMajor[1:] { + return nil + } + pathMajor = pathMajor[1:] } - return (pathMajor[0] == '/' || pathMajor[0] == '.') && m == pathMajor[1:] + return &InvalidVersionError{ + Version: v, + Err: fmt.Errorf("should be %s, not %s", pathMajor, semver.Major(v)), + } +} + +// PathMajorPrefix returns the major-version tag prefix implied by pathMajor. +// An empty PathMajorPrefix allows either v0 or v1. +// +// Note that MatchPathMajor may accept some versions that do not actually begin +// with this prefix: namely, it accepts a 'v0.0.0-' prefix for a '.v1' +// pathMajor, even though that pathMajor implies 'v1' tagging. +func PathMajorPrefix(pathMajor string) string { + if pathMajor == "" { + return "" + } + if pathMajor[0] != '/' && pathMajor[0] != '.' { + panic("pathMajor suffix " + pathMajor + " passed to PathMajorPrefix lacks separator") + } + if strings.HasPrefix(pathMajor, ".v") && strings.HasSuffix(pathMajor, "-unstable") { + pathMajor = strings.TrimSuffix(pathMajor, "-unstable") + } + m := pathMajor[1:] + if m != semver.Major(m) { + panic("pathMajor suffix " + pathMajor + "passed to PathMajorPrefix is not a valid major version") + } + return m } // CanonicalVersion returns the canonical form of the version string v. @@ -345,7 +577,10 @@ func CanonicalVersion(v string) string { return cv } -// Sort sorts the list by Path, breaking ties by comparing Versions. +// Sort sorts the list by Path, breaking ties by comparing Version fields. +// The Version fields are interpreted as semantic versions (using semver.Compare) +// optionally followed by a tie-breaking suffix introduced by a slash character, +// like in "v0.0.1/go.mod". func Sort(list []Version) { sort.Slice(list, func(i, j int) bool { mi := list[i] @@ -372,93 +607,36 @@ func Sort(list []Version) { }) } -// Safe encodings -// -// Module paths appear as substrings of file system paths -// (in the download cache) and of web server URLs in the proxy protocol. -// In general we cannot rely on file systems to be case-sensitive, -// nor can we rely on web servers, since they read from file systems. -// That is, we cannot rely on the file system to keep rsc.io/QUOTE -// and rsc.io/quote separate. Windows and macOS don't. -// Instead, we must never require two different casings of a file path. -// Because we want the download cache to match the proxy protocol, -// and because we want the proxy protocol to be possible to serve -// from a tree of static files (which might be stored on a case-insensitive -// file system), the proxy protocol must never require two different casings -// of a URL path either. -// -// One possibility would be to make the safe encoding be the lowercase -// hexadecimal encoding of the actual path bytes. This would avoid ever -// needing different casings of a file path, but it would be fairly illegible -// to most programmers when those paths appeared in the file system -// (including in file paths in compiler errors and stack traces) -// in web server logs, and so on. Instead, we want a safe encoding that -// leaves most paths unaltered. -// -// The safe encoding is this: -// replace every uppercase letter with an exclamation mark -// followed by the letter's lowercase equivalent. -// -// For example, -// github.com/Azure/azure-sdk-for-go -> github.com/!azure/azure-sdk-for-go. -// github.com/GoogleCloudPlatform/cloudsql-proxy -> github.com/!google!cloud!platform/cloudsql-proxy -// github.com/Sirupsen/logrus -> github.com/!sirupsen/logrus. -// -// Import paths that avoid upper-case letters are left unchanged. -// Note that because import paths are ASCII-only and avoid various -// problematic punctuation (like : < and >), the safe encoding is also ASCII-only -// and avoids the same problematic punctuation. -// -// Import paths have never allowed exclamation marks, so there is no -// need to define how to encode a literal !. -// -// Although paths are disallowed from using Unicode (see pathOK above), -// the eventual plan is to allow Unicode letters as well, to assume that -// file systems and URLs are Unicode-safe (storing UTF-8), and apply -// the !-for-uppercase convention. Note however that not all runes that -// are different but case-fold equivalent are an upper/lower pair. -// For example, U+004B ('K'), U+006B ('k'), and U+212A ('K' for Kelvin) -// are considered to case-fold to each other. When we do add Unicode -// letters, we must not assume that upper/lower are the only case-equivalent pairs. -// Perhaps the Kelvin symbol would be disallowed entirely, for example. -// Or perhaps it would encode as "!!k", or perhaps as "(212A)". -// -// Also, it would be nice to allow Unicode marks as well as letters, -// but marks include combining marks, and then we must deal not -// only with case folding but also normalization: both U+00E9 ('é') -// and U+0065 U+0301 ('e' followed by combining acute accent) -// look the same on the page and are treated by some file systems -// as the same path. If we do allow Unicode marks in paths, there -// must be some kind of normalization to allow only one canonical -// encoding of any character used in an import path. - -// EncodePath returns the safe encoding of the given module path. +// EscapePath returns the escaped form of the given module path. // It fails if the module path is invalid. -func EncodePath(path string) (encoding string, err error) { +func EscapePath(path string) (escaped string, err error) { if err := CheckPath(path); err != nil { return "", err } - return encodeString(path) + return escapeString(path) } -// EncodeVersion returns the safe encoding of the given module version. +// EscapeVersion returns the escaped form of the given module version. // Versions are allowed to be in non-semver form but must be valid file names // and not contain exclamation marks. -func EncodeVersion(v string) (encoding string, err error) { +func EscapeVersion(v string) (escaped string, err error) { if err := checkElem(v, true); err != nil || strings.Contains(v, "!") { - return "", fmt.Errorf("disallowed version string %q", v) + return "", &InvalidVersionError{ + Version: v, + Err: fmt.Errorf("disallowed version string"), + } } - return encodeString(v) + return escapeString(v) } -func encodeString(s string) (encoding string, err error) { +func escapeString(s string) (escaped string, err error) { haveUpper := false for _, r := range s { if r == '!' || r >= utf8.RuneSelf { // This should be disallowed by CheckPath, but diagnose anyway. - // The correctness of the encoding loop below depends on it. - return "", fmt.Errorf("internal error: inconsistency in EncodePath") + // The correctness of the escaping loop below depends on it. + return "", fmt.Errorf("internal error: inconsistency in EscapePath") } if 'A' <= r && r <= 'Z' { haveUpper = true @@ -480,39 +658,39 @@ func encodeString(s string) (encoding string, err error) { return string(buf), nil } -// DecodePath returns the module path of the given safe encoding. -// It fails if the encoding is invalid or encodes an invalid path. -func DecodePath(encoding string) (path string, err error) { - path, ok := decodeString(encoding) +// UnescapePath returns the module path for the given escaped path. +// It fails if the escaped path is invalid or describes an invalid path. +func UnescapePath(escaped string) (path string, err error) { + path, ok := unescapeString(escaped) if !ok { - return "", fmt.Errorf("invalid module path encoding %q", encoding) + return "", fmt.Errorf("invalid escaped module path %q", escaped) } if err := CheckPath(path); err != nil { - return "", fmt.Errorf("invalid module path encoding %q: %v", encoding, err) + return "", fmt.Errorf("invalid escaped module path %q: %v", escaped, err) } return path, nil } -// DecodeVersion returns the version string for the given safe encoding. -// It fails if the encoding is invalid or encodes an invalid version. +// UnescapeVersion returns the version string for the given escaped version. +// It fails if the escaped form is invalid or describes an invalid version. // Versions are allowed to be in non-semver form but must be valid file names // and not contain exclamation marks. -func DecodeVersion(encoding string) (v string, err error) { - v, ok := decodeString(encoding) +func UnescapeVersion(escaped string) (v string, err error) { + v, ok := unescapeString(escaped) if !ok { - return "", fmt.Errorf("invalid version encoding %q", encoding) + return "", fmt.Errorf("invalid escaped version %q", escaped) } if err := checkElem(v, true); err != nil { - return "", fmt.Errorf("disallowed version string %q", v) + return "", fmt.Errorf("invalid escaped version %q: %v", v, err) } return v, nil } -func decodeString(encoding string) (string, bool) { +func unescapeString(escaped string) (string, bool) { var buf []byte bang := false - for _, r := range encoding { + for _, r := range escaped { if r >= utf8.RuneSelf { return "", false } diff --git a/vendor/golang.org/x/tools/internal/semver/semver.go b/vendor/golang.org/x/mod/semver/semver.go similarity index 99% rename from vendor/golang.org/x/tools/internal/semver/semver.go rename to vendor/golang.org/x/mod/semver/semver.go index 4af7118e5..2988e3cf9 100644 --- a/vendor/golang.org/x/tools/internal/semver/semver.go +++ b/vendor/golang.org/x/mod/semver/semver.go @@ -107,7 +107,7 @@ func Build(v string) string { } // Compare returns an integer comparing two versions according to -// according to semantic version precedence. +// semantic version precedence. // The result will be 0 if v == w, -1 if v < w, or +1 if v > w. // // An invalid semantic version string is considered less than a valid one. @@ -263,7 +263,7 @@ func parseBuild(v string) (t, rest string, ok bool) { i := 1 start := 1 for i < len(v) { - if !isIdentChar(v[i]) { + if !isIdentChar(v[i]) && v[i] != '.' { return } if v[i] == '.' { diff --git a/vendor/golang.org/x/net/publicsuffix/table.go b/vendor/golang.org/x/net/publicsuffix/table.go index 8e1c9d3dd..ec2bde8cb 100644 --- a/vendor/golang.org/x/net/publicsuffix/table.go +++ b/vendor/golang.org/x/net/publicsuffix/table.go @@ -2,7 +2,7 @@ package publicsuffix -const version = "publicsuffix.org's public_suffix_list.dat, git revision dbe9da0a8abeab1b6257c57668d1ecb584189951 (2020-05-27T00:50:31Z)" +const version = "publicsuffix.org's public_suffix_list.dat, git revision bdbe9dfd268d040fc826766b1d4e27dc4416fe73 (2020-08-10T09:26:55Z)" const ( nodesBitsChildren = 10 @@ -23,490 +23,492 @@ const ( ) // numTLD is the number of top level domains. -const numTLD = 1525 +const numTLD = 1518 // Text is the combined text of all labels. -const text = "9guacuiababia-goracleaningroks-theatree12hpalermomahachijoinvill" + - "eksvikaracoldwarszawabogadobeaemcloud66bjarkoyusuharabjerkreimdb" + - "altimore-og-romsdalipayokozebizenakanotoddenavuotnarashinobninsk" + - "aragandaustevoll-o-g-i-naval-d-aosta-valleyboltateshinanomachimk" + - "entateyamagrocerybnikeisenbahn4tatarantours3-ap-northeast-2ix443" + - "2-balsan-suedtirolkuszczytnoipirangamvik12bjugnieznord-frontierb" + - "lackfridayusuisservehumourbloombergbauernishiazaindielddanuorrin" + - "digenamsosnowiechernihivgubs3-website-sa-east-1bloxcms3-website-" + - "us-east-1bluedaemoneyuu2-localhostoregontrailroadrayddnsfreebox-" + - "osascoli-picenordre-landraydns3-website-us-west-1bmoattachments3" + - "-website-us-west-2bms5yuzawabmwedeploybnrwegroweibolognagasakind" + - "eroybomloabathsbchernivtsiciliabondrivefsnillfjordrobaknoluoktac" + - "hikawakuyabukievennodesadoes-itvedestrandrudupontariobranconakan" + - "iikawatanagurabonnishigoddabookinghostfoldnavyboomlair-traffic-c" + - "ontrolleyboschaefflerdalivornohtawaramotoineppueblockbustermezja" + - "mpagefrontapparachutinglobalashovhachinohedmarkarpaczeladzlglobo" + - "avistanbulsan-sudtirolombardynaliaskimitsubatamibugattiffanycher" + - "novtsymantechnologybostikaruizawabostonakijinsekikogentappsselfi" + - "paraglidinglogoweirbotanicalgardenishiharabotanicgardenishiizuna" + - "zukindustriabotanynysagaeroclubmedecincinnationwidealerbouncemer" + - "ckmsdnipropetrovskjervoyageometre-experts-comptablesakyotanabell" + - "unord-aurdalpha-myqnapcloudaccesscambridgeiseiyoichippubetsubets" + - "ugarugbydgoszczecinemagentositecnologiabounty-fullensakerryprope" + - "rtiesalangenishikatakinoueboutiquebechirurgiens-dentistes-en-fra" + - "ncebozen-sudtirolomzaporizhzhegurindustriesteamfamberkeleybozen-" + - "suedtirolondrinapleskarumaifarmsteadurbanamexhibitionishikatsura" + - "git-reposalondonetskasaokamiminersaltdalorenskogloppenzaolbia-te" + - "mpio-olbiatempioolbialystokkepnogatagajobojinfinitintelligencebp" + - "lacedogawarabikomaezakirunorddalotenkawabrandywinevalleybrasilia" + - "brindisibenikindlebtimnetzparisor-fronishikawazukamisunagawabris" + - "toloseyouriparliamentjomeloyalistoragebritishcolumbialowiezagani" + - "shimerabroadcastleclerchiryukyuragifuchungbukharavennagatorodoyb" + - "roadwaybroke-itjxfinitybrokerbronnoysundurhamburglugmbhartipscbg" + - "minakamichiharabrothermesaverdealstahaugesunderseaportsinfolldal" + - "ottebrowsersafetymarketsaludweberbrumunddalottokonamegatakazakin" + - "ternationalfirearmsalvadordalibabalena-devicesalzburgmodellingmx" + - "javald-aostarnbergretakanabeautysvardoesntexisteingeekashibataka" + - "tsukiyosatokamachintaifun-dnsdojolsterbrunelastxn--0trq7p7nnishi" + - "nomiyashironomutashinaintuitkmaxxn--11b4c3dynathomebuiltwithdark" + - "ashiharabrusselsamegawabruxellesamnangerbryansklepparmattelekomm" + - "unikationishinoomotegobrynewhollandyndns-at-homedepotenzamamidsu" + - "ndyndns-at-workisboringrimstadyndns-blogdnsampalacebuskerudinewj" + - "erseybuzentsujiiebuzzwellbeingzonebwfarsundyndns-freeboxosloftra" + - "nakanojoetsuwanouchikujogaszkolancashirecreationishinoshimatsuur" + - "abzhitomirumalatvuopmicrolightingripebzzcommunexus-2community-pr" + - "ochowicecomoarekecomparemarkerryhotelsantacruzsantafedjejuifmetl" + - "ifeinsurancecompute-1computerhistoryofscience-fictioncomsecurity" + - "tacticsantamariakecondoshichinohealth-carereformitakeharaconfere" + - "nceconstructionconsuladonnaharimalopolskanlandyndns-remotewdyndn" + - "s-serverisignconsultanthropologyconsultingrpartycontactozsdeltaj" + - "irittogliattis-a-caterercontagematsubaracontemporaryarteducation" + - "alchikugodontexistmein-iservebeercontractorskenconventureshinode" + - "arthruherecipescaravantaacookingchannelsdvrdnsfor-better-thanawa" + - "tchandclockasukabedzin-berlindasdaburcooluroycooperativano-frank" + - "ivskolefrakkestadyndns-webhopencraftrani-andria-barletta-trani-a" + - "ndriacopenhagencyclopedichofunatoriginstitutemasekashiwaracoprod" + - "uctionsantoandreamhostersanukis-a-celticsfancorsicagliaricoharuo" + - "vatraniandriabarlettatraniandriacorvettemp-dnsaobernardocosenzak" + - "opanecosidnshome-webserverdalutskasumigaurawa-mazowszexnetnedalu" + - "xurycostumedicinakaiwamizawatchesaogoncartoonartdecologiacouchpo" + - "tatofriesaotomeldaluzerncouklugsmilegallocus-3councilvivanovolda" + - "couponsapporocq-acranbrookuwanalyticsardegnarusawacrdyndns-wikir" + - "kenesardiniacreditcardyndns-workshopitsitexaskoyabearalvahkikuch" + - "ikuseikarugalsacecreditunioncremonashgabadaddjaguarqcxn--12cfi8i" + - "xb8lcrewildlifedorainfracloudfrontdoorcricketrzyncrimeast-kazakh" + - "stanangercrotonecrownipasadenaritakurashikis-a-chefashioncrsvpas" + - "sagensarlcruisesarpsborgruecryptonomichigangwoncuisinellajollame" + - "ricanexpressexyculturalcentertainmentranoycuneocupcakecuritiback" + - "yardsarufutsunomiyawakasaikaitakofuefukihaboromskoguidegreecurva" + - "lled-aostaverncymrussiacyonabaruminamidaitomanchestercyouthachio" + - "jiyahooguyfetsundynnsasayamafgujohanamakinoharafhvalerfidoomdnst" + - "racefieldynservebbsasebofagemologicallyngenfigueresinstagingulen" + - "filateliafilegear-audnedalnfilegear-deatnulvikatowicefilegear-gb" + - "izfilegear-iefilegear-jpmorganfilegear-sgunmanxn--1ck2e1bananare" + - "publicasertairaumalborkarasjohkamikoaniikappuboliviajessheimemor" + - "ialaziobserverevistaples3-external-1filminamifuranofinalfinancef" + - "ineartsavonarutomobellevuelosangelesjabbottransurlfinlandynufcfa" + - "nfinnoyfirebaseappatriafirenzefirestonefirmdalegoldpoint2thisami" + - "tsukefishingolffansaxofitjarvodkafjordynv6fitnessettlementrapani" + - "izafjalerflesberguovdageaidnunusualpersonflickragerogerschoenbru" + - "nnflightschokokekschokoladenflirfloginlinefloraflorencefloridats" + - "unanjoburgushikamifuranorth-kazakhstanfloripaderbornfloristanoha" + - "takaharunzenflorokunohealthcareerscholarshipschoolschulezajskats" + - "ushikabeeldengeluidynvpnplus-4flowerschulserverfltravelchannelfl" + - "ynnhosting-clusterfndyroyrvikinguitarsaskatchewanfor-ourfor-some" + - "dizinhistorischeschwarzgwangjuniperfor-theaterforexrothachirogat" + - "akanezawaforgotdnschweizforli-cesena-forlicesenaforlillehammerfe" + - "ste-ipaviancarrdforsaleikangerforsandasuologoipfizerfortalfortmi" + - "ssoulancasterfortworthadanorthwesternmutualfosnesciencecentersci" + - "encehistoryfotaruis-a-cubicle-slavellinodeobjectscientistordalfo" + - "xfordebianfozorafredrikstadtvscjohnsonfreeddnsgeekgalaxyfreedesk" + - "topocznore-og-uvdalfreemasonryfreesitextileirfjordfreetlscotland" + - "freiburgwiddleitungsenfreseniuscountryestateofdelawareggio-calab" + - "riafribourgxn--1ctwolominamatarnobrzegyptianfriuli-v-giuliafriul" + - "i-ve-giuliafriuli-vegiuliafriuli-venezia-giuliafriuli-veneziagiu" + - "liafriuli-vgiuliafriuliv-giuliafriulive-giuliafriulivegiuliafriu" + - "livenezia-giuliafriuliveneziagiuliafriulivgiuliafrlfroganscrappe" + - "r-sitefrognfrolandfrom-akrehamnfrom-alfrom-arfrom-azfrom-capetow" + - "nnews-stagingfrom-coffeedbackplaneapplinzis-a-democratravelersin" + - "surancefrom-ctrdfrom-dchonanbulsan-suedtirolowiczest-le-patronis" + - "hitosashimizunaminamibosogndalpusercontentoyotapartsandnessjoeni" + - "shiwakinvestmentsandoyfrom-dedyn-berlincolnfrom-flanderscrapping" + - "from-gaulardalfrom-hichisochildrensgardenfrom-iafrom-idfrom-ilfr" + - "om-in-brbanzaicloudcontrolledekagaminombresciaustraliamusementdl" + - "lpages3-ap-south-1kappchizip6116-b-dataiji234lima-cityeatselinog" + - "radult3l3p0rtatamotors3-ap-northeast-1337from-kscrysechoseiroumu" + - "enchenissandiegofrom-kyowariasahikawafrom-lanciafrom-mamurogawaf" + - "rom-mdfrom-meeresistancefrom-mifunefrom-mnfrom-modalenfrom-mserv" + - "eirchoshibuyachiyodapliernewspaperfrom-mtnfrom-nctulangevagrigen" + - "tomologyeonggiehtavuoatnagahamaroyerfrom-ndfrom-nefrom-nh-serveb" + - "logsiteleafamilycompanyanagawafflecellclaimserveminecraftrentin-" + - "sud-tirolfrom-njaworznoticiasnesoddenmarkhangelskjakdnepropetrov" + - "skiervaapsteiermarkatsuyamarugame-hostrowiechoyodobashichikashuk" + - "ujitawarafrom-nminamiiserniafrom-nvalledaostargetmyiphostrodawar" + - "afrom-nyfrom-ohkurafrom-oketogurafrom-orfrom-padovaksdalfrom-pra" + - "tohmandalfrom-ris-a-designerfrom-schmidtre-gauldalfrom-sdfrom-tn" + - "from-txn--1lqs03nfrom-utsiracusaikisarazurecontainerdpolicefrom-" + - "val-daostavalleyfrom-vtrentin-sudtirolfrom-wafrom-wielunnerfrom-" + - "wvallee-aosteroyfrom-wyfrosinonefrostalowa-wolawafroyahikobierzy" + - "cefstcgroupgfoggiafujiiderafujikawaguchikonefujiminokamoenairlin" + - "edre-eikerfujinomiyadattowebcampinashikiminohostre-totendofinter" + - "net-dnsaliasiafujiokayamangonohejis-a-doctorayfujisatoshonairpor" + - "tland-4-salernoboribetsuckservemp3fujisawafujishiroishidakabirat" + - "oridefenseljordfujitsurugashimangyshlakasamatsudovre-eikerfujixe" + - "roxn--1lqs71dfujiyoshidavvenjargap-northeast-3fukayabeatservep2p" + - "harmacienservepicservequakefukuchiyamadavvesiidappnodebalancerti" + - "ficationfukudominichristiansburgrondarfukuis-a-financialadvisor-" + - "aurdalfukumitsubishigakishiwadazaifudaigojomedio-campidano-medio" + - "campidanomediofukuokazakisofukushimaniwakuratefukuroishikarikatu" + - "rindalfukusakisosakitagawafukuyamagatakahatakaishimoichinosekiga" + - "harafunabashiriuchinadafunagatakamatsukawafunahashikamiamakusats" + - "umasendaisennangooglecodespotrentin-sued-tirolfundaciofuoiskujuk" + - "uriyamannorfolkebibleirvikaufenfuosskoczowilliamhillfurnitureggi" + - "o-emilia-romagnakatombetsumitakagiizefurubirafurudonostiaafuruka" + - "wairtelebitbridgestonekobayashikshacknetcimbarcelonagawalmartatt" + - "oolforgehimejiitatebayashijonawatempresashibetsukuiiyamanouchiku" + - "hokuryugasakitaurayasudaustrheimatunduhrennesoyokosukanumazuryok" + - "oteastcoastaldefenceatonsbergjerdrumemergencyachts3-ca-central-1" + - "fusodegaurafussaintlouis-a-anarchistoireggiocalabriafutabayamagu" + - "chinomigawafutboldlygoingnowhere-for-morenakatsugawafuttsurugimp" + - "eriafuturecmservesarcasmatartanddesignfuturehostingfuturemailing" + - "fvgfylkesbiblackbaudcdn77-securebungoonord-odalfyresdalhangoutsy" + - "stemscloudhannanmokuizumodenaklodzkochikushinonsenergyhannosegaw" + - "ahanyuzenhapmircloudharstadharvestcelebrationhasamarburghasamina" + - "mi-alpsharis-a-hunterhashbanghasudahasura-appharmacysharphdfcban" + - "kazoologyhasvikazunow-dnshawaiijimaritimoduminamimakis-a-knightp" + - "ointtohobby-sitehatogayaitakaokalmykiahatoyamazakitakamiizumisan" + - "ofidelityhatsukaichikaiseiheijis-a-landscaperugiahattfjelldalhay" + - "ashimamotobungotakadagestangeorgeorgiahazuminobusells-for-usrcfa" + - "stlylbamblebesbyglandroverhalla-speziaustinnavigationavoizumizak" + - "ibigawajudaicable-modemocraciabruzzoologicalvinklein-addrammenuo" + - "rochesterimo-i-ranaamesjevuemielno-ipifonyaaarborteaches-yogasaw" + - "aracingdyniaetnabudapest-a-la-masion-riopretobamaceratabuseating" + - "-organic66helsinkitakatakarazukaluganskygearapphiladelphiaareadm" + - "yblogspotrentino-a-adigehembygdsforbundhemneshellaspeziahemsedal" + - "hepforgeherokussldheroyhgtvallee-d-aosteigenhidorahigashiagatsum" + - "agoianiahigashichichibunkyonanaoshimageandsoundandvisionthewifia" + - "tmallorcadaqueshimokawahigashihiroshimanehigashiizumozakitakyush" + - "uaiahigashikagawahigashikagurasoedahigashikawakitaaikitamihamada" + - "higashikurumeetrentino-aadigehigashimatsushimarcheapigeelvinckdd" + - "iethnologyhigashimatsuyamakitaakitadaitoigawahigashimurayamamoto" + - "rcycleshimokitayamahigashinarusells-itrentino-alto-adigehigashin" + - "ehigashiomihachimanagementrentino-altoadigehigashiosakasayamanak" + - "akogawahigashishirakawamatakasagoppdalhigashisumiyoshikawaminami" + - "aikitamotosumy-gatewayhigashitsunoshiroomurahigashiurausukitanak" + - "agusukumodernhigashiyamatokoriyamanashiibahccavuotnagareyamainte" + - "nancehigashiyodogawahigashiyoshinogaris-a-lawyerhiraizumisatohno" + - "shoooshikamaishimofusartshimonitayanagithubusercontentrentino-s-" + - "tirolhirakatashinagawahiranairtrafficplexus-1hirarahiratsukagawa" + - "hirayaizuwakamatsubushikusakadogawahistorichouseshimonosekikawah" + - "itachiomiyagildeskaliszhitachiotagotembaixadahitraeumtgeradelmen" + - "horstalbanshimosuwalkis-a-liberalhjartdalhjelmelandholeckodairah" + - "olidayhomeiphilatelyhomelinkitoolsztynsettlershimotsukehomelinux" + - "n--1qqw23ahomeofficehomesecuritymacaparecidahomesecuritypchristm" + - "aseratiresandvikcoromantovalle-d-aostatic-accessanfranciscofreak" + - "unemurorangehirnrtoyotomiyazakinzais-a-candidatehomesenseeringho" + - "meunixn--2m4a15ehondahongotpantheonsitehonjyoitakasakitashiobara" + - "hornindalhorsellsyourhomegoodshimotsumahorteneis-a-libertarianho" + - "spitalhoteleshinichinanhotmailhoyangerhoylandetroitskypehumaniti" + - "eshinjournalismailillesandefjordhurdalhurumajis-a-linux-useranis" + - "hiaritabashikaoirminamiminowahyllestadhyogoris-a-llamarriottrent" + - "ino-stirolhyugawarahyundaiwafuneis-very-badajozis-very-evillagei" + - "s-very-goodyearis-very-niceis-very-sweetpepperis-with-thebandois" + - "leofmanaustdaljevnakershuscultureggioemiliaromagnamsskoganeis-a-" + - "nascarfanjewelryjewishartgalleryjfkharkivalleedaostejgorajlljls-" + - "sto1jmphotographysiojnjcphonefosshintomikasaharajoyentrentino-su" + - "edtiroljoyokaichibajddarchitecturealtorlandjpnjprshiojirishirifu" + - "jiedajurkosherbrookegawakoshimizumakizunokunimimatakayamarylandk" + - "oshunantankhersonkosugekotohiradomainsurehabmerkotourakouhokutam" + - "akis-a-patsfankounosupplieshirakokaminokawanishiaizubangekouyama" + - "shikekouzushimashikis-a-personaltrainerkozagawakozakis-a-photogr" + - "apherokuapphilipsyno-dshinjukumanowtvalleeaosteinkjerusalembroid" + - "erykozowindmillkpnkppspdnshiranukamitsuekrasnikahokutokashikis-a" + - "-playershifteditchyouriphoenixn--2scrj9chromedicaltanissettaishi" + - "nomakinkobeardubaiduckdnsangokrasnodarkredstonekristiansandcatsh" + - "iraois-a-republicancerresearchaeologicaliforniakristiansundkrods" + - "heradkrokstadelvaldaostarostwodzislawindowskrakowinnershiraokamo" + - "gawakryminamioguni5kumatorinokumejimasoykumenantokigawakunisakis" + - "-a-rockstarachowicekunitachiarailwaykunitomigusukumamotoyamashik" + - "okuchuokunneppubtlshiratakahagitlaborkunstsammlungkunstunddesign" + - "kuokgroupilotshishikuis-a-socialistdlibestadkureisenkurgankurobe" + - "laudibleasingleshisognekurogiminamiashigarakuroisoftwarezzokurom" + - "atsunais-a-soxfankurotakikawasakis-a-studentalkushirogawakustana" + - "is-a-teacherkassyncloudkusupplykutchanelkutnokuzumakis-a-techiet" + - "is-a-musiciankvafjordkvalsundkvamlidlugolekadenagaivuotnagaokaky" + - "otambabyenebakkeshibechambagriculturennebudejjuedischesapeakebay" + - "ernukvanangenkvinesdalkvinnheradkviteseidatingkvitsoykwpspectrum" + - "inamisanrikubetsurfastvps-serveronakasatsunairguardiannakadomari" + - "nebraskauniversitychyattorneyagawakembuchikumagayagawakkanaibets" + - "ubamericanfamilydsclouderackmazerbaijan-mayen-rootaribeiraogashi" + - "madachicagoboatsassaris-a-conservativegarsheis-a-cpadualstackher" + - "o-networkinggroupassenger-associationkzmissileluxembourgmisugito" + - "kuyamatsumotofukemitourismolanxesshitaramamitoyoakemiuramiyazure" + - "websiteshikagamiishibukawamiyotamanomjondalenmlbfanmontrealestat" + - "efarmequipmentrentinoa-adigemonza-brianzapposhizukuishimogosenmo" + - "nza-e-della-brianzaptokyotangotsukitahatakamoriokakegawamonzabri" + - "anzaramonzaebrianzamonzaedellabrianzamoonscaleforcemordoviamoriy" + - "amatsunomoriyoshiminamiawajikis-an-actormormonstermoroyamatsusak" + - "ahoginankokubunjis-an-actresshinshinotsurgerymortgagemoscowioshi" + - "zuokanagawamoseushistorymosjoenmoskeneshoppingmosshopwarendalenu" + - "gmosvikhmelnytskyivaomoteginowaniihamatamakawajimansionshoujis-a" + - "n-anarchistoricalsocietymoviemovimientolgamozilla-iotrentinoaadi" + - "gemtranbymuenstermuginozawaonsenmuikamisatokaizukamikitayamatsur" + - "is-an-artistgorymukoebenhavnmulhouseoullensvanguardmunakatanemun" + - "cienciamuosattemupimientakkoelnmurmanskhplaystation-cloudmurotor" + - "craftrentinoalto-adigemusashimurayamatsushigemusashinoharamuseet" + - "rentinoaltoadigemuseumverenigingmusicargodaddyn-vpndnshowamutsuz" + - "awamy-vigorgemy-wanggouvichungnamdalseidfjordyndns-mailubindalub" + - "lindesnesanjotoyotsukaidomyactivedirectorymyasustor-elvdalmycdn7" + - "7-sslattuminamitanemydattolocalhistorymyddnskingmydissentrentino" + - "s-tirolmydobisshikis-an-engineeringmydroboehringerikemydshowtime" + - "lhusdecorativeartshriramsterdamnserverbaniamyeffectrentinostirol" + - "myfastly-terrariuminamiuonumasudamyfirewallonieruchomoscienceand" + - "industrynmyforuminamiyamashirokawanabelembetsukubankhmelnitskiya" + - "marumorimachidamyfritzmyftpaccesshwitdklabudhabikinokawabarthads" + - "electrentin-suedtirolmyhome-servermyjinomykolaivaporcloudmymaile" + - "rmymediapchurcharternidyndns-office-on-the-webhareidsbergentingr" + - "ongausdalucaniamyokohamamatsudamypepinkmpspbarclays3-sa-east-1my" + - "petsienarviikamishihoronobeauxartsandcraftsigdalmyphotoshibalati" + - "nogiftsilknx-serversailleshioyandexcloudmypictetrentinosud-tirol" + - "mypsxn--32vp30haebaruericssongdalenviknakayamaoris-a-geekautokei" + - "notteroymysecuritycamerakermyshopblocksimple-urlmytis-a-bookkeep" + - "erspectakasugais-an-entertainermytuleaprendemasakikonaikawachina" + - "ganoharamcoachampionshiphoptobishimadridvagsoygardendoftheintern" + - "etlifyis-bytomaritimekeepingmyvncircustomer-ociprianiigataitogit" + - "suldaluccarbonia-iglesias-carboniaiglesiascarboniamywirepbodynam" + - "ic-dnsirdalplatformshangrilapyplatter-appioneerplatterpippugliap" + - "lazaplcube-serversicherungplumbingoplurinacionalpodhalevangerpod" + - "lasiellaktyubinskiptveterinaireadthedocscappgafannefrankfurtrent" + - "inosudtirolpodzonepohlpoivronpokerpokrovskomakiyosunndalpolitica" + - "rrierpolitiendapolkowicepoltavalle-aostathellewismillerpomorzesz" + - "owithgoogleapiszponpesaro-urbino-pesarourbinopesaromasvuotnaroyp" + - "onypordenonepornporsangerporsangugeporsgrunnanyokoshibahikariwan" + - "umatamayufuelveruminanopoznanpraxis-a-bruinsfanprdpreservationpr" + - "esidioprgmrprimelbourneprincipeprivatizehealthinsuranceprofesion" + - "alprogressivenneslaskerrylogisticslupskomatsushimarylhurstjordal" + - "shalsenpromombetsurgeonshalloffameiwamassa-carrara-massacarraram" + - "assabusinessebyklecznagasukepropertyprotectionprotonetrentinosue" + - "d-tirolprudentialpruszkowithyoutuberspacekitagatargivestbytemark" + - "omforbarefootballooningjerstadotsuruokakamigaharauthordalandds3-" + - "eu-central-1prvcyberlevagangaviikanonjis-certifieducatorahimeshi" + - "mamateramobaraprzeworskogptplusgardenpulawypupittsburghofficialp" + - "vhagakhanamigawapvtrentinosuedtirolpwcistrondheimmobilienissayok" + - "kaichiropractichitachinakagawassamukawatarightathomeftparocherni" + - "governmentksatxn--12c1fe0bradescorporationrenderpzqhagebostadqld" + - "qponiatowadaqslingqualifioappiwatequickconnectrentinsud-tirolqui" + - "cksytestingquipelementslzqvcitadeliveryggeesusonosuzakanazawasuz" + - "ukaneyamazoesuzukis-into-animegurownprovidersvalbardunloppacific" + - "ivilaviationissedalucernesveiosvelvikomorotsukaminoyamaxunjargas" + - "vizzerasvn-reposologneswidnicasacamdvrcampinagrandebuilderschles" + - "ischesolundbeckommunalforbundswidnikkokonoeswiebodzin-butterswif" + - "tcoverswinoujscienceandhistoryswissmarterthanyousynology-disksta" + - "tionsynology-dsolutionsnoasakakinokiaturystykanmakiwientuscanytu" + - "shuissier-justicetuvalle-daostaticsootuxfamilytwmailvestnesopotr" + - "entinsudtirolvestre-slidreviewsaitoshimayfirstockholmestrandvest" + +const text = "9guacuiababia-goracleaningroks-theatree12hpalermomahachijolstere" + + "trosnubalsfjorddnslivelanddnss3-ap-south-1kappchizip6116-b-datai" + + "ji234lima-cityeatselinogradult3l3p0rtatamotors3-ap-northeast-133" + + "7birkenesoddtangenovaranzaninohekinannestadivttasvuotnakamuratak" + + "ahamalselvendrellimitediyukuhashimojindianapolis-a-bloggerbirthp" + + "lacebjarkoyurihonjournalistjohninomiyakonojorpelandnpanamatta-va" + + "rjjatjeldsundrangedalimoliseminebjerkreimdbamblebesbyglandroverh" + + "alla-speziaustevollaziobihirosakikamijimatsuzakibigawagrocerybni" + + "keisenbahnatuurwetenschappenaumburgdyniabogadobeaemcloud66bjugni" + + "eznord-frontierblackfridayusuharabloombergbauernirasakindianmark" + + "etingjesdalinkyard-cloudyclusterbloxcms3-website-us-west-2blueda" + + "gestangeologyusuisservehumourbmoattachments5yuulmemorialivornoce" + + "anographiquebmsakyotanabellunord-aurdalpha-myqnapcloudaccesscamb" + + "ridgeiseiyoichippubetsubetsugarugbydgoszczecinemagentositechnolo" + + "gyuzawabmweddingjovikariyameinforumzjampagexlombardynaliaskimits" + + "ubatamibugattiffanycateringebuildingladefinimakanegasakirabnrwed" + + "eploybomloabathsbcatholicaxiashorokanaiebondray-dnstracebonnishi" + + "azaindielddanuorrindigenaklodzkodairabookinghostedpictethnologyb" + + "oomlair-traffic-controlleyboschaefflerdalomzaporizhzhegurindustr" + + "iabostikarlsoybostonakijinsekikogentappsselfipanasonichernihivgu" + + "bsalangenishigocelotenkawabotanicalgardenishiharabotanicgardenis" + + "hiizunazukindustriesteamsterdamnserverbaniabotanynysagaeroclubme" + + "decincinnationwidealerbouncemerckmsdnipropetrovskjervoyagets-itj" + + "maxxxboxenapponazure-mobilebounty-fullensakerrypropertiesalondon" + + "etskarmoyboutiquebechernivtsiciliabozen-sudtirolondrinamsskogane" + + "infinitintelligencebozen-suedtirolorenskoglassassinationalherita" + + "gebplacedogawarabikomaezakirunorddalottebrandywinevalleybrasilia" + + "brindisibenikinderoybristoloseyouriparachutingleezebritishcolumb" + + "ialowiezaganishikatakinouebroadcastlebtimnetzlglitchattanooganor" + + "dlandrayddnsfreebox-osascoli-picenordre-landraydnsupdaternopilaw" + + "atchesaltdalottokonamegatakazakinternationalfirearmsaludrivefsni" + + "llfjordrobaknoluoktachikawakuyabukievennodesadoes-itvedestrandru" + + "dupontariobranconakaniikawatanagurabroadwaybroke-itjomeloyalisto" + + "ragebrokerbronnoysundurbanamexhibitionishikatsuragit-reposalvado" + + "rdalibabalena-devicesalzburgliwicebrothermesaverdealstahaugesund" + + "erseaportsinfolldalouvreisenishikawazukamisunagawabrowsersafetym" + + "arketsamegawabrumunddalowiczest-le-patronishimerabrunelastxfinit" + + "ybrusselsamnangerbruxellesampalacebryansklepparaglidinglobalasho" + + "vhachinohedmarkarpaczeladzparisor-fronishinomiyashironocparliame" + + "ntjxjavald-aostarnbergloboavistanbulsan-sudtirolpusercontentkmax" + + "xn--0trq7p7nnishinoomotegoddabrynewhollandurhamburglogowegroweib" + + "olognagareyamakeupowiathletajimabaridagawalbrzycharitydalaskanit" + + "tedallasalleangaviikaascolipicenodumemsettsupportksatxn--11b4c3d" + + "ynathomebuiltwithdarkaruizawabuskerudinewjerseybuzentsujiiebuzzw" + + "eirbwellbeingzonebzhitomirumalatvuopmicrolightingloppenzaolbia-t" + + "empio-olbiatempioolbialystokkepnogatagajobojintuitmparmattelekom" + + "munikationishinoshimatsuurabzzcolumbusheycommunexus-2community-p" + + "rochowicecomoarekecomparemarkerryhotelsaobernardocompute-1comput" + + "erhistoryofscience-fictioncomsecuritytacticsxn--12cfi8ixb8luxury" + + "condoshichinohealth-carereformitakeharaconferenceconstructioncon" + + "suladonnagatorodoyconsultanthropologyconsultingrondarcontactozsd" + + "eltajirittogliattis-a-chefashioncontagematsubaracontemporaryarte" + + "ducationalchikugodontexistmein-iservebeercontractorskenconventur" + + "eshinodearthruherecipescaravantaacookingchannelsdvrdnsdojoburgro" + + "ngausdaluzerncoolvivanovoldacooperativano-frankivskolefrakkestad" + + "yndns1copenhagencyclopedichitosetogakushimotoganewspapercoproduc" + + "tionsaogoncartoonartdecologiacorporationcorsicagliaricoharuovatm" + + "allorcadaquesaotomeldalcorvettemasekashiwazakiyosemitecosenzakop" + + "anelblagrarchaeologyeongbuk0cosidnsfor-better-thanawassamukawata" + + "rikuzentakatajimidorissagamiharacostumedicinaharimalopolskanland" + + "ynnsapporocouchpotatofriesardegnaroycouklugsmilegallocus-3counci" + + "lcouponsardiniacozoracq-acranbrookuwanalyticsarlcrdynservebbsarp" + + "sborgrossetouchihayaakasakawaharacreditcardynulvikasserversaille" + + "sarufutsunomiyawakasaikaitakofuefukihaboromskogroundhandlingrozn" + + "ycreditunioncremonashgabadaddjaguarqcxn--12co0c3b4evalleaostavan" + + "gercrewiencricketrzyncrimeast-kazakhstanangercrotonecrownipartsa" + + "sayamacrsvpartycruisesasebofageometre-experts-comptablesaskatche" + + "wancryptonomichigangwoncuisinellajollamericanexpressexyculturalc" + + "entertainmentrani-andria-barletta-trani-andriacuneocupcakecuriti" + + "backyardsassaris-a-conservativegarsheis-a-cpadualstackhero-netwo" + + "rkinggroupasadenarashinocurvalled-aostaverncymrussiacyonabarumet" + + "lifeinsurancecyouthachiojiyaitakanezawafetsundyroyrvikingrpassag" + + "ensaudafguidegreefhvalerfidoomdnsiskinkyotobetsulikes-piedmontic" + + "ellodingenfieldfigueresinstaginguitarsavonarusawafilateliafilege" + + "ar-audnedalnfilegear-deatnunusualpersonfilegear-gbizfilegear-ief" + + "ilegear-jpmorganfilegear-sgujoinvilleitungsenfilminamiechizenfin" + + "alfinancefineartsaxofinlandfinnoyfirebaseappassenger-association" + + "firenetranoyfirenzefirestonefirmdalegoldpoint2thisamitsukefishin" + + "golffanschoenbrunnfitjarvodkafjordvalledaostargetmyiphostre-tote" + + "ndofinternet-dnschokokekschokoladenfitnessettlementransportefjal" + + "erflesbergulenflickragerogerscholarshipschoolschulezajskasuyanai" + + "zunzenflightschulserverflirfloginlinefloraflorencefloridatsunanj" + + "oetsuwanouchikujogaszkolancashirecreationfloripaderbornfloristan" + + "ohatakaharuslivinghistoryflorokunohealthcareerschwarzgwangjunipe" + + "rflowerschweizfltransurlflynnhosting-clusterfndfor-ourfor-somedi" + + "zinhistorischesciencecentersciencehistoryfor-theaterforexrothach" + + "irogatakaokalmykiaforgotdnscientistordalforli-cesena-forlicesena" + + "forlillehammerfeste-ipatriaforsaleikangerforsandasuologoipavianc" + + "arrdfortalfortmissoulancasterfortworthadanorthwesternmutualfosne" + + "scjohnsonfotaruis-a-democratrapaniizafoxfordebianfozfredrikstadt" + + "vscrapper-sitefreeddnsgeekgalaxyfreedesktopensocialfreemasonryfr" + + "eesitexaskoyabearalvahkikuchikuseikarugalsaceofreetlscrappingunm" + + "anxn--1ctwolominamatarnobrzegyptianfreiburguovdageaidnusrcfastly" + + "lbananarepublicaseihicampobassociatest-iservecounterstrikehimeji" + + "itatebayashijonawatempresashibetsukuiiyamanouchikuhokuryugasakit" + + "auraustinnaval-d-aosta-valleyokosukanumazuryokoteastcoastaldefen" + + "ceatonsbergivingjemnes3-eu-central-1freseniuscountryestateofdela" + + "wareggio-calabriafribourgushikamifuranorth-kazakhstanfriuli-v-gi" + + "uliafriuli-ve-giuliafriuli-vegiuliafriuli-venezia-giuliafriuli-v" + + "eneziagiuliafriuli-vgiuliafriuliv-giuliafriulive-giuliafriuliveg" + + "iuliafriulivenezia-giuliafriuliveneziagiuliafriulivgiuliafrlfrog" + + "anscrysechocolatelemarkarumaifarsundyndns-homednsamsungmodelling" + + "mxn--12c1fe0bradescotlandyndns-iparochernigovernmentoyotaparsand" + + "nessjoenishiokoppegardyndns-mailubindalublindesnesandoyfrognfrol" + + "andfrom-akrehamnfrom-alfrom-arfrom-azfrom-capetownnews-stagingwi" + + "ddleksvikaszubyfrom-coffeedbackplaneapplinzis-a-designerfrom-ctr" + + "avelchannelfrom-dchofunatoriginstitutelevisionthewifiatoyotomiya" + + "zakinuyamashinatsukigatakashimarnardalucaniafrom-dedyn-berlincol" + + "nfrom-flanderserveirchonanbulsan-suedtiroluccarbonia-iglesias-ca" + + "rboniaiglesiascarboniafrom-gaulardalfrom-hichisochildrensgardenf" + + "rom-iafrom-idfrom-ilfrom-in-brbar0emmafann-arboretumbriamallamac" + + "eiobbcg12038from-kserveminecraftravelersinsurancefrom-kyowariasa" + + "hikawawiiheyakumoduminamifuranofrom-lanciafrom-mamurogawafrom-md" + + "from-meeresistancefrom-mifunefrom-mnfrom-modalenfrom-mservemp3fr" + + "om-mtnfrom-nctulangevagrigentomologyeonggiehtavuoatnabudapest-a-" + + "la-masion-riopretobamaceratabuseating-organichoseiroumuenchenish" + + "itosashimizunaminamibosogndalucernefrom-ndfrom-nefrom-nh-servebl" + + "ogsiteleafamilycompanyanagawafflecellclaimservep2pfizerfrom-njaw" + + "orznoticiasnesoddenmarkhangelskjakdnepropetrovskiervaapsteiermar" + + "katowicefrom-nminamiiserniafrom-nvallee-aosteroyfrom-nyfrom-ohku" + + "rafrom-oketogurafrom-orfrom-padovaksdalfrom-pratohmandalfrom-ris" + + "-a-doctorayfrom-schmidtre-gauldalfrom-sdfrom-tnfrom-txn--1lqs03n" + + "from-utsiracusaikisarazurecontainerdpolicefrom-val-daostavalleyf" + + "rom-vtrdfrom-wafrom-wiardwebhostingxn--1lqs71dfrom-wvallee-d-aos" + + "teigenfrom-wyfrosinonefrostalowa-wolawafroyahooguyfstcgroupgfogg" + + "iafujiiderafujikawaguchikonefujiminokamoenairguardiannakadomarin" + + "ebraskauniversitychyattorneyagawakembuchikumagayagawakkanaibetsu" + + "bamericanfamilydsclouderackmazerbaijan-mayen-rootaribeiraogashim" + + "adachicagoboatservepicservequakefujinomiyadattowebcampinashikimi" + + "nohostfoldnavyfujiokayamangonohejis-a-financialadvisor-aurdalfuj" + + "isatoshonairlinedre-eikerfujisawafujishiroishidakabiratoridefens" + + "eljordfujitsurugashimangyshlakasamatsudopaasiafujixeroxn--1qqw23" + + "afujiyoshidavvenjargap-northeast-3fukayabeatservesarcasmatartand" + + "designfukuchiyamadavvesiidappnodebalancertificationfukudomigawaf" + + "ukuis-a-geekatsushikabeeldengeluidfukumitsubishigakishiwadazaifu" + + "daigojomedio-campidano-mediocampidanomediofukuokazakisofukushima" + + "niwakuratextileirfjordfukuroishikarikaturindalfukusakisosakitaga" + + "wafukuyamagatakahatakaishimoichinosekigaharafunabashiriuchinadaf" + + "unagatakamatsukawafunahashikamiamakusatsumasendaisennangooglecod" + + "espotrentin-sud-tirolfundaciofunkfeuerfuoiskujukuriyamannore-og-" + + "uvdalfuosskoczowildlifedorainfracloudfrontdoorfurnitureggio-emil" + + "ia-romagnakasatsunairportland-4-salernoboribetsuckservicesevasto" + + "polefurubirafurudonostiaafurukawairtelebitbridgestonekobayashiks" + + "hacknetcimbar1fusodegaurafussaintlouis-a-anarchistoireggiocalabr" + + "iafutabayamaguchinomihachimanagementrentin-sudtirolfutboldlygoin" + + "gnowhere-for-morenakatombetsumitakagiizefuttsurugimperiafuturecm" + + "sevenassisicilyfuturehostingfuturemailingfvgfyresdalhangoutsyste" + + "mscloudhannanmokuizumodenakayamapartmentsharpharmacienshawaiijim" + + "aritimoldeloittemp-dnshellaspeziahannosegawahanyuzenhapmircloudh" + + "arstadharvestcelebrationhasamarburghasaminami-alpshimokawahashba" + + "nghasudahasura-appharmacyshimokitayamahasvikatsuyamarugame-hosty" + + "hostinghatogayaizuwakamatsubushikusakadogawahatoyamazakitakamiiz" + + "umisanofidelityhatsukaichikaiseiheijis-a-landscaperugiahattfjell" + + "dalhayashimamotobungotakadancehazuminobusells-for-utwentehelsink" + + "itakatakarazukaluganskygearapphdfcbankaufenhembygdsforbundhemnes" + + "himonitayanagithubusercontentrentin-suedtirolhemsedalhepforgeher" + + "okusslattuminamiizukaminoyamaxunjargaheroyhgtvalleeaosteinkjerus" + + "alembroideryhidorahigashiagatsumagoianiahigashichichibunkyonanao" + + "shimageandsoundandvisionrenderhigashihiroshimanehigashiizumozaki" + + "takyushuaiahigashikagawahigashikagurasoedahigashikawakitaaikitam" + + "ihamadahigashikurumeetrentino-a-adigehigashimatsushimarcheapigee" + + "lvinckautokeinotteroyhigashimatsuyamakitaakitadaitoigawahigashim" + + "urayamamotorcycleshimonosekikawahigashinarusells-itrentino-aadig" + + "ehigashinehigashiomitamamurausukitamotosumy-gatewayhigashiosakas" + + "ayamanakakogawahigashishirakawamatakasagopocznorfolkebibleirvika" + + "zoologyhigashisumiyoshikawaminamiaikitanakagusukumodernhigashits" + + "unoshiroomurahigashiurawa-mazowszexnetrentino-alto-adigehigashiy" + + "amatokoriyamanashiibahccavuotnagaraholtaleniwaizumiotsukumiyamaz" + + "onawsmpplanetariuminamimakis-a-lawyerhigashiyodogawahigashiyoshi" + + "nogaris-a-liberalhiraizumisatohnoshoooshikamaishimofusartshimosu" + + "walkis-a-libertarianhirakatashinagawahiranairtrafficplexus-1hira" + + "rahiratsukagawahirayakagehistorichouseshimotsukehitachiomiyagild" + + "eskaliszhitachiotagotembaixadahitraeumtgeradelmenhorstalbanshimo" + + "tsumahjartdalhjelmelandholeckochikushinonsenergyholidayhomegoods" + + "hinichinanhomeiphiladelphiaareadmyblogspotrentino-altoadigehomel" + + "inkitoolsztynsettlershinjournalismailillesandefjordhomelinuxn--2" + + "m4a15ehomeofficehomesecuritymacaparecidahomesecuritypchoshibuyac" + + "htsandvikcoromantovalle-d-aostatic-accessanfranciscofreakunemuro" + + "rangehirnrtoyotsukaidohtawaramotoineppueblockbustermezhomesensee" + + "ringhomeunixn--2scrj9choyodobashichikashukujitawarahondahongotpa" + + "ntheonsitehonjyoitakasakitashiobarahornindalhorsellsyourhomeftph" + + "ilatelyhorteneis-a-linux-useranishiaritabashikaoirminamiminowaho" + + "spitalhoteleshinjukumanowtvalleedaostehotmailhoyangerhoylandetro" + + "itskypehumanitieshinkamigotoyohashimototalhurdalhurumajis-a-llam" + + "arriottrentino-s-tirolhyllestadhyogoris-a-musicianhyugawarahyund" + + "aiwafuneis-very-evillageis-very-goodyearis-very-niceis-very-swee" + + "tpepperis-with-thebandownloadisleofmanaustdaljetztrentino-sudtir" + + "oljevnakershuscultureggioemiliaromagnamsosnowiechristiansburgret" + + "akanabeautysvardoesntexisteingeekasaokamikoaniikappuboliviajessh" + + "eimpertrixcdn77-ssldyndns-office-on-the-weberjewelryjewishartgal" + + "leryjfkfhappoujgorajlljls-sto1jmphotographysiojnjcloudjiffylkesb" + + "iblackbaudcdn77-securebungoonord-odaljoyentrentino-sued-tiroljoy" + + "okaichibajddarchitecturealtorlandjpnjprshirakokamiminershiranuka" + + "mitsuejurkosakaerodromegallupinbarclaycards3-sa-east-1koseis-a-p" + + "ainteractivegaskvollkosherbrookegawakoshimizumakizunokunimimatak" + + "ayamarylandkoshunantankharkivanylvenicekosugekotohiradomainsureg" + + "ruhostingkotourakouhokutamakis-a-patsfankounosupplieshiraois-a-p" + + "ersonaltrainerkouyamashikekouzushimashikis-a-photographerokuapph" + + "ilipsynology-diskstationkozagawakozakis-a-playershifteditchyouri" + + "phoenixn--30rr7ykozowinbarclays3-us-east-2kpnkppspdnshiraokamoga" + + "wakrasnikahokutokashikis-a-republicancerresearchaeologicaliforni" + + "akrasnodarkredstonekristiansandcatshiratakahagitlaborkristiansun" + + "dkrodsheradkrokstadelvaldaostarostwodzislawindmillkryminamioguni" + + "5kumatorinokumejimasoykumenantokigawakunisakis-a-rockstarachowic" + + "ekunitachiarailwaykunitomigusukumamotoyamashikokuchuokunneppubtl" + + "shishikuis-a-socialistdlibestadkunstsammlungkunstunddesignkuokgr" + + "oupilotshisognekurehabmerkurgankurobelaudibleasingleshisuifuette" + + "rtdasnetzkurogiminamiashigarakuroisoftwarezzokuromatsunais-a-sox" + + "fankurotakikawasakis-a-studentalkushirogawakustanais-a-teacherka" + + "ssyno-dshinshinotsurgerykusupplynxn--3bst00minamisanrikubetsurfa" + + "uskedsmokorsetagayaseralingenoamishirasatogokasells-for-lessauhe" + + "radynv6kutchanelkutnokuzumakis-a-techietis-a-nascarfankvafjordkv" + + "alsundkvamfamberkeleykvanangenkvinesdalkvinnheradkviteseidatingk" + + "vitsoykwpspectruminamitanekzmishimatsumaebashimodatemissileluxem" + + "bourgmisugitokuyamatsumotofukemitourismolanxesshitaramamitoyoake" + + "miuramiyazurewebsiteshikagamiishibukawamiyotamanomjondalenmlbfan" + + "montrealestatefarmequipmentrentinoa-adigemonza-brianzapposhizuku" + + "ishimogosenmonza-e-della-brianzaptokyotangotsukitahatakamoriokak" + + "egawamonzabrianzaramonzaebrianzamonzaedellabrianzamoonscaleforce" + + "mordoviamoriyamatsunomoriyoshiminamiawajikis-an-actormormonsterm" + + "oroyamatsusakahoginankokubunjis-an-actresshintokushimamortgagemo" + + "scowindowskrakowinnershizuokanagawamoseushistorymosjoenmoskenesh" + + "oppingmosshopwarendalenugmosvikhersonmoteginowaniihamatamakawaji" + + "mansionshoujis-an-anarchistoricalsocietymoviemovimientolgamozill" + + "a-iotrentinoaadigemtranbymuenstermuginozawaonsenmuikamiokameokam" + + "akurazakiwakunigamiharumukoebenhavnmulhouseoullensvanguardmunaka" + + "tanemuncienciamuosattemupimientakkoelnmurmanskhmelnitskiyamarumo" + + "rimachidamurotorcraftrentinoalto-adigemusashimurayamatsushigemus" + + "ashinoharamuseetrentinoaltoadigemuseumverenigingmusicargodaddyn-" + + "vpndnshowamutsuzawamy-vigorgemy-wanggouvichristmaseratiresangomu" + + "tashinainvestmentsanjotoyouramyactivedirectorymyasustor-elvdalmy" + + "cdmydattolocalhistorymyddnskingmydissentrentinos-tirolmydobisshi" + + "kis-an-artistgorymydroboehringerikemydshowtimelhusdecorativearts" + + "hriramlidlugolekadenagahamaroygardendoftheinternetlifyis-an-engi" + + "neeringmyeffectrentinostirolmyfastly-terrariuminamiuonumasudamyf" + + "irewallonieruchomoscienceandindustrynmyforuminamiyamashirokawana" + + "belembetsukubankharkovaomyfritzmyftpaccesshwiosienarutomobellevu" + + "elosangelesjabbottrentinosud-tirolmyhome-servermyjinomykolaivare" + + "servehalflifestylemymailermymediapchromedicaltanissettaishinomak" + + "inkobeardubaiduckdnsannanishiwakinzais-a-candidatemyokohamamatsu" + + "damypepinkhmelnytskyivaporcloudmypetsigdalmyphotoshibalatinogift" + + "silkhplaystation-cloudmypicturesimple-urlmypsxn--3ds443gmysecuri" + + "tycamerakermyshopblocksirdalmythic-beastsjcbnpparibaselburgmytis" + + "-a-bookkeeperspectakasugais-an-entertainermytuleaprendemasakikon" + + "aikawachinaganoharamcoachampionshiphoptobishimadridvagsoyermyvnc" + + "hungnamdalseidfjordyndns-picsannohelplfinancialukowhalingrimstad" + + "yndns-remotewdyndns-serverisignissandiegomywirepaircraftingvollo" + + "mbardiamondslupsklabudhabikinokawabarthadselectrentin-sued-tirol" + + "platformshangrilapyplatter-appioneerplatterpippugliaplazaplcube-" + + "serverplumbingoplurinacionalpodhalevangerpodlasiellaktyubinskipt" + + "veterinaireadthedocscappgafannefrankfurtrentinosudtirolpodzonepo" + + "hlpoivronpokerpokrovsknx-serversicherungpoliticarrierpolitiendap" + + "olkowicepoltavalle-aostathellewismillerpomorzeszowitdkomaganepon" + + "pesaro-urbino-pesarourbinopesaromasvuotnaritakurashikis-bytomari" + + "timekeepingponypordenonepornporsangerporsangugeporsgrunnanyokosh" + + "ibahikariwanumatamayufuelveruminanopoznanpraxis-a-bruinsfanprdpr" + + "eservationpresidioprgmrprimelbourneprincipeprivatizehealthinsura" + + "nceprofesionalprogressivenneslaskerrylogisticsnoasakakinokiaprom" + + "ombetsurgeonshalloffameiwamassa-carrara-massacarraramassabusines" + + "sebykleclerchurcharternidyndns-webhareidsbergentingripepropertyp" + + "rotectionprotonetrentinosued-tirolprudentialpruszkowithgoogleapi" + + "szprvcyberlevagangaviikanonjis-certifieducatorahimeshimamateramo" + + "baraprzeworskogptplusgardenpulawypupittsburghofficialpvhagakhana" + + "migawapvtrentinosuedtirolpwcircustomer-ociprianiigataitogitsulda" + + "luroypzqhagebostadqldqponiatowadaqslingqualifioappiwatequickconn" + + "ectrentinsud-tirolquicksytestingquipelementsokananiimihoboleslaw" + + "iecistrondheimmobilienissayokkaichiropractichernovtsyncloudyndns" + + "-at-homedepotenzamamidsundyndns-at-workisboringlugmbhartipscbgmi" + + "nakamichiharaqvcitadeliveryggeesusonosuzakanazawasuzukaneyamazoe" + + "suzukis-into-animegurownprovidersvalbardunloppacificitichirurgie" + + "ns-dentistes-en-francesvcivilaviationissedalutskashibatakatsukiy" + + "osatokamachintaifun-dnsaliasanokashiharasveiosvelvikommunalforbu" + + "ndsvizzerasvn-reposolutionsokndalswidnicasacamdvrcampinagrandebu" + + "ilderschlesischesomaswidnikkokonoeswiebodzin-butterswiftcoverswi" + + "noujscienceandhistoryswissmarterthanyousynology-dsomnarviikamisa" + + "tokaizukameyamatotakadatuscanytushuissier-justicetuvalle-daostat" + + "icsor-varangertuxfamilytwmailvestre-slidreportrevisohughesoovest" + "re-totennishiawakuravestvagoyvevelstadvibo-valentiavibovalentiav" + - "ideovillasor-odalvinnicasadelamonedancevinnytsiavipsinaappixolin" + - "ovirginiavirtual-userveftpizzavirtualservervirtualuservegame-ser" + - "vervirtueeldomein-vigorlicevirtuelvisakegawaviterboknowsitallviv" + - "olkenkundenvixn--3bst00miniservervlaanderenvladikavkazimierz-dol" + - "nyvladimirvlogintoyonezawavminnesotaketaketomisatokorozawavologd" + - "anskongsbergvolvolkswagentsor-varangervolyngdalvoorloperaunitero" + - "is-into-carshinshirovossevangenvotevotingvotoyonowmflabsorfoldwn" + - "extdirectroandinosaureplantationworldworse-thandawowiwatsukiyono" + - "tairestaurantritonwpdevcloudwritesthisblogsytewroclawloclawekong" + - "svingerwtcminterepaircraftingvollombardiamondshisuifuettertdasne" + - "tzwtfauskedsmokorsetagayaseralingenoamishirasatogokasells-for-le" + - "ssaudawuozuwzmiuwajimaxn--42c2d9axn--45br5cylxn--45brj9civilizat" + - "ionxn--45q11civilwarmiastagets-itoyouraxn--4gbriminingxn--4it168" + - "dxn--4it797konskowolayangroupictureshirahamatonbetsurnadalxn--4p" + - "vxs4allxn--54b7fta0cclanbibaidarmeniaxn--55qw42gxn--55qx5dxn--5j" + - "s045dxn--5rtp49cldmailovecollegefantasyleaguernseyxn--5rtq34kons" + - "ulatrobeepilepsykkylvenetodayxn--5su34j936bgsgxn--5tzm5gxn--6btw" + - "5axn--6frz82gxn--6orx2rxn--6qq986b3xlxn--7t0a264clic20001wwwhosw" + - "hokksundyndns-picsannohelplfinancialukowiiheyakagexn--80adxhksor" + - "ocabalestrandabergamo-siemensncfdxn--80ao21axn--80aqecdr1axn--80" + - "asehdbarreauction-webhostingjesdalillyolasitemrxn--80aswgxn--80a" + - "ugustowmcloudxn--8ltr62konyvelolipopiemontexn--8pvr4uxn--8y0a063" + - "axn--90a3academiamicaarpkomaganexn--90aeroportalabamagasakishima" + - "baraogakibichuoxn--90aishobarakawagoexn--90azhytomyravendbarrel-" + - "of-knowledgeapplicationcloudappspotagerxn--9dbhblg6digitalxn--9d" + - "bq2axn--9et52uxn--9krt00axn--andy-iraxn--aroport-byaotsurreyxn--" + - "asky-iraxn--aurskog-hland-jnbarrell-of-knowledgestackarasjokaras" + - "uyamarshallstatebankarateu-1xn--avery-yuasakuhokkaidownloadxn--b" + - "-5gaxn--b4w605ferdxn--balsan-sdtirol-nsbsorreisahayakawakamiichi" + - "kawamisatottoris-foundationxn--bck1b9a5dre4clickashiwazakiyosemi" + - "texn--bdddj-mrabdxn--bearalvhki-y4axn--berlevg-jxaxn--bhcavuotna" + - "-s4axn--bhccavuotna-k7axn--bidr-5nachikatsuuraxn--bievt-0qa2xn--" + - "bjarky-fyasakaiminatoyookaniepcexn--bjddar-ptarumizusawaxn--blt-" + - "elabourxn--bmlo-graingerxn--bod-2nativeamericanantiquesortlandxn" + - "--bozen-sdtirol-2obanazawaxn--brnny-wuacademy-firewall-gatewayxn" + - "--brnnysund-m8accident-investigation-aptibleadpagest-mon-blogueu" + - "rovision-k3sorumincomcastresindevicenzaporizhzhiaxn--brum-voagat" + - "rogstadxn--btsfjord-9zaxn--bulsan-sdtirol-nsbarsycenterprisesaki" + - "kugawaltervistaikimobetsuitainaioirasebastopologyeongnamegawakay" + - "amagazineat-urlimanowarudautomotiveconomiasakuchinotsuchiurakawa" + - "lesundeportevadsobetsumidatlanticaseihicampobassociatest-iservec" + - "ounterstrikebinagisoccertmgrazimutheworkpccwebredirectmembers3-e" + - "u-west-1xn--c1avgxn--c2br7gxn--c3s14misakis-a-therapistoiaxn--cc" + - "k2b3barsyonlinewhampshirealtysnes3-us-gov-west-1xn--cckwcxetdxn-" + - "-cesena-forl-mcbremangerxn--cesenaforl-i8axn--cg4bkis-into-carto" + - "onshintokushimaxn--ciqpnxn--clchc0ea0b2g2a9gcdxn--comunicaes-v6a" + - "2oxn--correios-e-telecomunicaes-ghc29axn--czr694bashkiriautoscan" + - "adaeguambulanceobihirosakikamijimatsuzakibmdevelopmentatsunobira" + - "ukraanghkeymachineustargardd-dnsiskinkyotobetsulikes-piedmontice" + - "llodingenatuurwetenschappenaumburggfarmerseine164-balsfjorddnsli" + - "velanddnss3-ap-southeast-1xn--czrs0tromsakataobaomoriguchiharahk" + - "keravjuegoshikijobservableusercontentrentinsuedtirolxn--czru2dxn" + - "--czrw28basicservercelliguriaveroykenglandgcahcesuoloans3-eu-wes" + - "t-2xn--d1acj3basilicataniavocatanzarowebspacebinordreisa-hockeyn" + - "utazuerichardlikescandyn53utilitiesquare7xn--d1alfaromeoxn--d1at" + - "romsojamisonxn--d5qv7z876clinichocolatelevisionishiokoppegardynd" + - "ns-ipartinuyamashinatsukigatakashimarnardalouvreitoyosatoyokawax" + - "n--davvenjrga-y4axn--djrs72d6uyxn--djty4kooris-a-nursembokukitch" + - "enxn--dnna-grajewolterskluwerxn--drbak-wuaxn--dyry-iraxn--e1a4cl" + - "iniquenoharaxn--eckvdtc9dxn--efvn9soundcastronomy-routerxn--efvy" + - "88haibarakitahiroshimapartmentservicesevastopolexn--ehqz56nxn--e" + - "lqq16hair-surveillancexn--eveni-0qa01gaxn--f6qx53axn--fct429kope" + - "rvikharkovanylvenicexn--fhbeiarnxn--finny-yuaxn--fiq228c5hsouthc" + - "arolinatalxn--fiq64basketballfinanzgoravoues3-eu-west-3xn--fiqs8" + - "southwestfalenxn--fiqz9sowaxn--fjord-lraxn--fjq720axn--fl-ziaxn-" + - "-flor-jraxn--flw351exn--forl-cesena-fcbsspeedpartnersokananiimih" + - "oboleslawiecitichitosetogakushimotoganewportlligatmparsamsclubar" + - "towhalingriwataraidyndns-homednsamsungroks-thisayamanobeokakudam" + - "atsuexn--forlcesena-c8axn--fpcrj9c3dxn--frde-grandrapidspjelkavi" + - "komonowruzhgorodeoxn--frna-woaraisaijosoyrorospreadbettingxn--fr" + - "ya-hraxn--fzc2c9e2clintonoshoesanokasserverrankoshigayameinforum" + - "zxn--fzys8d69uvgmailxn--g2xx48clothingdustdataiwanairforcebetsui" + - "kidsmynasushiobaragusabaejrietisalatinabenonicbcn-north-1xn--gck" + - "r3f0fbsbxn--12co0c3b4evalleaostavangerxn--gecrj9cn-northwest-1xn" + - "--ggaviika-8ya47hakatanortonxn--gildeskl-g0axn--givuotna-8yasugi" + - "vingxn--gjvik-wuaxn--gk3at1exn--gls-elacaixaxn--gmq050is-into-ga" + - "messinazawaxn--gmqw5axn--h-2failxn--h1aeghakodatexn--h2breg3even" + - "espydebergxn--h2brj9c8cngrossetouchihayaakasakawaharaxn--h3cuzk1" + - "discountyxn--hbmer-xqaxn--hcesuolo-7ya35batochiokinoshimakeupowi" + - "at-band-campaniaxaurskog-holandingjemnes3-ap-southeast-2xn--hery" + - "-iraxn--hgebostad-g3axn--hkkinen-5waxn--hmmrfeasta-s4accident-pr" + - "evention-rancherkasydneyxn--hnefoss-q1axn--hobl-iraxn--holtlen-h" + - "xaxn--hpmir-xqaxn--hxt814exn--hyanger-q1axn--hylandet-54axn--i1b" + - "6b1a6a2exn--imr513nxn--indery-fyasuokanoyakumoldeloittenrikuzent" + - "akatajimidorissagamiharaxn--io0a7is-leetrentino-sud-tirolxn--j1a" + - "efbx-osauheradyndns1xn--j1amhakonexn--j6w193gxn--jlq480n2rgxn--j" + - "lq61u9w7batsfjordiscoveryombolzano-altoadigeologyomitanoceanogra" + - "phics3-us-west-1xn--jlster-byatomitamamuraxn--jrpeland-54axn--jv" + - "r189misasaguris-an-accountantshinkamigotoyohashimototalxn--k7yn9" + - "5exn--karmy-yuaxn--kbrq7oxn--kcrx77d1x4axn--kfjord-iuaxn--klbu-w" + - "oaxn--klt787dxn--kltp7dxn--kltx9axn--klty5xn--3ds443gxn--koluokt" + - "a-7ya57hakubahcavuotnagaraholtaleniwaizumiotsukumiyamazonawsmppl" + - "anetariuminamiizukamiokameokameyamatotakadaxn--kprw13dxn--kpry57" + - "dxn--kpu716fbxosavannahgaxn--kput3is-lostrolekamakurazakiwakunig" + - "amiharustkannamilanotogawaxn--krager-gyatsukanraxn--kranghke-b0a" + - "xn--krdsherad-m8axn--krehamn-dxaxn--krjohka-hwab49jdfastpanelbla" + - "grarchaeologyeongbuk0emmafann-arboretumbriamallamaceiobbcg12038x" + - "n--ksnes-uuaxn--kvfjord-nxaxn--kvitsy-fyatsushiroxn--kvnangen-k0" + - "axn--l-1fairwindsrlxn--l1accentureklamborghinikolaeventsrvareser" + - "vehalflifestylexn--laheadju-7yawaraxn--langevg-jxaxn--lcvr32dxn-" + - "-ldingen-q1axn--leagaviika-52bauhausposts-and-telecommunications" + - "3-us-west-2xn--lesund-huaxn--lgbbat1ad8jelasticbeanstalkhakassia" + - "xn--lgrd-poacctrusteexn--lhppi-xqaxn--linds-pramericanartrvargga" + - "trentoyonakagyokutoyakolobrzegersundxn--lns-qlaquilanstorfjordxn" + - "--loabt-0qaxn--lrdal-sraxn--lrenskog-54axn--lt-liacnpyatigorskod" + - "jeffersonxn--lten-granexn--lury-iraxn--m3ch0j3axn--mely-iraxn--m" + - "erker-kuaxn--mgb2ddestorjdevcloudnshinyoshitomiokamitondabayashi" + - "ogamagoriziaxn--mgb9awbfedorapeoplegnicapebretonamicrosoftbankas" + - "uyanaizulminamiechizenxn--mgba3a3ejtrycloudflareportrevisohughes" + - "omaxn--mgba3a4f16axn--mgba3a4franamizuholdingstpetersburgxn--mgb" + - "a7c0bbn0axn--mgbaakc7dvfedoraprojectransportexn--mgbaam7a8hakuis" + - "-a-greenxn--mgbab2bdxn--mgbah1a3hjkrdxn--mgbai9a5eva00beneventoe" + - "idskoguchikuzenayorovigovtaxihuanflfanfshostrowwlkpmgjovikaratsu" + - "ginamikatagamilitaryonagoyaxn--mgbai9azgqp6jelenia-goraxn--mgbay" + - "h7gpaleoxn--mgbbh1a71exn--mgbc0a9azcgxn--mgbca7dzdoxn--mgberp4a5" + - "d4a87gxn--mgberp4a5d4arxn--mgbgu82axn--mgbi4ecexposedxn--mgbpl2f" + - "hskydivingxn--mgbqly7c0a67fbcnsantabarbaraxn--mgbqly7cvafranzisk" + - "anerimaringatlantakahashimamakiryuohdattorelayxn--mgbt3dhdxn--mg" + - "btf8flatangerxn--mgbtx2bentleyonagunicommbankarelianceu-2xn--mgb" + - "x4cd0abbvieeexn--mix082feiraquarelleaseeklogesaves-the-whalessan" + - "dria-trani-barletta-andriatranibarlettaandriaxn--mix891fermochiz" + - "ukirovogradoyxn--mjndalen-64axn--mk0axin-dslgbtrysiljanxn--mk1bu" + - "44cntoystre-slidrettozawaxn--mkru45is-not-certifiedugit-pagespee" + - "dmobilizeroticanonoichinomiyakexn--mlatvuopmi-s4axn--mli-tlarvik" + - "oryokamikawanehonbetsurutaharaxn--mlselv-iuaxn--moreke-juaxn--mo" + - "ri-qsakuragawaxn--mosjen-eyawatahamaxn--mot-tlavagiskexn--mre-og" + - "-romsdal-qqbuserveexchangexn--msy-ula0hakusanagochijiwadell-ogli" + - "astraderxn--mtta-vrjjat-k7aflakstadaokagakicks-assnasaarlandxn--" + - "muost-0qaxn--mxtq1misawaxn--ngbc5azdxn--ngbe9e0axn--ngbrxn--3e0b" + - "707exn--nit225kosaigawaxn--nmesjevuemie-tcbalsan-sudtirollagdene" + - "snaaseinet-freakstreamswatch-and-clockerxn--nnx388axn--nodessaku" + - "rais-savedunetflixilxn--nqv7fs00emaxn--nry-yla5gxn--ntso0iqx3axn" + - "--ntsq17gxn--nttery-byaeservehttplantsjcbnpparibaselburgxn--nvuo" + - "tna-hwaxn--nyqy26axn--o1acheltenham-radio-openairbusantiquest-a-" + - "la-maisondre-landroidxn--o3cw4haldenxn--o3cyx2axn--od0algxn--od0" + - "aq3beppublishproxyzgorzeleccogladefinimakanegasakiraxn--ogbpf8fl" + - "ekkefjordxn--oppegrd-ixaxn--ostery-fyaxn--osyro-wuaxn--otu796dxn" + - "--p1acferraraxn--p1ais-slickfhappoutwentexn--pbt977collectionxn-" + - "-pgbs0dhlxn--porsgu-sta26ferrarivnexn--pssu33lxn--pssy2uxn--q9jy" + - "b4colognewyorkshirecifedexeterxn--qcka1pmckinseyxn--qqqt11miscon" + - "fusedxn--qxa6axn--qxamuneuestudioxn--rady-iraxn--rdal-poaxn--rde" + - "-ulavangenxn--rdy-0nabaris-uberleetrentino-sudtirolxn--rennesy-v" + - "1axn--rhkkervju-01aferrerotikagoshimalvikaszubyxn--rholt-mragowo" + - "odsidemonmouthalsaitamatsukuris-a-guruslivinghistoryxn--rhqv96gx" + - "n--rht27zxn--rht3dxn--rht61exn--risa-5naturalhistorymuseumcenter" + - "xn--risr-iraxn--rland-uuaxn--rlingen-mxaxn--rmskog-byaxn--rny31h" + - "ammarfeastafricapitalonewmexicodyn-o-saurlandesevenassisicilyxn-" + - "-rovu88beskidyn-ip24xn--rros-granvindafjordxn--rskog-uuaxn--rst-" + - "0naturalsciencesnaturellestudynamisches-dnsokndalxn--rsta-franca" + - "iseharaxn--rvc1e0am3exn--ryken-vuaxn--ryrvik-byaxn--s-1faithamur" + - "akamigoris-a-hard-workersewinbarclaycards3-fips-us-gov-west-1xn-" + - "-s9brj9colonialwilliamsburgroundhandlingroznyxn--sandnessjen-ogb" + - "estbuyshouses3-website-ap-northeast-1xn--sandy-yuaxn--sdtirol-n2" + - "axn--seral-lraxn--ses554gxn--sgne-graphoxn--3hcrj9civilisationis" + - "shinguccircleverappsannaniyodogawaxn--skierv-utazastuff-4-salexn" + - "--skjervy-v1axn--skjk-soaxn--sknit-yqaxn--sknland-fxaxn--slat-5n" + - "aturbruksgymnxn--slt-elabcieszynxn--smla-hraxn--smna-gratangentl" + - "entapisa-geekosakaerodromegallupinbargainstantcloudfunctionswede" + - "nvironmentalconservationfabricafederationionjukudoyamaizuruhrhcl" + - "oudiscourses3-us-east-2xn--snase-nraxn--sndre-land-0cbetainaboxf" + - "usejnymemsettsupportcp4xn--snes-poaxn--snsa-roaxn--sr-aurdal-l8a" + - "xn--sr-fron-q1axn--sr-odal-q1axn--sr-varanger-ggbhzcasinordkappa" + - "lmasfjordenhktjeldsundishakotanhlfanhs3-website-ap-southeast-1xn" + - "--srfold-byaxn--srreisa-q1axn--srum-gratis-a-bulls-fanxn--stfold" + - "-9xaxn--stjrdal-s1axn--stjrdalshalsen-sqbieidsvollimitediskussio" + - "nsbereichaseljeepsondriodejaneirockartuzyoriikariyaltakatorin-th" + - "e-bandain-vpncateringebuildinglassassinationalheritageu-3xn--str" + - "e-toten-zcbielawashingtondclkarlsoyoshiokanzakiyokawaraxn--t60b5" + - "6axn--tckweatherchannelxn--tiq49xqyjeonnamerikawauexn--tjme-hrax" + - "n--tn0agrinetbankoseis-a-painteractivegaskvollxn--tnsberg-q1axn-" + - "-tor131oxn--trany-yuaxn--trentin-sd-tirol-rzbiellaakesvuemielecc" + - "eu-4xn--trentin-sdtirol-7vbrplsbxn--3oq18vl8pn36axn--trentino-sd" + - "-tirol-c3bieszczadygeyachimataipeigersundisrechtrainingleezevje-" + - "og-hornnes3-website-ap-southeast-2xn--trentino-sdtirol-szbievath" + - "letajimabaridagawalbrzycharitydalcesurancechirealmpmnikonanporov" + - "noceanographiquextraspace-to-rentalstomakomaibaraxn--trentinosd-" + - "tirol-rzbifukagawashtenawdev-myqnapcloudeitysfjordivtasvuodnakam" + - "agayahabaghdadivttasvuotnakamuratakahamalselvendrellimoliseminex" + - "n--trentinosdtirol-7vbigv-infoodnetworkangerxn--trentinsd-tirol-" + - "6vbihorologyukincheoninohekinannestadiyukuhashimojindianapolis-a" + - "-bloggerxn--trentinsdtirol-nsbikedaejeonbukcoalvdalaheadjudygarl" + - "andnpalmspringsakerxn--trgstad-r1axn--trna-woaxn--troms-zuaxn--t" + - "ysvr-vraxn--uc0atvaroyxn--uc0ay4axn--uist22handsonyoursidellogli" + - "astradingxn--uisz3gxn--unjrga-rtashkentunesomnarvikommunexn--unu" + - "p4yxn--uuwu58axn--vads-jraxn--valle-aoste-ebbtunkomvuxn--30rr7yx" + - "n--valle-d-aoste-ehbodollstufftoread-booksnesolarssonxn--valleao" + - "ste-e7axn--valledaoste-ebbvacationstuttgartrentinsued-tirolxn--v" + - "ard-jraxn--vegrshei-c0axn--vermgensberater-ctbilbaokinawashirosa" + - "tochigiessensiositelemarkarmoyurihonjournalistjohninomiyakonojor" + - "pelandrangedalinkyard-cloudyclusterxn--vermgensberatung-pwbillus" + - "trationredumbrellahppiacenzachpomorskienirasakindianmarketinglit" + - "chattanooganordlandray-dnsupdaternopilawaweddingliwicexn--vestvg" + - "y-ixa6oxn--vg-yiabkhaziaxn--vgan-qoaxn--vgsy-qoa0jetztrentino-su" + - "ed-tirolxn--vgu402coloradoplateaudioxn--vhquvestfoldxn--vler-qoa" + - "xn--vre-eiker-k8axn--vrggt-xqadxn--vry-yla5gxn--vuq861biocpanama" + - "tta-varjjatjmaxxxboxenapponazure-mobilexn--w4r85el8fhu5dnraxn--w" + - "4rs40lxn--wcvs22dxn--wgbh1columbusheyxn--wgbl6axn--xhq521birdart" + - "centerprisecloudcontrolappleborkdalwaysdatabaseballangenkainanae" + - "robatickets3-website-eu-west-1xn--xkc2al3hye2axn--xkc2dl3a5ee0ha" + - "ngglidingxn--y9a3aquariumishimatsumaebashimodatexn--yer-znaturhi" + - "storischesusakis-gonexn--yfro4i67oxn--ygarden-p1axn--ygbi2ammxn-" + - "-3pxu8koninjambylxn--ystre-slidre-ujbirkenesoddtangenovaranzanqu" + - "anpachigasakihokumakogengerdalaskanittedallasalleangaviikaascoli" + - "picenodumetacentrumeteorappanasonicatholicaxiashorokanaiexn--zbx" + - "025dxn--zf0ao64axn--zf0avxlxn--zfr164birthplacexnbayxz" + "ideovillasorocabalestrandabergamo-siemensncfdvinnicasadelamoneda" + + "pliernewportlligatritonvinnytsiavipsinaappixolinovirginiavirtual" + + "-userveftpizzavirtualservervirtualuservegame-servervirtueeldomei" + + "n-vigorlicevirtuelvisakegawaviterboknowsitallvivolkenkundenvixn-" + + "-3hcrj9civilisationisshinguccircleverappsantabarbaravlaanderenvl" + + "adikavkazimierz-dolnyvladimirvlogintoyonezawavminiservervologdan" + + "skomonowruzhgorodeovolvolkswagentsorreisahayakawakamiichikawamis" + + "atottoris-foundationvolyngdalvoorloperauniterois-into-carshintom" + + "ikasaharavossevangenvotevotingvotoyonowmcloudwmflabsortlandwnext" + + "directrogstadworldworse-thandawowithyoutuberspacekitagatargitpag" + + "efrontappkmpspbar2wpdevcloudwpenginepoweredwritesthisblogsytewro" + + "clawiwatsukiyonotairestaurantroandinosaurepbodynamic-dnsopotrent" + + "insudtirolwtcminnesotaketaketomisatokorozawawtfbsbxn--1ck2e1banz" + + "aicloudcontrolledekagaminombresciaustraliajudaicable-modemocraci" + + "abruzzoologicalvinklein-addrammenuorochesterimo-i-rana4u2-localh" + + "ostrowiec66wuozuwzmiuwajimaxn--45q11civilwarmiaxn--4gbriminingxn" + + "--4it168dxn--4it797kongsbergxn--4pvxs4allxn--54b7fta0cclanbibaid" + + "armeniaxn--55qw42gxn--55qx5dxn--5js045dxn--5rtp49cldmailovecolle" + + "gefantasyleaguernseyxn--5rtq34kongsvingerxn--5su34j936bgsgxn--5t" + + "zm5gxn--6btw5axn--6frz82gxn--6orx2rxn--6qq986b3xlxn--7t0a264clic" + + "20001wwwhoswhokksundyndns-wikirkenesantacruzsantafedjejuifmetace" + + "ntrumeteorappartis-a-catererxn--80adxhksorumincomcastresindevice" + + "nzaporizhzhiaxn--80ao21axn--80aqecdr1axn--80asehdbarefootballoon" + + "ingjerdrumckinseyolasiteu-1xn--80aswgxn--80augustowloclawekomoro" + + "tsukaminokawanishiaizubangexn--8ltr62koninjambylxn--8pvr4uxn--8y" + + "0a063axn--90a3academiamicaaarborteaches-yogasawaracingxn--90aero" + + "portalabamagasakishimabaraogakibichuoxn--90aishobarakawagoexn--9" + + "0azhytomyravendbargainstantcloudfunctionswedenvironmentalconserv" + + "ationfabricafederationionjukudoyamaintenanceu-2xn--9dbhblg6digit" + + "alxn--9dbq2axn--9et52uxn--9krt00axn--andy-iraxn--aroport-byaotsu" + + "rreyxn--asky-iraxn--aurskog-hland-jnbarreauction-webhopenairbusa" + + "ntiquest-a-la-maisondre-landroidiscourses3-us-gov-west-1xn--aver" + + "y-yuasakuhokkaidovre-eikerxn--b-5gaxn--b4w605ferdxn--balsan-sdti" + + "rol-nsbsoundcastronomy-routerxn--bck1b9a5dre4clickashiwaraxn--bd" + + "ddj-mrabdxn--bearalvhki-y4axn--berlevg-jxaxn--bhcavuotna-s4axn--" + + "bhccavuotna-k7axn--bidr-5nachikatsuuraxn--bievt-0qa2xn--bjarky-f" + + "yasakaiminatoyookaniepcexn--bjddar-ptarumizusawaxn--blt-elabourx" + + "n--bmlo-graingerxn--bod-2natalxn--bozen-sdtirol-2obanazawaxn--br" + + "nny-wuacademy-firewall-gatewayxn--brnnysund-m8accident-investiga" + + "tion-aptibleadpagest-mon-blogueurovision-k3southcarolinarvikomat" + + "sushimarylhurstjordalshalsenxn--brum-voagatromsakataobaomoriguch" + + "iharahkkeravjuegoshikijobservableusercontentrentoyonakagyokutoya" + + "kolobrzegersundxn--btsfjord-9zaxn--bulsan-sdtirol-nsbarrel-of-kn" + + "owledgeapplicationcloudappspotagerevistaples3-us-west-1xn--c1avg" + + "xn--c2br7gxn--c3s14mintereitrentino-suedtirolxn--cck2b3barrell-o" + + "f-knowledgestack12xn--cckwcxetdxn--cesena-forl-mcbremangerxn--ce" + + "senaforl-i8axn--cg4bkis-into-cartoonshinyoshitomiokamitondabayas" + + "hiogamagoriziaxn--ciqpnxn--clchc0ea0b2g2a9gcdxn--comunicaes-v6a2" + + "oxn--correios-e-telecomunicaes-ghc29axn--czr694barsycenterprises" + + "akimobetsuitainaioirasebastopologyeongnamegawakayamagazineat-url" + + "illyombolzano-altoadigeorgeorgiaustrheimatunduhrennesoyokozebina" + + "gisoccertmgrazimutheworkpccwebredirectmembers3-eu-west-1xn--czrs" + + "0tromsojamisonxn--czru2dxn--czrw28barsyonlinewhampshirealtysnes3" + + "-us-west-2xn--d1acj3bashkiriauthordalandeportenrivnebinordreisa-" + + "hockeynutazuerichardlikescandyn53utilitiesquare7xn--d1alfaromeox" + + "n--d1atrusteexn--d5qv7z876clinichiryukyuragifuchungbukharavennag" + + "asakindlecznagasukexn--davvenjrga-y4axn--djrs72d6uyxn--djty4kons" + + "kowolayangroupiemontexn--dnna-grajewolterskluwerxn--drbak-wuaxn-" + + "-dyry-iraxn--e1a4cliniquenoharaxn--eckvdtc9dxn--efvn9southwestfa" + + "lenxn--efvy88haibarakitahiroshimaoris-a-greenxn--ehqz56nxn--elqq" + + "16hair-surveillancexn--eveni-0qa01gaxn--f6qx53axn--fct429konsula" + + "trobeepilepsykkylvenetodayxn--fhbeiarnxn--finny-yuaxn--fiq228c5h" + + "sowaxn--fiq64basicservercelliguriautomotiveconomiastagemological" + + "lyngenflfanquanpachigasakihokumakogenebakkeshibechambagriculture" + + "nnebudejjuedischesapeakebayernufcfanavigationavoizumizakibmdevel" + + "opmentatsunobiramusementdllpages3-ap-southeast-2ix4432-balsan-su" + + "edtirolkuszczytnoipirangamvik-serverrankoshigayachimataikikugawa" + + "lesundd-dnshome-webserverdal-o-g-i-n4tatarantours3-ap-northeast-" + + "2xn--fiqs8speedpartnersolarssonxn--fiqz9sphinxn--3e0b707exn--fjo" + + "rd-lraxn--fjq720axn--fl-ziaxn--flor-jraxn--flw351exn--forl-cesen" + + "a-fcbsspjelkavikomforbarcelonagawalmartattoolforgemreviewsaitosh" + + "imayfirstockholmestrandgcahcesuoloans3-fips-us-gov-west-1xn--for" + + "lcesena-c8axn--fpcrj9c3dxn--frde-grandrapidspreadbettingxn--frna" + + "-woaraisaijosoyrorospydebergxn--frya-hraxn--fzc2c9e2clintonoshoe" + + "santamariakexn--fzys8d69uvgmailxn--g2xx48clothingdustdataiwanair" + + "forcebetsuikidsmynasushiobaragusabaejrietisalatinabenonicbcn-nor" + + "th-1xn--gckr3f0fbx-ostrowwlkpmgruexn--gecrj9cn-northwest-1xn--gg" + + "aviika-8ya47hakatanortonxn--gildeskl-g0axn--givuotna-8yasugivest" + + "bytemarkonyvelolipoppdalxn--gjvik-wuaxn--gk3at1exn--gls-elacaixa" + + "xn--gmq050is-into-gamessinazawaxn--gmqw5axn--h-2failxn--h1aeghak" + + "odatexn--h2breg3evenesrlxn--h2brj9c8cngriwataraidyndns-workshopi" + + "tsitevadsobetsumidatlantichitachinakagawashtenawdev-myqnapcloude" + + "itysfjordyndns-blogdnsamsclubartowfarmsteadyndns-freeboxosloftoy" + + "osatoyokawaxn--h3cuzk1discountyxn--hbmer-xqaxn--hcesuolo-7ya35ba" + + "silicataniautoscanadaeguambulancechirealmpmnavuotnapleskns3-eu-w" + + "est-2xn--hery-iraxn--hgebostad-g3axn--hkkinen-5waxn--hmmrfeasta-" + + "s4accident-prevention-rancherkasydneyxn--hnefoss-q1axn--hobl-ira" + + "xn--holtlen-hxaxn--hpmir-xqaxn--hxt814exn--hyanger-q1axn--hyland" + + "et-54axn--i1b6b1a6a2exn--imr513nxn--indery-fyasuokanoyaltakatori" + + "s-leetrentino-stirolxn--io0a7is-lostrodawaraxn--j1aefbxosavannah" + + "gaxn--j1amhakonexn--j6w193gxn--jlq480n2rgxn--jlq61u9w7basketball" + + "finanzgoraveroykengerdalces3-eu-west-3xn--jlster-byatominamidait" + + "omanchesterxn--jrpeland-54axn--jvr189misakis-a-therapistoiaxn--k" + + "7yn95exn--karmy-yuaxn--kbrq7oxn--kcrx77d1x4axn--kfjord-iuaxn--kl" + + "bu-woaxn--klt787dxn--kltp7dxn--kltx9axn--klty5xn--3oq18vl8pn36ax" + + "n--koluokta-7ya57hakubahcavuotnagaivuotnagaokakyotambabyenglandx" + + "n--kprw13dxn--kpry57dxn--kput3is-not-certifiedugit-pagespeedmobi" + + "lizeroticanonoichinomiyakexn--krager-gyatsukanraxn--kranghke-b0a" + + "xn--krdsherad-m8axn--krehamn-dxaxn--krjohka-hwab49jdevcloudnshir" + + "ahamatonbetsurnadalxn--ksnes-uuaxn--kvfjord-nxaxn--kvitsy-fyatsu" + + "shiroxn--kvnangen-k0axn--l-1fairwindsrvarggatrentinsued-tirolxn-" + + "-l1accentureklamborghinikolaeventstoregontrailroadxn--laheadju-7" + + "yawaraxn--langevg-jxaxn--lcvr32dxn--ldingen-q1axn--leagaviika-52" + + "batochiokinoshimaizuruhrhcloudiscoveryomitanobninskaracoldwarsza" + + "wavocatanzarowebspacebizenakanojohanamakinoharaukraanghkeymachin" + + "eustargardds3-ca-central-1xn--lesund-huaxn--lgbbat1ad8jdfastvps-" + + "serveronakanotoddenxn--lgrd-poacctrvaroyxn--lhppi-xqaxn--linds-p" + + "ramericanartrycloudflareplantationxn--lns-qlaquilanstorfjordxn--" + + "loabt-0qaxn--lrdal-sraxn--lrenskog-54axn--lt-liacnpyatigorskodje" + + "ffersonxn--lten-granexn--lury-iraxn--m3ch0j3axn--mely-iraxn--mer" + + "ker-kuaxn--mgb2ddestorjcphonefosshioyandexcloudxn--mgb9awbfedora" + + "peoplegnicapebretonamicrosoftbankasukabedzin-berlindasdaburxn--m" + + "gba3a3ejtrysiljanxn--mgba3a4f16axn--mgba3a4franamizuholdingstpet" + + "ersburgxn--mgba7c0bbn0axn--mgbaakc7dvfedoraprojectraniandriabarl" + + "ettatraniandriaxn--mgbaam7a8hakuis-a-gurustkannamilanotogawaxn--" + + "mgbab2bdxn--mgbah1a3hjkrdxn--mgbai9a5eva00batsfjordishakotanayor" + + "ovigovtaxihuanfshostrolekamishihoronobeauxartsandcrafts3-website" + + "-ap-northeast-1xn--mgbai9azgqp6jelasticbeanstalkddietnedalxn--mg" + + "bayh7gpaleoxn--mgbbh1a71exn--mgbc0a9azcgxn--mgbca7dzdoxn--mgberp" + + "4a5d4a87gxn--mgberp4a5d4arxn--mgbgu82axn--mgbi4ecexposedxn--mgbp" + + "l2fhskydivingxn--mgbqly7c0a67fbcnsantoandreamhostersanukis-a-cel" + + "ticsfanxn--mgbqly7cvafranziskanerimaringatlantakahashimamakiryuo" + + "hdattorelayxn--mgbt3dhdxn--mgbtf8flatangerxn--mgbtx2bauhausposts" + + "-and-telecommunications3-website-ap-southeast-1xn--mgbx4cd0abbvi" + + "eeexn--mix082feiraquarelleaseeklogesaveincloudynvpnplus-4xn--mix" + + "891fermochizukirovogradoyxn--mjndalen-64axn--mk0axin-dslgbtuneso" + + "r-odalxn--mk1bu44cntoystre-slidrettozawaxn--mkru45is-savedunetfl" + + "ixilxn--mlatvuopmi-s4axn--mli-tlarvikooris-a-nursembokukitchenxn" + + "--mlselv-iuaxn--moreke-juaxn--mori-qsakuragawaxn--mosjen-eyawata" + + "hamaxn--mot-tlavagiskexn--mre-og-romsdal-qqbuserveexchangexn--ms" + + "y-ula0hakusanagochijiwadell-ogliastraderxn--mtta-vrjjat-k7aflaks" + + "tadaokagakicks-assnasaarlandxn--muost-0qaxn--mxtq1misasaguris-an" + + "-accountantshinshiroxn--ngbc5azdxn--ngbe9e0axn--ngbrxn--3pxu8kom" + + "vuxn--32vp30haebaruericssongdalenviknakatsugawaxn--nit225kopervi" + + "khakassiaxn--nmesjevuemie-tcbalsan-sudtirollagdenesnaaseinet-fre" + + "akstreamswatch-and-clockerxn--nnx388axn--nodessakurais-slickazun" + + "ow-dnshiojirishirifujiedaxn--nqv7fs00emaxn--nry-yla5gxn--ntso0iq" + + "x3axn--ntsq17gxn--nttery-byaeservehttplantslzxn--nvuotna-hwaxn--" + + "nyqy26axn--o1acheltenham-radio-opencraftrainingxn--o3cw4haldenxn" + + "--o3cyx2axn--od0algorithmiasakuchinotsuchiurakawaxn--od0aq3benev" + + "entoeidskoguchikuzenhktcp4xn--ogbpf8flekkefjordxn--oppegrd-ixaxn" + + "--ostery-fyaxn--osyro-wuaxn--otu796dxn--p1acferraraxn--p1ais-ube" + + "rleetrentino-sud-tirolxn--pgbs0dhlxn--porsgu-sta26ferraris-a-cub" + + "icle-slavellinodeobjectsaves-the-whalessandria-trani-barletta-an" + + "driatranibarlettaandriaxn--pssu33lxn--pssy2uxn--q9jyb4collection" + + "xn--qcka1pmcdirxn--qqqt11misawaxn--qxa6axn--qxamuneuestudioxn--r" + + "ady-iraxn--rdal-poaxn--rde-ulavangenxn--rdy-0nabaris-very-badajo" + + "zxn--rennesy-v1axn--rhkkervju-01aferrerotikagoshimalvikasumigaur" + + "ayasudaxn--rholt-mragowoodsidemonmouthalsaitamatsukuris-a-hard-w" + + "orkersewilliamhillxn--rhqv96gxn--rht27zxn--rht3dxn--rht61exn--ri" + + "sa-5nativeamericanantiquestudynamisches-dnsolognexn--risr-iraxn-" + + "-rland-uuaxn--rlingen-mxaxn--rmskog-byaxn--rny31hammarfeastafric" + + "apitalonewmexicodyn-o-saurlandesharis-a-hunterxn--rovu88bentleyo" + + "nagoyavoues3-external-1xn--rros-granvindafjordxn--rskog-uuaxn--r" + + "st-0naturalhistorymuseumcenterxn--rsta-francaiseharaxn--rvc1e0am" + + "3exn--ryken-vuaxn--ryrvik-byaxn--s-1faithamurakamigoris-a-knight" + + "pointtohobby-sitexn--s9brj9colognewyorkshirecifedexeterxn--sandn" + + "essjen-ogbeppublishproxyzgorzeleccogjerstadotsuruokakamigaharaxa" + + "urskog-holandinggfarmerseine164-baltimore-og-romsdalipayboltates" + + "hinanomachimkentateyamaetnaamesjevuemielno-ipifonyaarpalmasfjord" + + "enaturhistorisches3-ap-southeast-1xn--sandy-yuaxn--sdtirol-n2axn" + + "--seral-lraxn--ses554gxn--sgne-graphoxn--42c2d9axn--skierv-utaza" + + "stuff-4-salexn--skjervy-v1axn--skjk-soaxn--sknit-yqaxn--sknland-" + + "fxaxn--slat-5naturalsciencesnaturellestufftoread-booksnesolundbe" + + "ckomakiyosunndalxn--slt-elabcieszynxn--smla-hraxn--smna-gratange" + + "ntlentapisa-geekoryokamikawanehonbetsurutaharaxn--snase-nraxn--s" + + "ndre-land-0cbeskidyn-ip24xn--snes-poaxn--snsa-roaxn--sr-aurdal-l" + + "8axn--sr-fron-q1axn--sr-odal-q1axn--sr-varanger-ggbestbuyshouses" + + "3-website-ap-southeast-2xn--srfold-byaxn--srreisa-q1axn--srum-gr" + + "atis-a-bulls-fanxn--stfold-9xaxn--stjrdal-s1axn--stjrdalshalsen-" + + "sqbetainaboxfusejnymemergencyahabaghdadiskussionsbereichaseljeep" + + "sondriodejaneirockartuzyonagunicommbankaragandaxn--stre-toten-zc" + + "bhzcasertairaumalborkarasjohkamikitayamatsurin-the-bandain-vpnca" + + "sinordkappalmspringsakerxn--t60b56axn--tckweatherchannelxn--tiq4" + + "9xqyjelenia-goraxn--tjme-hraxn--tn0agrinetbankosaigawaxn--tnsber" + + "g-q1axn--tor131oxn--trany-yuaxn--trentin-sd-tirol-rzbieidsvollim" + + "anowarudaxn--trentin-sdtirol-7vbrplsbxn--45br5cylxn--trentino-sd" + + "-tirol-c3bielawaltervistaipeigersundisrechtranakaiwamizawatchand" + + "clockarasjokarasuyamarshallstatebankarateu-3xn--trentino-sdtirol" + + "-szbiellaakesvuemielecceu-4xn--trentinosd-tirol-rzbieszczadygeya" + + "chiyodaejeonbukcoalvdalaheadjudygarlandivtasvuodnakamagayahikobi" + + "erzycevje-og-hornnes3-website-eu-west-1xn--trentinosdtirol-7vbie" + + "vat-band-campaniaxn--trentinsd-tirol-6vbifukagawashingtondclkara" + + "tsuginamikatagamilitaryoriikareliancextraspace-to-rentalstomakom" + + "aibaraxn--trentinsdtirol-nsbigv-infoodnetworkangerxn--trgstad-r1" + + "axn--trna-woaxn--troms-zuaxn--tysvr-vraxn--uc0atvestfoldxn--uc0a" + + "y4axn--uist22handsonyoursidellogliastradingxn--uisz3gxn--unjrga-" + + "rtashkentunkommunexn--unup4yxn--uuwu58axn--vads-jraxn--valle-aos" + + "te-ebbturystykanmakiwielunnerxn--valle-d-aoste-ehbodollstuttgart" + + "rentinsuedtirolxn--valleaoste-e7axn--valledaoste-ebbvacationsusa" + + "kis-gonexn--vard-jraxn--vegrshei-c0axn--vermgensberater-ctbihoro" + + "logyoshiokanzakiyokawaraxn--vermgensberatung-pwbikedaemoneyukinc" + + "heonhlfanhs3-website-sa-east-1xn--vestvgy-ixa6oxn--vg-yiabkhazia" + + "xn--vgan-qoaxn--vgsy-qoa0jeonnamerikawauexn--vgu402colonialwilli" + + "amsburgroks-thisayamanobeokakudamatsuexn--vhquvestnesorfoldxn--v" + + "ler-qoaxn--vre-eiker-k8axn--vrggt-xqadxn--vry-yla5gxn--vuq861bil" + + "baokinawashirosatochigiessensiositecnologiaxn--w4r85el8fhu5dnrax" + + "n--w4rs40lxn--wcvs22dxn--wgbh1coloradoplateaudioxn--wgbl6axn--xh" + + "q521billustrationredumbrellahppiacenzachpomorskienikonanporovnob" + + "serverxn--xkc2al3hye2axn--xkc2dl3a5ee0hangglidingxn--y9a3aquariu" + + "misconfusedxn--yer-znaturbruksgymnxn--yfro4i67oxn--ygarden-p1axn" + + "--ygbi2ammxn--45brj9civilizationiyodogawaxn--ystre-slidre-ujbioc" + + "eanographics3-website-us-east-1xn--zbx025dxn--zf0ao64axn--zf0avx" + + "lxn--zfr164birdartcenterprisecloudcontrolappleborkdalwaysdatabas" + + "eballangenkainanaerobatickets3-website-us-west-1xnbayxz" // nodes is the list of nodes. Each node is represented as a uint32, which // encodes the node's children, wildcard bit and node type (as an index into @@ -526,1818 +528,1812 @@ const text = "9guacuiababia-goracleaningroks-theatree12hpalermomahachijoinvill" // [15 bits] text index // [ 6 bits] text length var nodes = [...]uint32{ - 0x297a83, - 0x32a504, - 0x2eadc6, - 0x24c943, - 0x24c946, - 0x38b146, - 0x3b05c3, - 0x214bc4, - 0x201507, - 0x2eaa08, + 0x32f643, + 0x3b5c84, + 0x2f7846, + 0x2ed303, + 0x2ed306, + 0x391ec6, + 0x3ba683, + 0x242cc4, + 0x2089c7, + 0x2f7488, 0x1a000c2, - 0x1f36987, - 0x376649, - 0x36c4ca, - 0x36c4cb, - 0x201203, - 0x233985, - 0x2201602, - 0x2d2044, - 0x2eaf43, - 0x269045, - 0x2601742, - 0x343083, - 0x2a135c4, - 0x2982c5, - 0x2e0de42, - 0x26e24e, - 0x250b83, - 0x3a6286, - 0x3203082, - 0x306087, - 0x2372c6, - 0x3606bc2, - 0x27f943, - 0x27f944, - 0x399b86, - 0x35bc88, - 0x286046, - 0x26fc84, + 0x1f3c187, + 0x37b0c9, + 0x39a04a, + 0x39a04b, + 0x231983, + 0x234b85, + 0x2202642, + 0x280004, + 0x2f79c3, + 0x202645, + 0x2608c02, + 0x365e83, + 0x2a15d84, + 0x3b5585, + 0x2e12282, + 0x27520e, + 0x251a43, + 0x3adec6, + 0x3207d42, + 0x306e07, + 0x237306, + 0x3601f82, + 0x26d143, + 0x334e46, + 0x360f48, + 0x28e806, + 0x276804, 0x3a00ac2, - 0x34abc9, - 0x2236c7, - 0x202446, - 0x353689, - 0x32f208, - 0x2478c4, - 0x23f2c6, - 0x3c3846, - 0x3e041c2, - 0x36fcc6, - 0x242f4f, - 0x2d108e, - 0x219a44, - 0x218b45, - 0x32a405, - 0x2e7589, - 0x23d709, - 0x39a387, - 0x3ddf06, - 0x267243, - 0x42037c2, - 0x21adc3, - 0x35054a, - 0x460f283, - 0x3d95c5, - 0x29d282, - 0x38b6c9, - 0x4e01182, - 0x201c84, - 0x2f3146, - 0x28a745, - 0x36d1c4, - 0x560fbc4, - 0x220dc3, - 0x232d04, - 0x5a02d02, - 0x235784, - 0x5e79284, - 0x33cb4a, + 0x34cd89, + 0x222087, + 0x3b4c86, + 0x370f49, + 0x3c8608, + 0x354f84, + 0x25b9c6, + 0x3cdd86, + 0x3e029c2, + 0x2a7f06, + 0x24394f, + 0x27f04e, + 0x221684, + 0x2d4205, + 0x32f545, + 0x215589, + 0x23d909, + 0x335647, + 0x355246, + 0x203583, + 0x42272c2, + 0x22ce03, + 0x2937ca, + 0x4601ac3, + 0x3e1a45, + 0x239202, + 0x392449, + 0x4e03502, + 0x209784, + 0x2f4406, + 0x28fac5, + 0x3732c4, + 0x56263c4, + 0x233f03, + 0x233f04, + 0x5a02e42, + 0x385d04, + 0x5e83a84, + 0x25d6ca, 0x6200882, - 0x3c1f47, - 0x2d0548, - 0x76031c2, - 0x328287, - 0x2c9044, - 0x2c9047, - 0x3d57c5, - 0x378847, - 0x303c06, - 0x2f0e44, - 0x342e05, - 0x257187, - 0x8e01482, - 0x36fe43, - 0x9226782, - 0x3633c3, - 0x9606b42, - 0x274985, + 0x229547, + 0x27e508, + 0x7a07282, + 0x334a47, + 0x2ce984, + 0x2ce987, + 0x3dbac5, + 0x390e07, + 0x34b706, + 0x2a1184, + 0x36a285, + 0x257e87, + 0x8e07cc2, + 0x2a8083, + 0x9210642, + 0x3b3f43, + 0x96074c2, + 0x2173c5, 0x9a00202, - 0x2cd104, - 0x2c23c5, - 0x219987, - 0x249e0e, - 0x2b6e44, - 0x290f44, - 0x210603, - 0x286ac9, - 0x3aa74b, - 0x2edac8, - 0x303148, - 0x3b1888, - 0x3d9ac8, - 0x3534ca, - 0x378747, - 0x2cdf46, - 0x9e47402, - 0x374d83, - 0x3ccac3, - 0x3ce604, - 0x374dc3, - 0x35cb83, - 0x1732182, - 0xa2016c2, - 0x27cd05, - 0x224686, - 0x233744, - 0x38a5c7, - 0x2355c6, - 0x2c8984, - 0x3abfc7, - 0x215dc3, - 0xa6d5bc2, - 0xaa20f82, - 0xae20d42, - 0x220d46, - 0xb200282, - 0x284405, - 0x3336c3, - 0x3c8744, - 0x2f7784, - 0x2f7785, - 0x3d6d83, - 0xb602703, - 0xba019c2, - 0x205fc5, - 0x205fcb, - 0x20ec0b, - 0x229904, - 0x206689, - 0x208244, - 0xbe09c42, - 0x20a483, - 0x20a703, - 0xc202e82, - 0x398a0a, - 0xc601542, - 0x2d22c5, - 0x2e6a0a, - 0x247584, - 0x20b103, - 0x20b7c4, - 0x20d6c3, - 0x20d6c4, - 0x20d6c7, - 0x20e245, - 0x2114c6, - 0x211846, - 0x2124c3, - 0x217688, - 0x208f03, - 0xca0cd42, - 0x308648, - 0x2862cb, - 0x21ffc8, - 0x2205c6, - 0x2213c7, - 0x227208, - 0xda07682, - 0xde1d482, - 0x298408, - 0x210c07, - 0x30dcc5, - 0x30dcc8, - 0xe301108, - 0x26c243, - 0x22a444, - 0x38b1c2, - 0xe62a882, - 0xea16182, - 0xf22c042, - 0x22c043, - 0xf6086c2, - 0x296303, - 0x3b2744, - 0x2086c3, - 0x247884, - 0x296a4b, - 0x215843, - 0x2f1486, - 0x278704, - 0x2c340e, - 0x38f345, - 0x261e48, - 0x3a6387, - 0x3a638a, - 0x22f983, - 0x2343c7, - 0x3aa905, - 0x22f984, - 0x2526c6, - 0x2526c7, - 0x31a204, - 0xfb0d704, - 0x24a144, - 0x33c806, - 0x22b844, - 0x3b5cc6, - 0x232443, - 0x3ba348, - 0x3df888, - 0x290f03, - 0x3989c3, - 0x340384, - 0x355943, - 0x10215702, - 0x1068d502, - 0x218043, - 0x2435c6, - 0x343343, - 0x30c504, - 0x10a3fec2, - 0x24cf43, - 0x327783, - 0x212c02, - 0x10e00d42, - 0x2cb886, - 0x234807, - 0x3c25c7, - 0x3b91c5, - 0x3d3004, - 0x29fbc5, - 0x2253c7, - 0x2ade49, - 0x2c19c6, - 0x2ec246, - 0x1120b682, - 0x2f4b48, - 0x3ae1c6, - 0x2aed85, - 0x30a6c7, - 0x3562c4, - 0x3562c5, - 0x11668c84, - 0x268c88, - 0x11a06082, - 0x11e00482, - 0x26e986, - 0x200488, - 0x331ac5, - 0x34b646, - 0x34ef88, - 0x35b788, - 0x12201805, - 0x126136c4, - 0x2136c7, - 0x12a07cc2, - 0x12e167c2, - 0x14201242, - 0x2f3245, - 0x14a83545, - 0x262486, - 0x323647, - 0x39f087, - 0x14e14a83, - 0x338487, - 0x38a948, - 0x2022cd09, - 0x26e407, - 0x22d447, - 0x22e548, - 0x22ed46, - 0x22f486, - 0x2300cc, - 0x23180a, - 0x231c07, - 0x23384b, - 0x234647, - 0x23464e, - 0x20635944, - 0x235b44, - 0x238b07, - 0x25c7c7, - 0x23d006, - 0x23d007, - 0x3b3147, - 0x2d2c43, - 0x20a2ba02, - 0x23e306, - 0x23e30a, - 0x23f44b, - 0x240987, - 0x241405, - 0x241e43, - 0x242246, - 0x242247, - 0x2f8983, - 0x20e00102, - 0x242bca, - 0x21377dc2, - 0x2173da82, - 0x21a3fd02, - 0x21e373c2, - 0x245385, - 0x245d44, - 0x22a05582, - 0x235805, - 0x23fa43, - 0x314885, - 0x2688c4, - 0x2afb04, - 0x2cea06, - 0x250f06, - 0x2061c3, - 0x3bb644, - 0x303ec3, - 0x23a02a42, - 0x213c44, - 0x213c46, - 0x221745, - 0x244d46, - 0x30a7c8, - 0x223dc4, - 0x367bc8, - 0x231e85, - 0x262b88, - 0x2caa06, - 0x217c07, - 0x273504, - 0x24e73506, - 0x252239c3, - 0x39e183, - 0x31d988, - 0x29ffc4, - 0x2572ccc7, - 0x25ee4846, - 0x2e4849, - 0x362008, - 0x36a408, - 0x3b6544, - 0x3c7903, - 0x22dd42, - 0x2624e782, - 0x2660fa82, - 0x3c9083, - 0x26a01642, - 0x2f8904, - 0x279986, - 0x21c103, - 0x2bc7c7, - 0x303743, - 0x32fcc8, - 0x20b885, - 0x25a683, - 0x2c2345, - 0x2c2484, + 0x375d04, + 0x2ef285, + 0x2215c7, + 0x25d04e, + 0x2ba484, + 0x29a884, + 0x20ebc3, + 0x35c549, + 0x2c17cb, + 0x2c75c8, + 0x32cc48, + 0x3313c8, + 0x3e1f48, + 0x370d8a, + 0x390d07, + 0x356606, + 0x9e3de82, + 0x26f0c3, + 0x3d2103, + 0x3d3c84, + 0x26f103, + 0x361e43, + 0x1737f82, + 0xa206c02, + 0x284a05, + 0x2bc146, + 0x234944, + 0x3aee07, + 0x26bdc6, + 0x2cd644, + 0x3bdc87, + 0x20d483, + 0xa6d7f02, + 0xab0bf02, + 0xae7b6c2, 0x30bcc6, - 0x20cac6, - 0x2198c6, - 0x2f39c4, - 0x234a03, - 0x26e0fe02, - 0x27233fc5, - 0x200843, - 0x27a07382, - 0x22d1c3, - 0x2676c5, - 0x27e32dc3, - 0x28632dc9, - 0x28a00942, - 0x29208902, - 0x28ce05, - 0x213f06, - 0x28ec06, - 0x2e6608, - 0x2e660b, - 0x339c8b, - 0x3b93c5, - 0x2d6149, - 0x1600b42, - 0x2f0548, - 0x206984, - 0x29a03c42, - 0x34a843, - 0x2a25c986, - 0x3c2888, - 0x2a6142c2, - 0x35c708, - 0x2aaaac82, - 0x337f8a, - 0x2aedb383, - 0x2b776c86, - 0x392488, - 0x214886, - 0x387b87, - 0x243147, - 0x3c33ca, - 0x247604, - 0x360704, - 0x376209, - 0x2bba9dc5, - 0x26e286, - 0x210e03, - 0x24e3c4, - 0x2be196c4, - 0x3458c7, - 0x2c241c87, - 0x294f84, - 0x39f545, - 0x262548, - 0x39e647, - 0x3a24c7, - 0x2c60dec2, - 0x29cf44, - 0x293048, - 0x246d84, - 0x24b904, - 0x24bcc5, - 0x24be07, - 0x2ca7ebc9, - 0x2232c4, - 0x24d789, - 0x24d9c8, - 0x24e144, - 0x24e147, - 0x2ce4e583, - 0x24ea87, - 0x2d20bb42, - 0x16b8842, - 0x24fa46, - 0x250087, - 0x250704, - 0x251d07, - 0x253787, - 0x253f83, - 0x22dec2, - 0x20d982, - 0x303243, - 0x3c6704, - 0x3c670b, - 0x2d703248, - 0x25a044, - 0x255b85, - 0x257407, - 0x2e92c5, - 0x33144a, + 0xb200282, + 0x2a4d45, + 0x3394c3, + 0x3d5bc4, + 0x2f9284, + 0x2f9285, + 0x3dff03, + 0xb64ac43, + 0xba05102, + 0x2093c5, + 0x2093cb, + 0x2b2a0b, + 0x204cc4, + 0x209849, + 0x20ae84, + 0xbe0b742, + 0x20c303, + 0x20e1c3, + 0xc207f42, + 0x2f2aca, + 0xc608a02, + 0x280285, + 0x2e858a, + 0x242644, + 0x210143, + 0x210a04, + 0x211943, + 0x211944, + 0x211947, + 0x212685, + 0x213086, + 0x213386, + 0x214683, + 0x218248, + 0x217143, + 0xca0cfc2, + 0x266308, + 0x28ea8b, + 0x2208c8, + 0x221106, + 0x222887, + 0x225048, + 0xda0aac2, + 0xde1c942, + 0x272d48, + 0x20f1c7, + 0x20f705, + 0x310f88, + 0xe302e48, + 0x2b0ec3, + 0x22bec4, + 0x391f42, + 0xe62c0c2, + 0xea06cc2, + 0xf22c442, + 0x22c443, + 0xf60cf02, + 0x316343, + 0x332284, + 0x214803, + 0x354f44, + 0x32430b, + 0x20cf03, + 0x2f2086, + 0x25d544, + 0x2c888e, + 0x377205, + 0x268a88, + 0x3adfc7, + 0x3adfca, + 0x231503, + 0x2355c7, + 0x2c1985, + 0x231504, + 0x253a06, + 0x253a07, + 0x31dd84, + 0xfb109c4, + 0x25d384, + 0x25d386, + 0x252684, + 0x3c2f86, + 0x20f4c3, + 0x20f4c8, + 0x210448, + 0x29a843, + 0x2f2a83, + 0x343c04, + 0x35c0c3, + 0x1020cdc2, + 0x106bd282, + 0x205083, + 0x243fc6, + 0x25bac3, + 0x274784, + 0x10a30c82, + 0x25ce43, + 0x316a83, + 0x214dc2, + 0x10e00d42, + 0x2d3286, + 0x235a07, + 0x229bc7, + 0x3c0d85, + 0x21cc84, + 0x2a0dc5, + 0x30f247, + 0x2e5a49, + 0x2ee886, + 0x3032c6, + 0x11602282, + 0x307a08, + 0x31a706, + 0x2b1bc5, + 0x30c3c7, + 0x30dcc4, + 0x30dcc5, + 0x11a02284, + 0x202288, + 0x11e09482, + 0x12200482, + 0x275946, + 0x200488, + 0x337b45, + 0x34d686, + 0x350448, + 0x360a48, + 0x12608cc5, + 0x12a15e84, + 0x215e87, + 0x12e0a902, + 0x13361e82, + 0x14612402, + 0x2f4505, + 0x14e8af45, + 0x269506, + 0x327ec7, + 0x3b26c7, + 0x1522ea43, + 0x32bb87, + 0x3c17c8, + 0x2162ed49, + 0x2753c7, + 0x22f487, + 0x22fe88, + 0x230686, + 0x231006, + 0x231c4c, + 0x23294a, + 0x232d47, + 0x234a4b, + 0x235847, + 0x23584e, + 0x21a36344, + 0x236704, + 0x238a07, + 0x260b47, + 0x23d046, + 0x23d047, + 0x335887, + 0x226dc3, + 0x21e2c982, + 0x23e846, + 0x23e84a, + 0x24004b, + 0x241287, + 0x241d05, + 0x242183, + 0x2423c6, + 0x2423c7, + 0x2fa483, + 0x22200102, + 0x2435ca, + 0x2277c682, + 0x22b49682, + 0x22e40902, + 0x23237402, + 0x246ac5, + 0x247344, + 0x23e0da02, + 0x385d85, + 0x240643, + 0x299645, + 0x201ec4, + 0x21dd04, + 0x2d4e46, + 0x251dc6, + 0x2095c3, + 0x3cce44, + 0x37f243, + 0x24e0f982, + 0x216404, + 0x216406, + 0x222c05, + 0x2482c6, + 0x30c4c8, + 0x265e44, + 0x294208, + 0x232fc5, + 0x259508, + 0x2d0686, + 0x30e0c7, + 0x269c04, + 0x26269c06, + 0x26622383, + 0x3a47c3, + 0x2f7108, + 0x38bc44, + 0x26b32ec7, + 0x2e6946, + 0x2e6949, + 0x369588, + 0x37d748, + 0x389c84, + 0x204583, + 0x240702, + 0x2724e682, + 0x27626282, + 0x205c83, + 0x27a08b02, + 0x2fa404, + 0x2790c6, + 0x21a203, + 0x2c3d47, + 0x3b3a83, + 0x2ba548, + 0x21edc5, 0x259f83, - 0x2da05dc2, - 0x208e04, - 0x25c589, - 0x260c03, - 0x260cc7, - 0x240749, - 0x205dc8, - 0x22af03, - 0x27b3c7, - 0x27be89, - 0x225583, - 0x283b84, - 0x284d09, - 0x28b2c6, - 0x2f4103, - 0x2015c2, - 0x23c903, - 0x2b8647, - 0x23c905, - 0x3b1686, - 0x270744, - 0x35ff85, - 0x27c7c3, - 0x212706, - 0x292503, - 0x206882, - 0x248f84, - 0x2de299c2, - 0x2e2299c3, - 0x2e607082, - 0x248343, - 0x211cc4, - 0x2ece07, - 0x2946c6, - 0x262302, - 0x2ea5cd82, - 0x30a9c4, - 0x2f20d842, - 0x2f616902, - 0x2ef084, - 0x2ef085, - 0x302c85, - 0x35ef86, - 0x2fa0f582, - 0x39b745, - 0x3ba745, - 0x283483, - 0x20f586, - 0x20fec5, - 0x220cc2, - 0x35b3c5, - 0x220cc4, - 0x223d03, - 0x223f43, - 0x2fe05b42, - 0x2e29c7, - 0x24dbc4, + 0x2ef205, + 0x2ef344, + 0x30d9c6, + 0x220006, + 0x221506, + 0x2f4c84, + 0x235c03, + 0x27e11702, + 0x282351c5, + 0x200843, + 0x28a0da82, + 0x22f203, + 0x3233c5, + 0x28e33fc3, + 0x29633fc9, + 0x29a00942, + 0x2a20fc42, + 0x292845, + 0x2166c6, + 0x2ada86, + 0x2e9f08, + 0x2e9f0b, + 0x346d4b, + 0x3c0f85, + 0x2d8489, + 0x1600b42, + 0x39b4c8, + 0x209b44, + 0x2aa031c2, + 0x34ca03, + 0x2b260d06, + 0x2b600fc2, + 0x3619c8, + 0x2ba293c2, + 0x33d78a, + 0x2bedd983, + 0x2c77b706, + 0x397c88, + 0x242986, + 0x38dc47, + 0x243b47, + 0x3cd90a, + 0x2426c4, + 0x365c04, + 0x37a709, + 0x2cbb1905, + 0x275246, + 0x20f3c3, + 0x24e104, + 0x2ced8384, + 0x3b4447, + 0x2d233647, + 0x25ce84, + 0x3b2b85, + 0x2695c8, + 0x3a4c87, + 0x3a9847, + 0x2d60fa02, + 0x26acc4, + 0x2981c8, + 0x248604, + 0x24bb44, + 0x24bf45, + 0x24c087, + 0x2da81989, + 0x21eb04, + 0x24d4c9, + 0x24d708, + 0x24de84, + 0x24de87, + 0x2de4e483, + 0x24f8c7, + 0x2e201282, + 0x16be142, + 0x250386, + 0x251187, + 0x2515c4, + 0x252dc7, + 0x254047, + 0x254603, + 0x2ba882, + 0x20e782, + 0x32cd43, + 0x3ce884, + 0x3ce88b, + 0x2e72cd48, + 0x259a04, + 0x255d05, + 0x2576c7, + 0x20e785, + 0x31d28a, + 0x259943, + 0x2ea091c2, + 0x21d304, + 0x260909, + 0x264e43, + 0x264f07, + 0x28c949, + 0x2091c8, + 0x26f783, + 0x283187, + 0x283b89, + 0x26a503, + 0x28b544, + 0x28cb89, + 0x290cc6, + 0x2e9d03, + 0x207c82, + 0x23cc03, + 0x2bdf47, + 0x23cc05, + 0x2c15c6, + 0x296d84, + 0x365485, + 0x2844c3, + 0x2148c6, + 0x27eb43, + 0x209a42, + 0x24ac04, + 0x2ee08882, + 0x2f368483, + 0x2f6033c2, + 0x249f83, + 0x20dc44, + 0x303b07, + 0x348546, + 0x27cec2, + 0x2fa04d82, + 0x30c6c4, + 0x30211ac2, + 0x30621c42, + 0x2f0f04, + 0x2f0f05, + 0x363e85, + 0x260286, + 0x30a06d42, + 0x20f8c5, + 0x219a45, + 0x21bb43, + 0x225d86, + 0x227545, + 0x265d82, + 0x360685, + 0x30bc44, + 0x265d83, + 0x265fc3, + 0x30e08f42, + 0x2e4dc7, + 0x24d904, + 0x24d909, + 0x24e004, + 0x28adc3, + 0x2b9808, + 0x3128adc4, + 0x28adc6, + 0x2a49c3, + 0x256543, + 0x266a83, + 0x316fb9c2, + 0x308982, + 0x31a00642, + 0x33b208, + 0x3e0108, + 0x3bef86, + 0x351a05, + 0x303c85, + 0x207d87, + 0x31e46145, + 0x23ca82, + 0x3229cac2, + 0x32600042, + 0x27db48, + 0x31a645, + 0x2feac4, + 0x248205, + 0x2497c7, + 0x388944, + 0x2434c2, + 0x32a0b2c2, + 0x352084, + 0x228b07, + 0x292d07, + 0x390dc4, + 0x3d2c03, + 0x29a784, + 0x29a788, + 0x231346, + 0x25388a, + 0x2f5844, + 0x299e48, + 0x235384, + 0x222986, + 0x29ca84, + 0x2f4806, 0x24dbc9, - 0x24e2c4, - 0x2833c3, - 0x2b61c8, - 0x302833c4, - 0x2833c6, - 0x2a37c3, - 0x256303, - 0x308003, - 0x306f7642, - 0x309442, - 0x30a00642, - 0x335408, - 0x36af48, - 0x3b7906, - 0x3830c5, - 0x22c805, - 0x204287, - 0x30e77185, - 0x23c782, - 0x3129b602, - 0x31600042, - 0x2cfb88, - 0x3ae105, - 0x2fc7c4, - 0x244c85, - 0x2547c7, - 0x3a3884, - 0x242ac2, - 0x31a11442, - 0x351144, - 0x220a87, - 0x28ddc7, - 0x378804, - 0x3cd483, - 0x290e44, - 0x290e48, - 0x22f7c6, - 0x25254a, - 0x326584, - 0x299288, - 0x234184, - 0x2214c6, - 0x29b5c4, - 0x2f3546, - 0x24de89, - 0x2a9fc7, - 0x207543, - 0x31e3ee42, - 0x3b62c3, - 0x209e42, - 0x32204702, - 0x349e46, - 0x381988, - 0x2abfc7, - 0x228b09, - 0x2b1549, - 0x2ad505, - 0x2afc09, + 0x2abc07, + 0x213ec3, + 0x32e5b542, + 0x3a2503, + 0x20b942, + 0x33205742, + 0x34c006, + 0x386d08, + 0x2adc07, + 0x30b109, + 0x2addc9, 0x2b0405, - 0x2b1245, - 0x2b1f88, - 0x32608784, - 0x32a540c7, - 0x22d803, - 0x2b2187, - 0x22d806, - 0x2b25c7, - 0x2a9ac5, - 0x22d083, - 0x32e315c2, - 0x20b344, - 0x3320e782, - 0x33606502, - 0x380e86, - 0x2d04c5, - 0x2b54c7, - 0x343a03, - 0x35cb04, - 0x209283, - 0x2cd743, - 0x33a06182, - 0x34205bc2, - 0x38b244, - 0x22de83, - 0x3047c5, - 0x34600f42, - 0x34e01f02, - 0x304fc6, - 0x201f04, - 0x306bc4, - 0x306bca, - 0x356005c2, - 0x213903, - 0x21884a, - 0x21bac8, - 0x35a21dc4, + 0x2b2d89, + 0x2b3cc5, + 0x2b4b05, + 0x2b5f88, + 0x33611b04, + 0x33a54747, + 0x22f843, + 0x2b6187, + 0x22f846, + 0x2b6987, + 0x2ab845, + 0x22f0c3, + 0x33e32702, + 0x210384, + 0x3422cb02, + 0x3460b5c2, + 0x314d06, + 0x27e485, + 0x2b8ec7, + 0x356e03, + 0x361dc4, + 0x21d783, + 0x355e03, + 0x34a09582, + 0x35208fc2, + 0x391fc4, + 0x32ae03, + 0x305545, + 0x3560f782, + 0x35e02182, + 0x305d46, + 0x2069c4, + 0x30a304, + 0x30a30a, + 0x366005c2, + 0x2160c3, + 0x21528a, + 0x219008, + 0x36a0e704, 0x2005c3, - 0x35e96b43, - 0x237909, - 0x22e0c9, - 0x2bc8c6, - 0x3621bc83, - 0x21bc85, - 0x222f8d, - 0x226586, - 0x26518b, - 0x3660ccc2, - 0x205708, - 0x3a217782, - 0x3a605382, - 0x2bad85, - 0x3aa04582, - 0x2b3307, - 0x210083, - 0x210088, - 0x3ae07882, - 0x288304, - 0x20c743, - 0x33b805, - 0x23fb46, - 0x224004, - 0x398983, - 0x2b9583, - 0x3b201d82, - 0x3b9344, - 0x3d4c45, - 0x2b8247, - 0x279403, - 0x2b8e43, - 0x16b9102, - 0x2b9103, - 0x2b9503, - 0x3b600e02, - 0x3473c4, - 0x251106, - 0x2e42c3, - 0x2b9c03, - 0x3ba49582, - 0x249588, - 0x2bab84, - 0x347146, - 0x255707, - 0x284646, - 0x29ff44, - 0x49e03fc2, - 0x22d6cb, - 0x2ff50e, - 0x216d8f, - 0x39d6c3, - 0x4a65ac42, - 0x161fb02, - 0x4aa0af02, - 0x2928c3, - 0x2108c3, - 0x20af06, - 0x21d306, - 0x34dec7, - 0x310c44, - 0x4ae14042, - 0x4b20bd82, - 0x2e0685, - 0x2ff987, - 0x2bb206, - 0x4b662702, - 0x384c44, - 0x2c03c3, - 0x4ba01e42, - 0x4bf73103, - 0x2c2984, - 0x2c8009, - 0x4c2ced42, - 0x4c614d42, - 0x344945, - 0x4cad3942, - 0x4ce06002, - 0x35f947, - 0x3768cb, - 0x242f05, - 0x258109, - 0x26aa86, - 0x4d209504, - 0x295449, - 0x2d46c7, - 0x3dea87, - 0x22c343, - 0x2eef06, - 0x324047, - 0x25cc03, - 0x2a6a86, - 0x4da1ae42, - 0x4de33042, - 0x3b6403, - 0x38b885, - 0x21f407, - 0x236146, - 0x23c885, - 0x24db44, - 0x2a8985, - 0x38dac4, - 0x4e202482, - 0x2cc844, - 0x22dfc4, - 0x22dfcd, - 0x377189, - 0x22c648, - 0x344bc4, - 0x328845, - 0x3b8c47, - 0x3c5cc4, - 0x265907, - 0x2e4005, - 0x4e6ac604, - 0x2bf345, - 0x25f8c4, - 0x3a39c6, - 0x3973c5, - 0x4ea05442, - 0x26e903, - 0x267fc3, - 0x348cc4, - 0x348cc5, - 0x396886, - 0x23c9c5, - 0x22ae84, - 0x329743, - 0x4ee1a286, - 0x221fc5, - 0x222a85, - 0x323544, - 0x2f6283, - 0x32660c, - 0x4f208b02, - 0x4f605102, - 0x4fa02042, - 0x21a8c3, - 0x21a8c4, - 0x4fe08282, - 0x30e1c8, - 0x3b1745, - 0x2d3b84, - 0x23af86, - 0x50223502, - 0x5061b542, - 0x50a00c42, - 0x290c05, - 0x2f3886, - 0x219604, - 0x39a0c6, - 0x362dc6, - 0x211183, - 0x50ea240a, - 0x2795c5, - 0x350503, - 0x222446, - 0x3d2d09, - 0x222447, - 0x2b4d08, - 0x32f0c9, - 0x2adfc8, - 0x227d46, - 0x2105c3, - 0x512017c2, - 0x39fa08, - 0x51601f42, - 0x51a09e82, - 0x2137c3, - 0x2ec0c5, - 0x29f2c4, - 0x2fe389, - 0x2898c4, - 0x24aec8, - 0x52209e83, - 0x52696ec4, - 0x213f48, - 0x22df07, - 0x52b3d642, - 0x238442, - 0x32a385, - 0x37fc49, - 0x23c803, - 0x27e384, - 0x31d284, - 0x210943, - 0x27f28a, - 0x52e03f82, - 0x5320b182, - 0x2d5b43, - 0x390883, - 0x1627f82, - 0x374583, - 0x5361d542, - 0x53a00bc2, - 0x53f06c44, - 0x3d7846, - 0x26bbc4, - 0x277d83, - 0x281c83, - 0x54200bc3, - 0x23f7c6, - 0x208405, - 0x2d9ac7, - 0x2d9a06, - 0x2dab48, - 0x2dad46, - 0x20e944, - 0x2a0f0b, - 0x2dd603, - 0x2dd605, - 0x20f002, - 0x35fc42, - 0x54645402, - 0x54a02382, - 0x202383, - 0x54e6c9c2, - 0x26c9c3, - 0x2de083, - 0x55622902, - 0x55ae2406, - 0x258946, - 0x55e05902, - 0x5620a742, - 0x56623f82, - 0x56a15402, - 0x56e18482, - 0x57202802, - 0x214e83, - 0x385546, - 0x57619a04, - 0x213a4a, - 0x3a4986, - 0x20da84, - 0x204643, - 0x5820ce02, - 0x208482, - 0x23a0c3, - 0x5861a3c3, - 0x3bd287, - 0x3972c7, - 0x5aed3087, - 0x344407, - 0x228643, - 0x22864a, - 0x262044, - 0x31afc4, - 0x31afca, - 0x22cb45, - 0x5b21bb02, - 0x24fa03, - 0x5b600602, - 0x24e283, - 0x3b6283, - 0x5be00582, - 0x38a8c4, - 0x204484, - 0x3c2d05, - 0x3dd205, - 0x26a146, - 0x306e06, - 0x5c230a42, - 0x5c602902, - 0x310805, - 0x258652, - 0x35e586, - 0x207283, - 0x359106, - 0x2bf805, - 0x16535c2, - 0x64a0a9c2, - 0x372b43, - 0x20a9c3, - 0x292083, - 0x64e06e42, - 0x210e83, - 0x6521ad42, - 0x276e43, - 0x24b188, - 0x2624c3, - 0x2ad386, - 0x3cfc87, - 0x321486, - 0x32148b, - 0x20d9c7, - 0x31d784, - 0x65a00c02, - 0x3b15c5, - 0x65e08443, - 0x26d243, - 0x3b29c5, - 0x33f243, - 0x6673f246, - 0x3cac0a, - 0x2a7083, - 0x2366c4, + 0x36e0a2c3, + 0x26a749, + 0x247109, + 0x2c3e46, + 0x372191c3, + 0x2191c5, + 0x21e7cd, + 0x22db06, + 0x2e61cb, + 0x37607542, + 0x358448, + 0x3b20c202, + 0x3b603082, + 0x39e285, + 0x3ba04b82, + 0x2af7c7, + 0x205603, + 0x227708, + 0x3be022c2, + 0x25ef84, + 0x21fc83, + 0x354a05, + 0x240746, + 0x227104, + 0x2f2a43, + 0x384583, + 0x3c206142, + 0x3c0f04, + 0x2bab45, + 0x2bdb47, + 0x281403, + 0x2be4c3, + 0x1616fc2, + 0x2be783, + 0x2beb83, + 0x3c600e02, + 0x33f584, + 0x235e06, + 0x2e6503, + 0x2bf943, + 0x3ca4b202, + 0x24b208, + 0x2c0904, + 0x33f306, + 0x253e87, + 0x29a946, + 0x38bbc4, + 0x4ae03102, + 0x22f70b, + 0x30180e, + 0x217a8f, + 0x2be183, + 0x4b65a642, + 0x1641882, + 0x4ba03802, + 0x2563c3, + 0x20ee83, + 0x21b306, + 0x34e0c6, + 0x395dc7, + 0x3d2484, + 0x4be16802, + 0x4c21f2c2, + 0x2e2845, + 0x33dec7, + 0x2c2506, + 0x4c669782, + 0x3626c4, + 0x2c7a83, + 0x4ca06902, + 0x4cf78103, + 0x2c9284, + 0x2cde89, + 0x4d2d5182, + 0x4d60a342, + 0x248985, + 0x4dad5682, + 0x4de01582, + 0x364e47, + 0x37b34b, + 0x243905, + 0x258509, + 0x270906, + 0x4e201584, + 0x206d89, + 0x2d6a07, + 0x22a147, + 0x22c743, + 0x2f0d86, + 0x352f87, + 0x21df43, + 0x2a87c6, + 0x4ea29a82, + 0x4ee34242, + 0x2061c3, + 0x392605, + 0x303147, + 0x236d06, + 0x23cb85, + 0x24d884, + 0x2aad45, + 0x393dc4, + 0x4f201482, + 0x2e9184, + 0x247004, + 0x24700d, + 0x2ee249, + 0x22ca48, + 0x248c04, + 0x347fc5, + 0x204407, + 0x206504, + 0x26be87, + 0x267a45, + 0x4f60a284, + 0x2c6045, + 0x201484, + 0x253306, + 0x394fc5, + 0x4faa4c82, + 0x2758c3, + 0x357643, + 0x35d804, + 0x35d805, + 0x39d506, + 0x23ccc5, + 0x368e84, + 0x364343, + 0x4fe17e86, + 0x21a8c5, + 0x21e2c5, + 0x327dc4, + 0x2f58c3, + 0x2f58cc, + 0x502bdc42, + 0x50600e82, + 0x50a02702, + 0x21e1c3, + 0x21e1c4, + 0x50e0a682, + 0x3b9e88, + 0x2c1685, + 0x2d5ec4, + 0x230e86, + 0x51204202, + 0x5162d582, + 0x51a00c42, + 0x296545, + 0x2f4b46, + 0x265684, + 0x335386, + 0x229306, + 0x25bfc3, + 0x51e9068a, + 0x2815c5, + 0x293783, + 0x209f06, + 0x209f09, + 0x223fc7, + 0x2b7fc8, + 0x3c84c9, + 0x2e5bc8, + 0x22dd86, + 0x20eb83, + 0x52208c82, + 0x32d248, + 0x52606a02, + 0x52a0b982, + 0x215f83, + 0x2ee705, + 0x2a0484, + 0x300689, + 0x3c04c4, + 0x20bc08, + 0x5320b983, + 0x53724784, + 0x216708, + 0x246f47, + 0x53b49242, + 0x370242, + 0x32f4c5, + 0x385509, + 0x23cb03, + 0x31bb84, + 0x3424c4, + 0x204483, + 0x28698a, + 0x53f93b42, + 0x542101c2, + 0x2d7e83, + 0x396083, + 0x162dfc2, + 0x26e8c3, + 0x54615782, + 0x54a00bc2, + 0x54e17544, + 0x217546, + 0x271a44, + 0x27d983, + 0x289683, + 0x55200bc3, + 0x2403c6, + 0x3d5d85, + 0x2dbe07, + 0x2dbd46, + 0x2dcd88, + 0x2dcf86, + 0x202a04, + 0x2a21cb, + 0x2dfa03, + 0x2dfa05, + 0x20e982, + 0x365142, + 0x55646b42, + 0x55a0a942, + 0x216843, + 0x55e720c2, + 0x2720c3, + 0x2e0483, + 0x56603e42, + 0x56ae4806, + 0x258d46, + 0x56ee4942, + 0x5720e202, + 0x57666002, + 0x57a0cac2, + 0x57e0e882, + 0x58203882, + 0x20c543, + 0x3af006, + 0x5861e484, + 0x21620a, + 0x3b0106, + 0x281284, + 0x208143, + 0x59216102, + 0x203182, + 0x241c83, + 0x59617fc3, + 0x3c49c7, + 0x394ec7, + 0x5c245ec7, + 0x37efc7, + 0x228803, + 0x22880a, + 0x237bc4, + 0x31ef04, + 0x31ef0a, + 0x22eb85, + 0x5c60e742, + 0x250343, + 0x5ca00602, + 0x24dfc3, + 0x3a24c3, + 0x5d200582, + 0x3c1744, + 0x207f84, + 0x3dcc45, + 0x32e9c5, + 0x2f6786, + 0x30a546, + 0x5d63bec2, + 0x5da02542, + 0x301dc5, + 0x258a52, + 0x363486, + 0x291043, + 0x31c146, + 0x2b6585, + 0x1605cc2, + 0x65e0fec2, + 0x377b43, + 0x20fec3, + 0x39f483, + 0x66201102, + 0x20f443, + 0x666035c2, + 0x207583, + 0x3dcf88, + 0x269543, + 0x2b0286, + 0x3da087, + 0x34f0c6, + 0x34f0cb, + 0x2811c7, + 0x2f6f04, + 0x66e00c02, + 0x2c1505, + 0x67217f83, + 0x235fc3, + 0x332505, + 0x34a9c3, + 0x67b4a9c6, + 0x3d048a, + 0x2a98c3, + 0x2371c4, 0x2003c6, - 0x2af186, - 0x66a42543, - 0x299047, - 0x237807, - 0x2a2c85, - 0x2e4406, - 0x222003, - 0x6960f7c3, - 0x69a00a82, - 0x69e0f044, - 0x3df689, - 0x21d685, - 0x356cc4, - 0x355b48, - 0x264bc5, - 0x6a231ac5, - 0x241f49, - 0x202503, - 0x33da04, - 0x6a614282, - 0x214283, - 0x6aa57b82, - 0x257b86, - 0x1677282, - 0x6ae15302, - 0x290b08, - 0x290e03, - 0x2bf287, - 0x2b9605, - 0x2b9185, - 0x2b918b, - 0x2eec86, - 0x2b9386, - 0x27d384, - 0x2efa86, - 0x6b321708, - 0x27fd03, - 0x265f03, - 0x265f04, - 0x2ed8c4, - 0x2f6a07, - 0x315645, - 0x6b72a5c2, - 0x6ba0a882, - 0x6c21bfc5, - 0x2c1044, - 0x2e0a0b, - 0x2f7688, - 0x253604, - 0x6c62c4c2, - 0x6ca1b742, - 0x3ba2c3, - 0x2f9484, - 0x2f9745, - 0x2fa147, - 0x6cefc304, - 0x378904, - 0x6d2141c2, - 0x37ab09, - 0x2fd745, - 0x2431c5, - 0x2fe2c5, - 0x6d6141c3, - 0x237f04, - 0x237f0b, - 0x2fedc4, - 0x2ff08b, - 0x3001c5, - 0x216eca, - 0x301708, - 0x30190a, - 0x3021c3, - 0x3021ca, - 0x6de11e42, - 0x6e214b42, - 0x6e615d43, - 0x6eadbd02, - 0x3068c3, - 0x6eef6702, - 0x6f333e42, - 0x309004, - 0x2177c6, - 0x399e05, - 0x30a643, - 0x298046, - 0x399905, - 0x3573c4, - 0x6f600902, - 0x299ec4, - 0x2d5dca, - 0x2ba807, - 0x33f5c6, - 0x234207, - 0x23e343, - 0x2c29c8, - 0x3d21cb, - 0x2bc9c5, - 0x2c8b85, - 0x2c8b86, - 0x34cb84, - 0x38ab88, - 0x21aa43, - 0x26ee44, - 0x3c3747, - 0x31d3c6, - 0x380b86, - 0x2c324a, - 0x24d804, - 0x31c0ca, - 0x6fb12606, - 0x312607, - 0x255c07, - 0x2a9a04, - 0x34a189, - 0x238dc5, - 0x307a4b, - 0x2f6603, - 0x20cc83, - 0x6fe1e243, - 0x22fb84, - 0x70200682, - 0x307e06, - 0x706c60c5, - 0x359345, - 0x24fc86, - 0x2a4944, - 0x70a013c2, - 0x241e84, - 0x70e0ca42, - 0x2160c5, - 0x3b2e44, - 0x71a1c5c3, - 0x71e0aa02, - 0x20aa03, - 0x21f646, - 0x72205142, - 0x393d08, - 0x2222c4, - 0x2222c6, - 0x391106, - 0x726574c4, - 0x21a205, - 0x356d88, - 0x3577c7, - 0x2ae247, - 0x2ae24f, - 0x292f46, - 0x23d183, - 0x242144, - 0x209043, - 0x221604, - 0x24e484, - 0x72a0b382, - 0x28d203, - 0x330983, - 0x72e090c2, - 0x215803, - 0x220f03, - 0x20e2ca, - 0x273847, - 0x25284c, - 0x73252b06, - 0x252c86, - 0x255407, - 0x7362e987, - 0x25a749, - 0x308784, - 0x73a5be04, - 0x73e023c2, - 0x742045c2, - 0x2c3606, - 0x298e44, - 0x28d686, - 0x22ee08, - 0x38b944, - 0x2eafc6, - 0x28ebc5, - 0x74750788, - 0x242343, - 0x3a6b85, - 0x3aa603, - 0x2432c3, - 0x2432c4, - 0x208dc3, - 0x74a499c2, - 0x74e02d42, - 0x2f64c9, - 0x290d05, - 0x291604, - 0x29ab05, - 0x206a84, - 0x286707, - 0x35a685, - 0x7523e804, - 0x2db988, - 0x2dcdc6, - 0x2e2d44, - 0x2e6e08, - 0x2e7447, - 0x75607842, - 0x2ef184, - 0x314cc4, - 0x2c9247, - 0x75a07844, - 0x24a682, - 0x75e02f82, - 0x210883, - 0x2e5ac4, - 0x299943, - 0x2b2cc5, - 0x7622ae42, - 0x309345, - 0x23c7c2, - 0x30f885, - 0x23c7c5, - 0x76607242, - 0x327704, - 0x76a071c2, - 0x33d486, - 0x2c8686, - 0x37fd88, - 0x2c9c08, - 0x380e04, - 0x3cdb05, - 0x310389, - 0x2f0684, - 0x3cabc4, - 0x288b03, - 0x26c703, - 0x76f02905, - 0x3829c5, - 0x283644, - 0x359b4d, - 0x294ec2, - 0x376403, - 0x77206382, - 0x77603242, - 0x3937c5, - 0x24b447, - 0x224244, - 0x32f2c9, - 0x2d5f09, - 0x2770c3, - 0x2770c8, - 0x312b09, - 0x21e7c7, - 0x77a08805, - 0x396406, - 0x3a06c6, - 0x3a8645, - 0x377285, - 0x77e01bc2, - 0x27a585, - 0x2bd4c8, - 0x2cb646, - 0x783b4347, - 0x2cf3c4, - 0x2da987, - 0x30b306, - 0x78601082, - 0x396586, - 0x30ef0a, - 0x30f785, - 0x78af00c2, - 0x78e11102, - 0x365486, - 0x211108, - 0x7928df87, - 0x79601402, - 0x214b83, - 0x3c0706, - 0x2caac4, - 0x346ac6, - 0x271ac6, - 0x26930a, - 0x2047c5, - 0x286f06, - 0x384643, - 0x384644, - 0x79a35002, - 0x2869c3, - 0x79e1a902, - 0x2ea903, - 0x7a218ac4, - 0x211244, - 0x7a61124a, - 0x21bd03, - 0x237ac7, - 0x313146, - 0x33c284, - 0x20d942, - 0x2aad42, - 0x7aa007c2, - 0x226e83, - 0x2559c7, + 0x2b1fc6, + 0x67e3e083, + 0x273987, + 0x26a647, + 0x2a3e85, + 0x2b2346, + 0x21a903, + 0x6aa25fc3, + 0x6ae00a82, + 0x6b20e9c4, + 0x213b49, + 0x226685, + 0x266e44, + 0x35a3c8, + 0x241e85, + 0x6b642285, + 0x247e89, + 0x3b4d43, + 0x349604, + 0x6ba05b42, + 0x216a43, + 0x6be75c42, + 0x275c46, + 0x167ce82, + 0x6c20c182, + 0x296448, + 0x29a743, + 0x2c5f87, + 0x384605, + 0x2be805, + 0x2be80b, + 0x2f0b06, + 0x2bea06, + 0x2804c4, + 0x211c86, + 0x6c6f1608, + 0x287403, + 0x25be43, + 0x25be44, + 0x2f0184, + 0x2f8747, + 0x318245, + 0x6cb20202, + 0x6ce04fc2, + 0x6d604fc5, + 0x2c6a84, + 0x2f114b, + 0x2f9188, + 0x306444, + 0x6da2c8c2, + 0x6de2d782, + 0x3c2f03, + 0x2faf84, + 0x2fb245, + 0x2fbd47, + 0x6e2fe604, + 0x390ec4, + 0x6e616982, + 0x380fc9, + 0x2ffa45, + 0x243bc5, + 0x3005c5, + 0x6ea16983, + 0x237e84, + 0x237e8b, + 0x3010c4, + 0x30138b, + 0x301f05, + 0x217bca, + 0x303dc8, + 0x303fca, + 0x304883, + 0x30488a, + 0x6f213982, + 0x6f642c42, + 0x6fa0d403, + 0x6fede302, + 0x307643, + 0x702f8442, + 0x70739c42, + 0x308544, + 0x218386, + 0x3350c5, + 0x30c343, + 0x32fc06, + 0x3a0645, + 0x366b44, + 0x70a00902, + 0x2ae704, + 0x2d810a, + 0x2c0587, + 0x34ad46, + 0x235407, + 0x23e883, + 0x2c92c8, + 0x3dc44b, + 0x2ce445, + 0x223585, + 0x223586, + 0x342604, + 0x3cd748, + 0x2198c3, + 0x28b144, + 0x3cdc87, + 0x2f6b46, + 0x314a06, + 0x2c86ca, + 0x24d544, + 0x3214ca, + 0x70f5ccc6, + 0x35ccc7, + 0x255d87, + 0x2ab784, + 0x34c349, + 0x238cc5, + 0x2f8343, + 0x2201c3, + 0x7121b843, + 0x231704, + 0x71600682, + 0x266886, + 0x71acbc45, + 0x31c385, + 0x2505c6, + 0x2a6184, + 0x71e02b02, + 0x2421c4, + 0x7220d782, + 0x20d785, + 0x37d504, + 0x7361a6c3, + 0x73a08382, + 0x208383, + 0x34d886, + 0x73e07742, + 0x399508, + 0x223e44, + 0x223e46, + 0x396906, + 0x74257784, + 0x217e05, + 0x368548, + 0x265c07, + 0x2b1087, + 0x2b108f, + 0x2980c6, + 0x23c0c3, + 0x23db04, + 0x219b43, + 0x222ac4, + 0x24c404, + 0x74606c82, + 0x2bef83, + 0x337143, + 0x74a08502, + 0x20cec3, + 0x30be83, + 0x21270a, + 0x279407, + 0x25070c, + 0x74e509c6, + 0x250b46, + 0x253b87, + 0x752302c7, + 0x259009, + 0x75666444, + 0x75a0a1c2, + 0x75e02442, + 0x2c8a86, + 0x273784, + 0x2bf406, + 0x230748, + 0x3926c4, + 0x2f7a46, + 0x2ada45, + 0x7628dc88, + 0x2424c3, + 0x292005, + 0x3ab143, + 0x243cc3, + 0x243cc4, + 0x21d2c3, + 0x7664b642, + 0x76a04782, + 0x2f8209, + 0x293a05, + 0x293d84, + 0x294545, + 0x210f44, + 0x28eec7, + 0x35ff05, + 0x772ddf84, + 0x2ddf88, + 0x2df1c6, + 0x2e5144, + 0x2e8988, + 0x2e8fc7, + 0x7760ab02, + 0x2f1004, + 0x219c04, + 0x2ceb87, + 0x77a0ab04, + 0x2670c2, + 0x77e0ee42, + 0x20ee43, + 0x248884, + 0x29a503, + 0x2b7085, + 0x78201442, + 0x308885, + 0x23cac2, + 0x312645, + 0x23cac5, + 0x786010c2, + 0x316a04, + 0x78a018c2, + 0x349086, + 0x25ab46, + 0x385648, + 0x2cf888, + 0x314c84, + 0x35a585, + 0x310489, + 0x39b604, + 0x3d0444, + 0x2132c3, + 0x237c83, + 0x78f1fb05, + 0x24fd85, + 0x28b044, + 0x35eacd, + 0x25cdc2, + 0x366543, + 0x79201702, + 0x79600ec2, + 0x398fc5, + 0x341947, + 0x227344, + 0x3c86c9, + 0x2d8249, + 0x25fc83, + 0x27ccc8, + 0x35d1c9, + 0x220f47, + 0x79b7b845, + 0x39d086, + 0x3a7d46, + 0x3ac645, + 0x2ee345, + 0x79e06242, + 0x28db85, + 0x2c4b48, + 0x2d1686, + 0x7a22aa87, + 0x2d1ec4, + 0x2d1447, + 0x30d006, + 0x7a603c02, + 0x39d206, + 0x311cca, + 0x312545, + 0x7aa30ac2, + 0x7ae92ec2, + 0x36c7c6, + 0x7b292ec7, + 0x7b60d982, + 0x242c83, + 0x3c75c6, + 0x2d0744, + 0x33ec86, + 0x24eac6, + 0x20290a, + 0x359945, + 0x35c986, + 0x38a183, + 0x38a184, + 0x7ba1cc42, + 0x28f183, + 0x7be1e202, + 0x2fccc3, + 0x7c215504, + 0x20de04, + 0x7c60de0a, + 0x219243, + 0x239747, + 0x315146, + 0x3670c4, + 0x281142, + 0x2ac982, + 0x7ca007c2, + 0x22b3c3, + 0x255b47, 0x2007c7, - 0x285d84, - 0x3da107, - 0x2fa246, - 0x210d47, - 0x220e44, - 0x2ae145, - 0x205345, - 0x7ae1e502, - 0x3d7286, - 0x220383, - 0x2266c2, - 0x2266c6, - 0x7b21f3c2, - 0x7b633442, - 0x29d005, - 0x7ba02c02, - 0x7be02982, - 0x324605, - 0x2d7505, - 0x2ac745, - 0x7c65b003, - 0x279a45, - 0x2eed47, - 0x36cc85, - 0x204985, - 0x261f44, - 0x264a46, - 0x38e104, - 0x7ca008c2, - 0x7d793085, - 0x3cb087, - 0x3c0bc8, - 0x253c46, - 0x253c4d, - 0x262d49, - 0x262d52, - 0x37a385, - 0x37ae03, - 0x7da09d42, - 0x3027c4, - 0x226603, - 0x3cb985, - 0x310f85, - 0x7de0c782, - 0x25a6c3, - 0x7e226dc2, - 0x7ea1d602, - 0x7ee00082, - 0x2e9bc5, - 0x207643, - 0x7f205b02, - 0x7f60c2c2, - 0x38a886, - 0x2d020a, - 0x215003, - 0x2579c3, - 0x2ff8c3, - 0x80e01b82, - 0x8f20c1c2, - 0x8fa0a5c2, - 0x203642, - 0x3ce689, - 0x2ce144, - 0x2dfac8, - 0x8ff04382, - 0x90606482, - 0x3bd905, - 0x233c88, - 0x231148, - 0x2f738c, - 0x2398c3, - 0x90a075c2, - 0x90e00f02, - 0x24ac46, - 0x313fc5, - 0x295b43, - 0x254686, - 0x314106, - 0x296b03, - 0x314c03, - 0x315046, - 0x316884, - 0x29cc86, - 0x23cd44, - 0x316f44, - 0x31784a, - 0x912b7402, - 0x24e705, - 0x3193ca, - 0x319305, - 0x31a7c4, - 0x31a8c6, - 0x31aa44, - 0x214546, - 0x91602b42, - 0x24c5c6, - 0x33bf85, - 0x286d87, - 0x33a106, - 0x255604, - 0x2e3407, - 0x234f85, - 0x23b687, - 0x3bc247, - 0x3bc24e, - 0x278646, - 0x222705, - 0x207787, - 0x20a783, - 0x3d3a47, - 0x20ab85, - 0x2123c4, - 0x22a8c2, - 0x325ec7, - 0x310cc4, - 0x23dec4, - 0x284a4b, - 0x21cb83, - 0x2c4907, - 0x21cb84, - 0x2c4c07, - 0x3a6603, - 0x34e40d, - 0x3a2f88, - 0x91a292c4, - 0x23e705, - 0x31b805, - 0x31bc43, - 0x91e221c2, - 0x31d1c3, - 0x31e083, - 0x3d7404, - 0x27bf85, - 0x220407, - 0x3846c6, - 0x38d943, - 0x22680b, - 0x249b8b, - 0x2acfcb, - 0x2c174b, - 0x3ccd4a, - 0x31734b, - 0x36ea8b, - 0x394e0c, - 0x3ada0b, - 0x3bf811, - 0x3dcf4a, - 0x31f58b, - 0x31f84c, - 0x31fb4b, - 0x3200ca, - 0x3209ca, - 0x3221ce, - 0x32294b, - 0x322c0a, - 0x324751, - 0x324b8a, - 0x32508b, - 0x3255ce, - 0x326c8c, - 0x32784b, - 0x327b0e, - 0x327e8c, - 0x328bca, - 0x329d4c, - 0x9232a04a, - 0x32a808, - 0x32b3c9, - 0x32ce8a, - 0x32d10a, - 0x32d38b, - 0x33010e, - 0x3316d1, - 0x33e209, - 0x33e44a, - 0x33ee8b, - 0x33fc0d, - 0x340a8a, - 0x341616, - 0x34298b, - 0x34668a, - 0x347d0a, - 0x3490cb, - 0x34aa49, - 0x34ed89, - 0x34f30d, - 0x34fe0b, - 0x351c8b, - 0x352509, - 0x352b4e, - 0x35328a, - 0x353dca, - 0x35438a, - 0x354a0b, - 0x35524b, - 0x3585cd, - 0x35a18d, - 0x35b050, - 0x35b50b, - 0x35ce4c, - 0x35d98b, - 0x35f44b, - 0x360c4e, - 0x36128b, - 0x36128d, - 0x3663cb, - 0x366e4f, - 0x36720b, - 0x36860a, - 0x368e49, - 0x369509, - 0x9276988b, - 0x369b4e, - 0x369ece, - 0x36be8b, - 0x36d64f, - 0x370acb, - 0x370d8b, - 0x37104b, - 0x37164a, - 0x3764c9, - 0x37964f, - 0x37e24c, - 0x37f30c, - 0x38058e, - 0x3810cf, - 0x38148e, - 0x381e50, - 0x38224f, - 0x382b4e, - 0x38320c, - 0x383511, - 0x383952, - 0x3856d1, - 0x385dce, - 0x38620b, - 0x38620e, - 0x38658f, - 0x38694e, - 0x386cd3, - 0x387191, - 0x3875cc, - 0x3878ce, - 0x387d4c, - 0x388293, - 0x388a90, - 0x389b8c, - 0x389e8c, - 0x38a34b, - 0x38ae4e, - 0x38b34b, - 0x38cb4b, - 0x38ddcc, - 0x39424a, - 0x39460c, - 0x39490c, - 0x394c09, - 0x396a0b, - 0x396cc8, - 0x397509, - 0x39750f, - 0x3991cb, - 0x92b9a54a, - 0x39bd0c, - 0x39cccb, - 0x39cf89, - 0x39d348, - 0x39da4b, - 0x39df4b, - 0x39eb8a, - 0x39ee0b, - 0x39f78c, - 0x3a0149, - 0x3a0388, - 0x3a3d0b, - 0x3a6f4b, - 0x3a91ce, - 0x3aaf4b, - 0x3ad38b, - 0x3bbdcb, - 0x3bc089, - 0x3bc5cd, - 0x3cbe4a, - 0x3cf5d7, - 0x3d18d8, - 0x3d5909, - 0x3d6b0b, - 0x3d79d4, - 0x3d7ecb, - 0x3d844a, - 0x3d894a, - 0x3d8bcb, - 0x3da790, - 0x3dab91, - 0x3db24a, - 0x3dc54d, - 0x3dcc4d, - 0x3e06cb, - 0x3d7383, - 0x92f9b403, - 0x289b06, - 0x246085, - 0x30ccc7, - 0x2ef746, - 0x165c942, - 0x270b89, - 0x297e44, - 0x2ed408, - 0x21e183, - 0x302707, - 0x205602, - 0x2b5503, - 0x93201442, - 0x2d69c6, - 0x2d8084, - 0x38f1c4, - 0x268643, - 0x93ad3982, - 0x93e2a784, - 0x34a0c7, - 0x9422a502, - 0x214a83, - 0x232dc3, - 0x308003, - 0x23c803, - 0x21a3c3, - 0x242543, - 0x105dc8, - 0x203dc3, + 0x28e544, + 0x3e2587, + 0x2fbe46, + 0x20f307, + 0x30bdc4, + 0x2e5d45, + 0x218ac5, + 0x7ce05682, + 0x216f86, + 0x227043, + 0x227ec2, + 0x227ec6, + 0x7d21c882, + 0x7d62dc42, + 0x238f85, + 0x7da03d02, + 0x7de02a82, + 0x353545, + 0x2d9845, + 0x2af105, + 0x7e65aa03, + 0x279185, + 0x2f0bc7, + 0x2b7945, + 0x359b05, + 0x268b84, + 0x266cc6, + 0x3944c4, + 0x7ea008c2, + 0x7f798885, + 0x3d0907, + 0x3a09c8, + 0x269f86, + 0x269f8d, + 0x26f7c9, + 0x26f7d2, + 0x34d185, + 0x380843, + 0x7fa03b42, + 0x31f9c4, + 0x22db83, + 0x393e85, + 0x313785, + 0x7fe1fcc2, + 0x259fc3, + 0x8022b302, + 0x80a1cac2, + 0x80e00082, + 0x2ec2c5, + 0x213fc3, + 0x81208f02, + 0x81604642, + 0x3c1706, + 0x27e1ca, + 0x20c6c3, + 0x257c83, + 0x2f7343, + 0x832072c2, + 0x9161f702, + 0x91e07ac2, + 0x2034c2, + 0x3d3d09, + 0x2d4584, + 0x2e1c88, + 0x92305102, + 0x92a01502, + 0x2c2285, + 0x234e88, + 0x2f65c8, + 0x2fb70c, + 0x239683, + 0x92e13f42, + 0x9320e482, + 0x2bce06, + 0x315fc5, + 0x2e5583, + 0x247cc6, + 0x316106, + 0x253383, + 0x317803, + 0x317c46, + 0x319484, + 0x26aa06, + 0x236444, + 0x319b44, + 0x31ad0a, + 0x936bb102, + 0x24e605, + 0x31c58a, + 0x31c4c5, + 0x31e504, + 0x31e606, + 0x31e784, + 0x216d06, + 0x93a03c42, + 0x2ecf86, + 0x358f85, + 0x35c807, + 0x3c7386, + 0x253d84, + 0x2e5807, + 0x21dfc5, + 0x21dfc7, + 0x3c3a87, + 0x3c3a8e, + 0x280bc6, + 0x2bda05, + 0x20aa47, + 0x20e243, + 0x20e247, + 0x228f05, + 0x22bfc4, + 0x368842, + 0x32a1c7, + 0x241184, + 0x32a684, + 0x3ab1cb, + 0x21ab83, + 0x2dd0c7, + 0x21ab84, + 0x2dd3c7, + 0x3ae243, + 0x34f8cd, + 0x3aa588, + 0x93e45f84, + 0x366dc5, + 0x31f345, + 0x31f783, + 0x94223d42, + 0x322283, + 0x322b03, + 0x217104, + 0x283c85, + 0x224e87, + 0x38a206, + 0x393c43, + 0x22ad4b, + 0x322c8b, + 0x283d8b, + 0x2b32cb, + 0x2c718a, + 0x2d184b, + 0x2f1b4b, + 0x35ab4c, + 0x319f4b, + 0x374b91, + 0x39ad0a, + 0x3b794b, + 0x3c694c, + 0x3df28b, + 0x3256ca, + 0x325bca, + 0x326a4e, + 0x3271cb, + 0x32748a, + 0x328a51, + 0x328e8a, + 0x32938b, + 0x3298ce, + 0x32b70c, + 0x32c34b, + 0x32c60e, + 0x32c98c, + 0x32d6ca, + 0x32ee8c, + 0x9472f18a, + 0x32fd88, + 0x330949, + 0x33308a, + 0x33330a, + 0x33358b, + 0x3368ce, + 0x337751, + 0x341dc9, + 0x34200a, + 0x342b4b, + 0x34348d, + 0x34430a, + 0x3455d6, + 0x34694b, + 0x349e0a, + 0x34a38a, + 0x34b28b, + 0x34cc09, + 0x350249, + 0x3507cd, + 0x3510cb, + 0x352bcb, + 0x353689, + 0x353cce, + 0x35410a, + 0x35a04a, + 0x35a7ca, + 0x35b18b, + 0x35b9cb, + 0x35e2cd, + 0x35fa0d, + 0x360310, + 0x3607cb, + 0x36210c, + 0x36288b, + 0x36494b, + 0x36614e, + 0x36660b, + 0x36660d, + 0x36d70b, + 0x36e18f, + 0x36e54b, + 0x36f50a, + 0x36fb09, + 0x370089, + 0x94b7040b, + 0x3706ce, + 0x370a4e, + 0x3726cb, + 0x37374f, + 0x375fcb, + 0x37628b, + 0x37654a, + 0x37af49, + 0x37fa0f, + 0x3841cc, + 0x384bcc, + 0x385ece, + 0x38644f, + 0x38680e, + 0x3871d0, + 0x3875cf, + 0x3883ce, + 0x388f0c, + 0x389211, + 0x389652, + 0x38b3d1, + 0x38be8e, + 0x38c2cb, + 0x38c2ce, + 0x38c64f, + 0x38ca0e, + 0x38cd93, + 0x38d251, + 0x38d68c, + 0x38d98e, + 0x38de0c, + 0x38e353, + 0x38f1d0, + 0x3902cc, + 0x3905cc, + 0x390a8b, + 0x391bce, + 0x3920cb, + 0x392e4b, + 0x39418c, + 0x399a4a, + 0x39a50c, + 0x39a80c, + 0x39ab09, + 0x39d68b, + 0x39d948, + 0x39e649, + 0x39e64f, + 0x39ff0b, + 0x94fa0bca, + 0x3a268c, + 0x3a364b, + 0x3a3909, + 0x3a3cc8, + 0x3a458b, + 0x3a688a, + 0x3a6b0b, + 0x3a700c, + 0x3a77c9, + 0x3a7a08, + 0x3ab48b, + 0x3aeb8b, + 0x3b0d0e, + 0x3b244b, + 0x3b72cb, + 0x3c360b, + 0x3c38c9, + 0x3c3e0d, + 0x3d148a, + 0x3d4917, + 0x3d5618, + 0x3d8989, + 0x3d9ccb, + 0x3daad4, + 0x3dafcb, + 0x3db54a, + 0x3dbc0a, + 0x3dbe8b, + 0x3dd190, + 0x3dd591, + 0x3ddc4a, + 0x3de88d, + 0x3def8d, + 0x3e104b, + 0x217083, + 0x953b3583, + 0x2b0f46, + 0x27ca85, + 0x29c647, + 0x384906, + 0x1602342, + 0x2b3609, + 0x32fa04, + 0x2efcc8, + 0x21b783, + 0x31f907, + 0x230902, + 0x2b8f03, + 0x95603602, + 0x2d8d06, + 0x2da3c4, + 0x377084, + 0x201c43, + 0x95ed56c2, + 0x9622c344, + 0x34c287, + 0x9662bf82, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x23cb03, + 0x217fc3, + 0x23e083, + 0x106b48, + 0x205803, 0x2000c2, - 0x9a048, - 0x201242, - 0x308003, - 0x23c803, - 0x21a3c3, - 0x3dc3, - 0x242543, - 0x20e2c3, - 0x337396, - 0x364d53, - 0x3d9f89, - 0x2135c8, - 0x3b1449, - 0x319546, - 0x351190, - 0x20dcd3, - 0x31d488, - 0x277e87, - 0x279e87, - 0x2a86ca, - 0x343189, - 0x267d49, - 0x2d368b, - 0x303c06, - 0x30334a, - 0x2205c6, - 0x32a4c3, - 0x2e2905, - 0x3ba348, - 0x27decd, - 0x2f330c, - 0x2ec347, - 0x30bfcd, - 0x2136c4, - 0x22fe4a, - 0x23134a, - 0x23180a, - 0x20dfc7, - 0x23cb87, - 0x240104, - 0x273506, - 0x348a44, - 0x304c08, - 0x289909, - 0x2e6606, - 0x2e6608, - 0x24360d, - 0x2d6149, - 0x392488, - 0x243147, - 0x3b27ca, - 0x250086, - 0x2fd244, - 0x212107, - 0x30800a, - 0x3ab68e, - 0x277185, - 0x3daf8b, - 0x226bc9, - 0x22e0c9, - 0x2b3147, - 0x3d090a, - 0x2c9187, - 0x2ff649, - 0x33b048, - 0x2a5e8b, - 0x2ec0c5, - 0x22c50a, - 0x223d49, - 0x295aca, - 0x20f30b, - 0x21200b, - 0x2d3415, - 0x2c8205, - 0x2431c5, - 0x237f0a, - 0x22b98a, - 0x2f5d87, - 0x233dc3, - 0x2c3588, - 0x2e0eca, - 0x2222c6, - 0x259c09, - 0x350788, - 0x2e2d44, - 0x388049, - 0x2c9c08, - 0x2ca947, - 0x393086, - 0x3cb087, - 0x2be807, - 0x23f5c5, - 0x2d314c, - 0x23e705, - 0x214a83, - 0x232dc3, - 0x308003, - 0x21a3c3, - 0x3dc3, - 0x242543, - 0x201242, - 0x214a83, - 0x21a3c3, - 0x203dc3, - 0x242543, - 0x214a83, - 0x21a3c3, - 0x3dc3, - 0x2624c3, - 0x242543, - 0x1cc203, - 0x9a048, - 0x214a83, - 0x232dc3, - 0x308003, - 0x23c803, - 0x21a3c3, - 0x3dc3, - 0x242543, - 0x9a048, - 0x201242, - 0x214a83, - 0x22ca07, - 0x86504, - 0x21a3c3, - 0x97a04, - 0x242543, - 0x201242, - 0x2052c2, - 0x3192c2, - 0x207882, - 0x201582, - 0x2eda82, - 0x908c6, - 0x50849, - 0xf3fc7, - 0x481c5c3, - 0x86107, - 0x148406, - 0x7783, - 0x11af85, + 0xae888, + 0x212402, + 0x266a83, + 0x23cb03, + 0x217fc3, + 0x5803, + 0x23e083, + 0x208503, + 0x33cb96, + 0x36c093, + 0x3e2409, + 0x215d88, + 0x2c1389, + 0x31c706, + 0x3520d0, + 0x212113, + 0x2f6c08, + 0x282247, + 0x28d487, + 0x2aaa8a, + 0x36a609, + 0x3573c9, + 0x24cd4b, + 0x34b706, + 0x32ce4a, + 0x221106, + 0x32f603, + 0x2e4d05, + 0x20f4c8, + 0x28598d, + 0x2f45cc, + 0x3033c7, + 0x30e60d, + 0x215e84, + 0x2319ca, + 0x23248a, + 0x23294a, + 0x212407, + 0x23ce87, + 0x2410c4, + 0x269c06, + 0x35d584, + 0x305988, + 0x3c0509, + 0x2e9f06, + 0x2e9f08, + 0x24400d, + 0x2d8489, + 0x397c88, + 0x243b47, + 0x33230a, + 0x251186, + 0x2ff544, + 0x225c07, + 0x266a8a, + 0x23fb8e, + 0x246145, + 0x3dd98b, + 0x22b109, + 0x247109, + 0x205447, + 0x20544a, + 0x2ceac7, + 0x301949, + 0x347c88, + 0x33284b, + 0x2ee705, + 0x22c90a, + 0x265dc9, + 0x3568ca, + 0x21b8cb, + 0x225b0b, + 0x24cad5, + 0x2ce085, + 0x243bc5, + 0x237e8a, + 0x2527ca, + 0x321a07, + 0x234fc3, + 0x2c8a08, + 0x2e32ca, + 0x223e46, + 0x256689, + 0x28dc88, + 0x2e5144, + 0x38e109, + 0x2cf888, + 0x2d05c7, + 0x398886, + 0x3d0907, + 0x2c51c7, + 0x2401c5, + 0x245f8c, + 0x366dc5, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x217fc3, + 0x5803, + 0x23e083, + 0x212402, + 0x22ea43, + 0x217fc3, + 0x205803, + 0x23e083, + 0x22ea43, + 0x217fc3, + 0x5803, + 0x269543, + 0x23e083, + 0x1d1843, + 0xae888, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x23cb03, + 0x217fc3, + 0x5803, + 0x23e083, + 0xae888, + 0x212402, + 0x22ea43, + 0x22ea47, + 0x8ecc4, + 0x217fc3, + 0x1b5c04, + 0x23e083, + 0x212402, + 0x204542, + 0x2f6e82, + 0x2022c2, + 0x202582, + 0x2f2402, + 0x96206, + 0x51709, + 0xe9bc7, + 0x481a6c3, + 0x8e8c7, + 0x154546, + 0xaa43, + 0x11eec5, 0xc1, - 0x5214a83, - 0x232dc3, - 0x228503, - 0x308003, - 0x21bc83, - 0x23c803, - 0x2e2806, - 0x21a3c3, - 0x242543, - 0x233d43, - 0x9a048, - 0x345b44, - 0x296c87, - 0x268683, - 0x2bad84, - 0x2187c3, - 0x284d43, - 0x308003, - 0x17e707, + 0x522ea43, + 0x233fc3, + 0x280203, + 0x266a83, + 0x2191c3, + 0x23cb03, + 0x2e4c06, + 0x217fc3, + 0x23e083, + 0x234f43, + 0xae888, + 0x3b46c4, + 0x324547, + 0x201c83, + 0x39e284, + 0x2052c3, + 0x2054c3, + 0x266a83, + 0x178d87, 0x9c4, - 0x4e83, - 0x68b05, + 0x157bc3, + 0x2105, 0x66000c2, - 0x2703, - 0x6a01242, - 0x6c8c109, - 0x8c98d, - 0x8cccd, - 0x3192c2, - 0x21dc4, - 0x68b49, + 0x4ac43, + 0x6a12402, + 0x6e8b749, + 0x7091e09, + 0x923cd, + 0x9270d, + 0x2f6e82, + 0xe704, + 0x2149, 0x2003c2, - 0x7221cc8, - 0xfe7c4, - 0x31c843, - 0x9a048, - 0x1414882, + 0x7623188, + 0x100ac4, + 0x320c03, + 0xae888, + 0x41184, + 0x140ea82, 0x14005c2, - 0x1414882, - 0x1517146, - 0x22f043, - 0x26f6c3, - 0x7a14a83, - 0x22fe44, - 0x7e32dc3, - 0x8b08003, - 0x206182, - 0x221dc4, - 0x21a3c3, - 0x3b1e83, - 0x204042, - 0x242543, - 0x202642, - 0x308f43, - 0x205142, - 0x2263c3, - 0x223a43, - 0x201342, - 0x9a048, - 0x22f043, - 0x3df888, - 0x83b1e83, - 0x204042, - 0x308f43, - 0x205142, - 0x86263c3, - 0x223a43, - 0x201342, - 0x252b07, - 0x232dc9, - 0x308f43, - 0x205142, - 0x2263c3, - 0x223a43, - 0x201342, - 0x214a83, - 0x16c2, - 0x32443, - 0x3c42, - 0xaac82, - 0x5cd82, - 0x17c2, - 0x1b82, - 0x43342, - 0x202703, - 0x214a83, - 0x232dc3, - 0x308003, - 0x221dc4, - 0x21bc83, - 0x23c803, - 0x219a04, - 0x21a3c3, - 0x242543, - 0x20eb02, - 0x2141c3, - 0x9a048, - 0x214a83, - 0x232dc3, - 0x308003, - 0x23c803, - 0x21a3c3, - 0x242543, - 0x202703, - 0x201242, - 0x214a83, - 0x232dc3, - 0x308003, - 0x221dc4, - 0x21a3c3, - 0x242543, - 0x208805, - 0x20c782, + 0x140ea82, + 0x1519d46, + 0x230983, + 0x276243, + 0x7e2ea43, + 0x2319c4, + 0x8233fc3, + 0x8a66a83, + 0x209582, + 0x20e704, + 0x217fc3, + 0x3319c3, + 0x209282, + 0x23e083, + 0x2188c2, + 0x308483, + 0x207742, + 0x203b83, + 0x222403, + 0x207d02, + 0xae888, + 0x230983, + 0x210448, + 0x87319c3, + 0x209282, + 0x308483, + 0x207742, + 0x203b83, + 0x222403, + 0x207d02, + 0x2509c7, + 0x308483, + 0x207742, + 0x203b83, + 0x222403, + 0x207d02, + 0x22ea43, + 0x6c02, + 0xf4c3, + 0x31c2, + 0x293c2, + 0x4d82, + 0x8c82, + 0x72c2, + 0x43d42, + 0x24ac43, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x20e704, + 0x2191c3, + 0x23cb03, + 0x21e484, + 0x217fc3, + 0x23e083, + 0x201b02, + 0x216983, + 0xae888, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x23cb03, + 0x217fc3, + 0x23e083, + 0x24ac43, + 0x212402, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x20e704, + 0x217fc3, + 0x23e083, + 0x37b845, + 0x21fcc2, 0x2000c2, - 0x9a048, - 0x144ca88, - 0x12848a, - 0x308003, - 0x225cc1, + 0xae888, + 0x1454408, + 0x7b64a, + 0x266a83, + 0x202881, 0x2009c1, 0x200a01, - 0x204ac1, - 0x204781, - 0x20a541, - 0x201941, - 0x225dc1, - 0x23ff81, + 0x201781, + 0x202101, + 0x20bac1, + 0x201d01, + 0x203001, + 0x230d41, 0x200001, 0x2000c1, 0x200201, - 0x139b05, - 0x9a048, + 0x146bc5, + 0xae888, 0x200101, - 0x201301, + 0x201381, 0x200501, - 0x205dc1, + 0x201281, 0x200041, 0x200801, 0x200181, @@ -2348,7193 +2344,7257 @@ var nodes = [...]uint32{ 0x200581, 0x2003c1, 0x200a81, - 0x215481, + 0x20c241, 0x200401, 0x200741, 0x2007c1, 0x200081, - 0x200f01, - 0x201341, - 0x204f01, - 0x201b41, - 0x201441, - 0x214a83, - 0x232dc3, - 0x308003, - 0x21a3c3, - 0x242543, - 0x201242, - 0x214a83, - 0x232dc3, + 0x201501, + 0x207d01, + 0x20a8c1, + 0x202341, + 0x201c41, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x217fc3, + 0x23e083, + 0x212402, + 0x22ea43, + 0x233fc3, 0x2003c2, - 0x242543, - 0x1bf83, - 0x17e707, - 0xd1407, - 0x28886, - 0x34b8a, - 0x8b848, - 0x54e08, - 0x558c7, - 0x174586, - 0xea585, - 0x97805, - 0x125483, - 0x11ec6, - 0x365c6, - 0x2d3684, - 0x328147, - 0x9a048, - 0x2e3504, - 0x214a83, - 0x232dc3, - 0x308003, - 0x21a3c3, - 0x242543, - 0x1242, - 0x214a83, - 0x232dc3, - 0x308003, - 0x21a3c3, - 0x242543, - 0x32a248, - 0x204244, - 0x232d04, - 0x229904, - 0x24ab47, - 0x2e0007, - 0x214a83, - 0x235b4b, - 0x29650a, - 0x33c147, - 0x23bc48, - 0x33b888, - 0x232dc3, - 0x287847, - 0x228503, - 0x206f88, - 0x2130c9, - 0x221dc4, - 0x21bc83, - 0x23b248, - 0x23c803, - 0x2dd74a, - 0x2e2806, - 0x3a4987, - 0x21a3c3, - 0x267906, - 0x26f548, - 0x242543, - 0x24d446, - 0x2f78cd, - 0x2f9e08, - 0x2fedcb, - 0x20eb46, - 0x24b347, - 0x2225c5, - 0x21674a, - 0x308245, - 0x3828ca, - 0x20c782, - 0x207783, - 0x23dec4, + 0x23e083, + 0x1a083, + 0x178d87, + 0x7f3c7, + 0x36fc6, + 0x3a8ca, + 0x91248, + 0x54d88, + 0x55a47, + 0x6e8c6, + 0xec7c5, + 0x1b5a05, + 0x129783, + 0x13a06, + 0x134c46, + 0x24cd44, + 0x334907, + 0xae888, + 0x2e5904, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x217fc3, + 0x23e083, + 0x12402, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x217fc3, + 0x23e083, + 0x32f388, + 0x207d44, + 0x233f04, + 0x204cc4, + 0x2bcd07, + 0x2e21c7, + 0x22ea43, + 0x23670b, + 0x323dca, + 0x34b9c7, + 0x238548, + 0x354a88, + 0x233fc3, + 0x25e4c7, + 0x280203, + 0x211448, + 0x212e49, + 0x20e704, + 0x2191c3, + 0x23b948, + 0x23cb03, + 0x2dfb4a, + 0x2e4c06, + 0x3b0107, + 0x217fc3, + 0x323606, + 0x2760c8, + 0x23e083, + 0x257546, + 0x2f93cd, + 0x2fba08, + 0x3010cb, + 0x2b2946, + 0x341847, + 0x21ecc5, + 0x3da84a, + 0x22ac05, + 0x24fc8a, + 0x21fcc2, + 0x20aa43, + 0x32a684, 0x200006, - 0x3b05c3, - 0x299f43, - 0x27ee03, - 0x204243, - 0x296183, - 0x2041c2, - 0x355dc5, - 0x2ad8c9, - 0x23fc43, - 0x220dc3, - 0x21fdc3, + 0x3ba683, + 0x2ae783, + 0x281bc3, + 0x207d43, + 0x323a43, + 0x2029c2, + 0x309b85, + 0x2b07c9, + 0x201ac3, + 0x240843, + 0x233f03, + 0x232283, 0x200201, - 0x2f0447, - 0x2e9905, - 0x3b5c03, - 0x3d6d83, - 0x229904, - 0x343a43, - 0x20ff88, - 0x35d143, - 0x30d98d, - 0x278708, - 0x3dfa46, - 0x286a03, - 0x361583, - 0x38e083, - 0xce14a83, - 0x232608, - 0x235b44, - 0x240983, + 0x39b3c7, + 0x2ec005, + 0x3c2003, + 0x2a4d43, + 0x3dff03, + 0x204cc4, + 0x356e43, + 0x227608, + 0x322bc3, + 0x310c4d, + 0x280c88, + 0x210606, + 0x28f1c3, + 0x366903, + 0x394443, + 0xce2ea43, + 0x233808, + 0x236704, + 0x23d3c3, + 0x241283, 0x200106, - 0x243d88, - 0x27b0c3, - 0x216783, - 0x22d1c3, - 0x232dc3, - 0x21afc3, - 0x2532c3, - 0x283603, - 0x286983, - 0x2cbf83, - 0x2196c3, - 0x38b5c5, - 0x250804, - 0x251987, - 0x22dec2, - 0x254483, - 0x257d06, - 0x2592c3, - 0x25a283, - 0x277083, - 0x374e43, - 0x345843, - 0x29c047, - 0xd308003, - 0x2425c3, - 0x286a43, - 0x206c03, - 0x21bac3, - 0x24c903, - 0x20ef85, - 0x373c83, - 0x200e09, - 0x20bb83, - 0x311283, - 0xd63c883, - 0x2d3b03, - 0x219208, - 0x2ad806, - 0x374c06, - 0x2b7106, - 0x389147, - 0x227d43, - 0x2137c3, - 0x23c803, - 0x28b946, - 0x20f002, - 0x267d83, - 0x353145, - 0x21a3c3, - 0x319e87, - 0x1603dc3, - 0x202903, - 0x2089c3, - 0x21fec3, - 0x26d243, - 0x242543, - 0x209006, - 0x3b5f86, - 0x37a243, - 0x2f8a83, - 0x2141c3, - 0x220ec3, - 0x314c83, - 0x305fc3, - 0x309303, - 0x399905, - 0x22b983, - 0x39f446, - 0x2d1cc8, - 0x20cc83, - 0x20cc89, - 0x298948, - 0x223488, - 0x229a85, - 0x22f18a, - 0x23818a, - 0x239b4b, - 0x23b808, - 0x398943, - 0x38da83, - 0x30a583, - 0x326f48, - 0x376dc3, - 0x384644, - 0x235002, - 0x25ca83, + 0x244e88, + 0x20f983, + 0x21fa43, + 0x2b6ec3, + 0x222383, + 0x3da883, + 0x22f203, + 0x233fc3, + 0x22d003, + 0x249203, + 0x24cbc3, + 0x28b003, + 0x28f143, + 0x20a003, + 0x265743, + 0x392345, + 0x2516c4, + 0x252a47, + 0x2ba882, + 0x254b03, + 0x258106, + 0x259243, + 0x259c43, + 0x27cc83, + 0x26f183, + 0x20b183, + 0x3b43c3, + 0x29d847, + 0xd266a83, + 0x2c3fc3, + 0x28f203, + 0x204903, + 0x20e703, + 0x2ed2c3, + 0x20e905, + 0x37fd83, + 0x24b709, + 0x2012c3, + 0x313a83, + 0xd63cb83, + 0x2d5e43, + 0x204d03, + 0x218bc8, + 0x2b0706, + 0x26ef46, + 0x2ba8c6, + 0x38f887, + 0x205e03, + 0x215f83, + 0x23cb03, + 0x291346, + 0x20e982, + 0x2b8a83, + 0x33b645, + 0x217fc3, + 0x31da07, + 0x1605803, + 0x2760c3, + 0x212483, + 0x232383, + 0x235fc3, + 0x23e083, + 0x21d506, + 0x3b5d46, + 0x380703, + 0x2fa583, + 0x216983, + 0x250983, + 0x317883, + 0x306d43, + 0x308843, + 0x3a0645, + 0x235403, + 0x3b2a86, + 0x221d43, + 0x27fc88, + 0x2201c3, + 0x2201c9, + 0x273288, + 0x221e48, + 0x225645, + 0x36000a, + 0x38e84a, + 0x22f98b, + 0x238108, + 0x294983, + 0x2f2a03, + 0x393d83, + 0x39fa83, + 0x316248, + 0x37a903, + 0x38a184, + 0x21cc42, + 0x20de03, + 0x260e03, 0x2007c3, - 0x356c43, - 0x263343, - 0x233d43, - 0x20c782, - 0x229e43, - 0x2398c3, - 0x3172c3, - 0x318284, - 0x23dec4, - 0x20fe43, - 0x9a048, + 0x22dc43, + 0x27b143, + 0x234f43, + 0x21fcc2, + 0x22b8c3, + 0x239683, + 0x319ec3, + 0x31b744, + 0x32a684, + 0x21cb03, + 0xae888, 0x2000c2, 0x200ac2, - 0x2041c2, - 0x204b42, + 0x2029c2, + 0x201802, 0x200202, - 0x204342, - 0x240702, - 0x203c42, + 0x205082, + 0x249382, + 0x2031c2, 0x200382, 0x200c42, - 0x33d642, - 0x202382, - 0x26c9c2, + 0x349242, + 0x20a942, + 0x2720c2, 0x200a82, - 0x2eda82, - 0x214282, - 0x205742, - 0x2141c2, - 0x2c1902, - 0x2069c2, + 0x2f2402, + 0x205b42, + 0x211c82, + 0x216982, + 0x206002, + 0x205502, 0x200682, - 0x206f02, - 0x2013c2, - 0x2090c2, - 0x2045c2, - 0x26c702, - 0x202982, + 0x2113c2, + 0x202b02, + 0x208502, + 0x202442, + 0x207142, + 0x202a82, 0xc2, 0xac2, - 0x41c2, - 0x4b42, + 0x29c2, + 0x1802, 0x202, - 0x4342, - 0x40702, - 0x3c42, + 0x5082, + 0x49382, + 0x31c2, 0x382, 0xc42, - 0x13d642, - 0x2382, - 0x6c9c2, + 0x149242, + 0xa942, + 0x720c2, 0xa82, - 0xeda82, - 0x14282, - 0x5742, - 0x141c2, - 0xc1902, - 0x69c2, + 0xf2402, + 0x5b42, + 0x11c82, + 0x16982, + 0x6002, + 0x5502, 0x682, - 0x6f02, - 0x13c2, - 0x90c2, - 0x45c2, - 0x6c702, - 0x2982, - 0x214a83, - 0x232dc3, - 0x308003, - 0x21a3c3, - 0x242543, - 0x7782, - 0x214a83, - 0x232dc3, - 0x308003, - 0x21a3c3, - 0x242543, - 0x1242, - 0x201242, - 0x242543, - 0xee14a83, - 0x308003, - 0x23c803, - 0x1b4103, - 0x22aec2, - 0x9a048, - 0x214a83, - 0x232dc3, - 0x308003, - 0x21a3c3, - 0x3dc3, - 0x1b4103, - 0x242543, - 0x1442, + 0x113c2, + 0x2b02, + 0x8502, + 0x2442, + 0x7142, + 0x2a82, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x217fc3, + 0x23e083, + 0x83c2, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x217fc3, + 0x23e083, + 0x12402, + 0x212402, + 0x23e083, + 0xee2ea43, + 0x266a83, + 0x23cb03, + 0x1c0443, + 0x230242, + 0xae888, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x217fc3, + 0x5803, + 0x1c0443, + 0x23e083, + 0x3602, 0x2001c2, - 0x15c45c5, - 0x139b05, - 0x20b3c2, - 0x9a048, - 0x1242, - 0x2347c2, - 0x206782, - 0x204642, - 0x21bb02, - 0x230a42, - 0x97805, - 0x202f42, - 0x204042, - 0x206e42, - 0x205e42, - 0x214282, - 0x23fcc2, - 0x202f82, - 0x292882, - 0xfe98384, + 0x1567b85, + 0x146bc5, + 0x210402, + 0xae888, + 0x12402, + 0x2359c2, + 0x206b02, + 0x208142, + 0x20e742, + 0x23bec2, + 0x1b5a05, + 0x201402, + 0x209282, + 0x201102, + 0x2053c2, + 0x205b42, + 0x2408c2, + 0x20ee42, + 0x256382, + 0xfe72cc4, 0x142, - 0x17e707, - 0x12380d, - 0xea609, - 0x115e0b, - 0xeec08, - 0x65dc9, - 0x111b86, - 0x308003, - 0x9a048, + 0x178d87, + 0x30a83, + 0x12808d, + 0xec849, + 0x118a0b, + 0xf0a88, + 0x5bd09, + 0x1145c6, + 0x266a83, + 0xae888, 0x9c4, - 0x4e83, - 0x68b05, - 0x9a048, - 0xe5a47, - 0x56306, - 0x68b49, - 0x1d134e, - 0x14a887, + 0x157bc3, + 0x2105, + 0xae888, + 0xe7607, + 0x1104d007, + 0x56546, + 0x2149, + 0xa28e, + 0x14ca47, + 0x150e583, 0x2000c2, - 0x2d3684, - 0x201242, - 0x214a83, - 0x2052c2, - 0x232dc3, - 0x1bb43, + 0x24cd44, + 0x212402, + 0x22ea43, + 0x204542, + 0x233fc3, + 0xfa03, 0x200382, - 0x2e3504, - 0x21bc83, - 0x201f42, - 0x21a3c3, - 0x30a42, + 0x2e5904, + 0x2191c3, + 0x206a02, + 0x217fc3, + 0x3bec2, 0x2003c2, - 0x242543, - 0x2431c6, - 0x32d94f, + 0x23e083, + 0x243bc6, + 0x333b4f, 0x602, - 0x725e43, - 0x2f294a, - 0x9a048, - 0x201242, - 0x228503, - 0x308003, - 0x23c803, - 0x3dc3, - 0x1467206, - 0x1d1348, - 0x141650b, - 0x156518a, - 0xf1fc9, - 0x15d024a, - 0x1511707, - 0xa878b, - 0x10b7c5, - 0xebbc5, - 0x119bc9, - 0x139b05, - 0x17e707, - 0xfbfc4, - 0x201242, - 0x214a83, - 0x308003, - 0x21a3c3, + 0x72a143, + 0x2f3c0a, + 0xae888, + 0x212402, + 0x280203, + 0x266a83, + 0x23cb03, + 0x5803, + 0x1522f06, + 0x1c4104, + 0xa288, + 0x140dbcb, + 0x156c4ca, + 0xf3289, + 0x15da64a, + 0x1513f07, + 0xaab4b, + 0x10d4c5, + 0xf0545, + 0x11d749, + 0x146bc5, + 0x178d87, + 0x1c4104, + 0xfe2c4, + 0x212402, + 0x22ea43, + 0x266a83, + 0x217fc3, 0x2000c2, 0x200c82, - 0x2019c2, - 0x13214a83, - 0x23d342, - 0x232dc3, - 0x20bb42, - 0x2299c2, - 0x308003, - 0x23c782, - 0x25d282, - 0x22a742, + 0x205102, + 0x1362ea43, + 0x23d542, + 0x233fc3, + 0x201282, + 0x208882, + 0x266a83, + 0x23ca82, + 0x27b882, + 0x22c302, 0x200cc2, - 0x290602, + 0x295f42, 0x200802, 0x200d82, - 0x23ee42, - 0x27b8c2, - 0x204702, - 0x1b19cc, - 0x2b8e42, - 0x252e82, - 0x2203c2, - 0x248642, - 0x23c803, - 0x200bc2, - 0x21a3c3, - 0x243802, - 0x249b42, - 0x242543, - 0x308c82, - 0x2090c2, - 0x2023c2, - 0x202d42, - 0x207242, - 0x2f00c2, - 0x21e502, - 0x226dc2, - 0x223fc2, - 0x322c0a, - 0x36860a, - 0x39abca, - 0x3e0d02, - 0x210702, - 0x20ef42, - 0x1376fcc9, - 0x13b5cbca, - 0x142e307, - 0x13e026c2, - 0x14fe3c3, - 0x4a82, - 0x15cbca, - 0x15dc0e, - 0x24c0c4, - 0x572c5, - 0x14614a83, - 0x3dc83, - 0x232dc3, - 0x24d9c4, - 0x308003, - 0x221dc4, - 0x21bc83, - 0x137a89, - 0x68006, - 0x23c803, - 0xefa04, - 0x4743, - 0x21a3c3, - 0x1df105, - 0x203dc3, - 0x242543, - 0x1464b04, - 0x22b983, - 0x11b504, - 0x207783, - 0x9a048, - 0x1521403, - 0x125d86, - 0x1574504, - 0x68445, - 0x14a64a, - 0x129cc2, - 0x1500160d, - 0x1a6286, - 0x15291, - 0x1576fcc9, - 0x684c8, - 0x62888, - 0x1c12c707, - 0x1182, - 0x16fe47, - 0x2380e, - 0x139b0b, - 0x13f10b, - 0x1b3d4a, - 0x29907, - 0x9a048, - 0x11c988, - 0x7bc7, - 0x1c4169cb, - 0x1bf87, - 0xcd42, - 0x26ccd, - 0x1c2a07, - 0xaed8a, - 0x1d92cf, - 0x6738f, - 0x167c2, - 0x1242, - 0x83548, - 0x1c8f48cc, - 0xe770a, - 0xe554a, - 0x18990a, - 0x78508, - 0x8d08, - 0x5aa88, - 0xe5a08, - 0x145e88, - 0x2a42, - 0x1c464f, - 0xc134b, - 0x79108, - 0x25687, - 0x14474a, - 0x2490b, - 0x33249, - 0x46e07, - 0x8c08, - 0x3834c, - 0x15c087, - 0x1a67ca, - 0x106c8, - 0x2888e, - 0x2904e, - 0x2974b, - 0x2aa8b, - 0x15748b, - 0x14bf09, - 0xe3b0b, - 0xec58d, - 0x1261cb, - 0x30b4d, - 0x30ecd, - 0x3640a, - 0x3dd0b, - 0x3e54b, - 0x46405, - 0x1cd79a10, - 0x199e8f, - 0x9854f, - 0x63c4d, - 0x137c50, - 0xaac82, - 0x1d20c388, - 0xd1288, - 0xe8090, - 0xcf48e, - 0x1d75d105, - 0x4d1cb, - 0x136b90, - 0x8e0a, - 0x2ac49, - 0x61487, - 0x617c7, - 0x61987, - 0x61d07, - 0x631c7, - 0x63407, - 0x65587, - 0x65ac7, - 0x66007, - 0x66387, - 0x66a47, - 0x66c07, - 0x66dc7, - 0x66f87, - 0x69a47, - 0x6a407, - 0x6ac07, - 0x6afc7, - 0x6b607, - 0x6b8c7, - 0x6ba87, - 0x6bd87, - 0x6c887, - 0x6ca87, - 0x6d907, - 0x6dac7, - 0x6dc87, - 0x6f247, - 0x71247, - 0x71707, - 0x72207, - 0x724c7, - 0x72847, - 0x72a07, - 0x72e07, - 0x73247, - 0x73707, - 0x73c87, - 0x73e47, - 0x74007, - 0x74447, - 0x74ec7, - 0x75407, - 0x75987, - 0x75b47, - 0x75ec7, - 0x76407, - 0x6882, - 0x5ab8a, - 0x11cc8, - 0x1b0ecc, - 0x71b87, - 0x85805, - 0xa7951, - 0x1c0d86, - 0xfb14a, - 0x833ca, - 0x56306, - 0xb060b, - 0x642, - 0x2f7d1, - 0xbf089, - 0x9b209, - 0x9bb06, - 0x3ee42, - 0x9218a, - 0xacdc9, - 0xad50f, - 0xadb0e, - 0xaff88, - 0x6502, - 0x174a49, - 0x8a58e, - 0x1c7f0c, - 0xf1ccf, - 0x1b7a0e, - 0x3230c, - 0x41a89, - 0xd2551, - 0xd2b08, - 0x59452, - 0x62a4d, - 0x733cd, - 0x7984b, - 0x7ea95, - 0xf0c09, - 0x182f8a, - 0x1a3749, - 0x1aa210, - 0x9028b, - 0x9378f, - 0xa694b, - 0xab54c, - 0xb1b90, - 0xb4b0a, - 0xcc00d, - 0xb7ece, - 0x14db0a, - 0x1bd50c, - 0xbe4d4, - 0xbed11, - 0xc0f0b, - 0xc310f, - 0xc5f8d, - 0xc854e, - 0xca80c, - 0xcb00c, - 0xcbd0b, - 0x13ea4e, - 0x16c350, - 0xd974b, - 0xda40d, - 0xdcf0f, - 0xdf00c, - 0xe648e, - 0xf2391, - 0x10498c, - 0x1dc387, - 0x10b44d, - 0x11a00c, - 0x140cd0, - 0x15fd8d, - 0x168847, - 0x18e8d0, - 0x19d508, - 0x1a160b, - 0xb6b8f, - 0x1b1148, - 0x149d4d, - 0x10f810, - 0x17e609, - 0x1db799c8, - 0x1deb9c06, - 0xbaac3, - 0x15a889, - 0xc00c5, - 0x1e42, - 0x144bc9, - 0x14a34a, - 0x1e259906, - 0x145990d, - 0x1e7c2c04, - 0x57a46, - 0x1e08a, - 0x6474d, - 0x1e9df489, - 0x19a83, - 0x1175ca, - 0xe4651, - 0xe4a89, - 0xe54c7, - 0xe61c8, - 0xe68c7, - 0x71c48, - 0x1540b, - 0x12bc49, - 0xf1210, - 0xf16cc, - 0xf27c8, - 0xf4705, - 0x13b1c8, - 0x1961ca, - 0x184947, - 0x2902, - 0x1ef47415, - 0x13788a, - 0x1b2589, - 0x108a08, - 0x9cd89, - 0x46145, - 0x119d0a, - 0x8decf, - 0x10b84b, - 0xf04c, - 0x18ee12, - 0x77285, - 0x114e48, - 0xf678b, - 0xe0a11, - 0x4dcca, - 0x1f2fe185, - 0x19b18c, - 0x133e43, - 0x192286, - 0x3fcc2, - 0x10948b, - 0x109f4a, - 0x150a2cc, - 0xd1608, - 0x30d08, - 0x1f708a86, - 0x1b2f07, - 0xca42, - 0x5142, - 0x18bb50, - 0x69bc7, - 0x2ee0f, - 0x11ec6, - 0x11ed0e, - 0x94c0b, - 0x472c8, - 0x33609, - 0x13ce92, - 0x19234d, - 0x115488, - 0x115cc9, - 0x176f4d, - 0x198609, - 0x63cb, - 0x6bf08, - 0x7a688, - 0x7ce08, - 0x7d249, - 0x7d44a, - 0x8c30c, - 0x3e80a, - 0xf198a, - 0x114cc7, - 0x9994a, - 0x1c350d, - 0xd2e11, - 0x1fac8846, - 0x1cd64b, - 0x97c4c, - 0x39988, - 0x13d849, - 0x15b84d, - 0x61f50, - 0x1808cd, - 0xc2c2, - 0x4f70d, - 0x1b82, - 0xc1c2, - 0x114c0a, - 0x6e70a, - 0xfb04a, - 0x10260b, - 0x292cc, - 0x11c48a, - 0x11c70e, - 0x1d74cd, - 0x1fde0bc5, - 0x128948, - 0x1442, - 0x14239c3, - 0x15a6960e, - 0x16204b4e, - 0x16a6820a, - 0x1734630e, - 0x17b63a8e, - 0x18289d0c, - 0x142e307, - 0x142e309, - 0x14fe3c3, - 0x18b0400c, - 0x1933dfc9, - 0x19b48e89, - 0x1a353b89, - 0x4a82, - 0x69551, - 0x4a91, - 0x6814d, - 0x146251, - 0x1639d1, - 0x89c4f, - 0x103f4f, - 0x13df0c, - 0x148dcc, - 0x153acc, - 0x4b5cd, - 0x1aaa15, - 0xedc8c, - 0x1b32cc, - 0x13f810, - 0x16b10c, - 0x178fcc, - 0x1ac319, - 0x1b6959, - 0x1c1259, - 0x1da294, - 0x76d4, - 0x7d54, - 0x9754, - 0x9f94, - 0x1aa07989, - 0x1b008009, - 0x1bbb3389, - 0x15ed2d09, - 0x4a82, - 0x166d2d09, - 0x4a82, - 0x76ca, - 0x4a82, - 0x16ed2d09, - 0x4a82, - 0x76ca, - 0x4a82, - 0x176d2d09, - 0x4a82, - 0x17ed2d09, - 0x4a82, - 0x186d2d09, - 0x4a82, - 0x76ca, - 0x4a82, - 0x18ed2d09, - 0x4a82, - 0x76ca, - 0x4a82, - 0x196d2d09, - 0x4a82, - 0x19ed2d09, - 0x4a82, - 0x76ca, - 0x4a82, - 0x1a6d2d09, - 0x4a82, - 0x76ca, - 0x4a82, - 0x1aed2d09, - 0x4a82, - 0x1b6d2d09, - 0x4a82, - 0x1bed2d09, - 0x4a82, - 0x76ca, - 0x4a82, - 0x1400401, - 0x15285, - 0x1b3d44, - 0x14c86c3, - 0x15d6e03, - 0x14f8943, - 0x6960e, - 0x4b4e, - 0x7c80e, - 0x6820a, - 0x14630e, - 0x163a8e, - 0x89d0c, - 0x10400c, - 0x13dfc9, - 0x148e89, - 0x153b89, - 0x7989, - 0x8009, - 0x1b3389, - 0x13f8cd, - 0x9a09, - 0xa249, - 0x12f604, - 0x18ad44, - 0x1bad44, - 0x1bf004, - 0xa8a44, - 0x2cc04, - 0x3ca84, - 0x53684, - 0x11dc4, - 0x62b84, - 0x1588703, - 0x13dd87, - 0x147dc8c, - 0xf283, - 0xaac82, - 0xae8c6, - 0x1d74c3, - 0xf283, - 0x9fc83, - 0x8582, - 0x8588, - 0xe9247, - 0x12bcc7, - 0x2a42, - 0x2000c2, - 0x201242, - 0x2052c2, - 0x20dec2, - 0x200382, - 0x2003c2, - 0x205142, - 0x214a83, - 0x232dc3, - 0x308003, - 0x21bac3, - 0x21a3c3, - 0x242543, - 0x9a048, - 0x214a83, - 0x232dc3, - 0x21a3c3, - 0x242543, - 0xb2c3, - 0x308003, - 0x21dc4, - 0x2000c2, - 0x202703, - 0x22214a83, - 0x38b9c7, - 0x308003, - 0x21a8c3, - 0x219a04, - 0x21a3c3, - 0x242543, - 0x21e2ca, - 0x2431c5, - 0x2141c3, - 0x233442, - 0x9a048, - 0x226d8a4a, - 0xe01, - 0x9a048, - 0x1242, - 0x131a42, - 0x22fdf20b, - 0x23227fc4, - 0x1c2b45, - 0x1805, - 0xf48c6, - 0x23601805, - 0x53bc3, - 0x94e83, - 0x9c4, - 0x4e83, - 0x68b05, - 0x139b05, - 0x9a048, - 0x1bf87, - 0x14a83, - 0x2cd0d, - 0x23e3a147, - 0x144686, - 0x24146145, - 0x1b8dd2, - 0x3a247, - 0x1d35ca, - 0x1d3488, - 0x95c7, - 0x6574a, - 0x1a7308, - 0xe2b07, - 0x1a870f, - 0x169347, - 0x53486, - 0x136b90, - 0x11dccf, - 0x1a009, - 0x57ac4, - 0x2443a30e, - 0x35509, - 0x670c6, - 0x10ecc9, - 0x18d986, - 0x1ba1c6, - 0x78ecc, - 0x24b0a, - 0x333c7, - 0x14420a, - 0x33c9, - 0xf714c, - 0x1d40a, - 0x5c30a, - 0x68b49, - 0x57a46, - 0x3348a, - 0x11634a, - 0xa430a, - 0x14fbc9, - 0xe30c8, - 0xe3346, - 0xeb54d, - 0x5390b, - 0xc0545, - 0x24b55a0c, - 0x14a887, - 0x10d1c9, - 0xbf407, - 0x10fc14, - 0x11010b, - 0x254ca, - 0x13cd0a, - 0xaabcd, - 0x1502809, - 0x11524c, - 0x115acb, - 0x106c3, - 0x106c3, - 0x28886, - 0x106c3, - 0xf48c8, - 0x155983, - 0x44ec4, - 0x53f83, - 0x335c5, - 0x146e943, - 0x50849, - 0xf678b, - 0x14df283, - 0x148406, - 0x14ecac7, - 0x1aa487, - 0x2592c5c9, - 0x1a286, - 0x173d09, - 0x2703, - 0x9a048, - 0x1242, - 0x4d9c4, - 0x88c3, - 0x8805, - 0x214a83, - 0x232dc3, - 0x308003, - 0x21a3c3, - 0x242543, - 0x220dc3, - 0x214a83, - 0x232dc3, - 0x228503, - 0x308003, - 0x23c803, - 0x21a3c3, - 0x242543, - 0x29b3c3, - 0x207783, - 0x220dc3, - 0x2d3684, - 0x214a83, - 0x232dc3, - 0x308003, - 0x21a3c3, - 0x242543, - 0x2308c3, - 0x2752c6c5, - 0x142c943, - 0x214a83, - 0x232dc3, - 0x21bb43, - 0x228503, - 0x308003, - 0x221dc4, - 0x2059c3, - 0x2137c3, - 0x23c803, - 0x21a3c3, - 0x1b4103, - 0x242543, - 0x2141c3, - 0x2821e9c3, - 0x18ed09, - 0x1242, - 0x3c0743, - 0x28e14a83, - 0x232dc3, - 0x247103, - 0x308003, - 0x223703, - 0x2137c3, - 0x242543, - 0x2f4bc3, - 0x3b9a84, - 0x9a048, - 0x29614a83, - 0x232dc3, - 0x2b0043, - 0x308003, - 0x23c803, - 0x219a04, - 0x21a3c3, - 0x242543, - 0x22e983, - 0x9a048, - 0x29e14a83, - 0x232dc3, - 0x228503, - 0x203dc3, - 0x242543, - 0x9a048, - 0x142e307, - 0x202703, - 0x214a83, - 0x232dc3, - 0x308003, - 0x221dc4, - 0x219a04, - 0x21a3c3, - 0x242543, - 0x139b05, - 0x17e707, - 0x10fe4b, - 0xe4e84, - 0xc0545, - 0x144ca88, - 0x2a54d, - 0x2b231ac5, - 0x647c4, - 0x1242, - 0x3a83, - 0x17e505, - 0x2aec2, - 0x5e42, - 0x303dc5, - 0x9a048, - 0x106c2, - 0x1d2c3, - 0x16454f, - 0x1242, - 0x105646, - 0x2000c2, - 0x202703, - 0x214a83, - 0x308003, - 0x221dc4, - 0x23c803, - 0x219a04, - 0x21a3c3, - 0x242543, - 0x2141c3, - 0x2aec2, - 0x32a988, - 0x2d3684, - 0x349646, - 0x353986, - 0x9a048, - 0x3373c3, - 0x2cd549, - 0x217915, - 0x1791f, - 0x214a83, - 0xd1ac7, - 0x214892, - 0x169046, - 0x1712c5, - 0x8e0a, - 0x2ac49, - 0x21464f, - 0x2e3504, - 0x224445, - 0x311050, - 0x2137c7, - 0x203dc3, - 0x31bf88, - 0x1283c6, - 0x27cfca, - 0x221c84, - 0x2fdbc3, - 0x233442, - 0x2f850b, - 0x3dc3, - 0x17cc84, - 0x214a83, - 0x232dc3, - 0x308003, - 0x23c803, - 0x21a3c3, - 0x3dc3, - 0x242543, - 0x306403, - 0x201242, - 0x89ac3, - 0x1dee04, - 0x21a3c3, - 0x242543, - 0x2ed73e05, - 0x8346, - 0x214a83, - 0x232dc3, - 0x308003, - 0x23c803, - 0x242543, - 0x214a83, - 0x232dc3, - 0x308003, - 0x21a8c3, - 0x223d43, - 0x242543, - 0x2703, - 0x201242, - 0x214a83, - 0x232dc3, - 0x21a3c3, - 0x3dc3, - 0x242543, - 0x267c2, - 0x2000c2, - 0x214a83, - 0x232dc3, - 0x308003, - 0x21a3c3, - 0x242543, - 0x1805, - 0xf283, - 0x2d3684, - 0x214a83, - 0x232dc3, - 0x306c44, - 0x21a3c3, - 0x242543, - 0x9a048, - 0x214a83, - 0x232dc3, - 0x308003, - 0x21a3c3, - 0x1b4103, - 0x242543, - 0x1b3089, - 0x29904, - 0x214a83, - 0x2a42, - 0x232dc3, - 0x228503, - 0x206c03, - 0x23c803, - 0x21a3c3, - 0x3dc3, - 0x242543, - 0x2982, - 0x214a83, - 0x232dc3, - 0x308003, - 0x343104, - 0x221dc4, - 0x21a3c3, - 0x242543, - 0x207783, - 0x16c2, - 0x201242, - 0x214a83, - 0x232dc3, - 0x308003, - 0x21a3c3, - 0x1b4103, - 0x242543, - 0x9a048, - 0x214a83, - 0x232dc3, - 0x308003, - 0x2f3983, - 0x13903, - 0x1a8c3, - 0x21a3c3, - 0x1b4103, - 0x242543, - 0x322c0a, - 0x3413c9, - 0x35fb0b, - 0x3602ca, - 0x36860a, - 0x377c8b, - 0x38d74a, - 0x39424a, - 0x39abca, - 0x39ae4b, - 0x3bcfc9, - 0x3ca20a, - 0x3ca58b, - 0x3d818b, - 0x3e040a, - 0x15702, - 0x214a83, - 0x232dc3, - 0x228503, - 0x23c803, - 0x21a3c3, - 0x3dc3, - 0x242543, - 0x1558b, - 0xcf487, - 0x5b788, - 0x177084, - 0x8f308, - 0xe8006, - 0x15546, - 0x366c9, - 0x9a048, - 0x214a83, - 0x8e04, - 0x261484, - 0x202742, - 0x219a04, - 0x269045, - 0x220dc3, - 0x2d3684, - 0x214a83, - 0x235b44, - 0x232dc3, - 0x24d9c4, - 0x2e3504, - 0x221dc4, - 0x2137c3, - 0x21a3c3, - 0x242543, - 0x24f8c5, - 0x2308c3, - 0x2141c3, - 0x32bf03, - 0x23e804, - 0x325d04, - 0x373fc5, - 0x9a048, - 0x203ac4, - 0x3b5cc6, - 0x268c84, - 0x201242, - 0x38f207, - 0x3a25c7, - 0x24b904, - 0x2e92c5, - 0x35ff85, - 0x22d805, - 0x221dc4, - 0x389208, - 0x22b586, - 0x3295c8, - 0x27b905, - 0x2ec0c5, - 0x262044, - 0x242543, - 0x2fe7c4, - 0x376806, - 0x2432c3, - 0x23e804, - 0x3829c5, - 0x344b44, - 0x23acc4, - 0x233442, - 0x231d86, - 0x3aeb46, - 0x313fc5, - 0x2000c2, - 0x202703, - 0x33e01242, - 0x20c504, - 0x200382, - 0x23c803, - 0x215402, - 0x21a3c3, - 0x2003c2, - 0x2fb406, - 0x20e2c3, - 0x207783, - 0x9a048, - 0x9a048, - 0x308003, - 0x1b4103, - 0x2000c2, - 0x34a01242, - 0x308003, - 0x266d43, - 0x2059c3, - 0x227fc4, - 0x21a3c3, - 0x242543, - 0x9a048, - 0x2000c2, - 0x35201242, - 0x214a83, - 0x21a3c3, - 0x3dc3, - 0x242543, - 0x682, - 0x209d42, - 0x20c782, - 0x21a8c3, - 0x2f7103, - 0x2000c2, - 0x139b05, - 0x9a048, - 0x17e707, - 0x201242, - 0x232dc3, - 0x24d9c4, - 0x207083, - 0x308003, - 0x206c03, - 0x23c803, - 0x21a3c3, - 0x2125c3, - 0x242543, - 0x233dc3, - 0x12be53, - 0x12e714, - 0x139b05, - 0x17e707, - 0x1d35c9, - 0x10d8c6, - 0xf5ecb, - 0x28886, - 0x54c47, - 0x15aec6, - 0x649, - 0x15794a, - 0x8b70d, - 0x12350c, - 0x116cca, - 0x112988, - 0x97805, - 0x1d3608, - 0x11ec6, - 0x1c6606, - 0x365c6, - 0x602, - 0x2aac82, - 0x174ec4, - 0x9fc86, - 0x12c310, - 0x147498e, - 0x68846, - 0x6264c, - 0x36a6720b, - 0x139b05, - 0x14820b, - 0x36fc6544, - 0x1b3f07, - 0x22111, - 0x1ae28a, - 0x214a83, - 0x3727db88, - 0x656c5, - 0x19b808, - 0xca04, - 0x14a545, - 0x3755ca06, - 0xa7946, - 0xc7746, - 0x908ca, - 0x1b5c43, - 0x37a0dc84, - 0x50849, - 0x129747, - 0x1274ca, - 0x14d8949, - 0x605, - 0xec503, - 0x37e33f07, - 0x1df105, - 0x1538186, - 0x1498886, - 0xb06cc, - 0x101b88, - 0x3803fcc3, - 0xf874b, - 0x13864b, - 0x38647c0c, - 0x140a503, - 0xc2dc8, - 0xf89c5, - 0xc11c9, - 0xea803, - 0x102908, - 0x141dfc6, - 0x86107, - 0x38b5b849, - 0x19d887, - 0xebbca, - 0x39fbf648, - 0x11578d, - 0xa788, - 0xf283, - 0x1443f09, - 0x174483, - 0x28886, - 0xf48c8, - 0x11dc4, - 0x1205c5, - 0x148df83, - 0x239c7, - 0x38e239c3, - 0x393c0a06, - 0x39637f04, - 0x39b0a107, - 0xf48c4, - 0xf48c4, - 0xf48c4, - 0xf48c4, - 0x41, - 0x214a83, - 0x232dc3, - 0x308003, - 0x23c803, - 0x21a3c3, - 0x242543, - 0x2000c2, - 0x201242, - 0x308003, - 0x206182, - 0x21a3c3, - 0x242543, - 0x20e2c3, - 0x3810cf, - 0x38148e, - 0x9a048, - 0x214a83, - 0x43bc7, - 0x232dc3, - 0x308003, - 0x21bc83, - 0x21a3c3, - 0x242543, - 0x68784, - 0x4fc4, - 0x145bc4, - 0x21c9c3, - 0x296747, - 0x203082, - 0x26ce49, - 0x200ac2, - 0x38be4b, - 0x2a1b8a, - 0x2a2889, - 0x200542, - 0x20cdc6, - 0x236a55, - 0x38bf95, - 0x2391d3, - 0x38c513, - 0x2037c2, - 0x2037c5, - 0x2037cc, - 0x27514b, - 0x276205, - 0x204b42, - 0x29d282, - 0x37bb86, - 0x201182, - 0x2c9d46, - 0x20908d, - 0x3dee8c, - 0x379a84, - 0x200882, - 0x202b02, - 0x259808, - 0x200202, - 0x205086, - 0x395a8f, - 0x205090, - 0x3a1544, - 0x236c15, - 0x239353, - 0x24d2c3, - 0x34934a, - 0x214f07, - 0x383d89, - 0x327387, - 0x220f82, - 0x200282, - 0x3beb06, - 0x205fc2, - 0x9a048, - 0x202e82, - 0x201542, - 0x20ac47, - 0x36a6c7, - 0x36a6d1, - 0x2180c5, - 0x2180ce, - 0x218e8f, - 0x20cd42, - 0x2679c7, - 0x21d008, - 0x207682, - 0x21d482, - 0x2101c6, - 0x2101cf, - 0x263710, - 0x22c042, - 0x2086c2, - 0x238c48, - 0x2086c3, - 0x25cec8, - 0x2c1bcd, - 0x215843, - 0x363208, - 0x27fc4f, - 0x28000e, - 0x33c9ca, - 0x2f5211, - 0x2f5690, - 0x300acd, - 0x300e0c, - 0x24a147, - 0x3494c7, - 0x349709, - 0x220f42, - 0x204342, - 0x25678c, - 0x256a8b, - 0x200d42, - 0x2cbec6, - 0x20b682, - 0x200482, - 0x2167c2, - 0x201242, - 0x22d204, - 0x239e07, - 0x22ba02, - 0x23f707, - 0x241247, - 0x22f142, - 0x22ec02, - 0x243a85, - 0x205582, - 0x392dce, - 0x3cae0d, - 0x232dc3, - 0x28508e, - 0x2b794d, - 0x328b03, - 0x2027c2, - 0x21fc84, - 0x24cf02, - 0x222342, - 0x38cdc5, - 0x39d187, - 0x246702, - 0x20dec2, - 0x24d5c7, - 0x250bc8, - 0x22dec2, - 0x277306, - 0x25660c, - 0x25694b, - 0x205dc2, - 0x25db0f, - 0x25ded0, - 0x25e2cf, - 0x25e695, - 0x25ebd4, - 0x25f0ce, - 0x25f44e, - 0x25f7cf, - 0x25fb8e, - 0x25ff14, - 0x260413, - 0x2608cd, - 0x2765c9, - 0x28d003, - 0x207082, - 0x31e805, - 0x3ddc86, - 0x200382, - 0x37f147, - 0x308003, - 0x200642, - 0x361608, - 0x2f5451, - 0x2f5890, - 0x201f02, - 0x28bf47, - 0x204582, - 0x271547, - 0x201e42, - 0x295749, - 0x37bb47, - 0x29ac08, - 0x35c846, - 0x24b083, - 0x24b085, - 0x233042, - 0x2004c2, - 0x3bef05, - 0x39b605, - 0x202482, - 0x21bdc3, - 0x348587, - 0x20e5c7, - 0x201842, - 0x345044, - 0x210543, - 0x31d809, - 0x210548, - 0x202042, - 0x208282, - 0x2ed207, - 0x2f5145, - 0x298bc8, - 0x2ae507, - 0x20fac3, - 0x29fb06, - 0x30094d, - 0x300ccc, - 0x305086, - 0x206782, - 0x2017c2, - 0x209e82, - 0x27facf, - 0x27fece, - 0x360007, - 0x210942, - 0x372385, - 0x372386, - 0x21d542, - 0x200bc2, - 0x28e5c6, - 0x247703, - 0x3c5d46, - 0x2d6705, - 0x2d670d, - 0x2d6f95, - 0x2d7e0c, - 0x2d818d, - 0x2d84d2, - 0x202382, - 0x26c9c2, - 0x202802, - 0x219386, - 0x3c7dc6, - 0x202902, - 0x3ddd06, - 0x206e42, - 0x296f45, - 0x201582, - 0x392f09, - 0x21ae0c, - 0x21b14b, - 0x2003c2, - 0x251d88, - 0x202942, - 0x200a82, - 0x272b46, - 0x2d2c85, - 0x200a87, - 0x227c85, - 0x257145, - 0x215542, - 0x2a3882, - 0x214282, - 0x293a87, - 0x2fb4cd, - 0x2fb84c, - 0x234307, - 0x277282, + 0x25b542, + 0x2295c2, 0x205742, - 0x3d2508, - 0x344d48, - 0x3298c8, - 0x3b1104, - 0x33ecc7, - 0x3c2c83, - 0x21b742, - 0x20cb42, - 0x2fc0c9, - 0x228c87, - 0x2141c2, - 0x272f45, - 0x214b42, - 0x20eb42, - 0x2f6e43, - 0x2f6e46, - 0x305fc2, - 0x308c02, - 0x200402, - 0x35c406, - 0x21fbc7, - 0x213fc2, - 0x200902, - 0x25cd0f, - 0x284ecd, - 0x28a98e, - 0x2b77cc, - 0x206842, - 0x206142, - 0x35c685, - 0x320b86, - 0x200b82, - 0x2069c2, - 0x200682, - 0x285244, - 0x2c1a44, - 0x384486, - 0x205142, - 0x27a207, - 0x23d903, - 0x23d908, - 0x23e108, - 0x2d23c7, - 0x24c186, - 0x207842, - 0x20b603, - 0x20b607, - 0x3a6dc6, - 0x2ee0c5, - 0x274608, - 0x2071c2, - 0x3b9447, - 0x26c702, - 0x294ec2, - 0x206382, - 0x205249, - 0x201082, - 0xcb3c8, - 0x203882, - 0x234583, - 0x204847, - 0x203282, - 0x21af8c, - 0x21b28b, - 0x305106, - 0x2ec445, - 0x202c02, - 0x202982, - 0x2c55c6, - 0x216643, - 0x342e87, - 0x291f82, - 0x2008c2, - 0x2368d5, - 0x38c155, - 0x239093, - 0x38c693, - 0x24ee47, - 0x26ee11, - 0x275590, - 0x283712, - 0x2eb111, - 0x29a208, - 0x29a210, - 0x29f38f, - 0x2a1953, - 0x2a2652, - 0x2a7d50, - 0x2b4ecf, - 0x3689d2, - 0x3a1891, - 0x3d4d53, - 0x2b9d52, - 0x2d634f, - 0x2ddd0e, - 0x2e1112, - 0x2e1fd1, - 0x2e5e0f, - 0x2e7c8e, - 0x2efbd1, - 0x2f8ed0, - 0x301d52, - 0x306491, - 0x309b50, - 0x311f8f, - 0x3cea91, - 0x347910, - 0x37b006, - 0x380cc7, - 0x218987, - 0x209f42, - 0x280f85, - 0x310dc7, - 0x20c782, - 0x2018c2, - 0x229e45, - 0x21ec03, - 0x374946, - 0x2fb68d, - 0x2fb9cc, - 0x203642, - 0x20364b, - 0x27500a, - 0x22408a, - 0x2c43c9, - 0x2fa74b, - 0x2ae64d, - 0x3114cc, - 0x35d58a, - 0x244f8c, - 0x27188b, - 0x27604c, - 0x29b68e, - 0x2bf90b, - 0x2b8a0c, - 0x2dc483, - 0x376e46, - 0x3bf642, - 0x304382, - 0x24f4c3, - 0x206482, - 0x20c3c3, - 0x324506, - 0x25e847, - 0x352386, - 0x2e78c8, - 0x348408, - 0x2cf746, - 0x200f02, - 0x31398d, - 0x313ccc, - 0x338807, - 0x316b07, - 0x234a42, - 0x2143c2, - 0x20b582, - 0x27c602, - 0x330496, - 0x335f95, - 0x3395d6, - 0x33ff53, - 0x340612, - 0x355513, - 0x358152, - 0x3acc8f, - 0x3be558, - 0x3bf117, - 0x3bfc59, - 0x3c1898, - 0x3c3c58, - 0x3c5fd7, - 0x3c6b17, - 0x3c8216, - 0x3cc693, - 0x3ccfd5, - 0x3cdd52, - 0x3ce1d3, - 0x201242, - 0x21a3c3, - 0x242543, - 0x214a83, - 0x232dc3, - 0x308003, - 0x23c803, - 0x219a04, - 0x21a3c3, - 0x242543, - 0x20e2c3, + 0x13150c, + 0x2be4c2, + 0x250d42, + 0x227082, + 0x24a282, + 0x23cb03, + 0x200bc2, + 0x217fc3, + 0x209ec2, + 0x25c042, + 0x23e083, + 0x3081c2, + 0x208502, + 0x20a1c2, + 0x204782, + 0x2010c2, + 0x230ac2, + 0x205682, + 0x22b302, + 0x2270c2, + 0x32748a, + 0x36f50a, + 0x3a124a, + 0x3e2d42, + 0x208902, + 0x20e8c2, + 0x13aa7f09, + 0x13f61e8a, + 0x142fc47, + 0x142050c2, + 0x143a083, + 0x1742, + 0x161e8a, + 0x162b0e, + 0x241ec4, + 0x57fc5, + 0x14a2ea43, + 0x3dc03, + 0x233fc3, + 0x24d704, + 0x266a83, + 0x20e704, + 0x2191c3, + 0x13d289, + 0x157686, + 0x23cb03, + 0xf1584, + 0x1598c3, + 0x217fc3, + 0x2a7c5, + 0x205803, + 0x23e083, + 0x1466d84, + 0x235403, + 0x181584, + 0x20aa43, + 0xae888, + 0x154f043, + 0x12a086, + 0x146e844, + 0x1a45, + 0x14c80a, + 0x124d82, + 0x15408acd, + 0x1adec6, + 0x159a140b, + 0xc951, + 0x15ea7f09, + 0x1ac8, + 0x69908, + 0x1c9415c7, + 0x3502, + 0xa8087, + 0x221ce, + 0x146bcb, + 0x14a88b, + 0x1c008a, + 0x1683c7, + 0xae888, + 0x120d48, + 0xa807, + 0x1cc176cb, + 0x1a087, + 0xcfc2, + 0x2b20d, + 0x16a7c7, + 0xb1bca, + 0x1e174f, + 0x12308f, + 0x161e82, + 0x12402, + 0x8af48, + 0x1d10778c, + 0x1570a, + 0xe710a, + 0x19004a, + 0x80a88, + 0x1d208, + 0x5a488, + 0xe75c8, + 0x1388, + 0xf982, + 0x167c0f, + 0xc6d8b, + 0x10f508, + 0x35cc7, + 0x4878a, + 0xbc3cb, + 0x34449, + 0x48687, + 0x83986, + 0x1d108, + 0x18ea0c, + 0x161347, + 0x1ae40a, + 0xec88, + 0x10ae8e, + 0x10b64e, + 0x16820b, + 0x168a8b, + 0x658cb, + 0x66609, + 0x6754b, + 0xbd4cd, + 0xf548b, + 0xf5fcd, + 0xf634d, + 0x10360a, + 0x12a4cb, + 0x166c0b, + 0x3bfc5, + 0x1d58b810, + 0x13514f, + 0x72e8f, + 0x2470d, + 0x13d450, + 0x293c2, + 0x1da1f8c8, + 0x7f248, + 0xea790, + 0x17fe0e, + 0x1df22b85, + 0x4c84b, + 0x13c390, + 0x1d30a, + 0x168c49, + 0x680c7, + 0x68407, + 0x685c7, + 0x68947, + 0x69e07, + 0x6a2c7, + 0x6bb07, + 0x6c047, + 0x6d587, + 0x6d907, + 0x6dfc7, + 0x6e187, + 0x6e347, + 0x6e507, + 0x6f307, + 0x6fc47, + 0x70a87, + 0x70e47, + 0x71487, + 0x71747, + 0x71907, + 0x71c07, + 0x71f87, + 0x72187, + 0x748c7, + 0x74a87, + 0x74c47, + 0x75dc7, + 0x77207, + 0x776c7, + 0x77dc7, + 0x78087, + 0x78407, + 0x785c7, + 0x789c7, + 0x78e07, + 0x792c7, + 0x79847, + 0x79a07, + 0x79bc7, + 0x7a007, + 0x7aa87, + 0x7afc7, + 0x7b207, + 0x7b3c7, + 0x7bb87, + 0x7c187, + 0x9a42, + 0x5a58a, + 0x13808, + 0x1baf8c, + 0x4eb87, + 0x918c5, + 0x9b311, + 0x1bb46, + 0x104dca, + 0x8adca, + 0x56546, + 0xb3ecb, + 0x642, + 0x31351, + 0xc5d89, + 0x9bf49, + 0x9d306, + 0x5b542, + 0x1b21ca, + 0xafcc9, + 0xb040f, + 0xb0a0e, + 0xb3108, + 0x11b08, + 0xb5c2, + 0x6ed89, + 0x1e3586c9, + 0xbd049, + 0xbd04c, + 0x8f90e, + 0x4b8c, + 0xf2f8f, + 0x1bf08e, + 0x12b40c, + 0x33449, + 0x45391, + 0x45948, + 0x1a4e12, + 0x593cd, + 0x69acd, + 0x78f8b, + 0x81855, + 0x860c9, + 0x1518ca, + 0x188809, + 0x1aad50, + 0x1ae8cb, + 0x9890f, + 0xa868b, + 0xa914c, + 0xaa110, + 0xb7dca, + 0xb894d, + 0xd3a0e, + 0x195a0a, + 0xc1e8c, + 0xc4e94, + 0xc5a11, + 0xc694b, + 0xc858f, + 0xcbb0d, + 0xcd20e, + 0xd048c, + 0xd0c8c, + 0xd370b, + 0x172a8e, + 0x199ed0, + 0xdba8b, + 0xdc74d, + 0xdf30f, + 0xe804c, + 0xe9d8e, + 0xf3651, + 0x10570c, + 0x1d4047, + 0x10d14d, + 0x11db8c, + 0x144550, + 0x16528d, + 0x16efc7, + 0x176790, + 0x19dd08, + 0x1a3e8b, + 0xba1cf, + 0x1bb208, + 0x14bf0d, + 0x1125d0, + 0x178c89, + 0x1e78b7c8, + 0x1eabf946, + 0xc0843, + 0x3ec49, + 0xc7405, + 0x6902, + 0x48c09, + 0x14c50a, + 0x1efa52c6, + 0x15a52cd, + 0x1f36a9c4, + 0x57d06, + 0x1b68a, + 0x27bcd, + 0x1f52b109, + 0x216c3, + 0x11bb8a, + 0xe6751, + 0xe6b89, + 0xe7087, + 0xe7d88, + 0xe8447, + 0x4ec48, + 0xcacb, + 0x1311c9, + 0xf1e10, + 0xf22cc, + 0x1faf270d, + 0xf3a88, + 0xf4ec5, + 0x147e08, + 0x19ce4a, + 0x18a347, + 0x2542, + 0x1ff3f5d5, + 0x13d08a, + 0x1320c9, + 0x9e588, + 0x6ab09, + 0x7cb45, + 0x11d88a, + 0x92e0f, + 0x10d54b, + 0x11ff4c, + 0x176cd2, + 0xe9c6, + 0x7ce85, + 0x117a48, + 0xf84cb, + 0xf1151, + 0x16acc7, + 0x4da0a, + 0x20300485, + 0x1b330c, + 0x139c43, + 0x197a86, + 0x408c2, + 0x1089cb, + 0x10948a, + 0x150980c, + 0x7f5c8, + 0xf6188, + 0x2069e606, + 0x17d5c7, + 0xd782, + 0x7742, + 0x1a55d0, + 0x65087, + 0x3074f, + 0x13a06, + 0xd2b8e, + 0x99a0b, + 0x3dd48, + 0x34809, + 0x5da12, + 0x197b4d, + 0x118088, + 0x1188c9, + 0xee00d, + 0x19f749, + 0xb48b, + 0x6c348, + 0x71d88, + 0x75a88, + 0x80389, + 0x8058a, + 0x84b0c, + 0x166eca, + 0xf17ca, + 0x1178c7, + 0x9a50a, + 0x1cda4d, + 0x45c51, + 0x20acd506, + 0x1b994b, + 0x12f80c, + 0x94388, + 0x149449, + 0x160b0d, + 0x68b90, + 0x1812cd, + 0x4642, + 0x4a68d, + 0x72c2, + 0x1f702, + 0x11780a, + 0x756ca, + 0x20e7b508, + 0x104cca, + 0x11f80b, + 0x10b8cc, + 0x12048a, + 0x12070f, + 0x120ace, + 0x171cd, + 0x211e2c05, + 0x12d408, + 0x3602, + 0x1422383, + 0x415505, + 0x45d884, + 0x16202c0e, + 0x16b59cce, + 0x1720180a, + 0x17b9184e, + 0x1835788e, + 0x18b7f38c, + 0x142fc47, + 0x142fc49, + 0x143a083, + 0x1926060c, + 0x19b49bc9, + 0x1a36af09, + 0x1ab71749, + 0x1742, + 0x2b51, + 0x159c11, + 0x174d, + 0x1b6451, + 0x1577d1, + 0x17f2cf, + 0x6054f, + 0x149b0c, + 0x16ae4c, + 0x17168c, + 0x1af28d, + 0x15d915, + 0xc1a8c, + 0xc778c, + 0x135a10, + 0x141acc, + 0x14af8c, + 0x18ad99, + 0x191599, + 0x1bdfd9, + 0x1cb4d4, + 0x1d6294, + 0x1e02d4, + 0x1e2714, + 0xa994, + 0x1b2c1b49, + 0x1b9e0589, + 0x1c2c7849, + 0x16645b49, + 0x1742, + 0x16e45b49, + 0x1742, + 0xa98a, + 0x1742, + 0x17645b49, + 0x1742, + 0xa98a, + 0x1742, + 0x17e45b49, + 0x1742, + 0x18645b49, + 0x1742, + 0x18e45b49, + 0x1742, + 0xa98a, + 0x1742, + 0x19645b49, + 0x1742, + 0xa98a, + 0x1742, + 0x19e45b49, + 0x1742, + 0x1a645b49, + 0x1742, + 0xa98a, + 0x1742, + 0x1ae45b49, + 0x1742, + 0xa98a, + 0x1742, + 0x1b645b49, + 0x1742, + 0x1be45b49, + 0x1742, + 0x1c645b49, + 0x1742, + 0xa98a, + 0x1742, + 0x1400401, + 0xc945, + 0x1c0084, + 0x144ce03, + 0x1426d83, + 0x14fa443, + 0x2c0e, + 0x159cce, + 0x8450e, + 0x180a, + 0x19184e, + 0x15788e, + 0x17f38c, + 0x6060c, + 0x149bc9, + 0x16af09, + 0x171749, + 0xc1b49, + 0x1e0589, + 0xc7849, + 0x135acd, + 0x141b89, + 0xac49, + 0x12d5c4, + 0x132ac4, + 0x1c8a04, + 0x1c95c4, + 0xaae04, + 0x2ec44, + 0x3cd84, + 0x192d44, + 0x13904, + 0xbec06, + 0x59504, + 0x158e7c3, + 0x149987, + 0x148574c, + 0x1ac3, + 0x293c2, + 0x107788, + 0xd1784, + 0x14386, + 0xd8a84, + 0x15aa06, + 0x16b82, + 0xa8c1, + 0x20e44, + 0xb1706, + 0x171c3, + 0x1ac3, + 0xa0e83, + 0x13d385, + 0x124dc2, + 0x124dc8, + 0xeb947, + 0x131247, + 0xf982, 0x2000c2, - 0x201602, - 0x3be933c5, - 0x3c281445, - 0x3c746bc6, - 0x9a048, - 0x3caba385, - 0x201242, - 0x2052c2, - 0x3ce871c5, - 0x3d27e985, - 0x3d680387, - 0x3da806c9, - 0x3de1f844, + 0x212402, + 0x204542, + 0x20fa02, + 0x200382, + 0x2003c2, + 0x207742, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x20e703, + 0x217fc3, + 0x23e083, + 0xae888, + 0x22ea43, + 0x233fc3, + 0x217fc3, + 0x23e083, + 0x10303, + 0x266a83, + 0xe704, + 0x2000c2, + 0x24ac43, + 0x2362ea43, + 0x392747, + 0x266a83, + 0x21e1c3, + 0x21e484, + 0x217fc3, + 0x23e083, + 0x226e0a, + 0x243bc5, + 0x216983, + 0x22dc42, + 0xae888, + 0x23adad8a, + 0xe01, + 0xae888, + 0x12402, + 0x137ac2, + 0x2432ae8b, + 0x2462e004, + 0x16a905, + 0x8cc5, + 0x107786, + 0x24a08cc5, + 0x54383, + 0x5cd83, + 0x9c4, + 0x157bc3, + 0x2105, + 0x146bc5, + 0xae888, + 0x1a087, + 0x2ea43, + 0x2ed4d, + 0x2523a707, + 0x159146, + 0x25401645, + 0x1c0992, + 0x159207, + 0x1dbca, + 0x10ac8, + 0x1dac7, + 0x6bcca, + 0x1bc448, + 0xe4f07, + 0x1ac70f, + 0x36fc7, + 0x192b46, + 0x13c390, + 0xcee8f, + 0x21c49, + 0x57d84, + 0x259592ce, + 0x185a89, + 0x6e646, + 0x111a89, + 0x193c86, + 0x1c2e06, + 0x4f10c, + 0xbc5ca, + 0x345c7, + 0x17edca, + 0x1596c9, + 0xf8e8c, + 0x1c8ca, + 0x4b8ca, + 0x2149, + 0x57d06, + 0x3468a, + 0x118f4a, + 0xa3a4a, + 0x137509, + 0xe54c8, + 0xe5746, + 0xed88d, + 0x5130b, + 0xc7c05, + 0x25f5a28c, + 0x14ca47, + 0x110289, + 0xd1047, + 0xc6114, + 0x1129cb, + 0x10f34a, + 0x5d88a, + 0xac80d, + 0x151fa09, + 0x117e4c, + 0x1186cb, + 0x88c3, + 0x88c3, + 0x36fc6, + 0x88c3, + 0x107788, + 0x15c103, + 0x46604, + 0x54603, + 0x347c5, + 0x1475903, + 0x51709, + 0xf84cb, + 0x14e82c3, + 0x154546, + 0x15037c7, + 0x1aafc7, + 0x26d41489, + 0x17e86, + 0x4ac43, + 0xae888, + 0x12402, + 0x4d704, + 0x61083, + 0x17b845, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x217fc3, + 0x23e083, + 0x233f03, + 0x22ea43, + 0x233fc3, + 0x280203, + 0x266a83, + 0x23cb03, + 0x217fc3, + 0x23e083, + 0x2bd443, + 0x20aa43, + 0x233f03, + 0x24cd44, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x217fc3, + 0x23e083, + 0x204ac3, + 0x28541585, + 0x142e6c3, + 0x22ea43, + 0x233fc3, + 0x20fa03, + 0x280203, + 0x266a83, + 0x20e704, + 0x3433c3, + 0x215f83, + 0x23cb03, + 0x217fc3, + 0x1c0443, + 0x23e083, + 0x216983, + 0x29219f03, + 0x176bc9, + 0x12402, + 0x3c7603, + 0x29e2ea43, + 0x233fc3, + 0x249283, + 0x266a83, + 0x2220c3, + 0x215f83, + 0x23e083, + 0x3005c3, + 0x3cd604, + 0xae888, + 0x2a62ea43, + 0x233fc3, + 0x2b31c3, + 0x266a83, + 0x23cb03, + 0x21e484, + 0x217fc3, + 0x23e083, + 0x2302c3, + 0xae888, + 0x2ae2ea43, + 0x233fc3, + 0x280203, + 0x205803, + 0x23e083, + 0xae888, + 0x142fc47, + 0x24ac43, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x20e704, + 0x21e484, + 0x217fc3, + 0x23e083, + 0x146bc5, + 0x178d87, + 0xc634b, + 0xe6f84, + 0xc7c05, + 0x1454408, + 0x2c10d, + 0x2c242285, + 0x27c44, + 0x12402, + 0x10103, + 0x184485, + 0x30242, + 0x53c2, + 0x34b8c5, + 0xae888, + 0x88c2, + 0x1b2c3, + 0x16b88f, + 0x12402, + 0x1063c6, + 0x2000c2, + 0x24ac43, + 0x22ea43, + 0x266a83, + 0x20e704, + 0x23cb03, + 0x21e484, + 0x217fc3, + 0x23e083, + 0x216983, + 0x30242, + 0x32ff08, + 0x24cd44, + 0x37e046, + 0x3af146, + 0xae888, + 0x31a6c3, + 0x355c09, + 0x30ddd5, + 0x10dddf, + 0x22ea43, + 0x7fa87, + 0x242992, + 0x1623c6, + 0x16fd05, + 0x1d30a, + 0x168c49, + 0x24274f, + 0x2e5904, + 0x2bbf05, + 0x313850, + 0x215f87, + 0x205803, + 0x321388, + 0x134b86, + 0x293b0a, + 0x223144, + 0x2ffec3, + 0x22dc42, + 0x2fa00b, + 0x5803, + 0x182c04, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x23cb03, + 0x217fc3, + 0x5803, + 0x23e083, + 0x307183, + 0x212402, + 0x1c06c3, + 0x2a4c4, + 0x217fc3, + 0x23e083, + 0x2fc39fc5, + 0x1d5cc6, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x23cb03, + 0x23e083, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x21e1c3, + 0x265dc3, + 0x23e083, + 0x4ac43, + 0x212402, + 0x22ea43, + 0x233fc3, + 0x217fc3, + 0x5803, + 0x23e083, + 0x17082, + 0x2000c2, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x217fc3, + 0x23e083, + 0x8cc5, + 0x1ac3, + 0x24cd44, + 0x22ea43, + 0x233fc3, + 0x217544, + 0x217fc3, + 0x23e083, + 0xae888, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x217fc3, + 0x1c0443, + 0x23e083, + 0x1357c9, + 0x4cc4, + 0x22ea43, + 0xf982, + 0x233fc3, + 0x280203, + 0x204903, + 0x23cb03, + 0x217fc3, + 0x5803, + 0x23e083, + 0x2a82, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x36a584, + 0x20e704, + 0x217fc3, + 0x23e083, + 0x20aa43, + 0x6c02, + 0x212402, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x217fc3, + 0x1c0443, + 0x23e083, + 0xae888, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x2f4c43, + 0x160c3, + 0x1e1c3, + 0x217fc3, + 0x1c0443, + 0x23e083, + 0x32748a, + 0x345389, + 0x36500b, + 0x3657ca, + 0x36f50a, + 0x37c54b, + 0x393a4a, + 0x399a4a, + 0x3a124a, + 0x3a1c4b, + 0x3c4709, + 0x3cf9ca, + 0x3cfe0b, + 0x3db28b, + 0x3e0d8a, + 0xcdc2, + 0x22ea43, + 0x233fc3, + 0x280203, + 0x23cb03, + 0x217fc3, + 0x5803, + 0x23e083, + 0xcc4b, + 0x17fe07, + 0x5af88, + 0xee144, + 0x1c4104, + 0x94dc8, + 0xea706, + 0xcc06, + 0x1a07c9, + 0xae888, + 0x22ea43, + 0x1d304, + 0x2680c4, + 0x201c02, + 0x21e484, + 0x202645, + 0x233f03, + 0x24cd44, + 0x22ea43, + 0x236704, + 0x233fc3, + 0x24d704, + 0x2e5904, + 0x20e704, + 0x215f83, + 0x217fc3, + 0x23e083, + 0x24a845, + 0x204ac3, + 0x216983, + 0x204343, + 0x2ddf84, + 0x32a004, + 0x23a185, + 0xae888, + 0x3b4e04, + 0x3c2f86, + 0x202284, + 0x212402, + 0x3770c7, + 0x3a9947, + 0x24bb44, + 0x20e785, + 0x365485, + 0x22f845, + 0x20e704, + 0x38f948, + 0x2523c6, + 0x3641c8, + 0x2836c5, + 0x2ee705, + 0x237bc4, + 0x23e083, + 0x300ac4, + 0x37b286, + 0x243cc3, + 0x2ddf84, + 0x24fd85, + 0x248b84, + 0x2a67c4, + 0x22dc42, + 0x232ec6, + 0x3b7ec6, + 0x315fc5, + 0x2000c2, + 0x24ac43, + 0x34e12402, + 0x21fa44, + 0x200382, + 0x23cb03, + 0x20cac2, + 0x217fc3, + 0x2003c2, + 0x2fcf46, + 0x208503, + 0x20aa43, + 0xae888, + 0xae888, + 0x266a83, + 0x1c0443, + 0x2000c2, + 0x35a12402, + 0x266a83, + 0x26e2c3, + 0x3433c3, + 0x22e004, + 0x217fc3, + 0x23e083, + 0xae888, + 0x2000c2, + 0x36212402, + 0x22ea43, + 0x217fc3, + 0x5803, + 0x23e083, + 0x682, + 0x203b42, + 0x21fcc2, + 0x21e1c3, + 0x2f8e43, + 0x2000c2, + 0x146bc5, + 0xae888, + 0x178d87, + 0x212402, + 0x233fc3, + 0x24d704, + 0x2033c3, + 0x266a83, + 0x204903, + 0x23cb03, + 0x217fc3, + 0x213cc3, + 0x23e083, + 0x234fc3, + 0x140d13, + 0x142dd4, + 0x146bc5, + 0x178d87, + 0x1dbc9, + 0x110b86, + 0x121b4b, + 0x36fc6, + 0x54bc7, + 0xe786, + 0x649, + 0x1d818a, + 0x9110d, + 0x127d8c, + 0x1198ca, + 0x15d048, + 0x1b5a05, + 0x1dc08, + 0x13a06, + 0x1ce786, + 0x134c46, + 0x602, + 0x2293c2, + 0x6f204, + 0xa0e86, + 0x1411d0, + 0x147a54e, + 0x1e46, + 0x696cc, + 0x37b22f0b, + 0x146bc5, + 0x15434b, + 0x37fce6c4, + 0x1c0247, + 0x23c91, + 0x11a7ca, + 0x22ea43, + 0x38285648, + 0x6bc45, + 0xf988, + 0x1ff44, + 0x14c705, + 0x38561cc6, + 0x9b306, + 0xc9b46, + 0x9620a, + 0x96ecc, + 0x1c2043, + 0x1c4104, + 0x38a120c4, + 0x51709, + 0x164347, + 0x1167ca, + 0x14dac89, + 0x605, + 0x103583, + 0x38e35107, + 0x2a7c5, + 0x153d986, + 0x14731c6, + 0xb3f8c, + 0x104248, + 0x390408c3, + 0xfa24b, + 0x12bd4b, + 0x3964950c, + 0x140ba83, + 0xc96c8, + 0xfa4c5, + 0xc6c09, + 0xeca43, + 0x11fb08, + 0x141b5c6, + 0x8e8c7, + 0x39b60b09, + 0x99c87, + 0xf054a, + 0x3afc6788, + 0x11838d, + 0xff48, + 0x1ac3, + 0x1445009, + 0x3a643, + 0x36fc6, + 0x107788, + 0x13904, + 0x154c85, + 0x1492ec3, + 0x22387, + 0x39e22383, + 0x3a3c78c6, + 0x3a637e84, + 0x3ab09647, + 0x107784, + 0x107784, + 0x107784, + 0x107784, + 0x41, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x23cb03, + 0x217fc3, + 0x23e083, + 0x2000c2, + 0x212402, + 0x266a83, + 0x209582, + 0x217fc3, + 0x23e083, + 0x208503, + 0x38644f, + 0x38680e, + 0xae888, + 0x22ea43, + 0x44cc7, + 0x233fc3, + 0x266a83, + 0x2191c3, + 0x217fc3, + 0x23e083, + 0x1d84, + 0x157d04, + 0x1b4744, + 0x21afc3, + 0x324007, + 0x207d42, + 0x272549, + 0x200ac2, + 0x3a58cb, + 0x2a6b8a, + 0x2aec89, + 0x200542, + 0x220306, + 0x244495, + 0x3a5a15, + 0x387d93, + 0x3a5f93, + 0x2272c2, + 0x2272c5, + 0x25f44c, + 0x27ad0b, + 0x277a05, + 0x201802, + 0x239202, + 0x381b06, + 0x203502, + 0x2cf9c6, + 0x21d58d, + 0x22a54c, + 0x38b884, + 0x200882, + 0x222b02, + 0x3a51c8, + 0x200202, + 0x336d46, + 0x39c70f, + 0x357dd0, + 0x229804, + 0x244655, + 0x387f13, + 0x24c943, + 0x369f8a, + 0x20c5c7, + 0x3a1ec9, + 0x316687, + 0x30bf02, + 0x200282, + 0x3c90c6, + 0x204cc2, + 0xae888, + 0x207f42, + 0x208a02, + 0x228fc7, + 0x348187, + 0x348191, + 0x218885, + 0x21888e, + 0x2194cf, + 0x20cfc2, + 0x3236c7, + 0x21b008, + 0x20aac2, + 0x21c942, + 0x227846, + 0x22784f, + 0x26c690, + 0x22c442, + 0x20cf02, + 0x238b48, + 0x214803, + 0x261248, + 0x2eea8d, + 0x20cf03, + 0x3cc248, + 0x28734f, + 0x28770e, + 0x25d54a, + 0x26cb11, + 0x26cf90, + 0x30280d, + 0x302b4c, + 0x3c20c7, + 0x36a107, + 0x37e109, + 0x29a842, + 0x205082, + 0x256b8c, + 0x256e8b, + 0x200d42, + 0x2d38c6, + 0x202282, + 0x200482, + 0x361e82, + 0x212402, + 0x22f244, + 0x239d87, + 0x22c982, + 0x240307, + 0x241b47, + 0x230a82, + 0x211d02, + 0x244b85, + 0x20da02, + 0x3985ce, + 0x3d068d, + 0x233fc3, + 0x28cf0e, + 0x2bb64d, + 0x35cc43, + 0x203142, + 0x28ac84, + 0x29a802, + 0x223ec2, + 0x3930c5, + 0x3a3b07, + 0x2481c2, + 0x20fa02, + 0x24d307, + 0x251a88, + 0x2ba882, + 0x27cf06, + 0x256a0c, + 0x256d4b, + 0x2091c2, + 0x261d4f, + 0x262110, + 0x26250f, + 0x2628d5, + 0x262e14, + 0x26330e, + 0x26368e, + 0x263a0f, + 0x263dce, + 0x264154, + 0x264653, + 0x264b0d, + 0x27c349, + 0x292a43, + 0x2033c2, + 0x2d2685, + 0x2033c6, + 0x200382, + 0x3451c7, + 0x266a83, + 0x200642, + 0x23e108, + 0x26cd51, + 0x26d190, + 0x202182, + 0x291c47, + 0x204b82, + 0x277507, + 0x206902, + 0x207089, + 0x381ac7, + 0x294648, + 0x361b06, + 0x207483, + 0x207485, + 0x234242, + 0x2004c2, + 0x3c94c5, + 0x3b3785, + 0x201482, + 0x219303, + 0x3546c7, + 0x20bdc7, + 0x204d02, + 0x249084, + 0x20eb03, + 0x2f6f89, + 0x20eb08, + 0x202702, + 0x20a682, + 0x26b947, + 0x26ca45, + 0x273508, + 0x2b1347, + 0x209f03, + 0x2a0d06, + 0x30268d, + 0x302a0c, + 0x305e06, + 0x206b02, + 0x208c82, + 0x20b982, + 0x2871cf, + 0x2875ce, + 0x365507, + 0x204482, + 0x388c05, + 0x388c06, + 0x215782, + 0x200bc2, + 0x293506, + 0x206583, + 0x206586, + 0x2d8a45, + 0x2d8a4d, + 0x2d92d5, + 0x2da14c, + 0x2da4cd, + 0x2da812, + 0x20a942, + 0x2720c2, + 0x203882, + 0x36ac46, + 0x204a46, + 0x202542, + 0x203446, + 0x201102, + 0x324805, + 0x202582, + 0x398709, + 0x22ce4c, + 0x22d18b, + 0x2003c2, + 0x252e48, + 0x202a42, + 0x200a82, + 0x278706, + 0x245ac5, + 0x200a87, + 0x22dcc5, + 0x257e45, + 0x201b42, + 0x21dcc2, + 0x205b42, + 0x298c07, + 0x2fd00d, + 0x2fd38c, + 0x235507, + 0x27ce82, + 0x211c82, + 0x3dc788, + 0x248d88, + 0x34f348, + 0x3bb1c4, + 0x372d07, + 0x36aa43, + 0x22d782, + 0x204ac2, + 0x2fe3c9, + 0x30b287, + 0x216982, + 0x278b05, + 0x242c42, + 0x20d402, + 0x2f8b83, + 0x2f8b86, + 0x306d42, + 0x308142, + 0x200402, + 0x3616c6, + 0x34de07, + 0x216782, + 0x200902, + 0x26108f, + 0x28cd4d, + 0x28fd0e, + 0x2bb4cc, + 0x208842, + 0x205302, + 0x361945, + 0x325d86, + 0x200b82, + 0x205502, + 0x200682, + 0x28d0c4, + 0x2c14c4, + 0x389fc6, + 0x207742, + 0x28d807, + 0x23c643, + 0x23c648, + 0x23d1c8, + 0x245207, + 0x249946, + 0x20ab02, + 0x2186c3, + 0x2186c7, + 0x292246, + 0x2ecb85, + 0x27a1c8, + 0x2018c2, + 0x3c1007, + 0x207142, + 0x25cdc2, + 0x201702, + 0x219649, + 0x203c02, + 0x10acc8, + 0x201f42, + 0x235783, + 0x3599c7, + 0x200f02, + 0x22cfcc, + 0x22d2cb, + 0x305e86, + 0x3034c5, + 0x203d02, + 0x202a82, + 0x2cb146, + 0x20dd03, + 0x36a307, + 0x2b3f42, + 0x2008c2, + 0x244315, + 0x3a5bd5, + 0x387c53, + 0x3a6113, + 0x2596c7, + 0x28b111, + 0x2908d0, + 0x2f7b92, + 0x29b711, + 0x2a0548, + 0x2a0550, + 0x2a2c8f, + 0x2a6953, + 0x2aea52, + 0x2b8190, + 0x36f14f, + 0x3a4112, + 0x2bac51, + 0x2bfa93, + 0x3426d2, + 0x2d868f, + 0x2e010e, + 0x2e3512, + 0x2e43d1, + 0x2e79cf, + 0x2ea38e, + 0x2ed451, + 0x2fa9d0, + 0x304412, + 0x307211, + 0x309090, + 0x321ecf, + 0x37ab11, + 0x3d2fd0, + 0x33fac6, + 0x314b47, + 0x2153c7, + 0x202402, + 0x288985, + 0x3135c7, + 0x21fcc2, + 0x208d82, + 0x22b8c5, + 0x208743, + 0x26ec86, + 0x2fd1cd, + 0x2fd50c, + 0x2034c2, + 0x25f2cb, + 0x27abca, + 0x22718a, + 0x2ca549, + 0x2fc34b, + 0x2b148d, + 0x313ccc, + 0x240cca, + 0x2466cc, + 0x24e88b, + 0x27784c, + 0x27bd0e, + 0x29cb4b, + 0x2b668c, + 0x2ec543, + 0x2edf06, + 0x3c6782, + 0x305102, + 0x25cb43, + 0x201502, + 0x204243, + 0x353446, + 0x262a87, + 0x2c3846, + 0x2158c8, + 0x354548, + 0x3800c6, + 0x20e482, + 0x31598d, + 0x315ccc, + 0x32bf07, + 0x319707, + 0x223542, + 0x216b82, + 0x203b02, + 0x284302, + 0x336c56, + 0x33b795, + 0x3407d6, + 0x3437d3, + 0x343e92, + 0x35bc93, + 0x35de52, + 0x3b6bcf, + 0x3c5758, + 0x3c6257, + 0x3c6c59, + 0x3c8b18, + 0x3c96d8, + 0x3cb9d7, + 0x3cc457, + 0x3ce196, + 0x3d1cd3, + 0x3d2755, + 0x3d33d2, + 0x3d3853, + 0x212402, + 0x217fc3, + 0x23e083, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x23cb03, + 0x21e484, + 0x217fc3, + 0x23e083, + 0x208503, + 0x2000c2, + 0x202642, + 0x3ce98545, + 0x3d25ef05, + 0x3d73ed86, + 0xae888, + 0x3dac0105, + 0x212402, + 0x204542, + 0x3de5de45, + 0x3e285fc5, + 0x3e687a87, + 0x3ea87dc9, + 0x3ef4da84, 0x200382, 0x200642, - 0x3e249a05, - 0x3e69d789, - 0x3eb2fb48, - 0x3eeb4985, - 0x3f350107, - 0x3f61d988, - 0x3fb09745, - 0x3fe9e2c6, - 0x403a2709, - 0x406db0c8, - 0x40aca648, - 0x40e9ddca, - 0x412c21c4, - 0x4168e885, - 0x41ac6c08, - 0x41f44945, - 0x20fe82, - 0x42297703, - 0x426aa1c6, - 0x42aaf5c8, - 0x42ef2f86, - 0x4320ad88, - 0x43785546, - 0x43a02c44, - 0x43e08482, - 0x446f4cc7, - 0x44ab0b04, - 0x44e79487, - 0x453cfc87, + 0x3f25bf05, + 0x3f69e949, + 0x3fb36248, + 0x3feb87c5, + 0x403513c7, + 0x40623708, + 0x40b08c85, + 0x40e9f486, + 0x413a9a89, + 0x416dd6c8, + 0x41ad02c8, + 0x41e9ef8a, + 0x422ef084, + 0x426ad705, + 0x42acc788, + 0x42e48985, + 0x214882, + 0x4324bd03, + 0x436abe06, + 0x43a6af08, + 0x43ef4246, + 0x4434df48, + 0x447af006, + 0x44a463c4, + 0x44e03182, + 0x45707b87, + 0x45ab43c4, + 0x45e81487, + 0x463da087, 0x2003c2, - 0x456a2c85, - 0x45a13504, - 0x45fa3407, - 0x4623d187, - 0x466830c6, - 0x46a7f445, - 0x46e9d887, - 0x472daf48, - 0x477d0007, - 0x47b41189, - 0x47ed7505, - 0x48331207, - 0x48692a06, - 0x647cb, - 0x48b3aec8, - 0x225bcd, - 0x25d2c9, - 0x27418b, - 0x27c08b, - 0x2b014b, - 0x2f010b, - 0x320d8b, - 0x32104b, - 0x321e89, - 0x322e8b, - 0x32314b, - 0x323c8b, - 0x324e0a, - 0x32534a, - 0x32594c, - 0x32934b, - 0x329aca, - 0x33e6ca, - 0x34b30e, - 0x34d44e, - 0x34d7ca, - 0x34f64a, - 0x350c0b, - 0x350ecb, - 0x3519cb, - 0x36cdcb, - 0x36d3ca, - 0x36e08b, - 0x36e34a, - 0x36e5ca, - 0x36e84a, - 0x38e64b, - 0x39510b, - 0x397c0e, - 0x397f8b, - 0x39e8cb, - 0x39fc0b, - 0x3a3fca, - 0x3a4249, - 0x3a448a, - 0x3a5d8a, - 0x3bdf4b, - 0x3ca84b, - 0x3cb24a, - 0x3cc0cb, - 0x3d520b, - 0x3dfe4b, - 0x48e81788, - 0x4928ae89, - 0x496a5789, - 0x49aed408, - 0x3597c5, - 0x2041c3, - 0x251084, - 0x3015c5, - 0x21f586, - 0x307385, - 0x28a004, - 0x37f048, - 0x31bb05, - 0x294984, - 0x3c7607, - 0x2a4b0a, - 0x38f4ca, - 0x360107, - 0x34c207, - 0x2e6307, - 0x280947, - 0x334605, - 0x3b9b46, - 0x2f2207, - 0x39bc04, - 0x2f9b46, - 0x2f9a46, - 0x3c2d85, - 0x3b6684, - 0x29ee06, - 0x2a3bc7, - 0x34bb86, - 0x3adf47, - 0x251143, - 0x384106, - 0x238e85, - 0x280487, - 0x26a5ca, - 0x356504, - 0x219d88, - 0x31a2c9, - 0x2d4847, - 0x390606, - 0x3c5808, - 0x2f36c9, - 0x383f44, - 0x31eb84, - 0x2dea45, - 0x222b48, - 0x2d4b07, - 0x36c9c9, - 0x34cdc8, - 0x318406, - 0x264a46, - 0x29f988, - 0x36b706, - 0x281445, - 0x283186, - 0x279b88, - 0x27f9c6, - 0x255d8b, - 0x39d746, - 0x2a14cd, - 0x3d0845, - 0x2b09c6, - 0x20c045, - 0x24a789, - 0x370447, - 0x385188, - 0x291386, - 0x2a0689, - 0x3b1306, - 0x26a545, - 0x2a6dc6, - 0x2e5346, - 0x2d9209, - 0x2c0b06, - 0x2a4807, - 0x360b05, - 0x201583, - 0x21b7c5, - 0x34dd07, - 0x288f46, - 0x3d0749, - 0x346bc6, - 0x279686, - 0x20f849, - 0x282b89, - 0x2a8587, - 0x343488, - 0x2a7789, - 0x280c08, - 0x394486, - 0x2e2e85, - 0x2cfd8a, - 0x279706, - 0x33a806, - 0x2dc705, - 0x2523c8, - 0x326447, - 0x22f5ca, - 0x24e1c6, - 0x2e05c5, - 0x309186, - 0x215f87, - 0x3904c7, - 0x21c2c5, - 0x26a705, - 0x263586, - 0x269d46, - 0x26c0c6, - 0x2c70c4, - 0x282109, - 0x28bd06, - 0x30618a, - 0x2211c8, - 0x330f08, - 0x38f4ca, - 0x2af785, - 0x2a3b05, - 0x3c4f48, - 0x2c4f08, - 0x237c47, - 0x3b9d86, - 0x333988, - 0x2108c7, - 0x274848, - 0x2bebc6, - 0x283ec8, - 0x29c606, - 0x27ba87, - 0x3681c6, - 0x29ee06, - 0x2643ca, - 0x305206, - 0x2e2e89, - 0x36f506, - 0x2a5c8a, - 0x202c49, - 0x241706, - 0x2c2844, - 0x31e8cd, - 0x28b107, - 0x3b2a86, - 0x2ca505, - 0x3b1385, - 0x391106, - 0x2a7349, - 0x2bd687, - 0x27ab86, - 0x31db46, - 0x28a089, - 0x281384, - 0x244744, - 0x204088, - 0x356846, - 0x2a6ec8, - 0x318a88, - 0x2883c7, - 0x3c0549, - 0x26c2c7, - 0x2ba24a, - 0x2fcb8f, - 0x2e43ca, - 0x3d9e45, - 0x279dc5, - 0x2173c5, - 0x3c2147, - 0x215b43, - 0x343688, - 0x3de2c6, - 0x3de3c9, - 0x2f2b06, - 0x2d9047, - 0x2a0449, - 0x385088, - 0x2dc7c7, - 0x31f203, - 0x359845, - 0x215ac5, - 0x2c6f0b, - 0x344a04, - 0x23ba44, - 0x277906, - 0x31f3c7, - 0x39168a, - 0x3803c7, - 0x293607, - 0x27e985, - 0x3c8785, - 0x270489, - 0x29ee06, - 0x38024d, - 0x298b05, - 0x2bc683, - 0x226283, - 0x35c505, - 0x334285, - 0x3c5808, - 0x27b4c7, - 0x2444c6, - 0x2a5406, - 0x22a145, - 0x233087, - 0x287ec7, - 0x22b447, - 0x28e90a, - 0x3841c8, - 0x2c70c4, - 0x27f747, - 0x27d6c7, - 0x35e406, - 0x29bc87, - 0x2e9688, - 0x357b08, - 0x370346, - 0x34c448, - 0x2c0b84, - 0x2f2206, - 0x37ff46, - 0x3bbc46, - 0x204506, - 0x2a3044, - 0x280a06, - 0x2c95c6, - 0x29f1c6, - 0x245806, - 0x3d0d46, - 0x2e94c6, - 0x2443c8, - 0x2bb748, - 0x2dfcc8, - 0x307588, - 0x3c4ec6, - 0x206a05, - 0x21b786, - 0x2b4a05, - 0x393907, - 0x29d485, - 0x20d743, - 0x226305, - 0x2ed004, - 0x3d0e85, - 0x202943, - 0x395407, - 0x36c188, - 0x3ae006, - 0x37e88d, - 0x279d86, - 0x29e785, - 0x205243, - 0x2c65c9, - 0x281506, - 0x299486, - 0x292104, - 0x2e4347, - 0x35bf06, - 0x244945, - 0x23a8c3, - 0x206284, - 0x27d886, - 0x35ad44, - 0x26e588, - 0x3c7989, - 0x2bdc09, - 0x2a6cca, - 0x2a914d, - 0x361a87, - 0x3ba086, - 0x2afb04, - 0x2806c9, - 0x285b48, - 0x28ad06, - 0x234e86, - 0x29bc87, - 0x2c74c6, - 0x2261c6, - 0x287346, - 0x3cfd0a, - 0x21d988, - 0x265085, - 0x295e09, - 0x2d528a, - 0x30b048, - 0x2a34c8, - 0x299408, - 0x2b0d0c, - 0x34da05, - 0x2a5688, - 0x2bba46, - 0x372046, - 0x3db607, - 0x3802c5, - 0x283305, - 0x2bdac9, - 0x20d447, - 0x3de385, - 0x2283c7, - 0x226283, - 0x2d5745, - 0x2273c8, - 0x2d6d07, - 0x2a3389, - 0x2e2d45, - 0x380fc4, - 0x2a8e08, - 0x2c1e87, - 0x2dc988, - 0x20d188, - 0x2b1a85, - 0x20c206, - 0x2a5506, - 0x2dee09, - 0x380047, - 0x2b52c6, - 0x3de007, - 0x2027c3, - 0x21f844, - 0x2da0c5, - 0x2331c4, - 0x246744, - 0x389507, - 0x2664c7, - 0x27ad44, - 0x2a31d0, - 0x296007, - 0x3c8785, - 0x30394c, - 0x20cf44, - 0x2b9a08, - 0x27b989, - 0x3bcb46, - 0x302a48, - 0x267884, - 0x277c08, - 0x22fbc6, - 0x264248, - 0x2a4186, - 0x28ba4b, - 0x32b105, - 0x2d9f48, - 0x211b44, - 0x281e8a, - 0x2a3389, - 0x3680c6, - 0x2bbc48, - 0x259345, - 0x2c5bc4, - 0x2b9906, - 0x22b308, - 0x281788, - 0x32dc86, - 0x384404, - 0x2cfd06, - 0x26c347, - 0x279387, - 0x29bc8f, - 0x339e47, - 0x2417c7, - 0x372245, - 0x372ac5, - 0x2a8249, - 0x2ead06, - 0x389745, - 0x282e87, - 0x3db888, - 0x300805, - 0x3681c6, - 0x221008, - 0x2f2f8a, - 0x22b008, - 0x28e347, - 0x2fcfc6, - 0x295dc6, - 0x2003c3, - 0x212503, - 0x2d5449, - 0x2a7609, - 0x2b9806, - 0x2e2d45, - 0x2b0b88, - 0x2bbc48, - 0x36b888, - 0x2873cb, - 0x37eac7, - 0x31bdc9, - 0x29bf08, - 0x34f104, - 0x3bb888, - 0x28fe49, - 0x2b55c5, - 0x3c2047, - 0x21f8c5, - 0x281688, - 0x29324b, - 0x29d5d0, - 0x2b0545, - 0x211a8c, - 0x244685, - 0x27ea03, - 0x2bf706, - 0x2c8b04, - 0x368486, - 0x2a3bc7, - 0x202804, - 0x242808, - 0x34354d, - 0x318845, - 0x2a2cc4, - 0x2ae044, - 0x2b3e49, - 0x2b2308, - 0x32b5c7, - 0x22fc48, - 0x2821c8, - 0x27ae85, - 0x2d0e87, - 0x27ae07, - 0x2cd307, - 0x26a709, - 0x2879c9, - 0x3dfb46, - 0x301006, - 0x282f46, - 0x322605, - 0x3b8a04, - 0x3c4206, - 0x3c7086, - 0x27aec8, - 0x215c4b, - 0x3563c7, - 0x2afb04, - 0x35be46, - 0x2e99c7, - 0x36f805, - 0x2971c5, - 0x2ae204, - 0x287946, - 0x3c4288, - 0x2806c9, - 0x24bb46, - 0x285948, - 0x244a06, - 0x360a48, - 0x321a0c, - 0x27ad46, - 0x29e44d, - 0x29e8cb, - 0x2a48c5, - 0x288007, - 0x2c0c06, - 0x390388, - 0x3dfbc9, - 0x2ee4c8, - 0x3c8785, - 0x39b947, - 0x280d08, - 0x23b4c9, - 0x35bb86, - 0x25138a, - 0x390108, - 0x2ee30b, - 0x21dc0c, - 0x277d08, - 0x27cc06, - 0x2d0888, - 0x2f2c07, - 0x33a409, - 0x35024d, - 0x29ed06, - 0x2250c8, - 0x2bb609, - 0x2c71c8, - 0x283fc8, - 0x2c9ecc, - 0x2cab87, - 0x2cb7c7, - 0x26a545, - 0x2bdf87, - 0x3db748, - 0x2b9986, - 0x24b9cc, - 0x300288, - 0x2db2c8, - 0x307846, - 0x2af0c7, - 0x3dfd44, - 0x307588, - 0x2cf84c, - 0x28538c, - 0x3d9ec5, - 0x3c2e07, - 0x384386, - 0x2af046, - 0x24a948, - 0x21d284, - 0x34bb8b, - 0x27a34b, - 0x2fcfc6, - 0x3433c7, - 0x343ec5, - 0x272605, - 0x34bcc6, - 0x259305, - 0x3449c5, - 0x2d4287, - 0x20e989, - 0x269f04, - 0x25a2c5, - 0x2f6d85, - 0x35aac8, - 0x28d785, - 0x2cf209, - 0x2badc7, - 0x2badcb, - 0x2fbbc6, - 0x244109, - 0x3b65c8, - 0x290185, - 0x2cd408, - 0x287a08, - 0x253047, - 0x2b4247, - 0x389589, - 0x264187, - 0x29d389, - 0x2da70c, - 0x341088, - 0x2c0649, - 0x2c2f87, - 0x282289, - 0x33c3c7, - 0x21dd08, - 0x33a345, - 0x2f2186, - 0x2ca548, - 0x217488, - 0x2d5149, - 0x344a07, - 0x273005, - 0x3c3909, - 0x2f42c6, - 0x292a04, - 0x37b446, - 0x2af448, - 0x320807, - 0x215e48, - 0x34c509, - 0x33be47, - 0x2a4cc6, - 0x2880c4, - 0x226389, - 0x2d0d08, - 0x307707, - 0x367a06, - 0x215b86, - 0x33a784, - 0x34c706, - 0x239f83, - 0x32ac89, - 0x32b0c6, - 0x2a3705, - 0x2a5406, - 0x2d95c5, - 0x281188, - 0x347207, - 0x2306c6, - 0x287206, - 0x330f08, - 0x2a83c7, - 0x29ed45, - 0x2a2fc8, - 0x3aa048, - 0x390108, - 0x244545, - 0x2f2206, - 0x2bd9c9, - 0x2dec84, - 0x2d944b, - 0x225ecb, - 0x264f89, - 0x226283, - 0x257845, - 0x3ae4c6, - 0x246508, - 0x306ec4, - 0x3ae006, - 0x28ea49, - 0x2c93c5, - 0x2d41c6, - 0x2c1e86, - 0x222dc4, - 0x29958a, - 0x2a3648, - 0x217486, - 0x2cd045, - 0x343d47, - 0x3344c7, - 0x20c204, - 0x226107, - 0x2ba244, - 0x34ce46, - 0x210b43, - 0x26a705, - 0x2b6e45, - 0x23ef88, - 0x27f905, - 0x27aa89, - 0x2a9fc7, - 0x3073cb, - 0x2a9fcc, - 0x2aa5ca, - 0x350107, - 0x203d03, - 0x278808, - 0x244705, - 0x300885, - 0x359904, - 0x21dc06, - 0x27b986, - 0x34c747, - 0x23a80b, - 0x2a3044, - 0x355f44, - 0x2d4444, - 0x2d8ec6, - 0x202804, - 0x222c48, - 0x359705, - 0x21c145, - 0x36b7c7, - 0x288109, - 0x334285, - 0x39110a, - 0x3db9c9, - 0x2b174a, - 0x3cfe49, - 0x3545c4, - 0x31dc05, - 0x2c75c8, - 0x3a34cb, - 0x2dea45, - 0x24c386, - 0x241304, - 0x27afc6, - 0x33bcc9, - 0x2e9ac7, - 0x346d88, - 0x2a94c6, - 0x26c2c7, - 0x281788, - 0x377746, - 0x3c72c4, - 0x3817c7, - 0x382e85, - 0x392987, - 0x267784, - 0x2c0b86, - 0x30ad48, - 0x29ea88, - 0x2ff987, - 0x202808, - 0x29c6c5, - 0x226004, - 0x38f3c8, - 0x202904, - 0x217345, - 0x30af44, - 0x2109c7, - 0x28bdc7, - 0x2823c8, - 0x2dcb06, - 0x27f885, - 0x27a888, - 0x246848, - 0x2a6c09, - 0x2261c6, - 0x22f648, - 0x281d0a, - 0x36f888, - 0x309745, - 0x21b986, - 0x2a7208, - 0x39ba0a, - 0x219507, - 0x285f85, - 0x292c08, - 0x270fc4, - 0x252446, - 0x2cbb48, - 0x3d0d46, - 0x334c08, - 0x2d7ac7, - 0x3c7506, - 0x2c2844, - 0x237687, - 0x2bc004, - 0x33bc87, - 0x367e0d, - 0x237cc5, - 0x2d6b0b, - 0x285606, - 0x251e88, - 0x2427c4, - 0x3c50c6, - 0x27d886, - 0x2d0bc7, - 0x29e10d, - 0x304807, - 0x2bc5c8, - 0x284145, - 0x270648, - 0x2d4a86, - 0x29c748, - 0x238606, - 0x3036c7, - 0x282749, - 0x35a587, - 0x28afc8, - 0x34a005, - 0x22a1c8, - 0x2aef85, - 0x228e05, - 0x362b45, - 0x24dec3, - 0x204584, - 0x292e05, - 0x3a2709, - 0x367906, - 0x2e9788, - 0x2c2105, - 0x2bde47, - 0x371bca, - 0x2d4109, - 0x2e524a, - 0x2dfd48, - 0x22820c, - 0x282f0d, - 0x311883, - 0x334b08, - 0x206245, - 0x2f2d46, - 0x384f06, - 0x31e585, - 0x3de109, - 0x33d2c5, - 0x27a888, - 0x258546, - 0x369706, - 0x2a8cc9, - 0x3a9007, - 0x293506, - 0x371b48, - 0x3bbb48, - 0x2ed607, - 0x2c974e, - 0x2d4cc5, - 0x23b3c5, - 0x3d0c48, - 0x271e87, - 0x200e42, - 0x2c9b84, - 0x36838a, - 0x3077c8, - 0x287b46, - 0x2a0588, - 0x2a5506, - 0x288b88, - 0x2b52c8, - 0x228dc4, - 0x2be205, - 0x668c84, - 0x668c84, - 0x668c84, - 0x20aec3, - 0x215a06, - 0x27ad46, - 0x2a458c, - 0x202dc3, - 0x267786, - 0x21a604, - 0x281488, - 0x28e885, - 0x368486, - 0x2c6d08, - 0x2e0e46, - 0x230646, - 0x3c5608, - 0x2da147, - 0x263f49, - 0x3ae60a, - 0x266644, - 0x29d485, - 0x2e4305, - 0x2d7746, - 0x361ac6, - 0x2a50c6, - 0x3dc246, - 0x264084, - 0x26408b, - 0x264a44, - 0x244285, - 0x2b3a45, - 0x288486, - 0x201b48, - 0x282dc7, - 0x32b044, - 0x25b603, - 0x270ac5, - 0x37b307, - 0x282ccb, - 0x23ee87, - 0x2c6c08, - 0x2be347, - 0x26b746, - 0x25d588, - 0x2c51cb, - 0x301506, - 0x212849, - 0x2c5345, - 0x31f203, - 0x2d41c6, - 0x2d79c8, - 0x20c2c3, - 0x24c343, - 0x281786, - 0x2a5506, - 0x3759ca, - 0x27cc45, - 0x27d6cb, - 0x2a534b, - 0x213c03, - 0x20ea43, - 0x2ba1c4, - 0x370587, - 0x277d04, - 0x281484, - 0x2bb8c4, - 0x36fb88, - 0x2ccf88, - 0x214d49, - 0x2d7588, - 0x3b2d07, - 0x245806, - 0x2e93cf, - 0x2d4e06, - 0x2df404, - 0x2ccdca, - 0x37b207, - 0x2bc106, - 0x292a49, - 0x214cc5, - 0x23f0c5, - 0x214e06, - 0x22a303, - 0x271009, - 0x21db06, - 0x34c2c9, - 0x391686, - 0x26a705, - 0x35c905, - 0x204583, - 0x3706c8, - 0x32b787, - 0x3de2c4, - 0x281308, - 0x371dc4, - 0x359006, - 0x2bf706, - 0x23d646, - 0x2d9e09, - 0x300805, - 0x29ee06, - 0x2713c9, - 0x2d3e06, - 0x2e94c6, - 0x3a14c6, - 0x22bd85, - 0x30af46, - 0x3036c4, - 0x33a345, - 0x217484, - 0x2bcf46, - 0x298ac4, - 0x2109c3, - 0x285c05, - 0x233d88, - 0x3572c7, - 0x306f49, - 0x285e88, - 0x29f751, - 0x2c1f0a, - 0x2fcf07, - 0x357e46, - 0x21a604, - 0x2ca648, - 0x2ea008, - 0x29f90a, - 0x2cefcd, - 0x2a6dc6, - 0x3c5706, - 0x237746, - 0x21c147, - 0x2bc685, - 0x286c47, - 0x2813c5, - 0x2baf04, - 0x3c5e46, - 0x224dc7, - 0x270d0d, - 0x2a7147, - 0x37ef48, - 0x27ab89, - 0x21b886, - 0x35bb05, - 0x23c2c4, - 0x2af546, - 0x20c106, - 0x307946, - 0x2a0e08, - 0x21ad83, - 0x2465c3, - 0x349b05, - 0x31ec06, - 0x2b5285, - 0x2a96c8, - 0x2a3d8a, - 0x347344, - 0x281488, - 0x299408, - 0x2882c7, - 0x286589, - 0x2c6908, - 0x280747, - 0x2bbb46, - 0x3d0d4a, - 0x2af5c8, - 0x30c989, - 0x2b23c8, - 0x21ef89, - 0x357d07, - 0x312785, - 0x2a5906, - 0x2b9808, - 0x252008, - 0x224508, - 0x222dc8, - 0x244285, - 0x200d04, - 0x232708, - 0x241084, - 0x3cfc44, - 0x26a705, - 0x2949c7, - 0x287ec9, - 0x2d09c7, - 0x20f8c5, - 0x277b06, - 0x36f1c6, - 0x201c44, - 0x2a9006, - 0x27db04, - 0x291746, - 0x287c86, - 0x212e86, - 0x3c8785, - 0x2a9587, - 0x203d03, - 0x211609, - 0x330d08, - 0x2805c4, - 0x2805cd, - 0x29eb88, - 0x32a608, - 0x30c906, - 0x282849, - 0x2d4109, - 0x33b9c5, - 0x2a3e8a, - 0x291aca, - 0x2b444c, - 0x2b45c6, - 0x278406, - 0x2d5686, - 0x38ce89, - 0x2f2f86, - 0x21dd86, - 0x33d386, - 0x307588, - 0x202806, - 0x2de54b, - 0x294b45, - 0x21c145, - 0x279485, - 0x203e06, - 0x226043, - 0x23d5c6, - 0x2a70c7, - 0x2ca505, - 0x2d1c05, - 0x3b1385, - 0x310686, - 0x32fa44, - 0x32fa46, - 0x2ab289, - 0x203c8c, - 0x2bac48, - 0x22b284, - 0x30ac46, - 0x285706, - 0x2d79c8, - 0x2bbc48, - 0x203b89, - 0x343d47, - 0x356589, - 0x2726c6, - 0x22c144, - 0x2082c4, - 0x27f6c4, - 0x281788, - 0x287d0a, - 0x334206, - 0x3677c7, - 0x392c07, - 0x244205, - 0x36c944, - 0x28fe06, - 0x2bc6c6, - 0x21d2c3, - 0x330b47, - 0x20d088, - 0x33bb0a, - 0x22be48, - 0x20ad88, - 0x298b05, - 0x2a49c5, - 0x3564c5, - 0x2445c6, - 0x247486, - 0x33c885, - 0x32aec9, - 0x36c74c, - 0x2f4ec7, - 0x29f988, - 0x2521c5, - 0x668c84, - 0x265444, - 0x2d6e44, - 0x218706, - 0x2a650e, - 0x23f147, - 0x21c345, - 0x2dec0c, - 0x310b07, - 0x224d47, - 0x226fc9, - 0x219e49, - 0x285f85, - 0x330d08, - 0x2bd9c9, - 0x38ffc5, - 0x2ca448, - 0x2c0886, - 0x38f646, - 0x202c44, - 0x28ee48, - 0x21ba43, - 0x215604, - 0x270b45, - 0x396f07, - 0x2c2605, - 0x281bc9, - 0x2a118d, - 0x2b3046, - 0x3df784, - 0x3b9d08, - 0x20e7ca, - 0x21c847, - 0x367d45, - 0x215643, - 0x2a550e, - 0x3707cc, - 0x30b147, - 0x2a66c7, - 0x443960c7, - 0xaf286, - 0x647c4, - 0x203083, - 0x2f2fc5, - 0x2d6e45, - 0x2a0948, - 0x29dc09, - 0x22b186, - 0x277d04, - 0x2fce46, - 0x331bcb, - 0x2e844c, - 0x24df87, - 0x2de805, - 0x3a9f48, - 0x2ed3c5, - 0x2ccdc7, - 0x2f4cc7, - 0x245fc5, - 0x226043, - 0x20c584, - 0x2e4205, - 0x269e05, - 0x269e06, - 0x2a9dc8, - 0x224dc7, - 0x385206, - 0x33a686, - 0x362a86, - 0x225249, - 0x2d0f87, - 0x250f86, - 0x2e85c6, - 0x276d06, - 0x2b0ac5, - 0x20a586, - 0x39b0c5, - 0x28d808, - 0x29428b, - 0x28fb46, - 0x392c44, - 0x304e49, - 0x2a9fc4, - 0x2c0808, - 0x30e907, - 0x283ec4, - 0x2c5dc8, - 0x2cb5c4, - 0x2b0b04, - 0x274785, - 0x318886, - 0x36fac7, - 0x23db43, - 0x2a4d85, - 0x2fd1c4, - 0x23b406, - 0x33ba48, - 0x202705, - 0x293f49, - 0x350105, - 0x267788, - 0x21a4c7, - 0x32b1c8, - 0x2c5a07, - 0x241889, - 0x280886, - 0x33e906, - 0x2a78c4, - 0x355e85, - 0x31320c, - 0x279487, - 0x279c87, - 0x361708, - 0x2b3046, - 0x2a7004, - 0x34b0c4, - 0x389409, - 0x2d5786, - 0x270507, - 0x2d0804, - 0x326b06, - 0x38a805, - 0x2dc647, - 0x2de4c6, - 0x251249, - 0x2f0387, - 0x29bc87, - 0x2a8b46, - 0x326a45, - 0x27f408, - 0x21d988, - 0x245a06, - 0x202745, - 0x2cca86, - 0x20af03, - 0x2a07c9, - 0x2a4e4e, - 0x2c5748, - 0x371ec8, - 0x24580b, - 0x294186, - 0x385544, - 0x230644, - 0x2a4f4a, - 0x211987, - 0x251045, - 0x212849, - 0x2c9685, - 0x3cfc87, - 0x2305c4, - 0x3c7b07, - 0x318988, - 0x2d4906, - 0x2c0d09, - 0x2c6a0a, - 0x211906, - 0x29e6c6, - 0x2b39c5, - 0x398545, - 0x36ac07, - 0x245608, - 0x38a748, - 0x228dc6, - 0x35c985, - 0x36184e, - 0x2c70c4, - 0x245985, - 0x277489, - 0x2eab08, - 0x28e286, - 0x2a2acc, - 0x2a3990, - 0x2a614f, - 0x2a8148, - 0x350107, - 0x3c8785, - 0x292e05, - 0x36f949, - 0x292e09, - 0x2cfe06, - 0x2deac7, - 0x355d85, - 0x237c49, - 0x35e486, - 0x2f2dcd, - 0x27f589, - 0x281484, - 0x2c54c8, - 0x2327c9, - 0x3343c6, - 0x278a05, - 0x33e906, - 0x346c49, - 0x2d0688, - 0x206a05, - 0x281e04, - 0x2a2c8b, - 0x334285, - 0x246586, - 0x283246, - 0x3b4246, - 0x2875cb, - 0x294049, - 0x33a5c5, - 0x393807, - 0x2c1e86, - 0x231f46, - 0x281a88, - 0x224f09, - 0x37ed0c, - 0x37b108, - 0x31aac6, - 0x32dc83, - 0x22f306, - 0x241745, - 0x27e208, - 0x3d9d46, - 0x2dc888, - 0x380445, - 0x291805, - 0x21a608, - 0x3bba07, - 0x384e47, - 0x34c747, - 0x302a48, - 0x2d7848, - 0x2e9f06, - 0x2bcd87, - 0x21f707, - 0x2b3f4a, - 0x2168c3, - 0x203e06, - 0x22b3c5, - 0x213504, - 0x27ab89, - 0x241804, - 0x204844, - 0x2a4204, - 0x2a66cb, - 0x32b6c7, - 0x310645, - 0x29c3c8, - 0x277b06, - 0x277b08, - 0x27cb86, - 0x28ed85, - 0x28f045, - 0x290746, - 0x292408, - 0x292988, - 0x27ad46, - 0x29c20f, - 0x2a0290, - 0x3d0845, - 0x203d03, - 0x22c205, - 0x31bd08, - 0x292d09, - 0x390108, - 0x2de8c8, - 0x235288, - 0x32b787, - 0x2777c9, - 0x2dca88, - 0x291004, - 0x2a4088, - 0x35ab89, - 0x2bd387, - 0x34de44, - 0x2d0a88, - 0x2a934a, - 0x2fd446, - 0x2a6dc6, - 0x226089, - 0x2a3bc7, - 0x2d9c88, - 0x21fd08, - 0x33ad48, - 0x24ef85, - 0x3d3205, - 0x21c145, - 0x2d6e05, - 0x2bb447, - 0x226045, - 0x2ca505, - 0x3dde06, - 0x390047, - 0x3a3407, - 0x2a9646, - 0x2e0285, - 0x246586, - 0x241985, - 0x2c4d88, - 0x355d04, - 0x2d3e86, - 0x324604, - 0x2c5bc8, - 0x318c0a, - 0x27b4cc, - 0x23aa05, - 0x21c206, - 0x37eec6, - 0x202586, - 0x31ab44, - 0x3b9a05, - 0x27c447, - 0x2a3c49, - 0x2d9307, - 0x668c84, - 0x668c84, - 0x32b545, - 0x2dda04, - 0x2a204a, - 0x277986, - 0x2bd7c4, - 0x3c2d85, - 0x3bd405, - 0x2bc5c4, - 0x282e87, - 0x3c3a87, - 0x2d8ec8, - 0x26d5c8, - 0x206a09, - 0x372488, - 0x2a220b, - 0x2acc44, - 0x232045, - 0x3897c5, - 0x34c6c9, - 0x224f09, - 0x304d48, - 0x34cc48, - 0x288484, - 0x285745, - 0x2041c3, - 0x2d7705, - 0x29ee86, - 0x29da4c, - 0x20c006, - 0x278906, - 0x28e505, - 0x310708, - 0x2e86c6, - 0x357fc6, - 0x2a6dc6, - 0x22bbcc, - 0x389884, - 0x362bca, - 0x28e448, - 0x29d887, - 0x2fd0c6, - 0x22b247, - 0x2fca45, - 0x367a06, - 0x35ee86, - 0x372987, - 0x2c6704, - 0x210ac5, - 0x277484, - 0x2baf87, - 0x2776c8, - 0x27828a, - 0x280b87, - 0x2aa847, - 0x350087, - 0x2ed509, - 0x29da4a, - 0x22c103, - 0x357285, - 0x212ec3, - 0x2bb909, - 0x2d7c08, - 0x372247, - 0x390209, - 0x21da86, - 0x339f88, - 0x395385, - 0x24694a, - 0x343809, - 0x370209, - 0x3db607, - 0x2ea109, - 0x212d88, - 0x288d86, - 0x21c3c8, - 0x2d1f47, - 0x264187, - 0x3db9c7, - 0x2daf48, - 0x30aac6, - 0x2a9105, - 0x27c447, - 0x29e1c8, - 0x362a04, - 0x306044, - 0x293407, - 0x2b5647, - 0x2bd84a, - 0x288d06, - 0x32eeca, - 0x2c9ac7, - 0x2c6e87, - 0x210b84, - 0x29d444, - 0x2dc546, - 0x35c184, - 0x35c18c, - 0x30e845, - 0x2172c9, - 0x2ef004, - 0x2bc685, - 0x20e748, - 0x292a45, - 0x391106, - 0x292f44, - 0x2acb4a, - 0x2ef646, - 0x255fca, - 0x3d0007, - 0x215f85, - 0x22a305, - 0x24424a, - 0x292585, - 0x2a6cc6, - 0x241084, - 0x2ba346, - 0x36acc5, - 0x3d9e06, - 0x2ff98c, - 0x2e18ca, - 0x291bc4, - 0x245806, - 0x2a3bc7, - 0x2de444, - 0x307588, - 0x24c286, - 0x392a89, - 0x2c8349, - 0x341189, - 0x2d9606, - 0x2d2046, - 0x21c507, - 0x32ae08, - 0x2d1e49, - 0x32b6c7, - 0x29c546, - 0x26c347, - 0x237605, - 0x2c70c4, - 0x21c0c7, - 0x21f8c5, - 0x28a2c5, - 0x200cc7, - 0x245e88, - 0x3a9ec6, - 0x29f00d, - 0x2a0b4f, - 0x2a534d, - 0x202d84, - 0x233e86, - 0x2e1c88, - 0x33d345, - 0x2b4108, - 0x252f0a, - 0x281484, - 0x331e06, - 0x2abe07, - 0x2b7d47, - 0x2da209, - 0x21c385, - 0x2bc5c4, - 0x2be14a, - 0x2c64c9, - 0x2ea207, - 0x30b706, - 0x3343c6, - 0x285686, - 0x381886, - 0x2e158f, - 0x2e1b49, - 0x202806, - 0x389046, - 0x297e89, - 0x2bce87, - 0x2146c3, - 0x22bd46, - 0x212503, - 0x31e448, - 0x26c187, - 0x2a8349, - 0x2bf588, - 0x384f88, - 0x33c506, - 0x20bf49, - 0x2f4e05, - 0x22f244, - 0x312847, - 0x38cf05, - 0x202d84, - 0x361b48, - 0x211c44, - 0x2bcbc7, - 0x36c106, - 0x263645, - 0x2b23c8, - 0x33428b, - 0x331207, - 0x2444c6, - 0x2d4e84, - 0x3854c6, - 0x26a705, - 0x21f8c5, - 0x27f189, - 0x282a89, - 0x2641c4, - 0x264205, - 0x245845, - 0x2467c6, - 0x330e08, - 0x2c8e86, - 0x20cecb, - 0x3bc9ca, - 0x2c5b05, - 0x28f0c6, - 0x23eb85, - 0x24a285, - 0x292647, - 0x204088, - 0x292484, - 0x37fb46, - 0x292a06, - 0x212f47, - 0x31f1c4, - 0x27d886, - 0x3c2245, - 0x3c2249, - 0x2d2244, - 0x36cac9, - 0x27ad46, - 0x2cac48, - 0x245845, - 0x392d05, - 0x3d9e06, - 0x37ec09, - 0x219e49, - 0x278986, - 0x2eac08, - 0x2a12c8, - 0x23eb44, - 0x2be9c4, - 0x2be9c8, - 0x3b2b88, - 0x356689, - 0x29ee06, - 0x2a6dc6, - 0x33384d, - 0x3ae006, - 0x3218c9, - 0x268945, - 0x214e06, - 0x33aec8, - 0x32f985, - 0x21f744, - 0x26a705, - 0x2825c8, - 0x2a1e09, - 0x277544, - 0x2c0b86, - 0x30cb4a, - 0x30b048, - 0x2bd9c9, - 0x26ad4a, - 0x390186, - 0x2a0d08, - 0x2ccb85, - 0x2f0988, - 0x2fcac5, - 0x21d949, - 0x336449, - 0x20c642, - 0x2c5345, - 0x272346, - 0x27ac87, - 0x213505, - 0x3469c6, - 0x316908, - 0x2b3046, - 0x2c7489, - 0x279d86, - 0x281908, - 0x278d45, - 0x38e4c6, - 0x3037c8, - 0x281788, - 0x357c08, - 0x318488, - 0x20a584, - 0x20c243, - 0x2c76c4, - 0x280d86, - 0x237644, - 0x371e07, - 0x357ec9, - 0x2d4445, - 0x21fd06, - 0x22bd46, - 0x2a9c0b, - 0x2bc046, - 0x298d46, - 0x2d3f88, - 0x264a46, - 0x215d83, - 0x208503, - 0x2c70c4, - 0x22f545, - 0x244847, - 0x2776c8, - 0x2776cf, - 0x27c34b, - 0x330c08, - 0x2c0c06, - 0x330f0e, - 0x23b443, - 0x2447c4, - 0x2bbfc5, - 0x2bc446, - 0x28ff0b, - 0x294a86, - 0x221089, - 0x263645, - 0x23da88, - 0x3d29c8, - 0x219d0c, - 0x2a6706, - 0x2d7746, - 0x2e2d45, - 0x28ad88, - 0x27b4c5, - 0x34f108, - 0x2a2e4a, - 0x2a5789, - 0x668c84, - 0x2000c2, - 0x4a201242, - 0x200382, - 0x221dc4, - 0x209e82, - 0x306c44, - 0x208482, - 0x3dc3, - 0x2003c2, - 0x2090c2, - 0x9a048, - 0x29904, - 0x214a83, - 0x232dc3, - 0x308003, - 0xccc2, - 0x49582, - 0x23c803, - 0x21a3c3, - 0x242543, - 0xc782, - 0xc2c2, - 0x1b82, - 0x202703, - 0x214a83, - 0x232dc3, - 0x308003, - 0x221dc4, - 0x21a3c3, - 0x242543, - 0x241f83, - 0x2d3684, - 0x214a83, - 0x235b44, - 0x232dc3, - 0x2e3504, - 0x308003, - 0x2137c7, - 0x23c803, - 0x203dc3, - 0x31bf88, - 0x242543, - 0x27cfcb, - 0x2fdbc3, - 0x2431c6, - 0x233442, - 0x2f850b, - 0x232dc3, - 0x308003, - 0x21a3c3, - 0x242543, - 0x214a83, - 0x232dc3, - 0x308003, - 0x242543, - 0x21a103, - 0x208a03, - 0x2000c2, - 0x9a048, - 0x399c45, - 0x21f948, - 0x2e3648, - 0x201242, - 0x343045, - 0x3c8847, - 0x203c42, - 0x242a07, - 0x200382, - 0x2555c7, - 0x3742c9, - 0x26d188, - 0x33abc9, - 0x20b342, - 0x3c7387, - 0x22dd84, - 0x3c8907, - 0x3bc8c7, - 0x25ac42, - 0x23c803, - 0x202382, - 0x208482, - 0x2003c2, - 0x214282, - 0x200902, - 0x2090c2, - 0x2df885, - 0x210205, - 0x1242, - 0x32dc3, - 0x214a83, - 0x232dc3, - 0x29a643, - 0x308003, - 0x206c03, - 0x21a3c3, - 0x242543, - 0x214a83, - 0x232dc3, - 0x308003, - 0x21a3c3, - 0x242543, - 0x214a83, - 0x232dc3, - 0x308003, - 0x23c803, - 0x21a3c3, - 0x1b4103, - 0x242543, - 0xa983, - 0x101, - 0x214a83, - 0x232dc3, - 0x308003, - 0x221dc4, - 0x21bc83, - 0x21a3c3, - 0x1b4103, - 0x242543, - 0x214903, - 0x4d4ae8c6, - 0x239c3, - 0xd50c5, - 0x214a83, - 0x232dc3, - 0x308003, - 0x21a3c3, - 0x242543, - 0x201242, - 0x214a83, - 0x232dc3, - 0x308003, - 0x21a3c3, - 0x1b4103, - 0x242543, - 0x1b02, - 0x9a048, - 0x12a4c3, - 0x3dc3, - 0x1b4103, - 0x455c4, - 0x14226c4, - 0xed7c5, - 0x2000c2, - 0x393bc4, - 0x214a83, - 0x232dc3, - 0x308003, - 0x231a43, + 0x466a3e85, + 0x46a15cc4, + 0x46faaa07, + 0x4723c0c7, + 0x4768aac6, + 0x47a86b45, + 0x47e9ea47, + 0x482dd548, + 0x487da407, + 0x48adcb49, + 0x48ed9845, + 0x4931d047, + 0x49697b86, + 0x27c4b, + 0x49b47b08, + 0x22800d, + 0x25c089, + 0x279d4b, + 0x27b8cb, + 0x2afecb, + 0x39b08b, + 0x325f8b, + 0x32624b, + 0x326709, + 0x32770b, + 0x3279cb, + 0x32850b, + 0x32910a, + 0x32964a, + 0x329c4c, + 0x32e6cb, + 0x32ec0a, + 0x34228a, + 0x34d34e, + 0x34e94e, + 0x34ecca, + 0x350b0a, + 0x351b4b, + 0x351e0b, + 0x35290b, + 0x372ecb, + 0x3734ca, + 0x37418b, + 0x37444a, + 0x3746ca, + 0x37494a, + 0x394a0b, + 0x39bbcb, + 0x39ed4e, + 0x39f0cb, + 0x3a65cb, + 0x3a73cb, + 0x3ab74a, + 0x3ab9c9, + 0x3abc0a, + 0x3ad9ca, + 0x3c514b, + 0x3d00cb, + 0x3d0aca, + 0x3d170b, + 0x3d7a4b, + 0x3e07cb, + 0x49e89188, + 0x4a290209, + 0x4a6a7249, + 0x4aaefcc8, + 0x35f145, + 0x204083, + 0x251f44, + 0x34e385, + 0x34d7c6, + 0x367645, + 0x28f384, + 0x3450c8, + 0x31f645, + 0x299784, + 0x203787, + 0x2a634a, + 0x37738a, + 0x365607, + 0x26b0c7, + 0x2e7ec7, + 0x288047, + 0x33a405, + 0x20e506, + 0x2f34c7, + 0x20fd84, + 0x3ba146, + 0x3ba046, + 0x3dccc5, + 0x389dc4, + 0x29ffc6, + 0x2a5407, + 0x2671c6, + 0x31a487, + 0x235e43, + 0x3a2246, + 0x238d85, + 0x287b87, + 0x26fe0a, + 0x237784, + 0x2219c8, + 0x39a2c9, + 0x2d6b87, + 0x3bba06, + 0x203f48, + 0x2f4989, + 0x3a2084, + 0x2d2a04, + 0x313005, + 0x21e388, + 0x2d6e47, + 0x2b7689, + 0x3690c8, + 0x31b8c6, + 0x266cc6, + 0x2a0b88, + 0x371c86, + 0x25ef05, + 0x28ab86, + 0x281f48, + 0x2870c6, + 0x255f0b, + 0x2be206, + 0x2a280d, + 0x205385, + 0x2b4286, + 0x21f585, + 0x2bc949, + 0x2e0cc7, + 0x3cd248, + 0x39dec6, + 0x2a1949, + 0x2c1246, + 0x26fd85, + 0x2a9606, + 0x2d5506, + 0x2db549, + 0x2c8186, + 0x2a6047, + 0x2d5bc5, + 0x208a43, 0x22d805, - 0x21bc83, - 0x21a8c3, - 0x21a3c3, - 0x24e283, - 0x242543, - 0x20e2c3, - 0x266603, - 0x207783, - 0x5c2, - 0x2aec2, - 0x214a83, - 0x232dc3, - 0x308003, - 0x21a3c3, - 0x242543, - 0x2000c2, - 0x202703, - 0x201242, - 0x2a42, - 0x232dc3, - 0x308003, - 0x221dc4, - 0x21a3c3, - 0x242543, - 0x2090c2, - 0x9a048, - 0x308003, - 0x1b4103, - 0x9a048, - 0x1b4103, - 0x26f6c3, - 0x214a83, - 0x22fe44, - 0x232dc3, - 0x308003, - 0x206182, - 0x23c803, - 0x21a3c3, - 0x3dc3, - 0x242543, - 0x214a83, - 0x232dc3, - 0x308003, - 0x206182, - 0x2137c3, - 0x21a3c3, - 0x242543, - 0x2f7103, - 0x20e2c3, - 0x2000c2, - 0x201242, - 0x308003, - 0x21a3c3, - 0x242543, - 0x2431c5, - 0x14fc86, - 0x2d3684, - 0x233442, - 0x882, - 0x9a048, - 0x2a42, - 0x49582, - 0x2982, - 0x2000c2, - 0x139b05, - 0x1ce08, - 0x991c3, - 0x201242, - 0x3c604, - 0x51c6c486, - 0x8d04, - 0x10fe4b, - 0x34ac6, - 0xd1407, - 0x12eb09, - 0x232dc3, - 0x48248, - 0x4824b, - 0x486cb, - 0x48d4b, - 0x4908b, - 0x4934b, - 0x4978b, - 0x1d2f06, - 0x308003, - 0xf3605, - 0x68a44, - 0x210983, - 0x1182c7, - 0xe6d44, - 0x6cbc4, - 0x21a3c3, - 0x78a86, - 0x5684, - 0x1b4103, - 0x242543, - 0x2fe7c4, - 0x12bcc7, - 0x14f889, - 0x10fc08, - 0x1b44c4, - 0x1c8a44, - 0x365c6, - 0xa788, - 0x16a605, - 0x8649, - 0x2fb03, - 0x139b05, - 0x201242, - 0x214a83, - 0x232dc3, - 0x308003, - 0x23c803, - 0x203dc3, - 0x242543, - 0x2fdbc3, - 0x233442, - 0x9a048, - 0x214a83, - 0x232dc3, - 0x308003, - 0x21bac3, - 0x219a04, - 0x21a3c3, - 0x3dc3, - 0x242543, - 0x214a83, - 0x232dc3, - 0x2e3504, - 0x308003, - 0x21a3c3, - 0x242543, - 0x2431c6, - 0x232dc3, - 0x308003, - 0x10e83, - 0x1b4103, - 0x242543, - 0x214a83, - 0x232dc3, - 0x308003, - 0x21a3c3, - 0x242543, - 0x139b05, - 0xd1407, - 0x3103, - 0x2fb03, - 0x9a048, - 0x308003, - 0x214a83, - 0x232dc3, - 0x308003, - 0x5cf43, - 0x21a3c3, - 0x242543, - 0x55214a83, - 0x232dc3, - 0x21a3c3, - 0x242543, - 0x9a048, - 0x2000c2, - 0x201242, - 0x214a83, - 0x308003, - 0x21a3c3, - 0x2003c2, - 0x242543, - 0x336987, - 0x2cd68b, - 0x20dcc3, - 0x2cfac8, - 0x32ab87, - 0x3de706, - 0x218c05, - 0x343189, - 0x242f48, - 0x3359c9, - 0x3359d0, - 0x37ab8b, - 0x2e7589, - 0x203103, - 0x2f8bc9, - 0x231506, - 0x23150c, - 0x335bc8, - 0x3db448, - 0x374789, - 0x2c368e, - 0x37408b, - 0x2ba58c, - 0x220dc3, - 0x28c68c, - 0x3d8f49, - 0x23bb47, - 0x232d0c, - 0x2b858a, - 0x24c0c4, - 0x2ee78d, - 0x28c548, - 0x3ba7cd, - 0x3a6cc6, - 0x2d368b, - 0x34fa09, - 0x3892c7, - 0x295946, - 0x267b49, - 0x33b64a, - 0x30c108, - 0x2fd7c4, - 0x2b5a07, - 0x3c51c7, - 0x204684, - 0x223804, - 0x201fc9, - 0x286ac9, - 0x3d9ac8, - 0x398bc5, - 0x20b285, - 0x2068c6, - 0x2ee649, - 0x25318d, - 0x24c488, - 0x2067c7, - 0x218c88, - 0x2355c6, - 0x239c04, - 0x284405, - 0x3cfb46, - 0x3d1e84, - 0x3d8e47, - 0x3e094a, - 0x20d384, - 0x211846, - 0x2124c9, - 0x2124cf, - 0x212a8d, - 0x213306, - 0x21ca10, - 0x21ce06, - 0x21df07, - 0x21e987, - 0x21e98f, - 0x21f1c9, - 0x225986, - 0x227207, - 0x227208, - 0x2275c9, - 0x3ba588, - 0x305c87, - 0x20ed43, - 0x3d87c6, - 0x29d1c8, - 0x2c394a, - 0x215849, - 0x243083, - 0x342f46, - 0x37f98a, - 0x2f9f87, - 0x23b98a, - 0x31458e, - 0x21f306, - 0x338547, - 0x238886, - 0x2435c6, - 0x3d300b, - 0x39964a, - 0x2cdccd, - 0x2d2107, - 0x266688, - 0x266689, - 0x26668f, - 0x3070cc, - 0x34b789, - 0x27e48e, - 0x2138ca, - 0x216406, - 0x2f4806, - 0x3adc8c, - 0x31fdcc, - 0x320308, - 0x35a487, - 0x235185, - 0x3c8ac4, - 0x28918e, - 0x3a6744, - 0x201247, - 0x39dcca, - 0x3ab1d4, - 0x3d548f, - 0x21eb48, - 0x3d8688, - 0x378c8d, - 0x378c8e, - 0x22cd09, - 0x22e548, - 0x22e54f, - 0x232a0c, - 0x232a0f, - 0x233bc7, - 0x23714a, - 0x3087cb, - 0x239808, - 0x23b107, - 0x25c7cd, - 0x3620c6, - 0x2ee946, - 0x23d449, - 0x22ba08, - 0x2433c8, - 0x2433ce, - 0x2b7687, - 0x3043c5, - 0x245385, - 0x202404, - 0x3de9c6, - 0x3d99c8, - 0x296d83, - 0x2e710e, - 0x25cb88, - 0x2aae8b, - 0x26f887, - 0x228c05, - 0x273506, - 0x2b2b07, - 0x31b348, - 0x36a409, - 0x3cd545, - 0x285c48, - 0x2209c6, - 0x3a618a, - 0x289089, - 0x232dc9, - 0x232dcb, - 0x25d908, - 0x204549, - 0x398c86, - 0x3c5a8a, - 0x2bfe4a, - 0x23734c, - 0x3488c7, - 0x26cf8a, - 0x3b1fcb, - 0x3b1fd9, - 0x324208, - 0x243245, - 0x25c986, - 0x2a0049, - 0x39f606, - 0x219aca, - 0x26e286, - 0x2196c4, - 0x2d604d, - 0x3458c7, - 0x2196c9, - 0x247105, - 0x247ac8, - 0x248009, - 0x24b904, - 0x24bfc7, - 0x24bfc8, - 0x24cc87, - 0x265c08, - 0x250dc7, - 0x2d8b85, - 0x257e8c, - 0x258349, - 0x33144a, - 0x3a8e89, - 0x2f8cc9, - 0x388e0c, - 0x25b4cb, - 0x25c008, - 0x25d0c8, - 0x260cc4, - 0x283b88, - 0x284d09, - 0x2b8647, - 0x212706, - 0x2a43c7, - 0x29fd49, - 0x24768b, - 0x36aa87, - 0x2947c7, - 0x3d0147, - 0x3ba744, - 0x3ba745, - 0x2e3205, - 0x358b4b, - 0x33d684, - 0x323a88, - 0x30060a, - 0x220a87, - 0x3caac7, - 0x28f6d2, - 0x291646, - 0x22f7c6, - 0x28870e, - 0x29ab46, - 0x299288, - 0x29a60f, - 0x3bab88, - 0x28a808, - 0x2dd1ca, - 0x2dd1d1, - 0x2a98ce, - 0x25514a, - 0x25514c, - 0x22e747, - 0x22e750, - 0x3c7108, - 0x2a9ac5, - 0x2b2e0a, - 0x3d1ecc, - 0x29c88d, - 0x3c7c86, - 0x3c7c87, - 0x3c7c8c, - 0x3d2b8c, - 0x21bc8c, - 0x3bd6cb, - 0x38b644, - 0x226204, - 0x2b6f89, - 0x34b147, - 0x37d009, - 0x2bfc89, - 0x2b8247, - 0x2b8406, - 0x2b8409, - 0x2b8803, - 0x2b314a, - 0x2961c7, - 0x3c8e0b, - 0x2cdb4a, - 0x22de04, - 0x32f4c6, - 0x280e09, - 0x35c004, - 0x2df48a, - 0x2e0685, - 0x2c7945, - 0x2c794d, - 0x2c7c8e, - 0x2c7805, - 0x335046, - 0x242dc7, - 0x22b78a, - 0x3a6a46, - 0x37bc84, - 0x3cdb87, - 0x2fab0b, - 0x265907, - 0x262944, - 0x3a39c6, - 0x3a39cd, - 0x2e568c, - 0x21a286, - 0x24c68a, - 0x34ca86, - 0x21e648, - 0x30c487, - 0x2d3b8a, - 0x23c486, - 0x27d903, - 0x2f3886, - 0x29d048, - 0x245aca, - 0x2dcc07, - 0x2dcc08, - 0x249ac4, - 0x28fc47, - 0x2f4348, - 0x291848, - 0x2bbdc8, - 0x2ffc0a, - 0x2ec0c5, - 0x2c1ac7, - 0x254f93, - 0x26b2c6, - 0x24aec8, - 0x221589, - 0x2428c8, - 0x33c58b, - 0x385308, - 0x2c02c4, - 0x21a706, - 0x320c06, - 0x3186c9, - 0x2d39c7, - 0x257f88, - 0x2a51c6, - 0x200bc4, - 0x208405, - 0x3a3188, - 0x344e4a, - 0x2d5cc8, - 0x2dad46, - 0x2a0f0a, - 0x269f88, - 0x2de248, - 0x2df708, - 0x2dff46, - 0x2e1e86, - 0x3a4d0c, - 0x2e2410, - 0x2cc145, - 0x2230c8, - 0x2230d0, - 0x3ba990, - 0x33584e, - 0x3a498e, - 0x3a4994, - 0x3a804f, - 0x3a8406, - 0x3dbe51, - 0x345213, - 0x345688, - 0x2035c5, - 0x2d0008, - 0x3a05c5, - 0x33f34c, - 0x229f09, - 0x3a6589, - 0x356947, - 0x26c649, - 0x39f1c7, - 0x334686, - 0x284207, - 0x204c05, - 0x20a9c3, - 0x210e83, - 0x213404, - 0x36adcd, - 0x3c304f, - 0x200c05, - 0x33f246, - 0x20cb87, - 0x399a87, - 0x208886, - 0x20888b, - 0x2aa785, - 0x259fc6, - 0x30be47, - 0x251ac9, - 0x229b46, - 0x3860c5, - 0x3c910b, - 0x3d6e86, - 0x21d685, - 0x241588, - 0x290b08, - 0x299b8c, - 0x299b90, - 0x2ac2c9, - 0x2c15c7, - 0x2b918b, + 0x395c07, + 0x25fac6, + 0x205289, + 0x33ed86, + 0x281686, + 0x226049, + 0x28a589, + 0x2aa947, + 0x207648, + 0x29b149, + 0x288608, + 0x3a7646, + 0x2e5285, + 0x27dd4a, + 0x281706, + 0x347446, + 0x2deb05, + 0x253708, + 0x2f5707, + 0x23114a, + 0x24df06, + 0x2e2785, + 0x3086c6, + 0x20d647, + 0x3bb8c7, + 0x21a3c5, + 0x26ff45, + 0x26c506, + 0x273b06, + 0x2b0d46, + 0x2ccc44, + 0x289b09, + 0x291a06, + 0x306f0a, + 0x30c148, + 0x31cd48, + 0x37738a, + 0x2ef805, + 0x2a5345, + 0x3cac88, + 0x2c7e88, + 0x2398c7, + 0x36ee86, + 0x339788, + 0x20ee87, + 0x27a408, + 0x2c6806, + 0x28bac8, + 0x29de06, + 0x283847, + 0x23b3c6, + 0x29ffc6, + 0x27438a, + 0x305f86, + 0x2e5289, + 0x2a7746, + 0x22910a, + 0x2463c9, + 0x2fd9c6, + 0x2c9144, + 0x2d274d, + 0x285e07, + 0x3325c6, + 0x2d0185, + 0x2c12c5, + 0x396906, + 0x2a9b89, + 0x2c09c7, + 0x282946, + 0x2ced06, + 0x28f409, + 0x288d84, + 0x23f644, + 0x3b53c8, + 0x237ac6, + 0x2a9708, + 0x322708, + 0x3a9f87, + 0x358b89, + 0x3c9f87, + 0x2bffca, + 0x2fee8f, + 0x2b230a, + 0x3e22c5, + 0x282185, + 0x21c3c5, + 0x229747, + 0x20d203, + 0x207848, + 0x355606, + 0x355709, + 0x2f3dc6, + 0x2db387, + 0x2a1709, + 0x3cd148, + 0x2debc7, + 0x325343, + 0x35f1c5, + 0x20d185, + 0x2cca8b, + 0x248a44, + 0x238344, + 0x27d506, + 0x325507, + 0x396e8a, + 0x24bd87, + 0x298787, + 0x285fc5, + 0x3d5c05, + 0x296ac9, + 0x29ffc6, + 0x24bc0d, + 0x273445, + 0x2c3c03, + 0x2059c3, + 0x3617c5, + 0x33a085, + 0x203f48, + 0x283287, + 0x23f3c6, + 0x2a6ec6, + 0x22bbc5, + 0x234287, + 0x25eb47, + 0x252287, + 0x2ad78a, + 0x3a2308, + 0x2ccc44, + 0x286e47, + 0x285187, + 0x363306, + 0x29d487, + 0x2ebd88, + 0x3d8348, + 0x29c3c6, + 0x26b308, + 0x2c8204, + 0x2f34c6, + 0x250dc6, + 0x3d5486, + 0x208006, + 0x218e84, + 0x288106, + 0x2cf246, + 0x2a0386, + 0x24bc06, + 0x205886, + 0x2a7e46, + 0x23f2c8, + 0x2c2a48, + 0x2e1e88, + 0x367848, + 0x3cac06, + 0x210ec5, + 0x22d7c6, + 0x2b8845, + 0x399107, + 0x295d45, + 0x2119c3, + 0x2e5f45, + 0x235fc4, + 0x2059c5, + 0x202a43, + 0x3c4bc7, + 0x399d08, + 0x31a546, + 0x34490d, + 0x282146, + 0x29f945, + 0x219643, + 0x2cc149, + 0x288f06, + 0x23b1c6, + 0x3b2144, + 0x2b2287, + 0x3611c6, + 0x23f845, + 0x270483, + 0x20b344, + 0x285346, + 0x20e604, + 0x275548, + 0x204609, + 0x32e489, + 0x2a950a, + 0x29738d, + 0x23e587, + 0x3c2cc6, + 0x21dd04, + 0x287dc9, + 0x28e308, + 0x290086, + 0x23abc6, + 0x29d487, + 0x2c98c6, + 0x226c46, + 0x25dfc6, + 0x3da10a, + 0x223708, + 0x2ef705, + 0x356c09, + 0x2d75ca, + 0x30cd48, + 0x2a46c8, + 0x299fc8, + 0x2b45cc, + 0x395905, + 0x2a7148, + 0x2c2d46, + 0x2e1446, + 0x2d5707, + 0x24bc85, + 0x28ad05, + 0x32e349, + 0x214207, + 0x3556c5, + 0x2284c7, + 0x2059c3, + 0x2d7a85, + 0x224148, + 0x2d9047, + 0x2a4589, + 0x2e5145, + 0x311404, + 0x2ab1c8, + 0x2eed47, + 0x2ded88, + 0x2206c8, + 0x2b5285, + 0x21f746, + 0x2a6fc6, + 0x3c2909, + 0x250ec7, + 0x2b8cc6, + 0x355347, + 0x208683, + 0x34da84, + 0x2dc405, + 0x2343c4, + 0x24b684, + 0x38fc47, + 0x26da47, + 0x282b04, + 0x2a43d0, + 0x207bc7, + 0x3d5c05, + 0x3b3c8c, + 0x220484, + 0x31e048, + 0x283749, + 0x3d78c6, + 0x31fc48, + 0x27d804, + 0x27d808, + 0x231746, + 0x274208, + 0x2a38c6, + 0x39b90b, + 0x330685, + 0x2dc288, + 0x213684, + 0x28988a, + 0x2a4589, + 0x23b2c6, + 0x2c2f48, + 0x2592c5, + 0x2cb744, + 0x31df46, + 0x252148, + 0x289188, + 0x333e86, + 0x389f44, + 0x27dcc6, + 0x3ca007, + 0x281387, + 0x29d48f, + 0x346f07, + 0x2fda87, + 0x388ac5, + 0x377ac5, + 0x2aa609, + 0x2f7786, + 0x38fe85, + 0x28a887, + 0x2d5988, + 0x302545, + 0x23b3c6, + 0x30bf88, + 0x2f424a, + 0x37e648, + 0x293287, + 0x2ff2c6, + 0x356bc6, + 0x2003c3, + 0x20c483, + 0x2d7789, + 0x29afc9, + 0x2dca46, + 0x2e5145, + 0x2b4448, + 0x2c2f48, + 0x2a3508, + 0x25e04b, + 0x344b47, + 0x3211c9, + 0x29d708, + 0x3505c4, + 0x3d50c8, + 0x295909, + 0x2b8fc5, + 0x229647, + 0x34db05, + 0x289088, + 0x2983cb, + 0x29e790, + 0x2b3e05, + 0x2135cc, + 0x23f585, + 0x25e883, + 0x2b6486, + 0x2ce3c4, + 0x23b686, + 0x2a5407, + 0x203d44, + 0x243208, + 0x20770d, + 0x3224c5, + 0x23e5c4, + 0x2b5684, + 0x2b5689, + 0x2adfc8, + 0x330b47, + 0x2317c8, + 0x289bc8, + 0x282c45, + 0x27ee47, + 0x282bc7, + 0x3559c7, + 0x26ff49, + 0x25e649, + 0x210706, + 0x302d46, + 0x28a946, + 0x326e85, + 0x3c5d04, + 0x3cc9c6, + 0x3d4e86, + 0x282c88, + 0x20d30b, + 0x237647, + 0x21dd04, + 0x361106, + 0x2ec0c7, + 0x2a7a45, + 0x324a85, + 0x267c04, + 0x25e5c6, + 0x3cca48, + 0x287dc9, + 0x261846, + 0x28e108, + 0x23f906, + 0x365f48, + 0x37904c, + 0x282b06, + 0x29f60d, + 0x29fa8b, + 0x2a6105, + 0x25ec87, + 0x2c8286, + 0x3bb788, + 0x210789, + 0x38a7c8, + 0x3d5c05, + 0x20fac7, + 0x288708, + 0x3c7c49, + 0x360e46, + 0x26174a, + 0x3bb508, + 0x38a60b, + 0x22398c, + 0x27d908, + 0x284906, + 0x27e848, + 0x2f3ec7, + 0x347049, + 0x35150d, + 0x29fec6, + 0x30ef48, + 0x2c2909, + 0x2ccd48, + 0x28bbc8, + 0x2cfb4c, + 0x2d0807, + 0x2d31c7, + 0x26fd85, + 0x2c54c7, + 0x2d5848, + 0x31dfc6, + 0x2704cc, + 0x301fc8, + 0x2dd8c8, + 0x23ae06, + 0x2b1f07, + 0x210904, + 0x367848, + 0x28d20c, + 0x29144c, + 0x3e2345, + 0x3dcd47, + 0x389ec6, + 0x2b1e86, + 0x2bcb08, + 0x21b284, + 0x2671cb, + 0x28d94b, + 0x2ff2c6, + 0x207587, + 0x3572c5, + 0x2781c5, + 0x267306, + 0x259285, + 0x248a05, + 0x2d65c7, + 0x2b2789, + 0x273cc4, + 0x23d405, + 0x2f8ac5, + 0x358908, + 0x2bf505, + 0x2d1d09, + 0x39e2c7, + 0x39e2cb, + 0x2fd706, + 0x23f009, + 0x389d08, + 0x3ae7c5, + 0x355ac8, + 0x25e688, + 0x286407, + 0x2b5a87, + 0x38fcc9, + 0x274147, + 0x295c49, + 0x2d11cc, + 0x2dca48, + 0x2c0dc9, + 0x2c4d07, + 0x289c89, + 0x367207, + 0x223a88, + 0x358d45, + 0x2f3446, + 0x2d01c8, + 0x21c488, + 0x2d7489, + 0x248a47, + 0x278bc5, + 0x3cde49, + 0x2fde86, + 0x297b84, + 0x33ff06, + 0x26ad88, + 0x2e6587, + 0x20d508, + 0x26b3c9, + 0x3a1a87, + 0x2a3646, + 0x25ed44, + 0x2e5fc9, + 0x27ecc8, + 0x23acc7, + 0x2702c6, + 0x20d246, + 0x3473c4, + 0x26b5c6, + 0x205943, + 0x330209, + 0x330646, + 0x2a4905, + 0x2a6ec6, + 0x2db905, + 0x288b88, + 0x33f3c7, + 0x23bb46, + 0x25de86, + 0x31cd48, + 0x2aa787, + 0x29ff05, + 0x2a41c8, + 0x3b1b88, + 0x3bb508, + 0x23f445, + 0x2f34c6, + 0x32e249, + 0x3c2784, + 0x2db78b, + 0x22694b, + 0x2ef609, + 0x2059c3, + 0x257b05, + 0x2ef4c6, + 0x241f88, + 0x30a604, + 0x31a546, + 0x2ad8c9, + 0x2ce1c5, + 0x2d6506, + 0x2eed46, + 0x203f44, + 0x29a14a, + 0x2a4848, + 0x21c486, + 0x375c45, + 0x357147, + 0x33a2c7, + 0x21f744, + 0x226b87, + 0x2bffc4, + 0x369146, + 0x207883, + 0x26ff45, + 0x2ba485, + 0x25b688, + 0x287005, + 0x282849, + 0x2abc07, + 0x36768b, + 0x2abc0c, + 0x2ac20a, + 0x3513c7, + 0x203843, + 0x280d88, + 0x23f605, + 0x3025c5, + 0x35f284, + 0x223986, + 0x283746, + 0x26b607, + 0x3a9d8b, + 0x218e84, + 0x309d04, + 0x2d6784, + 0x2db206, + 0x203d44, + 0x21e488, + 0x35f085, + 0x21a245, + 0x2a3447, + 0x25ed89, + 0x33a085, + 0x39690a, + 0x2d5ac9, + 0x2aceca, + 0x3da249, + 0x354004, + 0x2cedc5, + 0x2c99c8, + 0x3aaacb, + 0x313005, + 0x2ecd46, + 0x241c04, + 0x282d86, + 0x3a1909, + 0x2ec1c7, + 0x33ef48, + 0x297706, + 0x3c9f87, + 0x289188, + 0x37c006, + 0x3d5e84, + 0x386b47, + 0x388705, + 0x398187, + 0x29f484, 0x2c8206, - 0x305b4a, - 0x36ff8b, - 0x31b58a, - 0x398806, - 0x2f6fc5, - 0x32aa86, - 0x279f48, - 0x356a0a, - 0x37891c, - 0x2fdc8c, - 0x2fdf88, - 0x2431c5, - 0x382747, - 0x24a006, - 0x24aac5, - 0x2177c6, - 0x208a48, - 0x2c6747, - 0x2c3588, - 0x26b38a, - 0x3b954c, - 0x297009, - 0x3b97c7, - 0x285244, - 0x245446, - 0x28a38a, - 0x2bfd85, - 0x22348c, - 0x223b48, - 0x26a208, - 0x2aebcc, - 0x38878c, - 0x22d949, - 0x22db87, - 0x25494c, - 0x3082c4, - 0x3713ca, - 0x30de8c, - 0x24fdcb, - 0x25044b, - 0x252b06, - 0x256447, - 0x22e987, - 0x22e98f, - 0x30f351, - 0x2e8d92, - 0x258c0d, - 0x258c0e, - 0x258f4e, - 0x3a8208, - 0x3a8212, - 0x25be08, - 0x221bc7, - 0x24ec0a, - 0x2ac988, - 0x29ab05, - 0x2bb28a, - 0x21d187, - 0x2ef184, - 0x210883, - 0x236085, - 0x2dd447, - 0x30d3c7, - 0x29ca8e, - 0x352e8d, - 0x354009, - 0x302905, - 0x361043, - 0x34a886, - 0x25a5c5, - 0x2ab0c8, - 0x224249, - 0x25c9c5, - 0x25c9cf, - 0x2e0b07, - 0x218a85, - 0x27024a, - 0x3d0b06, - 0x312b09, - 0x381b4c, - 0x3ce889, - 0x2062c6, - 0x30040c, - 0x32dd86, - 0x30cfc8, - 0x3b1ec6, - 0x365486, - 0x2bc1c4, - 0x31d203, - 0x21124a, - 0x227e11, - 0x34b94a, - 0x23ea05, - 0x25b907, - 0x2559c7, - 0x2e6f04, - 0x2f444b, - 0x33aa48, - 0x2c55c6, - 0x361785, - 0x261f44, - 0x3828c9, - 0x2008c4, - 0x20e087, - 0x37a385, - 0x37a387, - 0x288945, - 0x3801c3, - 0x221a88, - 0x2d020a, - 0x23db43, - 0x399c8a, - 0x2a74c6, - 0x25c74f, - 0x2b7609, - 0x2e7090, - 0x305748, - 0x2db3c9, - 0x29df47, - 0x3a394f, - 0x3905c4, - 0x2e3584, - 0x203946, - 0x2344c6, - 0x2ef40a, - 0x254686, - 0x2b5e07, - 0x315048, - 0x315247, - 0x3166c7, - 0x31784a, - 0x316fcb, - 0x33bf85, - 0x2e89c8, - 0x201343, - 0x3bb3cc, - 0x3965cf, - 0x234f8d, - 0x258787, - 0x354149, - 0x357087, - 0x2401c8, - 0x3ab3cc, - 0x2c01c8, - 0x23e708, - 0x32c9ce, - 0x341b94, - 0x3420a4, - 0x36080a, - 0x37b90b, - 0x39f284, - 0x39f289, - 0x331e88, - 0x245d85, - 0x29688a, - 0x291087, - 0x2135c4, - 0x202703, - 0x214a83, - 0x235b44, - 0x232dc3, - 0x308003, - 0x221dc4, - 0x21bc83, - 0x23c803, - 0x2e2406, - 0x219a04, - 0x21a3c3, - 0x242543, - 0x2141c3, + 0x30ca48, + 0x29fc48, + 0x33dec7, + 0x3801c8, + 0x29dec5, + 0x205804, + 0x377288, + 0x3802c4, + 0x21c345, + 0x30cc44, + 0x20ef87, + 0x291ac7, + 0x289dc8, + 0x2def06, + 0x286f85, + 0x282648, + 0x37e848, + 0x2a9449, + 0x226c46, + 0x2311c8, + 0x28970a, + 0x2a7ac8, + 0x308c85, + 0x22d9c6, + 0x2a9a48, + 0x20fb8a, + 0x265587, + 0x28e745, + 0x297d88, + 0x2b3a44, + 0x253786, + 0x2d3548, + 0x205886, + 0x33aa08, + 0x2d9e07, + 0x203686, + 0x2c9144, + 0x26a4c7, + 0x2c3304, + 0x3a18c7, + 0x23b00d, + 0x239945, + 0x2d8e4b, + 0x2916c6, + 0x252f48, + 0x2431c4, + 0x3c0706, + 0x285346, + 0x27eb87, + 0x29f2cd, + 0x305587, + 0x2c3b48, + 0x28bd45, + 0x296c88, + 0x2d6dc6, + 0x29df48, + 0x38ecc6, + 0x3b3a07, + 0x28a149, + 0x35fe07, + 0x290348, + 0x34c1c5, + 0x22bc48, + 0x2b1dc5, + 0x2d6d05, + 0x37d145, + 0x24dc03, + 0x208084, + 0x297f85, + 0x3a9a89, + 0x36ec46, + 0x2ebe88, + 0x2eefc5, + 0x2c5387, + 0x2e0fca, + 0x2d6449, + 0x2d540a, + 0x2e1f08, + 0x22830c, + 0x28a90d, + 0x314e43, + 0x33a908, + 0x20b305, + 0x2f4006, + 0x3ccfc6, + 0x2d2405, + 0x355449, + 0x348ec5, + 0x282648, + 0x258946, + 0x370286, + 0x2ab089, + 0x3b0b47, + 0x298686, + 0x2e0f48, + 0x3d5388, + 0x2efec7, + 0x2cf3ce, + 0x2d7005, + 0x3c7b45, + 0x205788, + 0x36f947, + 0x20d282, + 0x2cf804, + 0x23b58a, + 0x23ad88, + 0x25e7c6, + 0x2a1848, + 0x2a6fc6, + 0x25f708, + 0x2b8cc8, + 0x30b3c4, + 0x2c5745, + 0x602284, + 0x602284, + 0x602284, + 0x207783, + 0x20d0c6, + 0x282b06, + 0x2a5dcc, + 0x202503, + 0x2d75c6, + 0x207844, + 0x288e88, + 0x2ad705, + 0x23b686, + 0x2cc888, + 0x2e3246, + 0x23bac6, + 0x203d48, + 0x2dc487, + 0x273f09, + 0x3df7ca, + 0x26dbc4, + 0x295d45, + 0x2b7645, + 0x2d9a86, + 0x23e5c6, + 0x2a5b46, + 0x3d3f06, + 0x274044, + 0x27404b, + 0x266cc4, + 0x23f185, + 0x2b7cc5, + 0x3aa046, + 0x209648, + 0x28a7c7, + 0x3305c4, + 0x213c03, + 0x2b3545, + 0x33fdc7, + 0x28a6cb, + 0x25b587, + 0x2cc788, + 0x2c5887, + 0x2715c6, + 0x25c348, + 0x2cad4b, + 0x34e2c6, + 0x214a09, + 0x2caec5, + 0x325343, + 0x2d6506, + 0x2d9d08, + 0x215203, + 0x2a11c3, + 0x289186, + 0x2a6fc6, + 0x379eca, + 0x284945, + 0x28518b, + 0x2a6e0b, + 0x2163c3, + 0x206743, + 0x2bff44, + 0x2e0e07, + 0x27d904, + 0x25ef44, + 0x2c2bc4, + 0x2a7dc8, + 0x375b88, + 0x20c409, + 0x2d98c8, + 0x37d3c7, + 0x24bc06, + 0x2ebacf, + 0x2d7146, + 0x2e15c4, + 0x3759ca, + 0x33fcc7, + 0x2c3406, + 0x297bc9, + 0x20c385, + 0x25b7c5, + 0x20c4c6, + 0x22bd83, + 0x2b3a89, + 0x223886, + 0x26b189, + 0x396e86, + 0x26ff45, + 0x361bc5, + 0x206643, + 0x3131c8, + 0x330d07, + 0x355604, + 0x288d08, + 0x2e11c4, + 0x31c046, + 0x2b6486, + 0x23d846, + 0x2dc149, + 0x302545, + 0x29ffc6, + 0x277389, + 0x2d6146, + 0x2a7e46, + 0x3a8b46, + 0x22e405, + 0x30cc46, + 0x3b3a04, + 0x358d45, + 0x21c484, + 0x2c45c6, + 0x273404, + 0x207a43, + 0x28e3c5, + 0x234f88, + 0x366a47, + 0x30a689, + 0x28e648, + 0x2a0951, + 0x2eedca, + 0x2ff207, + 0x3d8686, + 0x207844, + 0x2d02c8, + 0x2e2e88, + 0x2a0b0a, + 0x2d1acd, + 0x2a9606, + 0x203e46, + 0x26a586, + 0x21a247, + 0x2c3c05, + 0x35c6c7, + 0x207705, + 0x39e404, + 0x206686, + 0x30ec47, + 0x2b378d, + 0x2a9987, + 0x344fc8, + 0x282949, + 0x22d8c6, + 0x360dc5, + 0x2393c4, + 0x26ae86, + 0x21f646, + 0x23af06, + 0x2a20c8, + 0x22cdc3, + 0x23e443, + 0x34bcc5, + 0x2d2a86, + 0x2b8c85, + 0x297908, + 0x2a55ca, + 0x33f504, + 0x288e88, + 0x299fc8, + 0x25ef47, + 0x28ed49, + 0x2cc488, + 0x287e47, + 0x2c2e46, + 0x20588a, + 0x26af08, + 0x32df09, + 0x2ae088, + 0x224a09, + 0x3d8547, + 0x35ce45, + 0x2a73c6, + 0x31de48, + 0x2530c8, + 0x2bbfc8, + 0x21e608, + 0x23f185, + 0x200d04, + 0x233908, + 0x241984, + 0x3da044, + 0x26ff45, + 0x2997c7, + 0x25eb49, + 0x27e987, + 0x2260c5, + 0x27d706, + 0x375446, + 0x209744, + 0x2ab3c6, + 0x2855c4, + 0x293ec6, + 0x25e906, + 0x215046, + 0x3d5c05, + 0x2977c7, + 0x203843, + 0x22b509, + 0x31cb48, + 0x287cc4, + 0x287ccd, + 0x29fd48, + 0x2fcd48, + 0x32de86, + 0x28a249, + 0x2d6449, + 0x3a1605, + 0x2a56ca, + 0x2a844a, + 0x2b5c8c, + 0x2b5e06, + 0x280986, + 0x2d79c6, + 0x393189, + 0x2f4246, + 0x223b06, + 0x348f86, + 0x367848, + 0x37e646, + 0x2e094b, + 0x299945, + 0x21a245, + 0x281485, + 0x3b5146, + 0x205843, + 0x23d7c6, + 0x2a9907, + 0x2d0185, + 0x27fbc5, + 0x2c12c5, + 0x301c46, + 0x336144, + 0x336146, + 0x2a9e49, + 0x3b4fcc, + 0x39e148, + 0x2520c4, + 0x30c946, + 0x2917c6, + 0x2d9d08, + 0x2c2f48, + 0x3b4ec9, + 0x357147, + 0x237809, + 0x278286, + 0x22c544, + 0x20af04, + 0x286dc4, + 0x289188, + 0x25e98a, + 0x33a006, + 0x36eb07, + 0x398407, + 0x23f105, + 0x2b7604, + 0x2958c6, + 0x2c3c46, + 0x21b2c3, + 0x31c987, + 0x2205c8, + 0x3a174a, + 0x22e4c8, + 0x34df48, + 0x273445, + 0x2a6205, + 0x237745, + 0x23f4c6, + 0x242546, + 0x25d405, + 0x330449, + 0x2b740c, + 0x307d87, + 0x2a0b88, + 0x251045, + 0x602284, + 0x267c84, + 0x2d9184, + 0x212d06, + 0x2a8d0e, + 0x25b847, + 0x21a445, + 0x3c270c, + 0x3d2347, + 0x30ebc7, + 0x30f7c9, + 0x221a89, + 0x28e745, + 0x31cb48, + 0x32e249, + 0x3bb3c5, + 0x2d00c8, + 0x2c1006, + 0x377506, + 0x2463c4, + 0x294908, + 0x204883, + 0x20ccc4, + 0x2b35c5, + 0x39db87, + 0x2e5e45, + 0x2895c9, + 0x29664d, + 0x2af506, + 0x213c44, + 0x36ee08, + 0x2b25ca, + 0x2144c7, + 0x34bb05, + 0x20cd03, + 0x2a6fce, + 0x3132cc, + 0x30ce47, + 0x2a8ec7, + 0x4539cd47, + 0xb20c6, + 0x27c44, + 0x215d03, + 0x2f4285, + 0x2d9185, + 0x2a1c08, + 0x29edc9, + 0x251fc6, + 0x27d904, + 0x2ff146, + 0x2398cb, + 0x2eab4c, + 0x24dcc7, + 0x2e0c05, + 0x3b1a88, + 0x2efc85, + 0x3759c7, + 0x307b87, + 0x2475c5, + 0x205843, + 0x21fac4, + 0x2e6445, + 0x273bc5, + 0x273bc6, + 0x2a2608, + 0x30ec47, + 0x3cd2c6, + 0x3472c6, + 0x37d086, + 0x30f0c9, + 0x27ef47, + 0x251e46, + 0x2eacc6, + 0x3cae06, + 0x2b4385, + 0x20e046, + 0x3b3245, + 0x2bf588, + 0x29940b, + 0x295606, + 0x398444, + 0x305bc9, + 0x2abc04, + 0x2c0f88, + 0x3116c7, + 0x28bac4, + 0x2cb948, + 0x2d1604, + 0x2b43c4, + 0x27a345, + 0x322506, + 0x2a7d07, + 0x249b03, + 0x2a3705, + 0x2ff4c4, + 0x3c7b86, + 0x3a1688, + 0x37e545, + 0x2990c9, + 0x3513c5, + 0x323488, + 0x2bc807, + 0x330748, + 0x2cb587, + 0x2fdb49, + 0x287f86, + 0x372946, + 0x29b284, + 0x309c45, + 0x31520c, + 0x281487, + 0x282047, + 0x23e208, + 0x2af506, + 0x2a9844, + 0x34a144, + 0x38fb49, + 0x2d7ac6, + 0x296b47, + 0x27e7c4, + 0x2ab4c6, + 0x3c1685, + 0x2dea47, + 0x2e08c6, + 0x261609, + 0x39b307, + 0x29d487, + 0x2aaf06, + 0x270205, + 0x286b08, + 0x223708, + 0x371f86, + 0x37e585, + 0x2e93c6, + 0x203803, + 0x2a1a89, + 0x2a58ce, + 0x2cb2c8, + 0x2e12c8, + 0x371d8b, + 0x299306, + 0x398304, + 0x23bac4, + 0x2a59ca, + 0x2134c7, + 0x251f05, + 0x214a09, + 0x2cf305, + 0x3da087, + 0x232144, + 0x204787, + 0x322608, + 0x2d6c46, + 0x2c8389, + 0x2cc58a, + 0x213446, + 0x29f886, + 0x2b7c45, + 0x39f685, + 0x37d947, + 0x246d48, + 0x3c15c8, + 0x30b3c6, + 0x361c45, + 0x23e34e, + 0x2ccc44, + 0x2a1b85, + 0x27d089, + 0x2f7588, + 0x2931c6, + 0x2a3ccc, + 0x2a51d0, + 0x2a894f, + 0x2aa508, + 0x3513c7, + 0x3d5c05, + 0x297f85, + 0x2a7b89, + 0x297f89, + 0x27ddc6, + 0x313087, + 0x309b45, + 0x337c49, + 0x363386, + 0x2f408d, + 0x286c89, + 0x25ef44, + 0x2cb048, + 0x2339c9, + 0x33a1c6, + 0x280f85, + 0x372946, + 0x33ee09, + 0x27e648, + 0x210ec5, + 0x289804, + 0x2a3e8b, + 0x33a085, + 0x242006, + 0x28ac46, + 0x22a986, + 0x25e24b, + 0x2991c9, + 0x347205, + 0x399007, + 0x2eed46, + 0x233086, + 0x289488, + 0x30ed89, + 0x344d8c, + 0x33fbc8, + 0x31e806, + 0x333e83, + 0x360186, + 0x2b58c5, + 0x285cc8, + 0x3e21c6, + 0x2dec88, + 0x24be05, + 0x293f85, + 0x2c0b88, + 0x3d5247, + 0x3ccf07, + 0x26b607, + 0x31fc48, + 0x2d9b88, + 0x2e2d86, + 0x2c4407, + 0x34d947, + 0x2b578a, + 0x238643, + 0x3b5146, + 0x23e2c5, + 0x215cc4, + 0x282949, + 0x2fdac4, + 0x2cbd84, + 0x2a3944, + 0x2a8ecb, + 0x330c47, + 0x23e585, + 0x29dbc8, + 0x27d706, + 0x27d708, + 0x284886, + 0x294845, + 0x294b05, + 0x296086, + 0x2971c8, + 0x297b08, + 0x282b06, + 0x29da0f, + 0x2a1550, + 0x205385, + 0x203843, + 0x22c605, + 0x321108, + 0x297e89, + 0x3bb508, + 0x312e88, + 0x385808, + 0x330d07, + 0x27d3c9, + 0x2dee88, + 0x2a4f84, + 0x2a37c8, + 0x3589c9, + 0x2c4a07, + 0x395d44, + 0x27ea48, + 0x29758a, + 0x2ff746, + 0x2a9606, + 0x226b09, + 0x2a5407, + 0x2dbfc8, + 0x2321c8, + 0x347988, + 0x259805, + 0x21ce85, + 0x21a245, + 0x2d9145, + 0x2c2747, + 0x205845, + 0x2d0185, + 0x203546, + 0x3bb447, + 0x3aaa07, + 0x297886, + 0x2e2445, + 0x242006, + 0x280e45, + 0x2c7d08, + 0x309ac4, + 0x2d61c6, + 0x353544, + 0x2cb748, + 0x32288a, + 0x28328c, + 0x2a6505, + 0x21a306, + 0x344f46, + 0x348d86, + 0x31e884, + 0x3cd585, + 0x284147, + 0x2a5489, + 0x2db647, + 0x602284, + 0x602284, + 0x330ac5, + 0x2dfe04, + 0x2a328a, + 0x27d586, + 0x2c0b04, + 0x3dccc5, + 0x2c1d85, + 0x2c3b44, + 0x28a887, + 0x3cdfc7, + 0x2db208, + 0x2e94c8, + 0x210ec9, + 0x388d08, + 0x29048b, + 0x2a7cc4, + 0x233185, + 0x38ff05, + 0x26b589, + 0x30ed89, + 0x305ac8, + 0x368f48, + 0x2e6bc4, + 0x291805, + 0x204083, + 0x2d9a45, + 0x2a0046, + 0x29ec0c, + 0x21f546, + 0x280e86, + 0x293445, + 0x301cc8, + 0x2eadc6, + 0x3d8806, + 0x2a9606, + 0x22e24c, + 0x38ffc4, + 0x37d1ca, + 0x293388, + 0x29ea47, + 0x2ff3c6, + 0x252087, + 0x2fed45, + 0x2702c6, + 0x363d86, + 0x377987, + 0x2cc284, + 0x20f085, + 0x27d084, + 0x39e487, + 0x27d2c8, + 0x28080a, + 0x288587, + 0x2ac487, + 0x351347, + 0x2efdc9, + 0x29ec0a, + 0x22c503, + 0x366a05, + 0x215083, + 0x2c2c09, + 0x2d9f48, + 0x388ac7, + 0x3bb609, + 0x223806, + 0x358e08, + 0x3c4b45, + 0x37e94a, + 0x2079c9, + 0x29c289, + 0x2d5707, + 0x2e2f89, + 0x214f48, + 0x25f906, + 0x21a4c8, + 0x27ff07, + 0x274147, + 0x2d5ac7, + 0x2dd548, + 0x30c7c6, + 0x297345, + 0x284147, + 0x29f388, + 0x37d004, + 0x306dc4, + 0x298587, + 0x2b9047, + 0x32e0ca, + 0x25f886, + 0x3c82ca, + 0x2cf747, + 0x2cca07, + 0x20f144, + 0x295d04, + 0x2de946, + 0x361444, + 0x36144c, + 0x311605, + 0x21c2c9, + 0x2f0e84, + 0x2c3c05, + 0x2b2548, + 0x297bc5, + 0x396906, + 0x2980c4, + 0x2ab98a, + 0x384806, + 0x24774a, + 0x3da407, + 0x20d645, + 0x22bd85, + 0x23f14a, + 0x247685, + 0x2a7b46, + 0x241984, + 0x2c00c6, + 0x37da05, + 0x3e2286, + 0x33decc, + 0x2e3cca, + 0x2a8544, + 0x24bc06, + 0x2a5407, + 0x2e0844, + 0x367848, + 0x2ecc46, + 0x398289, + 0x2cd009, + 0x2dcb49, + 0x2db946, + 0x280006, + 0x21a607, + 0x330388, + 0x27fe09, + 0x330c47, + 0x29dd46, + 0x3ca007, + 0x26a445, + 0x2ccc44, + 0x21a1c7, + 0x34db05, + 0x28f645, + 0x200cc7, + 0x247488, + 0x3b1a06, + 0x2a01cd, + 0x2a1e0f, + 0x2a6e0d, + 0x226104, + 0x235086, + 0x2e4088, + 0x348f45, + 0x2b5948, + 0x2862ca, + 0x25ef44, + 0x239b06, + 0x211787, + 0x218e87, + 0x2dc549, + 0x21a485, + 0x2c3b44, + 0x2c568a, + 0x2cc049, + 0x2e3087, + 0x30d406, + 0x33a1c6, + 0x291746, + 0x386c06, + 0x2e398f, + 0x2e3f49, + 0x37e646, + 0x38f786, + 0x32fa49, + 0x2c4507, + 0x220d03, + 0x22e3c6, + 0x20c483, + 0x2d22c8, + 0x2b0e07, + 0x2aa709, + 0x2b6308, + 0x3cd048, + 0x367346, + 0x21f489, + 0x307cc5, + 0x2a3504, + 0x35cf07, + 0x393205, + 0x226104, + 0x23e648, + 0x213784, + 0x2c4247, + 0x399c86, + 0x26c5c5, + 0x2ae088, + 0x33a08b, + 0x31d047, + 0x23f3c6, + 0x2d71c4, + 0x3aef86, + 0x26ff45, + 0x34db05, + 0x286889, + 0x28a489, + 0x274184, + 0x2741c5, + 0x24bc45, + 0x37e7c6, + 0x31cc48, + 0x2ce7c6, + 0x22040b, + 0x3d774a, + 0x2cb685, + 0x294b86, + 0x25b285, + 0x3c2205, + 0x256147, + 0x3b53c8, + 0x237804, + 0x385406, + 0x297b86, + 0x215107, + 0x325304, + 0x285346, + 0x229845, + 0x229849, + 0x280204, + 0x2b7789, + 0x282b06, + 0x2d08c8, + 0x24bc45, + 0x398505, + 0x3e2286, + 0x344c89, + 0x221a89, + 0x280f06, + 0x2f7688, + 0x296788, + 0x25b244, + 0x2c6604, + 0x2c6608, + 0x3326c8, + 0x237909, + 0x29ffc6, + 0x2a9606, + 0x33964d, + 0x31a546, + 0x378f09, + 0x201f45, + 0x20c4c6, + 0x347b08, + 0x336085, + 0x34d984, + 0x26ff45, + 0x289fc8, + 0x2a3049, + 0x27d144, + 0x2c8206, + 0x29c4ca, + 0x30cd48, + 0x32e249, + 0x270bca, + 0x3bb586, + 0x2a1fc8, + 0x375785, + 0x293608, + 0x2fedc5, + 0x2236c9, + 0x33bc49, + 0x21fb82, + 0x2caec5, + 0x277f06, + 0x282a47, + 0x215cc5, + 0x33eb86, + 0x319508, + 0x2af506, + 0x2c9889, + 0x282146, + 0x289308, + 0x24ef85, + 0x394886, + 0x3b3b08, + 0x289188, + 0x3d8448, + 0x31b948, + 0x20e044, + 0x21f783, + 0x2c9ac4, + 0x288786, + 0x26a484, + 0x2e1207, + 0x3d8709, + 0x2d6785, + 0x2321c6, + 0x22e3c6, + 0x2a244b, + 0x2c3346, + 0x273686, + 0x2d62c8, + 0x266cc6, + 0x20d443, + 0x20bb03, + 0x2ccc44, + 0x2310c5, + 0x23f747, + 0x27d2c8, + 0x27d2cf, + 0x28404b, + 0x31ca48, + 0x2c8286, + 0x31cd4e, + 0x23f583, + 0x23f6c4, + 0x2c32c5, + 0x2c39c6, + 0x2959cb, + 0x299886, + 0x30c009, + 0x26c5c5, + 0x249a48, + 0x209bc8, + 0x22194c, + 0x2a8f06, + 0x2d9a86, + 0x2e5145, + 0x290108, + 0x283285, + 0x3505c8, + 0x2a404a, + 0x2a7249, + 0x602284, 0x2000c2, - 0x202703, - 0x201242, - 0x214a83, - 0x235b44, - 0x232dc3, - 0x308003, - 0x21bc83, - 0x2e2406, - 0x21a3c3, - 0x242543, - 0x9a048, - 0x214a83, - 0x232dc3, - 0x228503, - 0x21a3c3, - 0x1b4103, - 0x242543, - 0x9a048, - 0x214a83, - 0x232dc3, - 0x308003, - 0x23c803, - 0x219a04, - 0x21a3c3, - 0x242543, - 0x2000c2, - 0x27ee03, - 0x201242, - 0x232dc3, - 0x308003, - 0x23c803, - 0x21a3c3, - 0x242543, - 0x2086c2, - 0x215702, - 0x201242, - 0x214a83, - 0x208c02, - 0x2005c2, - 0x221dc4, - 0x306c44, - 0x223f82, - 0x219a04, + 0x4b212402, + 0x200382, + 0x20e704, + 0x20b982, + 0x217544, + 0x203182, + 0x5803, 0x2003c2, - 0x242543, - 0x2141c3, - 0x252b06, - 0x20c782, - 0x201b82, - 0x2221c2, - 0x57a07403, - 0x57e2e743, - 0x56246, - 0x56246, - 0x2d3684, - 0x203dc3, - 0x160d, - 0x1d984a, - 0x16300c, - 0x1d76cc, - 0xd4ecd, - 0x139b05, - 0x86209, - 0x8d2cc, - 0x29907, - 0xdb86, - 0x13dc8, - 0x1bf87, - 0x201c8, - 0x1ac0ca, - 0x10d707, - 0x58a8d505, - 0xe4f09, - 0x58c3480b, - 0x125c08, - 0x1558b, - 0x12c5c8, - 0x1c48c9, - 0x4060a, - 0x1b1b8e, - 0x1d158d, - 0x2cd0d, - 0x14426cb, - 0xe554a, - 0x8d04, - 0x5a106, - 0x19b808, - 0x79108, - 0x25687, - 0x1d35c5, - 0xc607, - 0x33249, - 0x15c087, - 0x106c8, - 0x26a89, - 0x4ce04, - 0x4e945, - 0x98e8e, - 0x12c207, - 0x59225a86, - 0x78d8d, - 0xd1288, - 0x59694f86, - 0x5a094f88, - 0x56f88, - 0x136b90, - 0x53f8c, - 0x61b47, - 0x62347, - 0x6a947, - 0x72047, - 0x6882, - 0x120687, - 0x19980c, - 0x13c945, - 0x107c07, - 0xac186, - 0xacdc9, - 0xaff88, - 0x6502, + 0x208502, + 0xae888, + 0x4cc4, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x7542, + 0x4b202, + 0x23cb03, + 0x217fc3, + 0x23e083, + 0x1fcc2, + 0x4642, + 0x72c2, + 0x24ac43, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x20e704, + 0x217fc3, + 0x23e083, + 0x219ac3, + 0x24cd44, + 0x22ea43, + 0x236704, + 0x233fc3, + 0x2e5904, + 0x266a83, + 0x215f87, + 0x23cb03, + 0x205803, + 0x321388, + 0x23e083, + 0x293b0b, + 0x2ffec3, + 0x243bc6, + 0x22dc42, + 0x2fa00b, + 0x233fc3, + 0x266a83, + 0x217fc3, + 0x23e083, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x23e083, + 0x221d43, + 0x210cc3, + 0x2000c2, + 0xae888, + 0x334f05, + 0x34db88, + 0x2f4fc8, + 0x212402, + 0x36a4c5, + 0x3ca147, + 0x2031c2, + 0x243407, + 0x200382, + 0x253d47, + 0x23a489, + 0x272888, + 0x347809, + 0x210382, + 0x3d5f47, + 0x32ad04, + 0x3ca207, + 0x3d7647, + 0x25a642, + 0x23cb03, + 0x20a942, + 0x203182, + 0x2003c2, + 0x205b42, + 0x200902, + 0x208502, + 0x2e1a45, + 0x227885, + 0x12402, + 0x33fc3, + 0x22ea43, + 0x233fc3, + 0x27e883, + 0x266a83, + 0x204903, + 0x217fc3, + 0x23e083, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x217fc3, + 0x23e083, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x23cb03, + 0x217fc3, + 0x1c0443, + 0x23e083, + 0xfe83, + 0x101, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x20e704, + 0x2191c3, + 0x217fc3, + 0x1c0443, + 0x23e083, + 0x217c83, + 0x4e4b1706, + 0x22383, + 0xd7405, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x217fc3, + 0x23e083, + 0x212402, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x217fc3, + 0x1c0443, + 0x23e083, + 0x5242, + 0xae888, + 0x12f603, + 0x5803, + 0x1c0443, + 0x46d04, + 0x147b604, + 0xf0085, + 0x2000c2, + 0x3993c4, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x247e03, + 0x22f845, + 0x2191c3, + 0x21e1c3, + 0x217fc3, + 0x24dfc3, + 0x23e083, + 0x208503, + 0x24cdc3, + 0x20aa43, 0x5c2, - 0x18d986, - 0x1b9ecb, - 0x1ba1c6, - 0x174d04, - 0x978c7, - 0x41a89, - 0xf0c09, - 0x1b1148, - 0x49582, - 0x193a49, - 0xd788, - 0xef24a, - 0x15adc9, - 0x1b4186, - 0xd8949, - 0xe54c7, - 0xe5c09, - 0xe7ac8, - 0xea3c7, - 0xec049, - 0xf0005, - 0xf1210, - 0x1b6786, - 0x97805, - 0x91487, - 0xec74d, - 0x41485, - 0xf8ac6, - 0xf92c7, - 0xfe7d8, - 0xd1608, - 0x13db8a, - 0xca42, - 0x5020a, - 0x60e4d, - 0x45c2, - 0xce946, - 0x11ec6, - 0xa1788, - 0xafd0a, - 0x472c8, - 0x6de89, - 0x115488, - 0x6eace, - 0x6e0c8, - 0x14a887, - 0x5a694ec4, - 0xae8cd, - 0x10a085, - 0x69148, - 0x34088, - 0x111b86, - 0xc2c2, - 0xc53c4, - 0xe2c06, - 0x365c6, - 0x5a8ef74b, - 0x1442, + 0x30242, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x217fc3, + 0x23e083, + 0x2000c2, + 0x24ac43, + 0x212402, + 0xf982, + 0x233fc3, + 0x266a83, + 0x20e704, + 0x217fc3, + 0x23e083, + 0x208502, + 0xae888, + 0x266a83, + 0x1c0443, + 0xae888, + 0x1c0443, + 0x276243, + 0x22ea43, + 0x2319c4, + 0x233fc3, + 0x266a83, + 0x209582, + 0x23cb03, + 0x217fc3, + 0x5803, + 0x23e083, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x209582, + 0x215f83, + 0x217fc3, + 0x23e083, + 0x2f8e43, + 0x208503, + 0x2000c2, + 0x212402, + 0x266a83, + 0x217fc3, + 0x23e083, + 0x243bc5, + 0x1375c6, + 0x24cd44, + 0x22dc42, + 0x882, + 0xae888, + 0xf982, + 0x4b202, + 0x2a82, + 0x2000c2, + 0x146bc5, + 0x1ae08, + 0x125203, + 0x212402, + 0x3c904, + 0x52d16f86, + 0x1384, + 0xc634b, + 0x3a806, + 0x7f3c7, + 0x1431c9, + 0x233fc3, + 0x49e88, + 0x49e8b, + 0x4a30b, + 0x4a9cb, + 0x4ad0b, + 0x4afcb, + 0x4b40b, + 0x1cb86, + 0x266a83, + 0xf48c5, + 0x2044, + 0x20ef43, + 0x11b787, + 0xe88c4, + 0x722c4, + 0x217fc3, + 0x81006, + 0x1583c4, + 0x1c0443, + 0x23e083, + 0x300ac4, + 0x131247, + 0x1371c9, + 0xc6108, + 0x1a2584, + 0x1ca344, + 0x134c46, + 0xff48, + 0x1480c5, + 0x124e89, + 0xe783, + 0x146bc5, + 0x212402, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x23cb03, + 0x205803, + 0x23e083, + 0x2ffec3, + 0x22dc42, + 0xae888, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x20e703, + 0x21e484, + 0x217fc3, + 0x5803, + 0x23e083, + 0x22ea43, + 0x233fc3, + 0x2e5904, + 0x266a83, + 0x217fc3, + 0x23e083, + 0x243bc6, + 0x233fc3, + 0x266a83, + 0xf443, + 0x1c0443, + 0x23e083, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x217fc3, + 0x23e083, + 0x146bc5, + 0x7f3c7, + 0x15c3, + 0xe783, + 0xae888, + 0x266a83, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x612c3, + 0x217fc3, + 0x23e083, + 0x5622ea43, + 0x233fc3, + 0x217fc3, + 0x23e083, + 0xae888, + 0x2000c2, + 0x212402, + 0x22ea43, + 0x266a83, + 0x217fc3, + 0x2003c2, + 0x23e083, + 0x33c187, + 0x355d4b, + 0x211843, + 0x27da88, + 0x330107, + 0x229dc6, + 0x2d42c5, + 0x36a609, + 0x243948, + 0x381049, + 0x3ac290, + 0x38104b, + 0x215589, + 0x2015c3, + 0x2fa6c9, + 0x232646, + 0x23264c, + 0x334fc8, + 0x3dde48, + 0x26eac9, + 0x2c8b0e, + 0x23a24b, + 0x2c030c, + 0x233f03, + 0x284e8c, + 0x3e13c9, + 0x238447, + 0x233f0c, + 0x2bde8a, + 0x241ec4, + 0x38aa8d, + 0x284d48, + 0x219acd, + 0x292146, + 0x24cd4b, + 0x337349, + 0x38fa07, + 0x25f0c6, + 0x323849, + 0x35484a, + 0x30e748, + 0x2ffac4, + 0x3a8e87, + 0x3c0807, + 0x208184, + 0x2221c4, + 0x3b4809, + 0x35c549, + 0x3e1f48, + 0x2f2c85, + 0x2102c5, + 0x209a86, + 0x38a949, + 0x28654d, + 0x2ece48, + 0x209987, + 0x2d4348, + 0x26bdc6, + 0x22fa44, + 0x2a4d45, + 0x3d9f46, + 0x3dc104, + 0x3e12c7, + 0x204e8a, + 0x210e04, + 0x213386, + 0x214689, + 0x21468f, + 0x214c4d, + 0x215ac6, + 0x21aa10, + 0x21ae06, + 0x21b507, + 0x21bcc7, + 0x21bccf, + 0x21c689, + 0x224c46, + 0x225047, + 0x225048, + 0x225449, + 0x20f708, + 0x306a07, + 0x22b743, + 0x22e8c6, + 0x239148, + 0x2c8dca, + 0x20cf09, + 0x243a83, + 0x36a3c6, + 0x38524a, + 0x2fbb87, + 0x23828a, + 0x316c8e, + 0x21c7c6, + 0x32bc47, + 0x38ef46, + 0x243fc6, + 0x21cc8b, + 0x3a038a, + 0x35638d, + 0x2800c7, + 0x26dc08, + 0x26dc09, + 0x26dc0f, + 0x30a80c, + 0x265209, + 0x2bbbce, + 0x21608a, + 0x20dac6, + 0x3076c6, + 0x31a1cc, + 0x3df50c, + 0x325908, + 0x35fd07, + 0x39d4c5, + 0x3ca3c4, + 0x25fd0e, + 0x3ae384, + 0x37dd87, + 0x3a6d8a, + 0x3d7cd4, + 0x3db78f, + 0x21be88, + 0x22e788, + 0x39124d, + 0x39124e, + 0x22ed49, + 0x22fe88, + 0x22fe8f, + 0x233c0c, + 0x233c0f, + 0x234dc7, + 0x23718a, + 0x23874b, + 0x2395c8, + 0x23b807, + 0x260b4d, + 0x369646, + 0x38ac46, + 0x23d649, + 0x252848, + 0x243dc8, + 0x243dce, + 0x2bb387, + 0x305145, + 0x246ac5, + 0x206484, + 0x22a086, + 0x3e1e48, + 0x324643, + 0x2e8c8e, + 0x260f08, + 0x2acacb, + 0x276407, + 0x30b205, + 0x269c06, + 0x2b6ec7, + 0x321848, + 0x37d749, + 0x3d2cc5, + 0x28e408, + 0x228a46, + 0x3addca, + 0x25fc09, + 0x233fc9, + 0x233fcb, + 0x25c6c8, + 0x208049, + 0x2f2d46, + 0x2041ca, + 0x29d08a, + 0x23738c, + 0x375e07, + 0x27268a, + 0x331b0b, + 0x331b19, + 0x353148, + 0x243c45, + 0x260d06, + 0x211d89, + 0x3b2c46, + 0x22170a, + 0x275246, + 0x2d8384, + 0x2d838d, + 0x3b4447, + 0x368889, + 0x249285, + 0x2493c8, + 0x249c49, + 0x24bb44, + 0x24c247, + 0x24c248, + 0x24c507, + 0x26c188, + 0x251c87, + 0x2daec5, + 0x25828c, + 0x258749, + 0x31d28a, + 0x3b09c9, + 0x2fa7c9, + 0x38f54c, + 0x25accb, + 0x25c8c8, + 0x261448, + 0x264f04, + 0x28b548, + 0x28cb89, + 0x2bdf47, + 0x2148c6, + 0x2a3b07, + 0x2a0f49, + 0x354d4b, + 0x20b187, + 0x348647, + 0x3da547, + 0x219a44, + 0x219a45, + 0x2e5605, + 0x35e84b, + 0x349284, + 0x328308, + 0x30234a, + 0x228b07, + 0x3d0347, + 0x295192, + 0x293dc6, + 0x231346, + 0x34890e, + 0x294586, + 0x299e48, + 0x29aacf, + 0x219e88, + 0x28fb88, + 0x2df5ca, + 0x2df5d1, + 0x2ab64e, + 0x2550ca, + 0x2550cc, + 0x230087, + 0x230090, + 0x3d4f08, + 0x2ab845, + 0x2b71ca, + 0x3dc14c, + 0x29e08d, + 0x204906, + 0x204907, + 0x20490c, + 0x209d8c, + 0x2191cc, + 0x2c204b, + 0x3923c4, + 0x226c84, + 0x2ba749, + 0x34a1c7, + 0x382f89, + 0x29cec9, + 0x2bdb47, + 0x2bdd06, + 0x2bdd09, + 0x2be103, + 0x2af60a, + 0x323a87, + 0x3ca70b, + 0x35620a, + 0x32ad84, + 0x3c88c6, + 0x288809, + 0x3612c4, + 0x2e164a, + 0x2e2845, + 0x2cd7c5, + 0x2cd7cd, + 0x2cdb0e, + 0x2c9c05, + 0x33ae46, + 0x2437c7, + 0x2525ca, + 0x3ae686, + 0x381c04, + 0x35a607, + 0x2fc70b, + 0x26be87, + 0x2699c4, + 0x253306, + 0x25330d, + 0x2e724c, + 0x217e86, + 0x2ed04a, + 0x223486, + 0x220dc8, + 0x274707, + 0x2d5eca, + 0x2361c6, + 0x27ffc3, + 0x2f4b46, + 0x238fc8, + 0x37204a, + 0x2df007, + 0x2df008, + 0x25bfc4, + 0x295707, + 0x2fdf08, + 0x293fc8, + 0x2c30c8, + 0x33e14a, + 0x2ee705, + 0x2ee987, + 0x254f13, + 0x271146, + 0x20bc08, + 0x222a49, + 0x2432c8, + 0x3673cb, + 0x3cd3c8, + 0x2cab84, + 0x2c0c86, + 0x325e06, + 0x322349, + 0x2d5d07, + 0x258388, + 0x2a5c46, + 0x200bc4, + 0x3d5d85, + 0x3aa788, + 0x248e8a, + 0x2d8008, + 0x2dcf86, + 0x2a21ca, + 0x273d48, + 0x2e0648, + 0x2e18c8, + 0x2e2106, + 0x2e4286, + 0x3b048c, + 0x2e4810, + 0x2b8a85, + 0x219c88, + 0x21e910, + 0x219c90, + 0x3ac10e, + 0x3b010e, + 0x3b0114, + 0x3b934f, + 0x3b9706, + 0x3b6051, + 0x208253, + 0x2086c8, + 0x25f245, + 0x27dfc8, + 0x3a7c45, + 0x34aacc, + 0x22b989, + 0x3ae1c9, + 0x317147, + 0x237bc9, + 0x3b2807, + 0x33a486, + 0x2a4b47, + 0x202cc5, + 0x20fec3, + 0x20f443, + 0x215bc4, + 0x3dff8d, + 0x20bf4f, + 0x200c05, + 0x34a9c6, + 0x2200c7, + 0x334d47, + 0x37b8c6, + 0x37b8cb, + 0x2ac3c5, + 0x259986, + 0x30db47, + 0x252b89, + 0x225706, + 0x38c185, + 0x3c324b, + 0x205d06, + 0x226685, + 0x246248, + 0x296448, + 0x2ae3cc, + 0x2ae3d0, + 0x2b4f89, + 0x2c7007, + 0x2be80b, + 0x2ce086, + 0x3068ca, + 0x2a81cb, + 0x38160a, + 0x39f946, + 0x2f8d05, + 0x330006, + 0x28d548, + 0x31720a, + 0x390edc, + 0x2fff8c, + 0x300288, + 0x243bc5, + 0x387ac7, + 0x25d246, + 0x2bcc85, + 0x218386, + 0x37ba88, + 0x2cc2c7, + 0x2c8a08, + 0x27120a, + 0x3c110c, + 0x3248c9, + 0x3c1387, + 0x28d0c4, + 0x246b86, + 0x28f70a, + 0x29cfc5, + 0x221e4c, + 0x222508, + 0x2f6848, + 0x2b1a0c, + 0x31aa0c, + 0x32a8c9, + 0x32ab07, + 0x242dcc, + 0x22ac84, + 0x36fe0a, + 0x31114c, + 0x24e1cb, + 0x24f60b, + 0x2509c6, + 0x2541c7, + 0x2302c7, + 0x2302cf, + 0x312111, + 0x2eb492, + 0x25538d, + 0x25538e, + 0x2556ce, + 0x3b9508, + 0x3b9512, + 0x266448, + 0x223087, + 0x24fa4a, + 0x2af348, + 0x294545, + 0x2c258a, + 0x21b187, + 0x2f1004, + 0x20ee43, + 0x236c45, + 0x2df847, + 0x3aca87, + 0x29e28e, + 0x33dacd, + 0x350d49, + 0x31fb05, + 0x35f4c3, + 0x34ca46, + 0x259ec5, + 0x2acd08, + 0x227349, + 0x260d45, + 0x260d4f, + 0x2c6447, + 0x2154c5, + 0x276dca, + 0x205646, + 0x35d1c9, + 0x386ecc, + 0x3d2dc9, + 0x20b386, + 0x30214c, + 0x333f86, + 0x310088, + 0x331a06, + 0x36c7c6, + 0x2c34c4, + 0x3222c3, + 0x20de0a, + 0x22de51, + 0x26a90a, + 0x25b105, + 0x288207, + 0x255b47, + 0x2e8a84, + 0x2fe00b, + 0x347688, + 0x2cb146, + 0x23e285, + 0x268b84, + 0x24fc89, + 0x2008c4, + 0x2124c7, + 0x34d185, + 0x34d187, + 0x348b45, + 0x20bb83, + 0x222f48, + 0x27e1ca, + 0x249b03, + 0x334f4a, + 0x2a9d06, + 0x260acf, + 0x2bb309, + 0x2e8c10, + 0x3064c8, + 0x2dd9c9, + 0x29f107, + 0x25328f, + 0x3bb9c4, + 0x2e5984, + 0x21ac86, + 0x2356c6, + 0x23edca, + 0x247cc6, + 0x2b9447, + 0x317c48, + 0x317e47, + 0x3192c7, + 0x31ad0a, + 0x319bcb, + 0x358f85, + 0x2eb0c8, + 0x21a303, + 0x3ccbcc, + 0x39d24f, + 0x3c7e0d, + 0x258b87, + 0x350e89, + 0x2f5c87, + 0x28c3c8, + 0x3d7ecc, + 0x2caa88, + 0x366dc8, + 0x332bce, + 0x345b54, + 0x346064, + 0x365d0a, + 0x38188b, + 0x3b28c4, + 0x3b28c9, + 0x239b88, + 0x247385, + 0x32414a, + 0x2a5007, + 0x215d84, + 0x24ac43, + 0x22ea43, + 0x236704, + 0x233fc3, + 0x266a83, + 0x20e704, + 0x2191c3, + 0x23cb03, + 0x2e4806, + 0x21e484, + 0x217fc3, + 0x23e083, + 0x216983, + 0x2000c2, + 0x24ac43, + 0x212402, + 0x22ea43, + 0x236704, + 0x233fc3, + 0x266a83, + 0x2191c3, + 0x2e4806, + 0x217fc3, + 0x23e083, + 0xae888, + 0x22ea43, + 0x233fc3, + 0x280203, + 0x217fc3, + 0x1c0443, + 0x23e083, + 0xae888, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x23cb03, + 0x21e484, + 0x217fc3, + 0x23e083, + 0x2000c2, + 0x281bc3, + 0x212402, + 0x233fc3, + 0x266a83, + 0x23cb03, + 0x217fc3, + 0x23e083, + 0x20cf02, + 0x20cdc2, + 0x212402, + 0x22ea43, + 0x204302, + 0x2005c2, + 0x20e704, + 0x217544, + 0x266002, + 0x21e484, + 0x2003c2, + 0x23e083, + 0x216983, + 0x2509c6, + 0x21fcc2, + 0x2072c2, + 0x223d42, + 0x58a13d83, + 0x58e30083, + 0x56486, + 0x56486, + 0x24cd44, + 0x205803, + 0x8acd, + 0x1e1cca, + 0x1cc04c, + 0x173cc, + 0xd720d, + 0x6e784, + 0x8f284, + 0x120384, + 0x146bc5, + 0x8e9c9, + 0xbf04c, + 0x1683c7, + 0x11fc6, + 0x16588, + 0x1a087, + 0x20ac8, + 0x1bdd8a, + 0x1109c7, + 0x59abd285, + 0xbd289, + 0x59c35a0b, + 0x129f08, + 0xcc4b, + 0x141488, + 0x167e89, + 0x8c80a, + 0x1316ce, + 0xbec4a, + 0xa4cd, + 0x2ed4d, + 0x14430cb, + 0xe710a, + 0x1384, + 0x59ac6, + 0xf988, + 0x10f508, + 0x35cc7, + 0x1dbc5, + 0x1fb47, + 0x34449, + 0x161347, + 0xec88, + 0x2afc9, + 0x3ea84, + 0xd3085, + 0x737ce, + 0x1410c7, + 0x5a224d46, + 0x4efcd, + 0x7f248, + 0x5a65ce86, + 0x5b05ce88, + 0x57388, + 0x13c390, + 0x5460c, + 0x68787, + 0x693c7, + 0x707c7, + 0x77c07, + 0x9a42, + 0x16e07, + 0x1a054c, + 0x5d4c5, + 0xb4e07, + 0xae286, + 0xafcc9, + 0xb3108, + 0xb5c2, + 0x5c2, + 0x193c86, + 0x1c2b0b, + 0x1c2e06, + 0x6f044, + 0x1b5ac7, + 0x33449, + 0x860c9, + 0x1bb208, + 0x4b202, + 0x199249, + 0x11a08, + 0xfb54a, + 0xe689, + 0x2a8c6, + 0xdac89, + 0xe7087, + 0xe77c9, + 0xea1c8, + 0xec607, + 0xee689, + 0xf1a45, + 0xf1e10, + 0x1d60c6, + 0x1b5a05, + 0x19dfc7, + 0xbd68d, + 0x41d85, + 0xfa5c6, + 0xfadc7, + 0x100ad8, + 0x7f5c8, + 0x14978a, + 0xd782, + 0x5b7928cb, + 0x4f3ca, + 0x5a04d, + 0x2442, + 0xd4d86, + 0x13a06, + 0xa2ac8, + 0xb2e8a, + 0x3dd48, + 0x74e49, + 0x118088, + 0x6f48e, + 0x75088, + 0x14ca47, + 0x5ba5cdc4, + 0xb170d, + 0x1095c5, + 0x2748, + 0x35288, + 0x1145c6, + 0x4642, + 0xcaf44, + 0xe5006, + 0x134c46, + 0x5bd8490b, + 0x3602, 0x401, 0x81, - 0xb8f08, - 0x5bc87, - 0x150503, - 0x59a37f04, - 0x59e9b383, + 0xbe588, + 0x5bb87, + 0x93783, + 0x5aa37e84, + 0x5ae9c0c3, 0xc1, - 0xf586, + 0x25d86, 0xc1, 0x201, - 0xf586, - 0x150503, - 0x66603, - 0x647c4, - 0x1e7c7, - 0x7787, - 0x15c27c5, - 0x4e684, - 0x13d707, - 0x1242, - 0x24c0c4, - 0x214a83, - 0x24d9c4, - 0x221dc4, - 0x21a3c3, - 0x221445, - 0x214903, - 0x22b983, - 0x208805, - 0x207783, - 0x1243, - 0x5ba14a83, - 0x232dc3, - 0x4d9c4, - 0x7083, - 0x308003, + 0x25d86, + 0x93783, + 0x18b7c8, + 0x4cdc3, + 0x27c44, + 0x20f47, + 0xaa47, + 0x1571585, + 0x4e584, + 0x149307, + 0x12402, + 0x241ec4, + 0x22ea43, + 0x24d704, + 0x20e704, + 0x217fc3, + 0x222905, + 0x217c83, + 0x235403, + 0x37b845, + 0x20aa43, + 0x1be83, + 0x5ce2ea43, + 0x233fc3, + 0x4d704, + 0x33c3, + 0x266a83, 0x200181, - 0x1a8c3, - 0x23c803, - 0x306c44, - 0x219a04, - 0x21a3c3, - 0x4e283, - 0x242543, - 0x20e2c3, - 0x9a048, + 0x1e1c3, + 0x23cb03, + 0x217544, + 0x21e484, + 0x217fc3, + 0x4dfc3, + 0x23e083, + 0x208503, + 0xae888, 0x2000c2, - 0x202703, - 0x201242, - 0x214a83, - 0x232dc3, - 0x228503, + 0x24ac43, + 0x212402, + 0x22ea43, + 0x233fc3, + 0x280203, 0x2005c2, - 0x221dc4, - 0x21bc83, - 0x23c803, - 0x21a3c3, - 0x203dc3, - 0x242543, - 0x207783, - 0x196504, - 0x9a048, - 0x106947, - 0x1242, - 0x1a3105, - 0x540cf, - 0xe0986, - 0x144ca88, - 0x1160ce, - 0x5ca345c2, - 0x297ac8, - 0x35c5c6, - 0x24e806, - 0x395e87, - 0x5ce00c82, - 0x5d2b7488, - 0x20bd4a, - 0x2615c8, + 0x20e704, + 0x2191c3, + 0x23cb03, + 0x217fc3, + 0x205803, + 0x23e083, + 0x20aa43, + 0x19d184, + 0xae888, + 0x10a087, + 0x12402, + 0x1aa705, + 0x5474f, + 0xf10c6, + 0x1454408, + 0x118cce, + 0x5de2a502, + 0x32f688, + 0x361886, + 0x24e706, + 0x39cb07, + 0x5e200c82, + 0x5e6bb188, + 0x21f28a, + 0x268208, 0x200ac2, - 0x3c8c49, - 0x33bfc7, - 0x212686, - 0x2217c9, - 0x2c1c04, - 0x3c8b46, - 0x2cc7c4, - 0x20e904, - 0x257889, - 0x30dbc6, - 0x265445, - 0x266245, - 0x22d547, - 0x2dbd87, - 0x34c8c4, - 0x31e606, - 0x2ff485, - 0x210845, - 0x23eac5, - 0x3bd9c7, - 0x26f6c5, - 0x248489, - 0x343fc5, - 0x31b484, - 0x3a6987, - 0x36344e, - 0x2031c9, - 0x2885c9, - 0x348706, - 0x23f9c8, - 0x36f2cb, - 0x2a5a0c, - 0x322686, - 0x2ba447, - 0x2eee85, - 0x3270ca, - 0x3d9bc9, - 0x345c89, - 0x295146, - 0x30bc05, - 0x245705, - 0x36a209, - 0x23ec4b, - 0x2c2246, - 0x352686, - 0x2067c4, - 0x2ecd46, - 0x304448, - 0x3c1e46, - 0x2e3e06, - 0x3dd908, - 0x2019c7, - 0x201d49, - 0x205a85, - 0x9a048, - 0x3cd4c4, - 0x316c44, - 0x20b105, - 0x3403c9, - 0x220747, - 0x22074b, - 0x22284a, - 0x228585, - 0x5d60c282, - 0x2cda07, - 0x5da29cc8, - 0x295387, - 0x301345, - 0x348aca, - 0x1242, - 0x27c58b, - 0x27d98a, - 0x248906, - 0x20a803, - 0x206c8d, - 0x3c4c4c, - 0x3c534d, - 0x230585, - 0x27bbc5, - 0x296dc7, - 0x3d1149, - 0x20bc46, - 0x254505, - 0x37b708, - 0x2ce983, - 0x2e3948, - 0x2ecc48, - 0x383fc7, - 0x3b8a88, - 0x3c0809, - 0x2fd2c7, - 0x2cd207, - 0x3de588, - 0x23ae04, - 0x23ae07, - 0x3a6bc8, - 0x360f06, - 0x3c0ecf, - 0x22a907, - 0x31e106, - 0x22dcc5, - 0x222343, - 0x246287, - 0x387fc3, - 0x24d046, - 0x24e586, - 0x24f0c6, - 0x293d45, - 0x265c03, - 0x3936c8, - 0x38a109, - 0x39bf8b, - 0x24f248, - 0x250a85, - 0x252305, - 0x5de2dec2, - 0x2842c9, - 0x221e47, - 0x25a045, - 0x257787, - 0x258ac6, - 0x381745, - 0x25a40b, - 0x25c004, - 0x261185, - 0x2612c7, - 0x276806, - 0x276c45, - 0x283d87, - 0x2847c7, - 0x2a7484, - 0x28d0ca, - 0x28dbc8, - 0x2ccc09, - 0x23f285, - 0x205886, - 0x30460a, - 0x266146, - 0x2ed087, - 0x26d30d, - 0x2aa2c9, - 0x391c45, - 0x363847, - 0x289708, - 0x303588, - 0x3286c7, - 0x384d06, - 0x21abc7, - 0x24dbc3, - 0x30db44, - 0x37d485, - 0x3a7747, - 0x3b0d49, - 0x229588, - 0x2ecf85, - 0x2425c4, - 0x247ec5, - 0x24f40d, + 0x3ca549, + 0x358fc7, + 0x214846, + 0x222c89, + 0x2eeac4, + 0x3ca446, + 0x2e9104, + 0x2029c4, + 0x257b49, + 0x310e86, + 0x267c85, + 0x26b845, + 0x22f587, + 0x2de387, + 0x26b784, + 0x2d2486, + 0x301785, + 0x20ee05, + 0x25b1c5, + 0x2c2347, + 0x276245, + 0x24a0c9, + 0x37eb85, + 0x321984, + 0x3ae5c7, + 0x3b3fce, + 0x207289, + 0x3487c9, + 0x371246, + 0x2405c8, + 0x37554b, + 0x2a74cc, + 0x326f06, + 0x2c01c7, + 0x2f0d05, + 0x3163ca, + 0x3e2049, + 0x201189, + 0x206a86, + 0x30d905, + 0x246e45, + 0x389a89, + 0x25b34b, + 0x2ef106, + 0x353806, + 0x209984, + 0x303a46, + 0x3051c8, + 0x3cbf46, + 0x267846, + 0x203048, + 0x205107, + 0x206809, + 0x208e85, + 0xae888, + 0x3d2c44, + 0x319844, + 0x210145, + 0x343c49, + 0x221287, + 0x22128b, + 0x22434a, + 0x228745, + 0x5ea087c2, + 0x3560c7, + 0x5ee2b748, + 0x206cc7, + 0x303085, + 0x35d60a, + 0x12402, + 0x28428b, + 0x28544a, + 0x24a546, + 0x20ffc3, + 0x21114d, + 0x3ca98c, + 0x203a8d, + 0x232105, + 0x3363c5, + 0x324687, + 0x206309, + 0x21f186, + 0x247b45, + 0x3401c8, + 0x2d4dc3, + 0x2f52c8, + 0x303948, + 0x3a2107, + 0x3c5d88, + 0x3c76c9, + 0x2ff5c7, + 0x3558c7, + 0x371408, + 0x38bcc4, + 0x38bcc7, + 0x292048, + 0x366406, + 0x3cb14f, + 0x265747, + 0x2d1f86, + 0x32ac45, + 0x223ec3, + 0x2479c7, + 0x38e083, + 0x24c6c6, + 0x24e486, + 0x24ff06, + 0x298ec5, + 0x26c183, + 0x398ec8, + 0x390849, + 0x3a290b, + 0x250088, + 0x251945, + 0x253645, + 0x5f2ba882, + 0x2a4c09, + 0x223307, + 0x259a05, + 0x257a47, + 0x258ec6, + 0x386ac5, + 0x259d0b, + 0x25c8c4, + 0x267dc5, + 0x267f07, + 0x27c586, + 0x27c9c5, + 0x28b987, + 0x28c147, + 0x2a9cc4, + 0x2bee4a, + 0x292b08, + 0x375809, + 0x25b985, + 0x3585c6, + 0x30538a, + 0x26d6c6, + 0x236047, + 0x272a0d, + 0x2abf09, + 0x397445, + 0x2603c7, + 0x32d088, + 0x3b38c8, + 0x20a107, + 0x20e3c6, + 0x22cc07, + 0x24d903, + 0x310e04, + 0x383405, + 0x3af807, + 0x3bae09, + 0x2f5e08, + 0x235f45, + 0x362784, + 0x250245, + 0x25ca8d, 0x200cc2, - 0x2bca46, - 0x2eaf06, - 0x308cca, - 0x39a786, - 0x3a3345, - 0x26d6c5, - 0x26d6c7, - 0x3a5fcc, - 0x256e0a, - 0x28f246, - 0x2e1d85, - 0x2ecb86, - 0x28f507, - 0x291246, - 0x293c4c, - 0x221909, - 0x5e20fa07, - 0x29a9c5, - 0x29a9c6, - 0x29ae08, - 0x2c4005, - 0x2aab45, - 0x2ab848, - 0x2aba4a, - 0x5e67b8c2, - 0x5ea09e42, - 0x355fc5, - 0x237643, - 0x326008, - 0x228703, - 0x2abcc4, - 0x312c4b, - 0x36f688, - 0x2b9648, - 0x5ef03cc9, - 0x2b1009, - 0x2b19c6, - 0x2b2788, - 0x2b2989, - 0x2b3806, - 0x2b3985, - 0x246c06, - 0x2b4749, - 0x2c8947, - 0x38e386, - 0x20af47, - 0x345fc7, - 0x207584, - 0x5f2d1909, - 0x24ad08, - 0x2b7388, - 0x2257c7, - 0x2d5946, - 0x3d0f49, - 0x24e7c7, - 0x24a58a, - 0x32ed08, - 0x3bb707, - 0x3d0606, - 0x2f0e0a, - 0x23df48, - 0x2ea985, - 0x227b85, - 0x3cbc87, - 0x3190c9, - 0x31cf0b, - 0x351f08, - 0x344049, - 0x24fb47, - 0x2c2bcc, - 0x2c3bcc, - 0x2c3eca, - 0x2c414c, - 0x2cc348, - 0x2cc548, - 0x2cc744, - 0x2ce109, - 0x2ce349, - 0x2ce58a, - 0x2ce809, - 0x2ceb87, - 0x3bec0c, - 0x3d2406, - 0x26ccc8, - 0x266206, - 0x38fe86, - 0x391b47, - 0x3a1088, - 0x3debcb, - 0x295247, - 0x257549, - 0x25ba49, - 0x2844c7, - 0x2cca04, - 0x200fc7, - 0x2f0806, - 0x20e486, - 0x24c845, - 0x2f7d48, - 0x26c544, - 0x26c546, - 0x256ccb, - 0x2b3449, - 0x235686, - 0x2e4009, - 0x20b1c6, - 0x345048, - 0x210543, - 0x30bd85, - 0x21a9c9, - 0x21c7c5, - 0x30e1c4, - 0x275d46, - 0x235a05, - 0x254306, - 0x319a07, - 0x247946, - 0x22c2cb, - 0x3c5987, - 0x3a28c6, - 0x2730c6, - 0x22d606, - 0x34c889, - 0x3b604a, - 0x2c58c5, - 0x3d6f8d, - 0x2abb46, - 0x23c346, - 0x2e6f86, - 0x21e5c5, - 0x2f1507, - 0x228ec7, - 0x27390e, - 0x23c803, - 0x2d5909, - 0x297289, - 0x22d287, - 0x26bbc7, - 0x2919c5, - 0x367b05, - 0x5f60210f, - 0x2db607, - 0x2db7c8, - 0x2dbb84, - 0x2dc046, - 0x5fa45402, - 0x2e01c6, - 0x2e2406, - 0x29744e, - 0x2e378a, - 0x207106, - 0x2b7c0a, - 0x3c7709, - 0x2fbe85, - 0x2ee188, - 0x3cbb46, - 0x2b7188, - 0x202ac8, - 0x277fcb, - 0x395f85, - 0x26f748, - 0x3dda4c, - 0x301207, - 0x24eb46, - 0x30c2c8, - 0x3de888, - 0x5fe30a42, - 0x21504b, - 0x205c89, - 0x28d989, - 0x21c647, - 0x3b5dc8, - 0x603d32c8, - 0x20934b, - 0x349bc9, - 0x25b18d, - 0x202908, - 0x2f1008, - 0x60604042, - 0x20d5c4, - 0x60a2aec2, - 0x3cd9c6, - 0x60e01282, - 0x2fbc8a, - 0x2a3806, - 0x34bd48, - 0x3c6908, - 0x3d9746, - 0x2ba946, - 0x3054c6, - 0x2ab045, - 0x239e44, - 0x6122ae04, - 0x359946, - 0x276247, - 0x6160d8c7, - 0x278b4b, - 0x295589, - 0x27bc0a, - 0x26d804, - 0x2f3b08, - 0x38e14d, - 0x2fc409, - 0x2fc648, - 0x2fc8c9, - 0x2fe7c4, - 0x2aae04, - 0x38d205, - 0x346f0b, - 0x36f606, - 0x359785, - 0x236209, - 0x31e6c8, - 0x22af84, - 0x327249, - 0x24a345, - 0x2dbdc8, - 0x2cd8c7, - 0x2889c8, - 0x281006, - 0x3ba447, - 0x2e6b09, - 0x3c9289, - 0x21d705, - 0x3682c5, - 0x61a1e342, - 0x31b244, - 0x21fe85, - 0x395d86, - 0x346905, - 0x244b07, - 0x359a45, - 0x276844, - 0x3487c6, - 0x254587, - 0x238f46, - 0x30a945, - 0x217108, - 0x35c7c5, - 0x21a847, - 0x2277c9, - 0x2b358a, - 0x264cc7, - 0x264ccc, - 0x265406, - 0x2423c9, - 0x31f045, - 0x369188, - 0x211ec3, - 0x398c45, - 0x3b9285, - 0x27b207, - 0x61e08dc2, - 0x2f8087, - 0x2eea86, - 0x38dc46, - 0x2f6146, - 0x3de7c6, - 0x230988, - 0x2d0145, - 0x31e1c7, - 0x31e1cd, - 0x210883, - 0x3d28c5, - 0x270007, - 0x2f83c8, - 0x26fbc5, - 0x214408, - 0x37cf06, - 0x2e50c7, - 0x2d4605, - 0x396006, - 0x393c45, - 0x20ba0a, - 0x310586, - 0x2645c7, - 0x2c9485, - 0x3a8a87, - 0x3cdb04, - 0x30e146, - 0x3cba85, - 0x39a18b, - 0x2f0689, - 0x27ef0a, - 0x21d788, - 0x314248, - 0x31968c, - 0x31adc7, - 0x330a08, - 0x335d88, - 0x3382c5, - 0x358dca, - 0x361049, - 0x62203242, - 0x2945c6, - 0x25c9c4, - 0x2fa949, - 0x35d749, - 0x2451c7, - 0x29b947, - 0x2bfb09, - 0x2ffe08, - 0x2ffe0f, - 0x21b5c6, - 0x2e4bcb, - 0x259dc5, - 0x259dc7, - 0x37bd49, - 0x20c8c6, - 0x3271c7, - 0x2e9105, - 0x230484, - 0x2f5006, - 0x220904, - 0x2f9c47, - 0x321c88, - 0x6270bb08, + 0x2ce4c6, + 0x2f7986, + 0x30820a, + 0x3a0e06, + 0x3aa945, + 0x2e95c5, + 0x2e95c7, + 0x3adc0c, + 0x25720a, + 0x294d06, + 0x2e4185, + 0x303886, + 0x294fc7, + 0x296986, + 0x298dcc, + 0x222dc9, + 0x5f626207, + 0x29ae85, + 0x29ae86, + 0x29bb48, + 0x2ca185, + 0x2ac785, + 0x2ad148, + 0x2ad34a, + 0x5fa295c2, + 0x5fe0b942, + 0x309d85, + 0x26a483, + 0x32a308, + 0x210503, + 0x2ad5c4, + 0x35d30b, + 0x2a78c8, + 0x384648, + 0x6034b7c9, + 0x2b48c9, + 0x2b51c6, + 0x2b6b48, + 0x2b6d49, + 0x2b7a86, + 0x2b7c05, + 0x248486, + 0x2b8589, + 0x2cd607, + 0x394746, + 0x21b347, + 0x2014c7, + 0x213f04, + 0x6067f8c9, + 0x2bcec8, + 0x2bb088, + 0x200e07, + 0x2d7c86, + 0x205a89, + 0x24e6c7, + 0x3c250a, + 0x3c8108, + 0x2131c7, + 0x2180c6, + 0x2a114a, + 0x32a708, + 0x2f7405, + 0x225a05, + 0x3d12c7, + 0x3264c9, + 0x32878b, + 0x39be48, + 0x37ec09, + 0x250487, + 0x2c94cc, + 0x2c9d4c, + 0x2ca04a, + 0x2ca2cc, + 0x2d3d88, + 0x2d3f88, + 0x2d4184, + 0x2d4549, + 0x2d4789, + 0x2d49ca, + 0x2d4c49, + 0x2d4fc7, + 0x3c91cc, + 0x3dc686, + 0x2723c8, + 0x26d786, + 0x3957c6, + 0x397347, + 0x3a8708, + 0x22a28b, + 0x206b87, + 0x257809, + 0x288349, + 0x2a4e07, + 0x2e9344, + 0x269087, + 0x39b786, + 0x2128c6, + 0x2ed205, + 0x2f9848, + 0x317044, + 0x317046, + 0x2570cb, + 0x2af909, + 0x385c06, + 0x267a49, + 0x210206, + 0x249088, + 0x20eb03, + 0x30da85, + 0x219849, + 0x214445, + 0x3b9e84, + 0x3d25c6, + 0x308005, + 0x20a686, + 0x31d587, + 0x355006, + 0x22c6cb, + 0x2040c7, + 0x3a9c46, + 0x278c86, + 0x22f646, + 0x26b749, + 0x3b5e0a, + 0x2cb445, + 0x205e0d, + 0x2ad446, + 0x239446, + 0x2e8b06, + 0x220d45, + 0x2f2107, + 0x30b4c7, + 0x2794ce, + 0x23cb03, + 0x2d7c49, + 0x324b49, + 0x22f2c7, + 0x271a47, + 0x2703c5, + 0x294145, + 0x60bb494f, + 0x2ddc07, + 0x2dddc8, + 0x2de184, + 0x2de646, + 0x60e46b42, + 0x2e2386, + 0x2e4806, + 0x3b564e, + 0x2f510a, + 0x2bb906, + 0x218d4a, + 0x203889, + 0x23c7c5, + 0x312d08, + 0x33dd86, + 0x2ba948, + 0x36aac8, + 0x28238b, + 0x39cc05, + 0x2762c8, + 0x20318c, + 0x302f47, + 0x24f986, + 0x30e908, + 0x229f48, + 0x6123bec2, + 0x20c70b, + 0x209089, + 0x2bf709, + 0x21a747, + 0x3c3088, + 0x6161cf48, + 0x21d84b, + 0x34bd89, + 0x28be0d, + 0x3802c8, + 0x2a1348, + 0x61a09282, + 0x228644, + 0x61e30242, + 0x3b9cc6, + 0x62200e42, + 0x2fd7ca, + 0x364486, + 0x267388, + 0x3cea88, + 0x3e1bc6, + 0x2c06c6, + 0x306246, + 0x2acc85, + 0x239dc4, + 0x62768e04, + 0x35f2c6, + 0x277a47, + 0x62a810c7, + 0x24ed8b, + 0x206ec9, + 0x33640a, + 0x2f4dc4, + 0x2e9708, + 0x39450d, + 0x2fe709, + 0x2fe948, + 0x2febc9, + 0x300ac4, + 0x266344, + 0x393505, + 0x33f0cb, + 0x2a7846, + 0x35f105, + 0x236dc9, + 0x2d2548, + 0x2aa9c4, + 0x316549, + 0x3c22c5, + 0x2de3c8, + 0x355f87, + 0x348bc8, + 0x288a06, + 0x20f5c7, + 0x2e8689, + 0x3c33c9, + 0x226705, + 0x23b4c5, + 0x62e13242, + 0x321744, + 0x232345, + 0x39ca06, + 0x33eac5, + 0x23fa07, + 0x35f3c5, + 0x27c5c4, + 0x371306, + 0x247bc7, + 0x238e46, 0x30c645, - 0x30c787, - 0x324389, - 0x20d184, - 0x241048, - 0x62bd0448, - 0x2e6f04, - 0x31d648, - 0x295a04, - 0x3b6349, - 0x21e505, - 0x62e33442, - 0x21b605, - 0x2dd945, - 0x289548, - 0x233a07, - 0x632008c2, - 0x22af45, - 0x2de0c6, - 0x243906, - 0x31b208, - 0x338fc8, - 0x3468c6, - 0x34afc6, - 0x306a09, - 0x38db86, - 0x20c78b, - 0x3c2705, - 0x2ac8c6, - 0x3c4a88, - 0x33f6c6, - 0x224786, - 0x216bca, - 0x2df94a, - 0x248a45, - 0x30ce07, - 0x274586, - 0x63603642, - 0x270147, - 0x33c305, - 0x304584, - 0x304585, - 0x2f3a06, - 0x272c47, - 0x203945, - 0x2dfac4, - 0x352248, - 0x224845, - 0x37ae87, - 0x3ca445, - 0x20b945, - 0x2d2904, - 0x2d2909, - 0x2ff2c8, - 0x23a5c6, - 0x35aa06, - 0x302d06, - 0x63bd5b08, - 0x311d07, - 0x31234d, - 0x312f0c, - 0x313509, - 0x313749, - 0x63f75442, - 0x3d4a03, - 0x2010c3, - 0x2f08c5, - 0x3a784a, - 0x338e86, - 0x23cec5, - 0x31a504, - 0x31a50b, - 0x32d64c, - 0x32df0c, - 0x32e215, - 0x32f70d, - 0x33208f, - 0x332452, - 0x3328cf, - 0x332c92, - 0x333113, - 0x3335cd, - 0x333b8d, - 0x333f0e, - 0x33480e, - 0x334e0c, - 0x3351cc, - 0x33560b, - 0x33668e, - 0x336f92, - 0x338c4c, - 0x3391d0, - 0x34cfd2, - 0x34e08c, - 0x34e74d, - 0x34ea8c, - 0x351591, - 0x35280d, - 0x3546cd, - 0x354cca, - 0x354f4c, - 0x35890c, - 0x35948c, - 0x359e8c, - 0x35df93, - 0x35e710, - 0x35eb10, - 0x35f10d, - 0x35f70c, - 0x360549, - 0x36224d, - 0x362593, - 0x364111, - 0x364913, - 0x36560f, - 0x3659cc, - 0x365ccf, - 0x36608d, - 0x36668f, - 0x366a50, - 0x3674ce, - 0x36b40e, - 0x36ba90, - 0x36d08d, - 0x36da0e, - 0x36dd8c, - 0x36ed53, - 0x37268e, - 0x372c10, - 0x373011, - 0x37344f, - 0x373813, - 0x374fcd, - 0x37530f, - 0x3756ce, - 0x375c50, - 0x376049, - 0x3773d0, - 0x3778cf, - 0x377f4f, - 0x378312, - 0x3792ce, - 0x37a00d, - 0x37a54d, - 0x37a88d, - 0x37bf8d, - 0x37c2cd, - 0x37c610, - 0x37ca0b, - 0x37d24c, - 0x37d5cc, - 0x37dbcc, - 0x37dece, - 0x38d350, - 0x38f7d2, - 0x38fc4b, - 0x39078e, - 0x390b0e, - 0x39138e, - 0x39190b, - 0x64391d96, - 0x39268d, - 0x393214, - 0x393f0d, - 0x3955d5, - 0x3978cd, - 0x39824f, - 0x398e0f, - 0x39c24f, - 0x39c60e, - 0x39c98d, - 0x39e251, - 0x3a084c, - 0x3a0b4c, - 0x3a0e4b, - 0x3a128c, - 0x3a1ccf, - 0x3a2092, - 0x3a2bcd, - 0x3a470c, - 0x3a500c, - 0x3a530d, - 0x3a564f, - 0x3a5a0e, - 0x3a750c, - 0x3a7acd, - 0x3a7e0b, - 0x3a8c4c, - 0x3a954d, - 0x3a988e, - 0x3a9c09, - 0x3abb53, - 0x3ac94d, - 0x3ad04d, - 0x3ad64c, - 0x3ae88e, - 0x3aef8f, - 0x3af34c, - 0x3af64d, - 0x3af98f, - 0x3afd4c, - 0x3b034c, - 0x3b080c, - 0x3b0b0c, - 0x3b35cd, - 0x3b3912, - 0x3b45cc, - 0x3b48cc, - 0x3b4bd1, - 0x3b500f, - 0x3b53cf, - 0x3b5793, - 0x3b6f8e, - 0x3b730f, - 0x3b76cc, - 0x647b7d8e, - 0x3b810f, - 0x3b84d6, - 0x3bae52, - 0x3bcccc, - 0x3bdb8f, - 0x3be20d, - 0x3c94cf, - 0x3c988c, - 0x3c9b8d, - 0x3c9ecd, - 0x3cb4ce, - 0x3cc38c, - 0x3ceecc, - 0x3cf1d0, - 0x3d3d91, - 0x3d41cb, - 0x3d460c, - 0x3d490e, - 0x3d6011, - 0x3d644e, - 0x3d67cd, - 0x3dbc0b, - 0x3dc88f, - 0x3dd454, - 0x23c782, - 0x23c782, - 0x23e083, - 0x23c782, - 0x23e083, - 0x23c782, - 0x203802, - 0x246c45, - 0x3d5d0c, - 0x23c782, - 0x23c782, - 0x203802, - 0x23c782, - 0x29b485, - 0x2b3585, - 0x23c782, - 0x23c782, - 0x201542, - 0x29b485, - 0x32fec9, - 0x363e0c, - 0x23c782, - 0x23c782, - 0x23c782, - 0x23c782, - 0x246c45, - 0x23c782, - 0x23c782, - 0x23c782, - 0x23c782, - 0x201542, - 0x32fec9, - 0x23c782, - 0x23c782, - 0x23c782, - 0x2b3585, - 0x23c782, - 0x2b3585, - 0x363e0c, - 0x3d5d0c, - 0x202703, - 0x214a83, - 0x232dc3, - 0x308003, - 0x221dc4, - 0x21a3c3, - 0x242543, - 0x1d904f, - 0x145988, - 0x1a704, - 0x3dc3, - 0x86808, - 0x1cc203, - 0x2000c2, - 0x65601242, - 0x240983, - 0x224c84, - 0x207083, - 0x384584, - 0x22f7c6, - 0x310c83, - 0x310c44, - 0x2f0b05, - 0x23c803, - 0x21a3c3, - 0x1b4103, - 0x242543, - 0x21e2ca, - 0x252b06, - 0x390e8c, - 0x9a048, - 0x201242, - 0x214a83, - 0x232dc3, - 0x308003, - 0x2137c3, - 0x2e2406, - 0x21a3c3, - 0x242543, - 0x2141c3, - 0x2fb03, - 0xac508, - 0x661d2145, - 0x47d47, - 0x139b05, - 0x156c9, - 0x1742, - 0x13a14a, - 0x66fa0505, - 0x139b05, - 0x29907, - 0x6dfc8, - 0x9c4e, - 0x8b392, - 0x9630b, - 0x10d806, - 0x6728d505, - 0x6768d50c, - 0x13d547, - 0x17e707, - 0x12364a, - 0x3be50, - 0x146145, - 0x10fe4b, - 0x79108, - 0x25687, - 0x2490b, - 0x33249, - 0x46e07, - 0x15c087, - 0xc2487, - 0x34a06, - 0x106c8, - 0x67c28886, - 0x47207, - 0x18ec46, - 0x78d8d, - 0xf3c90, - 0x680aac82, - 0xd1288, - 0x40350, - 0x17f5cc, - 0x687825cd, - 0x5a988, - 0x5ae0b, - 0x6b187, - 0x70749, - 0x56306, - 0x9b008, - 0x3ee42, - 0x9218a, - 0x157647, - 0x107c07, - 0xacdc9, - 0xaff88, - 0xf3605, - 0x18d986, - 0x1ba1c6, - 0xfd84e, - 0xaf88e, - 0x3874f, - 0x41a89, - 0xf0c09, - 0x91d0b, - 0xb3b4f, - 0xbd08c, - 0xca18b, - 0x131388, - 0x171887, - 0x197088, - 0xb580b, - 0xb5bcc, - 0xb5fcc, - 0xb63cc, - 0xb66cd, - 0x1b1148, - 0x52e82, - 0x193a49, - 0x112988, - 0x19fe8b, - 0xd5b46, - 0xdda8b, - 0x136acb, - 0xe884a, - 0xea585, - 0xf1210, - 0xf5c86, - 0x184806, - 0x97805, - 0x91487, - 0xe0448, - 0xf92c7, - 0xf9587, - 0x12c807, - 0xc7346, - 0x1cd80a, - 0x99eca, - 0x11ec6, - 0xb130d, - 0x472c8, - 0x115488, - 0x115cc9, - 0xc0545, - 0x1aec8c, - 0xb68cb, - 0x86fc9, - 0x1ccb44, - 0x111949, - 0x111b86, - 0x4cec6, - 0x3c686, - 0x1b82, - 0x365c6, - 0x13dacb, - 0x129187, - 0x11ac47, - 0x1442, - 0xd7445, - 0x27e04, - 0x101, - 0x4fd83, - 0x67a37806, - 0x9b383, - 0x382, - 0x26bc4, - 0xac2, - 0xd3684, - 0x882, - 0x31c2, - 0x16c2, - 0x20f82, - 0x86c2, - 0x8d502, - 0xd42, - 0x167c2, - 0x373c2, - 0x5582, - 0x2a42, - 0x4e782, - 0x32dc3, - 0x942, - 0x3c42, - 0xdec2, - 0x5dc2, - 0x642, - 0x315c2, - 0x6502, - 0x5bc2, - 0xf42, - 0x5c2, - 0x1bc83, - 0x4582, - 0x7882, - 0x49582, - 0x1e42, - 0x2042, - 0x8282, - 0x23502, - 0x17c2, - 0x9e82, - 0x3f82, - 0x6c9c2, - 0x15402, - 0x1a3c3, - 0x602, - 0x30a42, - 0x2902, - 0x1ad42, - 0x1d685, - 0xa882, - 0x14b42, - 0x3d383, - 0x682, - 0xca42, - 0x45c2, - 0x7842, - 0x2f82, - 0x8c2, - 0xc2c2, - 0x1b82, - 0x1805, - 0x68a03802, - 0x68edf283, - 0xf283, - 0x69203802, - 0xf283, - 0x74c47, - 0x201503, - 0x2000c2, - 0x214a83, - 0x232dc3, - 0x228503, - 0x2005c3, - 0x2137c3, - 0x21a3c3, - 0x203dc3, - 0x242543, - 0x29b3c3, - 0xc0584, - 0x19405, - 0x104305, - 0x3a83, - 0x9a048, - 0x214a83, - 0x232dc3, - 0x228503, - 0x23c803, - 0x21a3c3, - 0x203dc3, - 0x1b4103, - 0x242543, - 0x214a83, - 0x232dc3, - 0x242543, - 0x214a83, - 0x232dc3, - 0x308003, - 0x200181, - 0x23c803, - 0x21a3c3, - 0x24e283, - 0x242543, - 0x69944, - 0x202703, - 0x214a83, - 0x232dc3, - 0x218bc3, - 0x228503, - 0x2c26c3, - 0x208943, - 0x2a37c3, - 0x216243, - 0x308003, - 0x221dc4, - 0x21a3c3, - 0x242543, - 0x207783, - 0x204244, - 0x24f603, - 0x20dc3, - 0x29cfc3, - 0x328fc8, - 0x2f0e44, - 0x20020a, - 0x235406, - 0x124184, - 0x3a71c7, - 0x21ec8a, - 0x21b489, - 0x3bb287, - 0x3c024a, - 0x202703, - 0x35604b, - 0x216189, - 0x2f4245, - 0x3b0647, - 0x1242, - 0x214a83, - 0x20fcc7, - 0x263bc5, - 0x2cc8c9, - 0x232dc3, - 0x373ec6, - 0x2cb983, - 0xeeb03, - 0x118f86, - 0x98186, - 0x1d3bc7, - 0x212286, - 0x220fc5, - 0x205b47, - 0x316507, - 0x6bf08003, - 0x34e2c7, - 0x23c783, - 0x3d3905, - 0x221dc4, - 0x26f3c8, - 0x385acc, - 0x2b8d05, - 0x2aa446, - 0x20fb87, - 0x3b9887, - 0x3a2a07, - 0x248b48, - 0x317ccf, - 0x21b6c5, - 0x240a87, - 0x28e747, - 0x276e0a, - 0x37b549, - 0x3dd185, - 0x3212ca, - 0xc4ac6, - 0xc0a07, - 0x2cba05, - 0x2f6684, - 0x3d9686, - 0x101406, - 0x37f847, - 0x252d87, - 0x33b388, - 0x218405, - 0x263ac6, - 0x156ec8, - 0x2e3d85, - 0xe3f46, - 0x3268c5, - 0x28ce44, - 0x24a407, - 0x2307ca, - 0x23ab88, - 0x288e06, - 0x137c3, - 0x2ec0c5, - 0x320506, - 0x3bee46, - 0x297706, - 0x23c803, - 0x3a2e47, - 0x28e6c5, - 0x21a3c3, - 0x2e8b0d, - 0x203dc3, - 0x33b488, - 0x213484, - 0x276b05, - 0x2abd06, - 0x205406, - 0x2ac7c7, - 0x25b047, - 0x350ac5, - 0x242543, - 0x271d87, - 0x371989, - 0x2708c9, - 0x384a4a, - 0x215542, - 0x3d38c4, - 0x2fb304, - 0x2f7c07, - 0x2f7f48, - 0x2fa3c9, - 0x3d2789, - 0x2fadc7, - 0x109049, - 0x362f06, - 0xfd5c6, - 0x2fe7c4, - 0x22cf8a, - 0x302448, - 0x305389, - 0x305946, - 0x2bc745, - 0x23aa48, - 0x2d5dca, - 0x32bf03, - 0x2043c6, - 0x2faec7, - 0x35a785, - 0x3b4045, - 0x2432c3, - 0x23e804, - 0x227b45, - 0x2848c7, - 0x2ff405, - 0x2ff846, - 0x111e85, - 0x2071c3, - 0x2071c9, - 0x2768cc, - 0x2c61cc, - 0x3444c8, - 0x2ab3c7, - 0x30d588, - 0x10e747, - 0x30eaca, - 0x30f18b, - 0x2162c8, - 0x205508, - 0x22b686, - 0x302bc5, - 0x25d70a, - 0x2df2c5, - 0x233442, - 0x2d44c7, - 0x253c46, - 0x376785, - 0x310949, - 0x2d0405, - 0x372185, - 0x3c2409, - 0x320446, - 0x201348, - 0x3d39c3, - 0x20aa46, - 0x275c86, - 0x31cd05, - 0x31cd09, - 0x2c4709, - 0x25d487, - 0x11cb84, - 0x31cb87, - 0x3d2689, + 0x214048, + 0x361a85, + 0x21e147, + 0x225209, + 0x2afa4a, + 0x266f07, + 0x266f0c, + 0x267c46, + 0x23df09, + 0x248085, + 0x2d2ec8, + 0x202443, + 0x2f2d05, + 0x3c0e45, + 0x282fc7, + 0x63201242, + 0x2f9b87, + 0x2f0906, + 0x3862c6, + 0x2f2586, + 0x229e86, + 0x23be08, + 0x27e105, + 0x2d2047, + 0x2d204d, + 0x20ee43, + 0x3dcb45, + 0x276b87, + 0x2f9ec8, + 0x276745, + 0x216bc8, + 0x382e86, + 0x29c107, + 0x2d6945, + 0x39cc86, + 0x399445, + 0x21ef4a, + 0x301b46, + 0x274587, + 0x2ce285, + 0x310687, + 0x35a584, + 0x3b9e06, + 0x312c45, + 0x33544b, + 0x39b609, + 0x281cca, + 0x226788, + 0x393f88, + 0x31408c, + 0x3d8d07, + 0x31c848, + 0x31ecc8, + 0x32b9c5, + 0x35c18a, + 0x35f4c9, + 0x63600ec2, + 0x20b086, + 0x260d44, + 0x2fc549, + 0x240e89, + 0x246907, + 0x27bfc7, + 0x29cd49, + 0x33e348, + 0x33e34f, + 0x22d606, + 0x2e6ccb, + 0x256845, + 0x256847, + 0x381cc9, + 0x21fe06, + 0x3164c7, + 0x2eb805, + 0x232004, + 0x307ec6, + 0x206244, + 0x3ba247, + 0x3792c8, + 0x63b0d808, + 0x30fa05, + 0x30fb47, + 0x3532c9, + 0x20c4c4, + 0x241948, + 0x63e653c8, + 0x2e8a84, + 0x2f6dc8, + 0x25f184, + 0x206109, + 0x220c85, + 0x6422dc42, + 0x22d645, + 0x2dfd45, + 0x2600c8, + 0x234c07, + 0x646008c2, + 0x3c7a85, + 0x2e04c6, + 0x24d186, + 0x321708, + 0x31f148, + 0x33ea86, + 0x34a046, + 0x30a149, + 0x386206, + 0x21fccb, + 0x229d05, + 0x2af286, + 0x368048, + 0x34ae46, + 0x2bc246, + 0x2178ca, + 0x2e1b0a, + 0x23eb45, + 0x29c787, + 0x27a146, + 0x64a034c2, + 0x276cc7, + 0x367145, + 0x305304, + 0x305305, + 0x2f4cc6, + 0x278807, + 0x21ac85, + 0x240f44, + 0x2c3708, + 0x2bc305, + 0x37a987, + 0x3808c5, 0x21ee85, - 0x39f48, - 0x349845, - 0x353885, - 0x39b489, - 0x204b42, - 0x357204, - 0x209282, - 0x204582, - 0x2ed985, - 0x323f08, - 0x2c0485, - 0x2ced43, - 0x2ced45, - 0x2e03c3, - 0x20a742, - 0x2b4384, - 0x269f03, + 0x245744, + 0x245749, + 0x3015c8, + 0x359586, + 0x358846, + 0x363f06, + 0x64fcfc08, + 0x3d8b87, + 0x31474d, + 0x314f0c, + 0x315509, + 0x315749, + 0x65379942, + 0x3d7403, + 0x20e483, + 0x39b845, + 0x3af90a, + 0x33e946, + 0x2365c5, + 0x31e244, + 0x31e24b, + 0x33384c, + 0x33410c, + 0x334415, + 0x335e0d, + 0x337e8f, + 0x338252, + 0x3386cf, + 0x338a92, + 0x338f13, + 0x3393cd, + 0x33998d, + 0x339d0e, + 0x33a60e, + 0x33ac0c, + 0x33afcc, + 0x33b40b, + 0x33be8e, + 0x33c792, + 0x33e70c, + 0x3403d0, + 0x34e4d2, + 0x34f54c, + 0x34fc0d, + 0x34ff4c, + 0x3524d1, + 0x35398d, + 0x35ae4d, + 0x35b44a, + 0x35b6cc, + 0x35e60c, + 0x35ee0c, + 0x35f70c, + 0x362e93, + 0x363610, + 0x363a10, + 0x36460d, + 0x364c0c, + 0x365a49, + 0x3697cd, + 0x369b13, + 0x36b451, + 0x36bc53, + 0x36c94f, + 0x36cd0c, + 0x36d00f, + 0x36d3cd, + 0x36d9cf, + 0x36dd90, + 0x36e80e, + 0x37198e, + 0x3722d0, + 0x37318d, + 0x373b0e, + 0x373e8c, + 0x374fd3, + 0x37768e, + 0x377c10, + 0x378011, + 0x37844f, + 0x378813, + 0x3794cd, + 0x37980f, + 0x379bce, + 0x37a150, + 0x37a549, + 0x37bc90, + 0x37c18f, + 0x37c80f, + 0x37cbd2, + 0x37f68e, + 0x3804cd, + 0x380a0d, + 0x380d4d, + 0x381f0d, + 0x38224d, + 0x382590, + 0x38298b, + 0x3831cc, + 0x38354c, + 0x383b4c, + 0x383e4e, + 0x393650, + 0x395112, + 0x39558b, + 0x395f8e, + 0x39630e, + 0x396b8e, + 0x39710b, + 0x65797596, + 0x397e8d, + 0x398a14, + 0x39970d, + 0x39c255, + 0x39ea0d, + 0x39f38f, + 0x39fb4f, + 0x3a2bcf, + 0x3a2f8e, + 0x3a330d, + 0x3a4891, + 0x3a7ecc, + 0x3a81cc, + 0x3a84cb, + 0x3a890c, + 0x3a904f, + 0x3a9412, + 0x3aa1cd, + 0x3abe8c, + 0x3acc4c, + 0x3acf4d, + 0x3ad28f, + 0x3ad64e, + 0x3af5cc, + 0x3afb8d, + 0x3afecb, + 0x3b078c, + 0x3b108d, + 0x3b13ce, + 0x3b1749, + 0x3b2dd3, + 0x3b688d, + 0x3b6f8d, + 0x3b758c, + 0x3b7c0e, + 0x3b830f, + 0x3b86cc, + 0x3b89cd, + 0x3b8d0f, + 0x3b90cc, + 0x3ba40c, + 0x3ba8cc, + 0x3babcc, + 0x3bbb8d, + 0x3bbed2, + 0x3bc64c, + 0x3bc94c, + 0x3bcc51, + 0x3bd08f, + 0x3bd44f, + 0x3bd813, + 0x3be60e, + 0x3be98f, + 0x3bed4c, + 0x65bbf40e, + 0x3bf78f, + 0x3bfb56, + 0x3c1bd2, + 0x3c440c, + 0x3c4d8f, + 0x3c540d, + 0x3cec8f, + 0x3cf04c, + 0x3cf34d, + 0x3cf68d, + 0x3d0d4e, + 0x3d19cc, + 0x3d420c, + 0x3d4510, + 0x3d6791, + 0x3d6bcb, + 0x3d700c, + 0x3d730e, + 0x3d91d1, + 0x3d960e, + 0x3d998d, + 0x3de2cb, + 0x3debcf, + 0x3dfa54, + 0x23ca82, + 0x23ca82, + 0x203183, + 0x23ca82, + 0x203183, + 0x23ca82, + 0x201082, + 0x2484c5, + 0x3d8ecc, + 0x23ca82, + 0x23ca82, + 0x201082, + 0x23ca82, + 0x29c945, + 0x2afa45, + 0x23ca82, + 0x23ca82, + 0x208a02, + 0x29c945, + 0x336689, + 0x36b14c, + 0x23ca82, + 0x23ca82, + 0x23ca82, + 0x23ca82, + 0x2484c5, + 0x23ca82, + 0x23ca82, + 0x23ca82, + 0x23ca82, + 0x208a02, + 0x336689, + 0x23ca82, + 0x23ca82, + 0x23ca82, + 0x2afa45, + 0x23ca82, + 0x2afa45, + 0x36b14c, + 0x3d8ecc, + 0x24ac43, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x20e704, + 0x217fc3, + 0x23e083, + 0x1e14cf, + 0x1b4508, + 0x6704, + 0x5803, + 0x8efc8, + 0x1d1843, + 0x2000c2, + 0x66a12402, + 0x241283, + 0x25a584, + 0x2033c3, + 0x38a0c4, + 0x231346, + 0x222743, + 0x3d2484, + 0x3517c5, + 0x23cb03, + 0x217fc3, + 0x1c0443, + 0x23e083, + 0x226e0a, + 0x2509c6, + 0x39668c, + 0xae888, + 0x212402, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x215f83, + 0x2e4806, + 0x217fc3, + 0x23e083, + 0x216983, + 0xe783, + 0xaeec8, + 0x675dc3c5, + 0x49647, + 0x146bc5, + 0xcd89, + 0x8c02, + 0x1c73ca, + 0x683a7b85, + 0x146bc5, + 0x1683c7, + 0x74f88, + 0xb74e, + 0x90d92, + 0x123bcb, + 0x110ac6, + 0x686bd285, + 0x68abf28c, + 0x149147, + 0x178d87, + 0x127eca, + 0x3c290, + 0x1645, + 0xc634b, + 0x10f508, + 0x35cc7, + 0xbc3cb, + 0x34449, + 0x48687, + 0x161347, + 0xef347, + 0x35c06, + 0xec88, + 0x69036fc6, + 0x3dc87, + 0x176b06, + 0x4efcd, + 0xe9890, + 0x694293c2, + 0x7f248, + 0x8c550, + 0x184e8c, + 0x69b8794d, + 0x5a388, + 0x5a80b, + 0x71007, + 0x96d89, + 0x56546, + 0x9bd48, + 0x5b542, + 0x1b21ca, + 0x65a87, + 0xb4e07, + 0xafcc9, + 0xb3108, + 0xf48c5, + 0x193c86, + 0x1c2e06, + 0xffb4e, + 0xef90e, + 0x18ee0f, + 0x33449, + 0x860c9, + 0x1b1d4b, + 0xb538f, + 0xc470c, + 0xcfe0b, + 0x11d1c8, + 0x16f747, + 0x194c88, + 0x1a8c8b, + 0xb920c, + 0xb960c, + 0xb9a0c, + 0xb9d0d, + 0x1bb208, + 0x50d42, + 0x199249, + 0x15d048, + 0x1de00b, + 0xd7e86, + 0xdfe8b, + 0x13c2cb, + 0xeaf4a, + 0xec7c5, + 0xf1e10, + 0xf6a46, + 0x155146, + 0x1b5a05, + 0x19dfc7, + 0xe2608, + 0xfadc7, + 0xfb087, + 0x1416c7, + 0xccec6, + 0x1b9b0a, + 0xae70a, + 0x13a06, + 0xb4bcd, + 0x3dd48, + 0x118088, + 0x1188c9, + 0xc7c05, + 0x1b800c, + 0xb9f0b, + 0x15ca49, + 0x1d1204, + 0x114389, + 0x1145c6, + 0x156786, + 0x3c986, + 0x72c2, + 0x134c46, + 0x1496cb, + 0x11e987, + 0x11eb47, + 0x3602, + 0xd9785, + 0x2de44, + 0x101, + 0x506c3, + 0x68e6a646, + 0x9c0c3, + 0x382, + 0x2b104, + 0xac2, + 0x4cd44, + 0x882, + 0x7282, + 0x6c02, + 0x10bf02, + 0xcf02, + 0xbd282, + 0xd42, + 0x161e82, + 0x37402, + 0xda02, + 0xf982, + 0x4e682, + 0x33fc3, + 0x942, + 0x31c2, + 0xfa02, + 0x91c2, + 0x642, + 0x32702, + 0xb5c2, + 0x8fc2, + 0xf782, + 0x5c2, + 0x191c3, + 0x4b82, + 0x22c2, + 0x4b202, + 0x6902, + 0x2702, + 0xa682, + 0x4202, + 0x8c82, + 0xb982, + 0x193b42, + 0x720c2, + 0xcac2, + 0x17fc3, + 0x602, + 0x3bec2, + 0x2542, + 0x35c2, + 0x26685, + 0x4fc2, + 0x42c42, + 0x3d583, + 0x682, + 0xd782, + 0x2442, + 0xab02, + 0xee42, + 0x8c2, + 0x4642, + 0x72c2, + 0x8cc5, + 0x69e01082, + 0x6a2e82c3, + 0x1ac3, + 0x6a601082, + 0x1ac3, + 0x7a807, + 0x2089c3, + 0x2000c2, + 0x22ea43, + 0x233fc3, + 0x280203, + 0x2005c3, + 0x215f83, + 0x217fc3, + 0x205803, + 0x23e083, + 0x2bd443, + 0xc7c44, + 0x16acc5, + 0x105085, + 0x10103, + 0xae888, + 0x22ea43, + 0x233fc3, + 0x280203, + 0x23cb03, + 0x217fc3, + 0x205803, + 0x1c0443, + 0x23e083, + 0x22ea43, + 0x233fc3, + 0x23e083, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x200181, + 0x23cb03, + 0x217fc3, + 0x24dfc3, + 0x23e083, + 0x2f44, + 0x24ac43, + 0x22ea43, + 0x233fc3, + 0x275243, + 0x280203, + 0x2d2a83, + 0x2381c3, + 0x2a49c3, + 0x20d903, + 0x266a83, + 0x20e704, + 0x217fc3, + 0x23e083, + 0x20aa43, + 0x207d44, + 0x25cc83, + 0x33f03, + 0x238f43, + 0x32dac8, + 0x2a1184, + 0x20020a, + 0x385986, + 0x1530c4, + 0x3bc307, + 0x21bfca, + 0x22d4c9, + 0x3c7247, + 0x3c9c8a, + 0x24ac43, + 0x309e0b, + 0x20d849, + 0x2fde05, + 0x3ba707, + 0x12402, + 0x22ea43, + 0x2264c7, + 0x224685, + 0x2e9209, + 0x233fc3, + 0x23a086, + 0x2d3383, + 0xf0983, + 0x11bf06, + 0x8886, + 0x226c7, + 0x228c86, + 0x30bf45, + 0x208f47, + 0x319107, + 0x6d266a83, + 0x34f787, + 0x23ca83, + 0x21df05, + 0x20e704, + 0x275f48, + 0x3c410c, + 0x2be385, + 0x2ac086, + 0x226387, + 0x3c1447, + 0x269207, + 0x277008, + 0x31b18f, + 0x22d705, + 0x241387, + 0x211647, + 0x3caf0a, + 0x340009, + 0x32e945, + 0x34ef0a, + 0xdd286, + 0xc8087, + 0x2d3405, + 0x2f83c4, + 0x3e1b06, + 0x14e1c6, + 0x385107, + 0x250c47, + 0x3c5f48, + 0x212a05, + 0x224586, + 0x168688, + 0x2677c5, + 0x67986, + 0x2f5b85, + 0x267704, + 0x3c2387, + 0x23bc4a, + 0x2a6688, + 0x25f986, + 0x15f83, + 0x2ee705, + 0x354bc6, + 0x3c9406, + 0x3b5906, + 0x23cb03, + 0x3aa447, + 0x2115c5, + 0x217fc3, + 0x2eb20d, + 0x205803, + 0x3c6048, + 0x215c44, + 0x27c885, + 0x2ad606, + 0x358146, + 0x2af187, + 0x2a4a07, + 0x28dfc5, + 0x23e083, + 0x36f847, + 0x38a449, + 0x325009, + 0x3624ca, + 0x201b42, + 0x21dec4, + 0x304f84, + 0x2f9707, + 0x2f9a48, + 0x2fbfc9, + 0x3dca09, + 0x2fc9c7, + 0x108589, + 0x229446, + 0xff8c6, + 0x300ac4, + 0x22efca, + 0x304b08, + 0x306109, + 0x3066c6, + 0x2c3cc5, + 0x2a6548, + 0x2d810a, + 0x204343, + 0x207ec6, + 0x2fcac7, + 0x30f6c5, + 0x3c0385, + 0x243cc3, + 0x2ddf84, + 0x2259c5, + 0x28c247, + 0x301705, + 0x2f72c6, + 0x121dc5, + 0x288d83, + 0x2bb9c9, + 0x27c64c, + 0x2cbd4c, + 0x37f088, + 0x2a9f87, + 0x310848, + 0x111507, + 0x31188a, + 0x311f4b, + 0x20d988, + 0x358248, + 0x2524c6, + 0x31fdc5, + 0x25c4ca, + 0x2e8305, + 0x22dc42, + 0x2d6807, + 0x269f86, + 0x37b205, + 0x3d2189, + 0x27e3c5, + 0x388a05, + 0x229a09, + 0x325a46, + 0x37de88, + 0x270103, + 0x228dc6, + 0x3d2506, + 0x32dc85, + 0x32dc89, + 0x2ca889, + 0x25c247, + 0x120f44, + 0x320f47, + 0x3dc909, + 0x21c1c5, + 0x39ec8, + 0x37e245, + 0x371145, + 0x3b3609, + 0x201802, + 0x366984, + 0x20d2c2, + 0x204b82, + 0x320245, + 0x352e48, + 0x2c7b45, + 0x2d5183, + 0x2d5185, + 0x2e2583, + 0x20e202, + 0x2b5bc4, + 0x273cc3, 0x200a82, - 0x3b17c4, - 0x309703, - 0x20cb42, - 0x2c0503, - 0x211e44, - 0x305ac3, - 0x255544, - 0x205142, - 0x2140c3, - 0x21ab03, - 0x2071c2, - 0x294ec2, - 0x2c4549, - 0x205b02, - 0x28bec4, - 0x206542, - 0x23a8c4, - 0x362ec4, - 0x3b4384, - 0x201b82, - 0x22b2c2, - 0x22db03, - 0x29cec3, - 0x3269c4, - 0x3aa684, - 0x2dae84, - 0x2ea884, - 0x31bcc3, - 0x312743, - 0x2c4a44, - 0x31f184, - 0x31f2c6, - 0x21d642, - 0x1242, - 0x41483, - 0x201242, - 0x232dc3, - 0x308003, - 0x21a3c3, - 0x242543, - 0x6c05, + 0x2c1704, + 0x308c43, + 0x204ac2, + 0x2c7bc3, + 0x213984, + 0x306843, + 0x253cc4, + 0x207742, + 0x216883, + 0x218e43, + 0x2018c2, + 0x25cdc2, + 0x2ca6c9, + 0x208f02, + 0x291bc4, + 0x208742, + 0x3a9e44, + 0x229404, + 0x22aac4, + 0x2072c2, + 0x23d882, + 0x32aa83, + 0x26ac43, + 0x270184, + 0x2c7504, + 0x2ecac4, + 0x2fcc44, + 0x3210c3, + 0x35ce03, + 0x2dd204, + 0x3252c4, + 0x325406, + 0x226642, + 0x12402, + 0x41d83, + 0x212402, + 0x233fc3, + 0x266a83, + 0x217fc3, + 0x23e083, + 0x110c5, 0x2000c2, - 0x202703, - 0x214a83, - 0x232dc3, - 0x209b03, - 0x308003, - 0x221dc4, - 0x2c4804, - 0x219a04, - 0x21a3c3, - 0x242543, - 0x2141c3, - 0x2fedc4, - 0x297a83, - 0x2ad843, - 0x37a2c4, - 0x349646, - 0x210603, - 0x139b05, - 0x17e707, - 0x2d2c43, - 0x6da46f08, - 0x2532c3, - 0x2bb143, - 0x25cc03, - 0x2137c3, - 0x3c0ac5, - 0x1b0603, - 0x214a83, - 0x232dc3, - 0x308003, - 0x21a3c3, - 0x242543, - 0x20a883, - 0x22ee03, - 0x9a048, - 0x214a83, - 0x232dc3, - 0x308003, - 0x21bc83, - 0x21a3c3, - 0x2801c4, - 0x1b4103, - 0x242543, - 0x24a004, - 0x139b05, - 0x2c8f85, - 0x17e707, - 0x201242, - 0x2052c2, + 0x24ac43, + 0x22ea43, + 0x233fc3, + 0x203983, + 0x266a83, + 0x20e704, + 0x2ca984, + 0x21e484, + 0x217fc3, + 0x23e083, + 0x216983, + 0x3010c4, + 0x32f643, + 0x2b0743, + 0x380784, + 0x37e046, + 0x20ebc3, + 0x146bc5, + 0x178d87, + 0x226dc3, + 0x6ee10c08, + 0x24cbc3, + 0x2c1183, + 0x21df43, + 0x215f83, + 0x3c7985, + 0x1ba6c3, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x217fc3, + 0x23e083, + 0x210043, + 0x230743, + 0xae888, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x2191c3, + 0x217fc3, + 0x2878c4, + 0x1c0443, + 0x23e083, + 0x25d244, + 0x146bc5, + 0x2ce8c5, + 0x178d87, + 0x212402, + 0x204542, 0x200382, - 0x208482, - 0x3dc3, + 0x203182, + 0x5803, 0x2003c2, - 0x4f04, - 0x214a83, - 0x235b44, - 0x232dc3, - 0x308003, - 0x23c803, - 0x21a3c3, - 0x242543, - 0x9a048, - 0x214a83, - 0x232dc3, - 0x308003, - 0x23c803, - 0x219a04, - 0x21a3c3, - 0x3dc3, - 0x242543, - 0x20e2c3, - 0x2d3684, - 0x9a048, - 0x214a83, - 0x203dc3, - 0x3a83, - 0x122504, - 0x24c0c4, - 0x9a048, - 0x214a83, - 0x24d9c4, - 0x221dc4, - 0x203dc3, - 0x204042, - 0x1b4103, - 0x242543, - 0x22b983, - 0x3e804, - 0x208805, - 0x233442, - 0x325e43, - 0x68b49, - 0xe5986, - 0x149948, + 0x157c44, + 0x22ea43, + 0x236704, + 0x233fc3, + 0x266a83, + 0x23cb03, + 0x217fc3, + 0x23e083, + 0xae888, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x23cb03, + 0x21e484, + 0x217fc3, + 0x5803, + 0x23e083, + 0x208503, + 0x24cd44, + 0xae888, + 0x22ea43, + 0x205803, + 0x10103, + 0x126d84, + 0x241ec4, + 0xae888, + 0x22ea43, + 0x24d704, + 0x20e704, + 0x205803, + 0x209282, + 0x1c0443, + 0x23e083, + 0x235403, + 0xddf84, + 0x37b845, + 0x22dc42, + 0x32a143, + 0x2149, + 0xe7546, + 0x17e348, 0x2000c2, - 0x9a048, - 0x201242, - 0x232dc3, - 0x308003, + 0xae888, + 0x212402, + 0x233fc3, + 0x266a83, 0x2005c2, - 0x3dc3, - 0x242543, - 0x7c42, + 0x5803, + 0x23e083, + 0xa882, 0x82, 0xc2, - 0x1c0407, - 0x142c09, - 0x7aec3, - 0x9a048, - 0x20f43, - 0x713233c7, - 0x14a83, - 0x944c8, - 0x32dc3, - 0x108003, - 0x1ab9c6, - 0x1bc83, - 0x92788, - 0xcae48, - 0xcff06, - 0x3c803, - 0xd8cc8, - 0x44b83, - 0x714eb886, - 0xf1c05, - 0x32fc7, - 0x1a3c3, - 0x11003, - 0x42543, - 0xeb02, - 0x17cd0a, - 0x2cc3, - 0xeda43, - 0x10a104, - 0x117acb, - 0x118088, - 0x90602, - 0x14540c7, - 0x15636c7, - 0x14cee08, - 0x14cf583, - 0x143acb, - 0xb342, - 0x12bcc7, - 0x11b504, + 0x1c9e47, + 0x14b509, + 0x3043, + 0xae888, + 0x10bec3, + 0x72727c47, + 0x2ea43, + 0xaf88, + 0x33fc3, + 0x66a83, + 0x3fec6, + 0x191c3, + 0x56288, + 0xd0ac8, + 0x7dec6, + 0x729a7285, + 0x3cb03, + 0xdb008, + 0x3fa83, + 0x72cedbc6, + 0xf2ec5, + 0x124d04, + 0x341c7, + 0x17fc3, + 0x3443, + 0x3e083, + 0x1b02, + 0x182c8a, + 0x9c43, + 0x732c3f4c, + 0x120303, + 0x5d884, + 0x11af8b, + 0x11b548, + 0x95f42, + 0x17ff03, + 0x1454747, + 0x15b4247, + 0x14d5248, + 0x157ff03, + 0x18b7c8, + 0x156ecb, + 0x10382, + 0x131247, + 0x181584, 0x2000c2, - 0x201242, - 0x235b44, - 0x308003, - 0x23c803, - 0x21a3c3, - 0x242543, - 0x214a83, - 0x232dc3, - 0x308003, - 0x2137c3, - 0x21a3c3, - 0x242543, - 0x215d43, - 0x20e2c3, - 0x2fb03, - 0x214a83, - 0x232dc3, - 0x308003, - 0x21a3c3, - 0x242543, - 0x214a83, - 0x232dc3, - 0x308003, - 0x21a3c3, - 0x242543, + 0x212402, + 0x236704, + 0x266a83, + 0x23cb03, + 0x217fc3, + 0x23e083, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x215f83, + 0x217fc3, + 0x23e083, + 0x20d403, + 0x208503, + 0xe783, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x217fc3, + 0x23e083, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x217fc3, + 0x23e083, 0x602, - 0x3a83, - 0x108003, - 0x214a83, - 0x232dc3, - 0x308003, - 0x221dc4, - 0x2137c3, - 0x21a3c3, - 0x242543, - 0x20c782, + 0x10103, + 0x66a83, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x20e704, + 0x215f83, + 0x217fc3, + 0x23e083, + 0x21fcc2, 0x2000c1, 0x2000c2, 0x200201, - 0x332182, - 0x9a048, - 0x21ca05, + 0x337f82, + 0xae888, + 0x21aa05, 0x200101, - 0x14a83, - 0x2fe44, - 0x201301, + 0x2ea43, + 0x319c4, + 0x201381, 0x200501, - 0x205dc1, - 0x246bc2, - 0x387fc4, - 0x246bc3, + 0x201281, + 0x248442, + 0x38e084, + 0x248443, 0x200041, 0x200801, 0x200181, 0x200701, - 0x302fc7, - 0x30e38f, - 0x3ccc06, + 0x35c3c7, + 0x30fccf, + 0x39af46, 0x2004c1, - 0x322546, + 0x326dc6, 0x200bc1, 0x200581, - 0x3affce, + 0x3de50e, 0x2003c1, - 0x242543, + 0x23e083, 0x200a81, - 0x34c105, - 0x20eb02, - 0x2431c5, + 0x32b305, + 0x201b02, + 0x243bc5, 0x200401, 0x200741, 0x2007c1, - 0x233442, + 0x22dc42, 0x200081, - 0x201341, - 0x204f01, - 0x201b41, - 0x201441, - 0x50849, - 0x9a048, - 0x214a83, - 0x232dc3, - 0x308003, - 0x21a3c3, - 0x242543, - 0x214903, - 0x214a83, - 0x308003, - 0x90548, - 0x23c803, - 0x21a3c3, - 0x7283, - 0x242543, - 0x14f62c8, - 0x1e0603, - 0xa788, - 0x139b05, - 0x9a048, - 0x3dc3, - 0x139b05, - 0xcd184, - 0xd1488, - 0x455c4, - 0xcf487, - 0xd3b05, - 0x50849, - 0x11d287, - 0x14f62ca, - 0x9a048, - 0x1b4103, - 0x214a83, - 0x232dc3, - 0x308003, - 0x21a3c3, - 0x242543, - 0x220dc3, - 0x9a048, - 0x214a83, - 0x232dc3, - 0x2e3504, - 0x242543, - 0x24f8c5, - 0x2d0204, - 0x214a83, - 0x232dc3, - 0x308003, - 0x209e82, - 0x21a3c3, - 0x242543, - 0xe2c3, - 0xac60a, - 0xe8006, - 0x102804, - 0x122046, - 0x202703, - 0x214a83, - 0x232dc3, - 0x308003, - 0x21a3c3, - 0x242543, - 0x201242, - 0x214a83, - 0x2303c9, - 0x232dc3, - 0x2aa909, - 0x308003, - 0x23c803, - 0x21a3c3, - 0x78a84, - 0x3dc3, - 0x242543, - 0x2fe5c8, - 0x23c207, - 0x208805, - 0xdbc48, - 0x1d4408, - 0x1c0407, - 0xf81ca, - 0x6f98b, - 0x122787, - 0x3f888, - 0xd174a, - 0xf648, - 0x142c09, - 0x27a07, - 0x1fa87, - 0x3ec8, - 0x944c8, - 0x40d4f, - 0x3ad45, - 0x947c7, - 0x1ab9c6, - 0x3cd87, - 0x1dd2c6, - 0x92788, - 0x99786, - 0x1147, - 0x2fc9, - 0x18ab07, - 0x179dc9, - 0xc2749, - 0xc8d06, - 0xcae48, - 0xdbf05, - 0x7b74a, - 0xd8cc8, - 0x44b83, - 0xe07c8, - 0x32fc7, - 0x95d05, - 0x51590, - 0x11003, - 0x1b4103, - 0x2e47, - 0x1acc5, - 0xf9888, - 0x66605, - 0xeda43, - 0x1cb7c8, - 0xee06, - 0x32109, - 0xb2b87, - 0x68e0b, - 0x6cc44, - 0x111444, - 0x117acb, - 0x118088, - 0x118e87, - 0x139b05, - 0x214a83, - 0x232dc3, - 0x228503, - 0x242543, - 0x23e343, - 0x308003, - 0x1b4103, - 0x214a83, - 0x232dc3, - 0x308003, - 0x23c803, - 0x21a3c3, - 0x242543, - 0x91e4b, + 0x207d01, + 0x20a8c1, + 0x202341, + 0x201c41, + 0x51709, + 0xae888, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x217fc3, + 0x23e083, + 0x217c83, + 0x22ea43, + 0x266a83, + 0x95e88, + 0x23cb03, + 0x217fc3, + 0x91043, + 0x23e083, + 0x76ef8008, + 0x1e0f83, + 0xff48, + 0x10402, + 0xb9c3, + 0x293c2, + 0x72c2, + 0x146bc5, + 0xae888, + 0x11fe87, + 0x5803, + 0x146bc5, + 0x175d84, + 0x7f448, + 0x46d04, + 0x17fe07, + 0x1c4104, + 0xd5e45, + 0x51709, + 0x1424c7, + 0x5aa4a, + 0x14f800a, + 0xae888, + 0x1c0443, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x217fc3, + 0x23e083, + 0x233f03, + 0xae888, + 0x22ea43, + 0x233fc3, + 0x2e5904, + 0x23e083, + 0x24a845, + 0x27e1c4, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x20b982, + 0x217fc3, + 0x23e083, + 0x8503, + 0xaefca, + 0xea706, + 0x11fa04, + 0x1268c6, + 0x24ac43, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x217fc3, + 0x23e083, + 0x212402, + 0x22ea43, + 0x231f49, + 0x233fc3, + 0x2ac549, + 0x266a83, + 0x23cb03, + 0x217fc3, + 0x81004, + 0x5803, + 0x23e083, + 0x3008c8, + 0x239307, + 0x37b845, + 0xde248, + 0x1d6e08, + 0x1c9e47, + 0xf9cca, + 0x7650b, + 0x127007, + 0x40488, + 0x7f70a, + 0x25e48, + 0x14b509, + 0x25887, + 0x14dcc7, + 0x1b5208, + 0xaf88, + 0x4164f, + 0xa6845, + 0x148647, + 0x3fec6, + 0x36487, + 0x12ea86, + 0x56288, + 0x9a346, + 0x17dc87, + 0x1c1989, + 0x1cd6c7, + 0x19c009, + 0xc9049, + 0xce646, + 0xd0ac8, + 0xde505, + 0x8350a, + 0xdb008, + 0x3fa83, + 0xe2988, + 0x341c7, + 0x156b05, + 0x61950, + 0x3443, + 0x1c0443, + 0x17db07, + 0x2cd05, + 0xfb388, + 0x6db85, + 0x120303, + 0x1d1048, + 0xb2c06, + 0x33249, + 0xb6f47, + 0x240b, + 0x72344, + 0x113c44, + 0x11af8b, + 0x11b548, + 0x11be07, + 0x146bc5, + 0x22ea43, + 0x233fc3, + 0x280203, + 0x23e083, + 0x23e883, + 0x266a83, + 0x1c0443, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x23cb03, + 0x217fc3, + 0x23e083, + 0x1b1e8b, 0x2000c2, - 0x201242, - 0x242543, + 0x212402, + 0x23e083, 0xd42, - 0x9e82, - 0x7782, - 0x9a048, - 0x1b3089, - 0x1242, + 0xb982, + 0x83c2, + 0xae888, + 0x1357c9, + 0x18b7c8, + 0x12402, 0x2000c2, - 0x201242, + 0x212402, 0x200382, 0x2005c2, - 0x210942, - 0x21a3c3, - 0x13f246, + 0x204482, + 0x217fc3, + 0x14a9c6, 0x2003c2, - 0x3e804, + 0xddf84, 0x2000c2, - 0x202703, - 0x201242, - 0x214a83, - 0x232dc3, + 0x24ac43, + 0x212402, + 0x22ea43, + 0x233fc3, 0x200382, - 0x308003, - 0x21bc83, - 0x23c803, - 0x219a04, - 0x21a3c3, - 0x2125c3, - 0x3dc3, - 0x242543, - 0x30a104, - 0x207783, - 0x308003, - 0x201242, - 0x214a83, - 0x232dc3, - 0x308003, - 0x23c803, - 0x21a3c3, - 0x203dc3, - 0x242543, - 0x3bd187, - 0x214a83, - 0x27b0c7, - 0x397206, - 0x216c83, - 0x21bb43, - 0x308003, - 0x206c03, - 0x221dc4, - 0x28a404, - 0x3383c6, - 0x213a43, - 0x21a3c3, - 0x242543, - 0x24f8c5, - 0x2af384, - 0x323b43, - 0x2ce043, - 0x2d44c7, - 0x2cd845, - 0x68703, - 0x214a83, - 0x232dc3, - 0x308003, - 0x23c803, - 0x21a3c3, - 0x6e544, - 0x242543, - 0x14583, - 0x7c30988c, - 0x53547, - 0xe4846, - 0x91487, - 0x67f05, - 0x202b02, - 0x247403, - 0x214f03, - 0x202703, - 0x7ce14a83, - 0x208c02, - 0x232dc3, - 0x207083, - 0x308003, - 0x221dc4, - 0x2059c3, - 0x21b6c3, - 0x23c803, - 0x219a04, - 0x7d20ce02, - 0x21a3c3, - 0x242543, - 0x2308c3, - 0x21bd03, - 0x21a443, - 0x20c782, - 0x207783, - 0x9a048, - 0x308003, - 0x3a83, - 0x2135c4, - 0x202703, - 0x201242, - 0x214a83, - 0x235b44, - 0x232dc3, - 0x308003, - 0x221dc4, - 0x21bc83, - 0x3473c4, - 0x306c44, - 0x2e2406, - 0x219a04, - 0x21a3c3, - 0x242543, - 0x2141c3, - 0x253c46, - 0x34c8b, - 0x28886, - 0xec90a, - 0x11b94a, - 0x9a048, - 0x2136c4, - 0x7e614a83, - 0x2026c4, - 0x232dc3, - 0x270744, - 0x308003, - 0x2f3983, - 0x23c803, - 0x21a3c3, - 0x1b4103, - 0x242543, - 0x4cbc3, - 0x347f8b, - 0x3ca20a, - 0x3e010c, - 0xebe48, + 0x266a83, + 0x2191c3, + 0x23cb03, + 0x21e484, + 0x217fc3, + 0x213cc3, + 0x5803, + 0x23e083, + 0x25d884, + 0x20aa43, + 0x266a83, + 0x212402, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x23cb03, + 0x217fc3, + 0x205803, + 0x23e083, + 0x3c48c7, + 0x22ea43, + 0x282e87, + 0x394e06, + 0x208483, + 0x20fa03, + 0x266a83, + 0x204903, + 0x20e704, + 0x28f784, + 0x32bac6, + 0x208243, + 0x217fc3, + 0x23e083, + 0x24a845, + 0x2b21c4, + 0x3283c3, + 0x356703, + 0x2d6807, + 0x355f05, + 0x1d03, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x23cb03, + 0x217fc3, + 0x75504, + 0x23e083, + 0x16d43, + 0x7e308dcc, + 0x4e803, + 0x192c07, + 0xe6946, + 0x19dfc7, + 0x157585, + 0x222b02, + 0x23de83, + 0x20c5c3, + 0x24ac43, + 0x7ee2ea43, + 0x204302, + 0x233fc3, + 0x2033c3, + 0x266a83, + 0x20e704, + 0x3433c3, + 0x22d703, + 0x23cb03, + 0x21e484, + 0x7f216102, + 0x217fc3, + 0x23e083, + 0x204ac3, + 0x219243, + 0x218043, + 0x21fcc2, + 0x20aa43, + 0xae888, + 0x266a83, + 0x10103, + 0x215d84, + 0x24ac43, + 0x212402, + 0x22ea43, + 0x236704, + 0x233fc3, + 0x266a83, + 0x20e704, + 0x2191c3, + 0x33f584, + 0x217544, + 0x2e4806, + 0x21e484, + 0x217fc3, + 0x23e083, + 0x216983, + 0x269f86, + 0x3a9cb, + 0x36fc6, + 0xbd84a, + 0x11f48a, + 0xae888, + 0x215e84, + 0x8062ea43, + 0x37e504, + 0x233fc3, + 0x296d84, + 0x266a83, + 0x2f4c43, + 0x23cb03, + 0x217fc3, + 0x1c0443, + 0x23e083, + 0x54543, + 0x34a60b, + 0x3cf9ca, + 0x3e0a8c, + 0xee488, 0x2000c2, - 0x201242, + 0x212402, 0x200382, - 0x22d805, - 0x221dc4, - 0x209e82, - 0x23c803, - 0x306c44, - 0x208482, + 0x22f845, + 0x20e704, + 0x20b982, + 0x23cb03, + 0x217544, + 0x203182, 0x2003c2, - 0x2090c2, - 0x20c782, - 0x2703, - 0x15702, - 0x2cb209, - 0x365308, - 0x307e89, - 0x2073c9, - 0x20b40a, - 0x210f0a, - 0x206082, - 0x2167c2, - 0x1242, - 0x214a83, - 0x22ba02, - 0x240c46, - 0x377dc2, - 0x208d42, - 0x26fd0e, - 0x21410e, - 0x27e307, - 0x21a347, - 0x24dc82, - 0x232dc3, - 0x308003, - 0x210d82, + 0x208502, + 0x21fcc2, + 0x4ac43, + 0xcdc2, + 0x2d0e89, + 0x36c648, + 0x266909, + 0x213d49, + 0x2184ca, + 0x30aaca, + 0x209482, + 0x361e82, + 0x12402, + 0x22ea43, + 0x22c982, + 0x241546, + 0x37c682, + 0x2013c2, + 0x27688e, + 0x2168ce, + 0x217f47, + 0x211c42, + 0x233fc3, + 0x266a83, + 0x20f342, 0x2005c2, - 0x1bac3, - 0x235d4f, - 0x21fb02, - 0x2b8887, - 0x3520c7, - 0x2bc287, - 0x2e9ccc, - 0x2dc18c, - 0x20c304, - 0x38d04a, - 0x214042, - 0x201e42, - 0x2c5104, + 0xe703, + 0x23690f, + 0x241882, + 0x2c3587, + 0x2ec3c7, + 0x2de787, + 0x2e2b4c, + 0x2f024c, + 0x21f844, + 0x39334a, + 0x216802, + 0x206902, + 0x2cac84, 0x200702, - 0x2cc342, - 0x2dc3c4, - 0x20fe82, - 0x202042, - 0x1a8c3, - 0x299807, - 0x23a705, - 0x223502, - 0x23cd04, - 0x203f82, - 0x2eba08, - 0x21a3c3, - 0x376b08, - 0x2029c2, - 0x20c4c5, - 0x396e06, - 0x242543, - 0x20a882, - 0x2fa607, - 0xeb02, - 0x39e785, - 0x3c2f45, - 0x206442, - 0x20b382, - 0x33a90a, - 0x35094a, - 0x23c7c2, - 0x2a4284, - 0x203282, - 0x3d3788, - 0x20a5c2, - 0x359208, - 0xf01, - 0x314447, - 0x3149c9, - 0x2b7402, - 0x319985, - 0x3b9c45, - 0x2184cb, - 0x33894c, - 0x22c088, - 0x32bb08, - 0x21d642, - 0x2ac882, + 0x2c23c2, + 0x2f0484, + 0x214882, + 0x202702, + 0x1e1c3, + 0x29a3c7, + 0x30eac5, + 0x204202, + 0x236404, + 0x393b42, + 0x2edd48, + 0x217fc3, + 0x37b588, + 0x203242, + 0x21fa05, + 0x39da86, + 0x23e083, + 0x204fc2, + 0x2fc207, + 0x1b02, + 0x34bbc5, + 0x3dce85, + 0x20b502, + 0x206c82, + 0x34754a, + 0x28de4a, + 0x23cac2, + 0x2a39c4, + 0x200f02, + 0x21dd88, + 0x207ac2, + 0x31c248, + 0x1501, + 0x316b47, + 0x3175c9, + 0x2bb102, + 0x31d505, + 0x36ed45, + 0x212acb, + 0x32c04c, + 0x22c488, + 0x331088, + 0x226642, + 0x2af242, 0x2000c2, - 0x9a048, - 0x201242, - 0x214a83, + 0xae888, + 0x212402, + 0x22ea43, 0x200382, - 0x208482, - 0x3dc3, + 0x203182, + 0x5803, 0x2003c2, - 0x242543, - 0x2090c2, + 0x23e083, + 0x208502, 0x2000c2, - 0x139b05, - 0x7fa01242, - 0x1099c4, - 0x37e85, - 0x80708003, - 0x21a8c3, - 0x209e82, - 0x21a3c3, - 0x3b68c3, - 0x80a42543, - 0x2f7103, - 0x274d46, - 0x160e2c3, - 0x139b05, - 0x13f10b, - 0x9a048, - 0x7ff02e08, - 0x5c1c7, - 0x802c108a, - 0x6ddc7, - 0x97805, - 0x2a54d, - 0x8e242, - 0x118682, + 0x146bc5, + 0x81a12402, + 0x108f04, + 0x37e05, + 0x82a66a83, + 0x21e1c3, + 0x20b982, + 0x217fc3, + 0x3d6203, + 0x82e3e083, + 0x2f8e43, + 0x27a906, + 0x1608503, + 0x146bc5, + 0x14a88b, + 0xae888, + 0x81f64008, + 0x68f47, + 0x822c6aca, + 0x74d87, + 0x1b5a05, + 0x82600f89, + 0x2c10d, + 0x3fcc2, + 0x11bb42, 0xe01, - 0xad28a, - 0x150787, - 0x20c04, - 0x20c43, - 0x3c704, - 0x81202842, - 0x81600ac2, - 0x81a01182, - 0x81e02d02, - 0x82206b42, - 0x826086c2, - 0x17e707, - 0x82a01242, - 0x82e2ec02, - 0x8321f2c2, - 0x83602a42, - 0x214103, - 0xca04, - 0x22dcc3, - 0x83a0e442, - 0x5a988, - 0x83e015c2, - 0x4e2c7, - 0x1ad887, - 0x84200042, - 0x84600d82, - 0x84a00182, - 0x84e06182, - 0x85200f42, - 0x856005c2, - 0xf4185, - 0x24dec3, - 0x35c004, - 0x85a00702, - 0x85e14d42, - 0x86206002, - 0x7a04b, - 0x86600c42, - 0x86e01f42, - 0x87209e82, - 0x87610942, - 0x87a1d542, - 0x87e00bc2, - 0x88202382, - 0x8866c9c2, - 0x88a0ce02, - 0x88e03142, - 0x89208482, - 0x89637242, - 0x89a510c2, - 0x89e43802, - 0x5684, - 0x310543, - 0x8a200ec2, - 0x8a610e82, - 0x8aa0e742, - 0x8ae006c2, - 0x8b2003c2, - 0x8b600a82, - 0xf6bc8, - 0x91fc7, - 0x8ba141c2, - 0x8be06142, - 0x8c2090c2, - 0x8c6023c2, - 0x1aec8c, - 0x8ca02c02, - 0x8ce25b82, - 0x8d20f482, - 0x8d603642, - 0x8da00f02, - 0x8de0b582, - 0x8e201342, - 0x8e607302, - 0x8ea76002, - 0x8ee76542, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x2059c3, - 0x23703, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x223703, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x86a059c3, - 0x223703, - 0x3c0b44, - 0x307d86, - 0x306403, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x2059c3, - 0x223703, - 0x374689, - 0x215702, - 0x39b6c3, - 0x2c2a43, - 0x2894c5, - 0x207083, - 0x2059c3, - 0x223703, - 0x267d83, - 0x211243, - 0x3c4409, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x223703, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x223703, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x223703, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x2059c3, - 0x223703, - 0x215702, - 0x215702, - 0x2059c3, - 0x223703, - 0x8f614a83, - 0x232dc3, - 0x207603, - 0x23c803, - 0x21a3c3, - 0x3dc3, - 0x242543, - 0x9a048, - 0x201242, - 0x214a83, - 0x21a3c3, - 0x242543, - 0x141842, - 0x214a83, - 0x232dc3, - 0x308003, - 0x901192c2, - 0x23c803, - 0x21a3c3, - 0x3dc3, - 0x242543, - 0x1301, - 0x24c0c4, - 0x201242, - 0x214a83, + 0x107784, + 0xb018a, + 0x8dc87, + 0x10bb84, + 0x3ca03, + 0x3ca04, + 0x83603d82, + 0x83a00ac2, + 0x83e03502, + 0x84202e42, + 0x846074c2, + 0x84a0cf02, + 0x178d87, + 0x84e12402, + 0x85211d02, + 0x8561c782, + 0x85a0f982, + 0x2168c3, + 0x1ff44, + 0x28c543, + 0x85e12882, + 0x5a388, + 0x86207c82, + 0x4e007, + 0x1b77c7, + 0x86600042, + 0x86a00d82, + 0x86e00182, + 0x87209582, + 0x8760f782, + 0x87a005c2, + 0xfdd45, + 0x24dc03, + 0x3612c4, + 0x87e00702, + 0x8820a342, + 0x88601582, + 0x8d64b, + 0x88a00c42, + 0x89206a02, + 0x8960b982, + 0x89a04482, + 0x89e15782, + 0x8a200bc2, + 0x8a60a942, + 0x8aa720c2, + 0x8ae16102, + 0x8b201602, + 0x8b603182, + 0x8ba37282, + 0x8be05402, + 0x8c209ec2, + 0x1583c4, + 0x3169c3, + 0x8c634e42, + 0x8ca0f442, + 0x8ce03742, + 0x8d2006c2, + 0x8d6003c2, + 0x8da00a82, + 0xf8908, + 0x1b2007, + 0x8de16982, + 0x8e205302, + 0x8e608502, + 0x8ea0a1c2, + 0x1b800c, + 0x8ee03d02, + 0x8f224e42, + 0x8f601942, + 0x8fa034c2, + 0x8fe0e482, + 0x90203b02, + 0x90607d02, + 0x90a16382, + 0x90e7bcc2, + 0x9127c2c2, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x3433c3, + 0x220c3, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x2220c3, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x88f433c3, + 0x2220c3, + 0x3c7a04, + 0x266806, + 0x307183, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x26e9c9, + 0x20cdc2, + 0x3b3843, + 0x2c9343, + 0x260045, + 0x2033c3, + 0x3433c3, + 0x2220c3, + 0x2b8a83, + 0x20de03, + 0x3679c9, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x2220c3, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x2220c3, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x2220c3, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x20cdc2, + 0x20cdc2, + 0x3433c3, + 0x2220c3, + 0x91a2ea43, + 0x233fc3, + 0x213f83, + 0x23cb03, + 0x217fc3, + 0x5803, + 0x23e083, + 0xae888, + 0x212402, + 0x22ea43, + 0x217fc3, + 0x23e083, + 0x6e842, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x924f6e82, + 0x23cb03, + 0x217fc3, + 0x5803, + 0x23e083, + 0x1381, + 0x241ec4, + 0x212402, + 0x22ea43, 0x200983, - 0x232dc3, - 0x24d9c4, - 0x228503, - 0x308003, - 0x221dc4, - 0x21bc83, - 0x23c803, - 0x21a3c3, - 0x242543, - 0x22b983, - 0x208805, - 0x211243, - 0x207783, + 0x233fc3, + 0x24d704, + 0x280203, + 0x266a83, + 0x20e704, + 0x2191c3, + 0x23cb03, + 0x217fc3, + 0x23e083, + 0x235403, + 0x37b845, + 0x20de03, + 0x20aa43, 0x882, - 0x3dc3, - 0x201242, - 0x214a83, - 0x2059c3, - 0x21a3c3, - 0x242543, + 0x5803, + 0x212402, + 0x22ea43, + 0x3433c3, + 0x217fc3, + 0x23e083, 0x2000c2, - 0x202703, - 0x9a048, - 0x214a83, - 0x232dc3, - 0x308003, - 0x22f7c6, - 0x221dc4, - 0x21bc83, - 0x219a04, - 0x21a3c3, - 0x242543, - 0x2141c3, - 0x29904, - 0x214a83, - 0x239c3, - 0x232dc3, - 0x9e82, - 0x21a3c3, - 0x242543, - 0x2aec2, - 0x2982, - 0x147ee07, - 0x1807, - 0x214a83, - 0x28886, - 0x232dc3, - 0x308003, - 0xedf86, - 0x21a3c3, - 0x242543, - 0x328e48, - 0x32b949, - 0x33e209, - 0x34ae08, - 0x399488, - 0x399489, - 0x322c0a, - 0x3602ca, - 0x39424a, - 0x39abca, - 0x3ca20a, - 0x3d818b, - 0x30838d, - 0x23fd4f, - 0x35d210, - 0x361d4d, - 0x37d8cc, - 0x39a90b, - 0x6dfc8, - 0x11d548, - 0x19d705, - 0x1486107, - 0xd7445, + 0x24ac43, + 0xae888, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x231346, + 0x20e704, + 0x2191c3, + 0x21e484, + 0x217fc3, + 0x23e083, + 0x216983, + 0x4cc4, + 0x161e82, + 0x22ea43, + 0x22383, + 0x233fc3, + 0xb982, + 0x217fc3, + 0x23e083, + 0x30242, + 0x2a82, + 0x1481bc7, + 0x8cc7, + 0x22ea43, + 0x36fc6, + 0x233fc3, + 0x266a83, + 0xf07c6, + 0x217fc3, + 0x23e083, + 0x32d948, + 0x330ec9, + 0x341dc9, + 0x34cfc8, + 0x3a01c8, + 0x3a01c9, + 0x32748a, + 0x3657ca, + 0x399a4a, + 0x3a124a, + 0x3cf9ca, + 0x3db28b, + 0x26604d, + 0x230b0f, + 0x240950, + 0x3692cd, + 0x38384c, + 0x3a0f8b, + 0x74f88, + 0xf6cc8, + 0xbe1c5, + 0x148e8c7, + 0xd9785, 0x2000c2, - 0x2cd685, - 0x202003, - 0x93601242, - 0x232dc3, - 0x308003, - 0x27e7c7, - 0x25cc03, - 0x23c803, - 0x21a3c3, - 0x24e283, - 0x2125c3, - 0x206a03, - 0x203dc3, - 0x242543, - 0x252b06, - 0x233442, - 0x207783, - 0x9a048, + 0x355d45, + 0x21e183, + 0x95a12402, + 0x233fc3, + 0x266a83, + 0x232b87, + 0x21df43, + 0x23cb03, + 0x217fc3, + 0x24dfc3, + 0x213cc3, + 0x210ec3, + 0x205803, + 0x23e083, + 0x2509c6, + 0x22dc42, + 0x20aa43, + 0xae888, 0x2000c2, - 0x202703, - 0x201242, - 0x214a83, - 0x232dc3, - 0x308003, - 0x221dc4, - 0x23c803, - 0x21a3c3, - 0x242543, - 0x20e2c3, - 0x1807, - 0xb342, - 0x68b44, - 0x151c306, + 0x24ac43, + 0x212402, + 0x22ea43, + 0x233fc3, + 0x266a83, + 0x20e704, + 0x23cb03, + 0x217fc3, + 0x23e083, + 0x208503, + 0x8cc7, + 0x10382, + 0x2144, + 0x1517446, 0x2000c2, - 0x201242, - 0x308003, - 0x23c803, - 0x242543, + 0x212402, + 0x266a83, + 0x23cb03, + 0x23e083, } // children is the list of nodes' children, the parent's wildcard bit and the @@ -9554,597 +9614,606 @@ var children = [...]uint32{ 0x40000000, 0x50000000, 0x60000000, - 0x17ec5f5, - 0x17f05fb, - 0x17f45fc, - 0x18185fd, - 0x1970606, - 0x198865c, - 0x199c662, - 0x19b4667, - 0x19d466d, - 0x19f4675, - 0x1a0c67d, - 0x1a2c683, - 0x1a3068b, - 0x1a5868c, + 0x17d05ee, + 0x17d45f4, + 0x17d85f5, + 0x17fc5f6, + 0x19545ff, + 0x196c655, + 0x198065b, + 0x1998660, + 0x19b8666, + 0x19d866e, + 0x19f0676, + 0x1a1067c, + 0x1a14684, + 0x1a3c685, + 0x1a4068f, + 0x1a58690, 0x1a5c696, - 0x1a74697, - 0x1a7869d, - 0x1a7c69e, - 0x1ab869f, - 0x1abc6ae, - 0x1ac06af, - 0x61ac86b0, - 0x21ad06b2, - 0x1b186b4, - 0x1b1c6c6, - 0x1b406c7, - 0x1b446d0, + 0x1a60697, + 0x1aa0698, + 0x1aa46a8, + 0x1aa86a9, + 0x21aac6aa, + 0x61ab46ab, + 0x21abc6ad, + 0x1b046af, + 0x1b086c1, + 0x1b2c6c2, + 0x1b306cb, + 0x1b446cc, 0x1b486d1, - 0x1b5c6d2, - 0x1b606d7, - 0x1b806d8, - 0x1bb06e0, - 0x1bcc6ec, - 0x1bf46f3, - 0x1c046fd, - 0x1c08701, - 0x1ca0702, - 0x1cb4728, - 0x1cc872d, - 0x1d00732, - 0x1d10740, - 0x1d24744, - 0x1d3c749, - 0x1de074f, - 0x1fe4778, - 0x1fe87f9, - 0x20547fa, - 0x20c0815, - 0x20d8830, - 0x20ec836, - 0x20f083b, - 0x20f883c, - 0x210c83e, - 0x2110843, - 0x2130844, - 0x218084c, - 0x2184860, - 0x22188861, - 0x21a4862, - 0x21a8869, - 0x21ac86a, - 0x21d086b, - 0x2214874, - 0x2218885, - 0x6221c886, - 0x2238887, - 0x226488e, - 0x2270899, - 0x228089c, - 0x23348a0, - 0x23388cd, - 0x223488ce, - 0x2234c8d2, - 0x223548d3, - 0x23ac8d5, - 0x23b08eb, - 0x23b48ec, - 0x28dc8ed, - 0x28e0a37, - 0x22988a38, - 0x2298ca62, - 0x22990a63, - 0x2299ca64, - 0x229a0a67, - 0x229aca68, - 0x229b0a6b, - 0x229b4a6c, - 0x229b8a6d, - 0x229bca6e, - 0x229c0a6f, - 0x229cca70, - 0x229d0a73, - 0x229dca74, - 0x229e0a77, - 0x229e4a78, - 0x229e8a79, - 0x229f4a7a, - 0x229f8a7d, - 0x22a04a7e, - 0x22a08a81, - 0x22a0ca82, + 0x1b686d2, + 0x1b986da, + 0x1bb46e6, + 0x1bdc6ed, + 0x1bec6f7, + 0x1bf06fb, + 0x1c886fc, + 0x1c9c722, + 0x1cb0727, + 0x1ce872c, + 0x1cf873a, + 0x1d0c73e, + 0x1d24743, + 0x1dc8749, + 0x1ffc772, + 0x20007ff, + 0x206c800, + 0x20d881b, + 0x20f0836, + 0x210483c, + 0x2108841, + 0x2110842, + 0x2124844, + 0x2128849, + 0x214884a, + 0x2198852, + 0x219c866, + 0x221a0867, + 0x21c0868, + 0x21c4870, + 0x21c8871, + 0x21f0872, + 0x621f487c, + 0x223887d, + 0x223c88e, + 0x6224088f, + 0x225c890, + 0x228c897, + 0x229c8a3, + 0x22ac8a7, + 0x23608ab, + 0x23648d8, + 0x223748d9, + 0x223788dd, + 0x223808de, + 0x23d88e0, + 0x23dc8f6, + 0x23e08f7, + 0x29348f8, + 0x2938a4d, + 0x62940a4e, + 0x229e8a50, + 0x229eca7a, + 0x229f0a7b, + 0x229fca7c, + 0x22a00a7f, + 0x22a0ca80, 0x22a10a83, - 0x2a14a84, + 0x22a14a84, 0x22a18a85, - 0x22a24a86, - 0x22a28a89, - 0x2a2ca8a, - 0x2a34a8b, - 0x62a40a8d, - 0x2a84a90, - 0x22aa4aa1, - 0x22aa8aa9, - 0x22aacaaa, - 0x22ab0aab, - 0x22ab8aac, - 0x22abcaae, - 0x2ac0aaf, - 0x22ac4ab0, - 0x22ac8ab1, - 0x22accab2, - 0x22ad0ab3, - 0x2ad8ab4, - 0x2ae0ab6, - 0x2ae4ab8, - 0x2b00ab9, - 0x2b18ac0, - 0x2b1cac6, - 0x2b2cac7, - 0x2b38acb, - 0x2b6cace, - 0x2b74adb, - 0x22b78add, - 0x2b90ade, - 0x22b98ae4, - 0x22b9cae6, - 0x22ba4ae7, - 0x2ca0ae9, - 0x22ca4b28, - 0x2cacb29, - 0x2cb0b2b, - 0x22cb4b2c, - 0x2cb8b2d, - 0x2ce0b2e, - 0x2ce4b38, - 0x2ce8b39, - 0x2cecb3a, - 0x2d04b3b, - 0x2d18b41, - 0x2d40b46, - 0x2d60b50, - 0x2d64b58, - 0x62d68b59, - 0x2d9cb5a, - 0x2da0b67, - 0x22da4b68, - 0x2da8b69, - 0x2dd0b6a, - 0x2dd4b74, - 0x2df8b75, - 0x2dfcb7e, - 0x2e10b7f, - 0x2e14b84, - 0x2e18b85, - 0x2e38b86, - 0x2e54b8e, + 0x22a1ca86, + 0x22a20a87, + 0x22a2ca88, + 0x22a30a8b, + 0x22a3ca8c, + 0x22a40a8f, + 0x22a44a90, + 0x22a48a91, + 0x22a54a92, + 0x22a58a95, + 0x22a64a96, + 0x22a68a99, + 0x22a6ca9a, + 0x22a70a9b, + 0x2a74a9c, + 0x22a78a9d, + 0x22a84a9e, + 0x22a88aa1, + 0x2a8caa2, + 0x2a94aa3, + 0x62aa0aa5, + 0x2ae4aa8, + 0x22b04ab9, + 0x22b08ac1, + 0x22b0cac2, + 0x22b10ac3, + 0x22b14ac4, + 0x22b1cac5, + 0x22b20ac7, + 0x2b24ac8, + 0x22b44ac9, + 0x22b48ad1, + 0x22b4cad2, + 0x22b50ad3, + 0x22b54ad4, + 0x22b58ad5, + 0x2b60ad6, + 0x2b68ad8, + 0x2b6cada, + 0x2b88adb, + 0x2ba0ae2, + 0x2ba4ae8, + 0x2bb4ae9, + 0x2bc0aed, + 0x2bf4af0, + 0x2bfcafd, + 0x22c00aff, + 0x2c18b00, + 0x22c20b06, + 0x22c24b08, + 0x22c2cb09, + 0x2d28b0b, + 0x22d2cb4a, + 0x2d34b4b, + 0x2d38b4d, + 0x22d3cb4e, + 0x2d40b4f, + 0x2d68b50, + 0x2d6cb5a, + 0x2d70b5b, + 0x2d88b5c, + 0x2d9cb62, + 0x2dc4b67, + 0x2de4b71, + 0x2de8b79, + 0x62decb7a, + 0x2e20b7b, + 0x2e24b88, + 0x22e28b89, + 0x2e2cb8a, + 0x2e54b8b, 0x2e58b95, - 0x22e5cb96, - 0x2e60b97, - 0x2e64b98, - 0x2e68b99, - 0x2e70b9a, - 0x2e84b9c, - 0x2e88ba1, - 0x2e8cba2, - 0x2eb4ba3, - 0x2eb8bad, - 0x2f2cbae, - 0x2f30bcb, - 0x2f34bcc, - 0x2f54bcd, - 0x2f6cbd5, - 0x2f70bdb, - 0x2f84bdc, - 0x2f9cbe1, - 0x2fbcbe7, - 0x2fd4bef, - 0x2fd8bf5, - 0x2ff4bf6, - 0x3010bfd, - 0x3014c04, - 0x3040c05, - 0x3060c10, - 0x3080c18, - 0x30e4c20, + 0x2e7cb96, + 0x2e80b9f, + 0x2e94ba0, + 0x2e98ba5, + 0x2e9cba6, + 0x2ebcba7, + 0x2ed8baf, + 0x2edcbb6, + 0x22ee0bb7, + 0x2ee4bb8, + 0x2ee8bb9, + 0x2eecbba, + 0x2ef4bbb, + 0x2f08bbd, + 0x2f0cbc2, + 0x2f10bc3, + 0x2f38bc4, + 0x2f3cbce, + 0x2fb0bcf, + 0x2fb4bec, + 0x2fb8bed, + 0x2fd8bee, + 0x2ff0bf6, + 0x2ff4bfc, + 0x3008bfd, + 0x3020c02, + 0x3040c08, + 0x3058c10, + 0x305cc16, + 0x3078c17, + 0x3094c1e, + 0x3098c25, + 0x30c4c26, + 0x30e4c31, 0x3104c39, - 0x3120c41, - 0x3124c48, - 0x313cc49, - 0x3180c4f, - 0x3200c60, - 0x3230c80, - 0x3234c8c, - 0x3240c8d, - 0x3260c90, - 0x3264c98, - 0x3288c99, - 0x3290ca2, - 0x32ccca4, - 0x3320cb3, - 0x3324cc8, - 0x3328cc9, - 0x3404cca, - 0x2340cd01, - 0x23410d03, - 0x23414d04, - 0x3418d05, - 0x2341cd06, - 0x23420d07, - 0x3424d08, - 0x23428d09, - 0x23438d0a, - 0x2343cd0e, - 0x23440d0f, - 0x23444d10, - 0x23448d11, - 0x2344cd12, - 0x3464d13, - 0x3488d19, - 0x34a8d22, - 0x3b14d2a, - 0x3b20ec5, - 0x3b40ec8, - 0x3d00ed0, - 0x3dd0f40, - 0x3e40f74, - 0x3e98f90, - 0x3f80fa6, - 0x3fd8fe0, - 0x4014ff6, - 0x4111005, - 0x41dd044, - 0x4275077, - 0x430509d, - 0x43690c1, - 0x45a10da, - 0x4659168, - 0x4725196, - 0x47711c9, - 0x47f91dc, - 0x48351fe, - 0x488520d, - 0x48fd221, - 0x6490123f, - 0x64905240, - 0x64909241, - 0x4985242, - 0x49e1261, - 0x4a5d278, - 0x4ad5297, - 0x4b552b5, - 0x4bc12d5, - 0x4ced2f0, - 0x4d4533b, - 0x64d49351, - 0x4de1352, - 0x4de9378, - 0x24ded37a, - 0x4e7537b, - 0x4ec139d, - 0x4f293b0, - 0x4fd13ca, - 0x50993f4, - 0x5101426, - 0x5215440, - 0x65219485, - 0x6521d486, - 0x5279487, - 0x52d549e, - 0x53654b5, - 0x53e14d9, - 0x54254f8, - 0x5509509, - 0x553d542, - 0x559d54f, - 0x5611567, - 0x5699584, - 0x56d95a6, - 0x57495b6, - 0x6574d5d2, - 0x57755d3, - 0x57795dd, - 0x57a95de, - 0x57c55ea, - 0x58095f1, - 0x5819602, - 0x5831606, - 0x58a960c, - 0x58b162a, - 0x58cd62c, - 0x58e1633, - 0x58fd638, - 0x592963f, - 0x592d64a, - 0x593564b, - 0x594964d, - 0x5969652, - 0x597965a, - 0x598565e, - 0x59c1661, - 0x59c9670, - 0x59dd672, - 0x5a05677, - 0x5a11681, - 0x5a19684, - 0x5a41686, - 0x5a65690, - 0x5a7d699, - 0x5a8169f, - 0x5a896a0, - 0x5a9d6a2, - 0x5b456a7, - 0x5b496d1, - 0x5b4d6d2, - 0x5b516d3, - 0x5b756d4, - 0x5b996dd, - 0x5bb56e6, - 0x5bc96ed, - 0x5bdd6f2, - 0x5be56f7, - 0x5bed6f9, - 0x5bf56fb, - 0x5c0d6fd, - 0x5c1d703, - 0x5c21707, - 0x5c3d708, - 0x64c570f, - 0x64fd931, - 0x652993f, - 0x654594a, - 0x6565951, - 0x6585959, - 0x65c9961, - 0x65d1972, - 0x265d5974, - 0x265d9975, - 0x65e1976, - 0x67cd978, - 0x267d19f3, - 0x67d59f4, - 0x267d99f5, - 0x267e99f6, - 0x267f19fa, - 0x267fd9fc, - 0x68019ff, - 0x26809a00, - 0x6811a02, - 0x6821a04, - 0x6849a08, - 0x6885a12, - 0x6889a21, - 0x68c1a22, - 0x68e5a30, - 0x743da39, - 0x7441d0f, - 0x7445d10, - 0x27449d11, - 0x744dd12, - 0x27451d13, - 0x7455d14, - 0x27461d15, - 0x7465d18, - 0x7469d19, - 0x2746dd1a, - 0x7471d1b, - 0x27479d1c, - 0x747dd1e, - 0x7481d1f, - 0x27491d20, - 0x7495d24, - 0x7499d25, - 0x749dd26, - 0x74a1d27, - 0x274a5d28, - 0x74a9d29, - 0x74add2a, - 0x74b1d2b, - 0x74b5d2c, - 0x274bdd2d, - 0x74c1d2f, - 0x74c5d30, - 0x74c9d31, - 0x274cdd32, - 0x74d1d33, - 0x274d9d34, - 0x274ddd36, - 0x74f9d37, - 0x7511d3e, - 0x7555d44, + 0x3168c41, + 0x3188c5a, + 0x31a8c62, + 0x31acc6a, + 0x31c4c6b, + 0x3208c71, + 0x3288c82, + 0x32b8ca2, + 0x32bccae, + 0x32c8caf, + 0x32e8cb2, + 0x32eccba, + 0x3310cbb, + 0x3318cc4, + 0x3354cc6, + 0x33a8cd5, + 0x33accea, + 0x33b0ceb, + 0x3494cec, + 0x2349cd25, + 0x234a0d27, + 0x234a4d28, + 0x34a8d29, + 0x234acd2a, + 0x234b0d2b, + 0x34b4d2c, + 0x234b8d2d, + 0x234c8d2e, + 0x234ccd32, + 0x234d0d33, + 0x234d4d34, + 0x234d8d35, + 0x234dcd36, + 0x34f4d37, + 0x3518d3d, + 0x3538d46, + 0x3ba4d4e, + 0x3bb0ee9, + 0x3bd0eec, + 0x3d90ef4, + 0x3e60f64, + 0x3ed0f98, + 0x3f28fb4, + 0x4010fca, + 0x4069004, + 0x40a501a, + 0x41a1029, + 0x426d068, + 0x430509b, + 0x43950c1, + 0x43f90e5, + 0x46310fe, + 0x46e918c, + 0x47b51ba, + 0x48011ed, + 0x4889200, + 0x48c5222, + 0x4915231, + 0x498d245, + 0x64991263, + 0x64995264, + 0x64999265, + 0x4a15266, + 0x4a71285, + 0x4aed29c, + 0x4b652bb, + 0x4be52d9, + 0x4c512f9, + 0x4d7d314, + 0x4dd535f, + 0x64dd9375, + 0x4e71376, + 0x4e7939c, + 0x24e7d39e, + 0x4f0539f, + 0x4f513c1, + 0x4fb93d4, + 0x50613ee, + 0x5129418, + 0x519144a, + 0x52a5464, + 0x652a94a9, + 0x652ad4aa, + 0x53094ab, + 0x53654c2, + 0x53f54d9, + 0x54714fd, + 0x54b551c, + 0x559952d, + 0x55cd566, + 0x562d573, + 0x56a158b, + 0x57295a8, + 0x57695ca, + 0x57d95da, + 0x657dd5f6, + 0x58055f7, + 0x5809601, + 0x5839602, + 0x585560e, + 0x5899615, + 0x58a9626, + 0x58c162a, + 0x5939630, + 0x594164e, + 0x595d650, + 0x5971657, + 0x598d65c, + 0x59b9663, + 0x59bd66e, + 0x59c566f, + 0x59d9671, + 0x59f9676, + 0x5a0967e, + 0x5a15682, + 0x5a51685, + 0x5a59694, + 0x5a6d696, + 0x5a9569b, + 0x5aa16a5, + 0x5aa96a8, + 0x5ad16aa, + 0x5af56b4, + 0x5b0d6bd, + 0x5b116c3, + 0x5b196c4, + 0x5b2d6c6, + 0x5bd56cb, + 0x5bd96f5, + 0x5bdd6f6, + 0x5be16f7, + 0x5c056f8, + 0x5c29701, + 0x5c4570a, + 0x5c59711, + 0x5c6d716, + 0x5c7571b, + 0x5c7d71d, + 0x5c8571f, + 0x5c9d721, + 0x5cad727, + 0x5cb172b, + 0x5ccd72c, + 0x6555733, + 0x658d955, + 0x65b9963, + 0x65d596e, + 0x65f5975, + 0x661597d, + 0x6659985, + 0x6661996, + 0x26665998, + 0x26669999, + 0x667199a, + 0x687199c, + 0x26875a1c, + 0x6879a1d, + 0x2687da1e, + 0x2688da1f, + 0x26895a23, + 0x268a1a25, + 0x68a5a28, + 0x268a9a29, + 0x268b1a2a, + 0x68b9a2c, + 0x68c9a2e, + 0x68f1a32, + 0x692da3c, + 0x6931a4b, + 0x6969a4c, + 0x698da5a, + 0x74e5a63, + 0x74e9d39, + 0x74edd3a, + 0x274f1d3b, + 0x74f5d3c, + 0x274f9d3d, + 0x74fdd3e, + 0x27509d3f, + 0x750dd42, + 0x7511d43, + 0x27515d44, + 0x7519d45, + 0x27521d46, + 0x7525d48, + 0x7529d49, + 0x27539d4a, + 0x753dd4e, + 0x7541d4f, + 0x7545d50, + 0x7549d51, + 0x2754dd52, + 0x7551d53, + 0x7555d54, 0x7559d55, - 0x757dd56, - 0x7589d5f, - 0x758dd62, - 0x7591d63, - 0x7755d64, - 0x27759dd5, - 0x27761dd6, - 0x27765dd8, - 0x27769dd9, - 0x7771dda, - 0x784dddc, - 0x27859e13, - 0x2785de16, - 0x27861e17, - 0x27865e18, - 0x7869e19, - 0x7895e1a, - 0x78a1e25, - 0x78a5e28, - 0x78c9e29, - 0x78d5e32, - 0x78f5e35, - 0x78f9e3d, - 0x7931e3e, - 0x7be1e4c, - 0x7c9def8, - 0x7ca1f27, - 0x7ca5f28, - 0x7cb9f29, - 0x7cbdf2e, - 0x7cf1f2f, - 0x7d29f3c, - 0x27d2df4a, - 0x7d49f4b, - 0x7d71f52, - 0x7d75f5c, - 0x7d99f5d, - 0x7db5f66, - 0x7dddf6d, - 0x7dedf77, - 0x7df1f7b, - 0x7df5f7c, - 0x7e2df7d, - 0x7e39f8b, - 0x7e61f8e, - 0x7ee1f98, - 0x27ee5fb8, - 0x7ef5fb9, - 0x7f05fbd, - 0x7f21fc1, - 0x7f41fc8, - 0x7f45fd0, - 0x7f59fd1, - 0x7f6dfd6, - 0x7f71fdb, - 0x7f75fdc, - 0x7f79fdd, - 0x7f99fde, - 0x8041fe6, - 0x8046010, - 0x8062011, - 0x808a018, - 0x808e022, - 0x8096023, - 0x80ba025, - 0x80c202e, - 0x80d6030, - 0x80f6035, - 0x811203d, - 0x8122044, - 0x813a048, - 0x817204e, - 0x817605c, - 0x824a05d, + 0x755dd56, + 0x27565d57, + 0x7569d59, + 0x756dd5a, + 0x7571d5b, + 0x27575d5c, + 0x7579d5d, + 0x27581d5e, + 0x27585d60, + 0x75a1d61, + 0x75b9d68, + 0x75fdd6e, + 0x7601d7f, + 0x7625d80, + 0x7631d89, + 0x7635d8c, + 0x7639d8d, + 0x77fdd8e, + 0x27801dff, + 0x27809e00, + 0x2780de02, + 0x27811e03, + 0x7819e04, + 0x78f5e06, + 0x27901e3d, + 0x27905e40, + 0x27909e41, + 0x2790de42, + 0x7911e43, + 0x793de44, + 0x7949e4f, + 0x794de52, + 0x7971e53, + 0x797de5c, + 0x799de5f, + 0x79a1e67, + 0x79d9e68, + 0x7c89e76, + 0x7d45f22, + 0x7d49f51, + 0x7d4df52, + 0x7d61f53, + 0x7d65f58, + 0x7d99f59, + 0x7dd1f66, + 0x27dd5f74, + 0x7df1f75, + 0x7e19f7c, + 0x7e1df86, + 0x7e41f87, + 0x7e5df90, + 0x7e85f97, + 0x7e95fa1, + 0x7e99fa5, + 0x7e9dfa6, + 0x7ed5fa7, + 0x7ee1fb5, + 0x7f09fb8, + 0x7f95fc2, + 0x27f99fe5, + 0x7f9dfe6, + 0x7fadfe7, + 0x27fb1feb, + 0x7fc1fec, + 0x7fddff0, + 0x7ffdff7, + 0x8001fff, + 0x8016000, + 0x802a005, + 0x802e00a, + 0x803200b, + 0x803600c, + 0x805600d, + 0x80fe015, + 0x810203f, + 0x811e040, + 0x8146047, + 0x28156051, + 0x815a055, + 0x8166056, + 0x8192059, + 0x819a064, + 0x81ae066, + 0x81ce06b, + 0x81ea073, + 0x81fa07a, + 0x821207e, + 0x824a084, 0x824e092, - 0x8262093, - 0x826a098, - 0x828209a, - 0x82860a0, - 0x82920a1, - 0x829e0a4, - 0x82a20a7, - 0x82a60a8, - 0x82aa0a9, - 0x82ce0aa, - 0x830e0b3, - 0x83120c3, - 0x83320c4, - 0x83820cc, - 0x83ae0e0, - 0x283b20eb, - 0x83ba0ec, - 0x84120ee, - 0x8416104, - 0x841a105, - 0x841e106, - 0x8462107, - 0x8472118, - 0x84b211c, - 0x84b612c, - 0x84e612d, - 0x8632139, - 0x865a18c, - 0x8692196, - 0x86b61a4, - 0x286be1ad, - 0x286c21af, - 0x86ca1b0, - 0x86d61b2, - 0x87f21b5, - 0x87fe1fc, - 0x880a1ff, - 0x8816202, - 0x8822205, - 0x882e208, - 0x883a20b, - 0x884620e, - 0x8852211, - 0x885e214, - 0x886a217, - 0x887621a, - 0x888221d, - 0x888e220, - 0x8896223, - 0x88a2225, - 0x88ae228, - 0x88ba22b, - 0x88c622e, - 0x88d2231, - 0x88de234, - 0x88ea237, - 0x88f623a, - 0x890223d, - 0x890e240, - 0x891a243, - 0x8946246, - 0x8952251, - 0x895e254, - 0x896a257, - 0x897625a, - 0x898225d, - 0x898a260, - 0x8996262, - 0x89a2265, - 0x89ae268, - 0x89ba26b, - 0x89c626e, - 0x89d2271, - 0x89de274, - 0x89ea277, - 0x89f627a, - 0x8a0227d, - 0x8a0e280, - 0x8a16283, - 0x8a22285, - 0x8a2a288, + 0x8322093, + 0x83260c8, + 0x833a0c9, + 0x83420ce, + 0x835a0d0, + 0x835e0d6, + 0x836a0d7, + 0x83760da, + 0x837a0dd, + 0x83820de, + 0x83860e0, + 0x83aa0e1, + 0x83ea0ea, + 0x83ee0fa, + 0x840e0fb, + 0x845e103, + 0x848e117, + 0x28492123, + 0x849a124, + 0x84f2126, + 0x84f613c, + 0x84fa13d, + 0x84fe13e, + 0x854213f, + 0x8552150, + 0x8592154, + 0x8596164, + 0x85c6165, + 0x870e171, + 0x87361c3, + 0x876e1cd, + 0x87961db, + 0x2879e1e5, + 0x287a21e7, + 0x287a61e8, + 0x87ae1e9, + 0x87ba1eb, + 0x88d61ee, + 0x88e2235, + 0x88ee238, + 0x88fa23b, + 0x890623e, + 0x8912241, + 0x891e244, + 0x892a247, + 0x893624a, + 0x894224d, + 0x894e250, + 0x895a253, + 0x8966256, + 0x8972259, + 0x897a25c, + 0x898625e, + 0x8992261, + 0x899e264, + 0x89aa267, + 0x89b626a, + 0x89c226d, + 0x89ce270, + 0x89da273, + 0x89e6276, + 0x89f2279, + 0x89fe27c, + 0x8a2a27f, 0x8a3628a, 0x8a4228d, 0x8a4e290, 0x8a5a293, 0x8a66296, - 0x8a72299, - 0x8a7e29c, - 0x8a8a29f, - 0x8a8e2a2, - 0x8a9a2a3, - 0x8ab62a6, - 0x8aba2ad, - 0x8aca2ae, - 0x8aee2b2, - 0x8af22bb, - 0x8b362bc, - 0x8b3e2cd, - 0x8b522cf, - 0x8b862d4, - 0x8ba22e1, - 0x8baa2e8, - 0x8bce2ea, - 0x8be62f3, - 0x8bfe2f9, - 0x8c162ff, - 0x8c2a305, - 0x28c7230a, - 0x8c7631c, - 0x8ca231d, - 0x8cb2328, - 0x8cc632c, + 0x8a6e299, + 0x8a7a29b, + 0x8a8629e, + 0x8a922a1, + 0x8a9e2a4, + 0x8aaa2a7, + 0x8ab62aa, + 0x8ac22ad, + 0x8ace2b0, + 0x8ada2b3, + 0x8ae62b6, + 0x8af22b9, + 0x8afa2bc, + 0x8b062be, + 0x8b0e2c1, + 0x8b1a2c3, + 0x8b262c6, + 0x8b322c9, + 0x8b3e2cc, + 0x8b4a2cf, + 0x8b562d2, + 0x8b622d5, + 0x8b6e2d8, + 0x8b722db, + 0x8b7e2dc, + 0x8b9a2df, + 0x8b9e2e6, + 0x8bae2e7, + 0x8bd22eb, + 0x8bd62f4, + 0x8c1a2f5, + 0x8c22306, + 0x8c36308, + 0x8c6a30d, + 0x8c8a31a, + 0x8c92322, + 0x8cb6324, + 0x8cce32d, + 0x8ce6333, + 0x8cfe339, + 0x8d1233f, + 0x28d5a344, + 0x8d5e356, + 0x8d8a357, + 0x8d9a362, + 0x8dae366, } -// max children 592 (capacity 1023) -// max text offset 30772 (capacity 32767) +// max children 601 (capacity 1023) +// max text offset 30901 (capacity 32767) // max text length 36 (capacity 63) -// max hi 9009 (capacity 16383) -// max lo 9004 (capacity 16383) +// max hi 9067 (capacity 16383) +// max lo 9062 (capacity 16383) diff --git a/vendor/golang.org/x/tools/go/ast/astutil/imports.go b/vendor/golang.org/x/tools/go/ast/astutil/imports.go index 3e4b19536..2087ceec9 100644 --- a/vendor/golang.org/x/tools/go/ast/astutil/imports.go +++ b/vendor/golang.org/x/tools/go/ast/astutil/imports.go @@ -275,9 +275,10 @@ func DeleteNamedImport(fset *token.FileSet, f *ast.File, name, path string) (del // We deleted an entry but now there may be // a blank line-sized hole where the import was. - if line-lastLine > 1 { + if line-lastLine > 1 || !gen.Rparen.IsValid() { // There was a blank line immediately preceding the deleted import, - // so there's no need to close the hole. + // so there's no need to close the hole. The right parenthesis is + // invalid after AddImport to an import statement without parenthesis. // Do nothing. } else if line != fset.File(gen.Rparen).LineCount() { // There was no blank line. Close the hole. diff --git a/vendor/golang.org/x/tools/go/gcexportdata/gcexportdata.go b/vendor/golang.org/x/tools/go/gcexportdata/gcexportdata.go index 98b3987b9..f8363d8fa 100644 --- a/vendor/golang.org/x/tools/go/gcexportdata/gcexportdata.go +++ b/vendor/golang.org/x/tools/go/gcexportdata/gcexportdata.go @@ -100,7 +100,7 @@ func Read(in io.Reader, fset *token.FileSet, imports map[string]*types.Package, // Write writes encoded type information for the specified package to out. // The FileSet provides file position information for named objects. func Write(out io.Writer, fset *token.FileSet, pkg *types.Package) error { - b, err := gcimporter.BExportData(fset, pkg) + b, err := gcimporter.IExportData(fset, pkg) if err != nil { return err } diff --git a/vendor/golang.org/x/tools/go/internal/gcimporter/bimport.go b/vendor/golang.org/x/tools/go/internal/gcimporter/bimport.go index e3c310782..e9f73d14a 100644 --- a/vendor/golang.org/x/tools/go/internal/gcimporter/bimport.go +++ b/vendor/golang.org/x/tools/go/internal/gcimporter/bimport.go @@ -332,7 +332,7 @@ func (p *importer) pos() token.Pos { p.prevFile = file p.prevLine = line - return p.fake.pos(file, line) + return p.fake.pos(file, line, 0) } // Synthesize a token.Pos @@ -341,7 +341,9 @@ type fakeFileSet struct { files map[string]*token.File } -func (s *fakeFileSet) pos(file string, line int) token.Pos { +func (s *fakeFileSet) pos(file string, line, column int) token.Pos { + // TODO(mdempsky): Make use of column. + // Since we don't know the set of needed file positions, we // reserve maxlines positions per file. const maxlines = 64 * 1024 @@ -976,10 +978,11 @@ const ( aliasTag ) +var predeclOnce sync.Once var predecl []types.Type // initialized lazily func predeclared() []types.Type { - if predecl == nil { + predeclOnce.Do(func() { // initialize lazily to be sure that all // elements have been initialized before predecl = []types.Type{ // basic types @@ -1026,7 +1029,7 @@ func predeclared() []types.Type { // used internally by gc; never used by this package or in .a files anyType{}, } - } + }) return predecl } diff --git a/vendor/golang.org/x/tools/go/internal/gcimporter/gcimporter.go b/vendor/golang.org/x/tools/go/internal/gcimporter/gcimporter.go index 9cf186605..8dcd8bbb7 100644 --- a/vendor/golang.org/x/tools/go/internal/gcimporter/gcimporter.go +++ b/vendor/golang.org/x/tools/go/internal/gcimporter/gcimporter.go @@ -344,7 +344,7 @@ func (p *parser) expectKeyword(keyword string) { // PackageId = string_lit . // -func (p *parser) parsePackageId() string { +func (p *parser) parsePackageID() string { id, err := strconv.Unquote(p.expect(scanner.String)) if err != nil { p.error(err) @@ -384,7 +384,7 @@ func (p *parser) parseDotIdent() string { // func (p *parser) parseQualifiedName() (id, name string) { p.expect('@') - id = p.parsePackageId() + id = p.parsePackageID() p.expect('.') // Per rev f280b8a485fd (10/2/2013), qualified names may be used for anonymous fields. if p.tok == '?' { @@ -696,7 +696,7 @@ func (p *parser) parseInterfaceType(parent *types.Package) types.Type { // Complete requires the type's embedded interfaces to be fully defined, // but we do not define any - return types.NewInterface(methods, nil).Complete() + return newInterface(methods, nil).Complete() } // ChanType = ( "chan" [ "<-" ] | "<-" "chan" ) Type . @@ -785,7 +785,7 @@ func (p *parser) parseType(parent *types.Package) types.Type { func (p *parser) parseImportDecl() { p.expectKeyword("import") name := p.parsePackageName() - p.getPkg(p.parsePackageId(), name) + p.getPkg(p.parsePackageID(), name) } // int_lit = [ "+" | "-" ] { "0" ... "9" } . diff --git a/vendor/golang.org/x/tools/go/internal/gcimporter/iexport.go b/vendor/golang.org/x/tools/go/internal/gcimporter/iexport.go index be671c79b..4be32a2e5 100644 --- a/vendor/golang.org/x/tools/go/internal/gcimporter/iexport.go +++ b/vendor/golang.org/x/tools/go/internal/gcimporter/iexport.go @@ -6,8 +6,6 @@ // This file was derived from $GOROOT/src/cmd/compile/internal/gc/iexport.go; // see that file for specification of the format. -// +build go1.11 - package gcimporter import ( @@ -28,7 +26,10 @@ import ( const iexportVersion = 0 // IExportData returns the binary export data for pkg. +// // If no file set is provided, position info will be missing. +// The package path of the top-level package will not be recorded, +// so that calls to IImportData can override with a provided package path. func IExportData(fset *token.FileSet, pkg *types.Package) (b []byte, err error) { defer func() { if e := recover(); e != nil { @@ -48,6 +49,7 @@ func IExportData(fset *token.FileSet, pkg *types.Package) (b []byte, err error) stringIndex: map[string]uint64{}, declIndex: map[types.Object]uint64{}, typIndex: map[types.Type]uint64{}, + localpkg: pkg, } for i, pt := range predeclared() { @@ -73,7 +75,7 @@ func IExportData(fset *token.FileSet, pkg *types.Package) (b []byte, err error) // Append indices to data0 section. dataLen := uint64(p.data0.Len()) w := p.newWriter() - w.writeIndex(p.declIndex, pkg) + w.writeIndex(p.declIndex) w.flush() // Assemble header. @@ -95,14 +97,14 @@ func IExportData(fset *token.FileSet, pkg *types.Package) (b []byte, err error) // we're writing out the main index, which is also read by // non-compiler tools and includes a complete package description // (i.e., name and height). -func (w *exportWriter) writeIndex(index map[types.Object]uint64, localpkg *types.Package) { +func (w *exportWriter) writeIndex(index map[types.Object]uint64) { // Build a map from packages to objects from that package. pkgObjs := map[*types.Package][]types.Object{} // For the main index, make sure to include every package that // we reference, even if we're not exporting (or reexporting) // any symbols from it. - pkgObjs[localpkg] = nil + pkgObjs[w.p.localpkg] = nil for pkg := range w.p.allPkgs { pkgObjs[pkg] = nil } @@ -121,12 +123,12 @@ func (w *exportWriter) writeIndex(index map[types.Object]uint64, localpkg *types } sort.Slice(pkgs, func(i, j int) bool { - return pkgs[i].Path() < pkgs[j].Path() + return w.exportPath(pkgs[i]) < w.exportPath(pkgs[j]) }) w.uint64(uint64(len(pkgs))) for _, pkg := range pkgs { - w.string(pkg.Path()) + w.string(w.exportPath(pkg)) w.string(pkg.Name()) w.uint64(uint64(0)) // package height is not needed for go/types @@ -143,6 +145,8 @@ type iexporter struct { fset *token.FileSet out *bytes.Buffer + localpkg *types.Package + // allPkgs tracks all packages that have been referenced by // the export data, so we can ensure to include them in the // main index. @@ -195,6 +199,13 @@ type exportWriter struct { prevLine int64 } +func (w *exportWriter) exportPath(pkg *types.Package) string { + if pkg == w.p.localpkg { + return "" + } + return pkg.Path() +} + func (p *iexporter) doDecl(obj types.Object) { w := p.newWriter() w.setPkg(obj.Pkg(), false) @@ -267,6 +278,11 @@ func (w *exportWriter) tag(tag byte) { } func (w *exportWriter) pos(pos token.Pos) { + if w.p.fset == nil { + w.int64(0) + return + } + p := w.p.fset.Position(pos) file := p.Filename line := int64(p.Line) @@ -299,7 +315,7 @@ func (w *exportWriter) pkg(pkg *types.Package) { // Ensure any referenced packages are declared in the main index. w.p.allPkgs[pkg] = true - w.string(pkg.Path()) + w.string(w.exportPath(pkg)) } func (w *exportWriter) qualifiedIdent(obj types.Object) { @@ -394,7 +410,7 @@ func (w *exportWriter) doTyp(t types.Type, pkg *types.Package) { w.pos(f.Pos()) w.string(f.Name()) w.typ(f.Type(), pkg) - w.bool(f.Embedded()) + w.bool(f.Anonymous()) w.string(t.Tag(i)) // note (or tag) } diff --git a/vendor/golang.org/x/tools/go/internal/gcimporter/iimport.go b/vendor/golang.org/x/tools/go/internal/gcimporter/iimport.go index 3cb7ae5b9..a31a88026 100644 --- a/vendor/golang.org/x/tools/go/internal/gcimporter/iimport.go +++ b/vendor/golang.org/x/tools/go/internal/gcimporter/iimport.go @@ -63,8 +63,8 @@ const ( // If the export data version is not recognized or the format is otherwise // compromised, an error is returned. func IImportData(fset *token.FileSet, imports map[string]*types.Package, data []byte, path string) (_ int, pkg *types.Package, err error) { - const currentVersion = 0 - version := -1 + const currentVersion = 1 + version := int64(-1) defer func() { if e := recover(); e != nil { if version > currentVersion { @@ -77,9 +77,9 @@ func IImportData(fset *token.FileSet, imports map[string]*types.Package, data [] r := &intReader{bytes.NewReader(data), path} - version = int(r.uint64()) + version = int64(r.uint64()) switch version { - case currentVersion: + case currentVersion, 0: default: errorf("unknown iexport format version %d", version) } @@ -93,7 +93,8 @@ func IImportData(fset *token.FileSet, imports map[string]*types.Package, data [] r.Seek(sLen+dLen, io.SeekCurrent) p := iimporter{ - ipath: path, + ipath: path, + version: int(version), stringData: stringData, stringCache: make(map[uint64]string), @@ -142,20 +143,18 @@ func IImportData(fset *token.FileSet, imports map[string]*types.Package, data [] p.pkgIndex[pkg] = nameIndex pkgList[i] = pkg } - var localpkg *types.Package - for _, pkg := range pkgList { - if pkg.Path() == path { - localpkg = pkg - } + if len(pkgList) == 0 { + errorf("no packages found for %s", path) + panic("unreachable") } - - names := make([]string, 0, len(p.pkgIndex[localpkg])) - for name := range p.pkgIndex[localpkg] { + p.ipkg = pkgList[0] + names := make([]string, 0, len(p.pkgIndex[p.ipkg])) + for name := range p.pkgIndex[p.ipkg] { names = append(names, name) } sort.Strings(names) for _, name := range names { - p.doDecl(localpkg, name) + p.doDecl(p.ipkg, name) } for _, typ := range p.interfaceList { @@ -165,17 +164,19 @@ func IImportData(fset *token.FileSet, imports map[string]*types.Package, data [] // record all referenced packages as imports list := append(([]*types.Package)(nil), pkgList[1:]...) sort.Sort(byPath(list)) - localpkg.SetImports(list) + p.ipkg.SetImports(list) // package was imported completely and without errors - localpkg.MarkComplete() + p.ipkg.MarkComplete() consumed, _ := r.Seek(0, io.SeekCurrent) - return int(consumed), localpkg, nil + return int(consumed), p.ipkg, nil } type iimporter struct { - ipath string + ipath string + ipkg *types.Package + version int stringData []byte stringCache map[uint64]string @@ -226,6 +227,9 @@ func (p *iimporter) pkgAt(off uint64) *types.Package { return pkg } path := p.stringAt(off) + if path == p.ipath { + return p.ipkg + } errorf("missing package %q in %q", path, p.ipath) return nil } @@ -255,6 +259,7 @@ type importReader struct { currPkg *types.Package prevFile string prevLine int64 + prevColumn int64 } func (r *importReader) obj(name string) { @@ -448,6 +453,19 @@ func (r *importReader) qualifiedIdent() (*types.Package, string) { } func (r *importReader) pos() token.Pos { + if r.p.version >= 1 { + r.posv1() + } else { + r.posv0() + } + + if r.prevFile == "" && r.prevLine == 0 && r.prevColumn == 0 { + return token.NoPos + } + return r.p.fake.pos(r.prevFile, int(r.prevLine), int(r.prevColumn)) +} + +func (r *importReader) posv0() { delta := r.int64() if delta != deltaNewFile { r.prevLine += delta @@ -457,12 +475,18 @@ func (r *importReader) pos() token.Pos { r.prevFile = r.string() r.prevLine = l } +} - if r.prevFile == "" && r.prevLine == 0 { - return token.NoPos +func (r *importReader) posv1() { + delta := r.int64() + r.prevColumn += delta >> 1 + if delta&1 != 0 { + delta = r.int64() + r.prevLine += delta >> 1 + if delta&1 != 0 { + r.prevFile = r.string() + } } - - return r.p.fake.pos(r.prevFile, int(r.prevLine)) } func (r *importReader) typ() types.Type { diff --git a/vendor/golang.org/x/tools/go/internal/packagesdriver/sizes.go b/vendor/golang.org/x/tools/go/internal/packagesdriver/sizes.go index fdc7da056..dc6177c12 100644 --- a/vendor/golang.org/x/tools/go/internal/packagesdriver/sizes.go +++ b/vendor/golang.org/x/tools/go/internal/packagesdriver/sizes.go @@ -11,17 +11,15 @@ import ( "encoding/json" "fmt" "go/types" - "log" - "os" "os/exec" "strings" - "time" + + "golang.org/x/tools/internal/gocommand" ) var debug = false -// GetSizes returns the sizes used by the underlying driver with the given parameters. -func GetSizes(ctx context.Context, buildFlags, env []string, dir string, usesExportData bool) (types.Sizes, error) { +func GetSizes(ctx context.Context, buildFlags, env []string, gocmdRunner *gocommand.Runner, dir string) (types.Sizes, error) { // TODO(matloob): Clean this up. This code is mostly a copy of packages.findExternalDriver. const toolPrefix = "GOPACKAGESDRIVER=" tool := "" @@ -41,7 +39,7 @@ func GetSizes(ctx context.Context, buildFlags, env []string, dir string, usesExp } if tool == "off" { - return GetSizesGolist(ctx, buildFlags, env, dir, usesExportData) + return GetSizesGolist(ctx, buildFlags, env, gocmdRunner, dir) } req, err := json.Marshal(struct { @@ -77,84 +75,43 @@ func GetSizes(ctx context.Context, buildFlags, env []string, dir string, usesExp return response.Sizes, nil } -func GetSizesGolist(ctx context.Context, buildFlags, env []string, dir string, usesExportData bool) (types.Sizes, error) { - args := []string{"list", "-f", "{{context.GOARCH}} {{context.Compiler}}"} - args = append(args, buildFlags...) - args = append(args, "--", "unsafe") - stdout, err := InvokeGo(ctx, env, dir, usesExportData, args...) - if err != nil { - return nil, err +func GetSizesGolist(ctx context.Context, buildFlags, env []string, gocmdRunner *gocommand.Runner, dir string) (types.Sizes, error) { + inv := gocommand.Invocation{ + Verb: "list", + Args: []string{"-f", "{{context.GOARCH}} {{context.Compiler}}", "--", "unsafe"}, + Env: env, + BuildFlags: buildFlags, + WorkingDir: dir, } - fields := strings.Fields(stdout.String()) - if len(fields) < 2 { - return nil, fmt.Errorf("could not determine GOARCH and Go compiler") + stdout, stderr, friendlyErr, rawErr := gocmdRunner.RunRaw(ctx, inv) + var goarch, compiler string + if rawErr != nil { + if strings.Contains(rawErr.Error(), "cannot find main module") { + // User's running outside of a module. All bets are off. Get GOARCH and guess compiler is gc. + // TODO(matloob): Is this a problem in practice? + inv := gocommand.Invocation{ + Verb: "env", + Args: []string{"GOARCH"}, + Env: env, + WorkingDir: dir, + } + envout, enverr := gocmdRunner.Run(ctx, inv) + if enverr != nil { + return nil, enverr + } + goarch = strings.TrimSpace(envout.String()) + compiler = "gc" + } else { + return nil, friendlyErr + } + } else { + fields := strings.Fields(stdout.String()) + if len(fields) < 2 { + return nil, fmt.Errorf("could not parse GOARCH and Go compiler in format \" \":\nstdout: <<%s>>\nstderr: <<%s>>", + stdout.String(), stderr.String()) + } + goarch = fields[0] + compiler = fields[1] } - goarch := fields[0] - compiler := fields[1] return types.SizesFor(compiler, goarch), nil } - -// InvokeGo returns the stdout of a go command invocation. -func InvokeGo(ctx context.Context, env []string, dir string, usesExportData bool, args ...string) (*bytes.Buffer, error) { - if debug { - defer func(start time.Time) { log.Printf("%s for %v", time.Since(start), cmdDebugStr(env, args...)) }(time.Now()) - } - stdout := new(bytes.Buffer) - stderr := new(bytes.Buffer) - cmd := exec.CommandContext(ctx, "go", args...) - // On darwin the cwd gets resolved to the real path, which breaks anything that - // expects the working directory to keep the original path, including the - // go command when dealing with modules. - // The Go stdlib has a special feature where if the cwd and the PWD are the - // same node then it trusts the PWD, so by setting it in the env for the child - // process we fix up all the paths returned by the go command. - cmd.Env = append(append([]string{}, env...), "PWD="+dir) - cmd.Dir = dir - cmd.Stdout = stdout - cmd.Stderr = stderr - if err := cmd.Run(); err != nil { - exitErr, ok := err.(*exec.ExitError) - if !ok { - // Catastrophic error: - // - executable not found - // - context cancellation - return nil, fmt.Errorf("couldn't exec 'go %v': %s %T", args, err, err) - } - - // Export mode entails a build. - // If that build fails, errors appear on stderr - // (despite the -e flag) and the Export field is blank. - // Do not fail in that case. - if !usesExportData { - return nil, fmt.Errorf("go %v: %s: %s", args, exitErr, stderr) - } - } - - // As of writing, go list -export prints some non-fatal compilation - // errors to stderr, even with -e set. We would prefer that it put - // them in the Package.Error JSON (see https://golang.org/issue/26319). - // In the meantime, there's nowhere good to put them, but they can - // be useful for debugging. Print them if $GOPACKAGESPRINTGOLISTERRORS - // is set. - if len(stderr.Bytes()) != 0 && os.Getenv("GOPACKAGESPRINTGOLISTERRORS") != "" { - fmt.Fprintf(os.Stderr, "%s stderr: <<%s>>\n", cmdDebugStr(env, args...), stderr) - } - - // debugging - if false { - fmt.Fprintf(os.Stderr, "%s stdout: <<%s>>\n", cmdDebugStr(env, args...), stdout) - } - - return stdout, nil -} - -func cmdDebugStr(envlist []string, args ...string) string { - env := make(map[string]string) - for _, kv := range envlist { - split := strings.Split(kv, "=") - k, v := split[0], split[1] - env[k] = v - } - - return fmt.Sprintf("GOROOT=%v GOPATH=%v GO111MODULE=%v PWD=%v go %v", env["GOROOT"], env["GOPATH"], env["GO111MODULE"], env["PWD"], args) -} diff --git a/vendor/golang.org/x/tools/go/packages/doc.go b/vendor/golang.org/x/tools/go/packages/doc.go index 3799f8ed8..4bfe28a51 100644 --- a/vendor/golang.org/x/tools/go/packages/doc.go +++ b/vendor/golang.org/x/tools/go/packages/doc.go @@ -60,8 +60,7 @@ causes Load to run in LoadFiles mode, collecting minimal information. See the documentation for type Config for details. As noted earlier, the Config.Mode controls the amount of detail -reported about the loaded packages, with each mode returning all the data of the -previous mode with some extra added. See the documentation for type LoadMode +reported about the loaded packages. See the documentation for type LoadMode for details. Most tools should pass their command-line arguments (after any flags) diff --git a/vendor/golang.org/x/tools/go/packages/external.go b/vendor/golang.org/x/tools/go/packages/external.go index 22ff769ef..8c8473fd0 100644 --- a/vendor/golang.org/x/tools/go/packages/external.go +++ b/vendor/golang.org/x/tools/go/packages/external.go @@ -12,18 +12,34 @@ import ( "bytes" "encoding/json" "fmt" + "os" "os/exec" "strings" ) -// Driver +// The Driver Protocol +// +// The driver, given the inputs to a call to Load, returns metadata about the packages specified. +// This allows for different build systems to support go/packages by telling go/packages how the +// packages' source is organized. +// The driver is a binary, either specified by the GOPACKAGESDRIVER environment variable or in +// the path as gopackagesdriver. It's given the inputs to load in its argv. See the package +// documentation in doc.go for the full description of the patterns that need to be supported. +// A driver receives as a JSON-serialized driverRequest struct in standard input and will +// produce a JSON-serialized driverResponse (see definition in packages.go) in its standard output. + +// driverRequest is used to provide the portion of Load's Config that is needed by a driver. type driverRequest struct { - Command string `json:"command"` - Mode LoadMode `json:"mode"` - Env []string `json:"env"` - BuildFlags []string `json:"build_flags"` - Tests bool `json:"tests"` - Overlay map[string][]byte `json:"overlay"` + Mode LoadMode `json:"mode"` + // Env specifies the environment the underlying build system should be run in. + Env []string `json:"env"` + // BuildFlags are flags that should be passed to the underlying build system. + BuildFlags []string `json:"build_flags"` + // Tests specifies whether the patterns should also return test packages. + Tests bool `json:"tests"` + // Overlay maps file paths (relative to the driver's working directory) to the byte contents + // of overlay files. + Overlay map[string][]byte `json:"overlay"` } // findExternalDriver returns the file path of a tool that supplies @@ -61,15 +77,21 @@ func findExternalDriver(cfg *Config) driver { } buf := new(bytes.Buffer) + stderr := new(bytes.Buffer) cmd := exec.CommandContext(cfg.Context, tool, words...) cmd.Dir = cfg.Dir cmd.Env = cfg.Env cmd.Stdin = bytes.NewReader(req) cmd.Stdout = buf - cmd.Stderr = new(bytes.Buffer) + cmd.Stderr = stderr + if err := cmd.Run(); err != nil { return nil, fmt.Errorf("%v: %v: %s", tool, err, cmd.Stderr) } + if len(stderr.Bytes()) != 0 && os.Getenv("GOPACKAGESPRINTDRIVERERRORS") != "" { + fmt.Fprintf(os.Stderr, "%s stderr: <<%s>>\n", cmdDebugStr(cmd, words...), stderr) + } + var response driverResponse if err := json.Unmarshal(buf.Bytes(), &response); err != nil { return nil, err diff --git a/vendor/golang.org/x/tools/go/packages/golist.go b/vendor/golang.org/x/tools/go/packages/golist.go index 3a0d4b012..bc04503c1 100644 --- a/vendor/golang.org/x/tools/go/packages/golist.go +++ b/vendor/golang.org/x/tools/go/packages/golist.go @@ -6,24 +6,25 @@ package packages import ( "bytes" + "context" "encoding/json" "fmt" "go/types" - "io/ioutil" "log" "os" "os/exec" + "path" "path/filepath" "reflect" - "regexp" + "sort" "strconv" "strings" "sync" - "time" + "unicode" "golang.org/x/tools/go/internal/packagesdriver" - "golang.org/x/tools/internal/gopathwalk" - "golang.org/x/tools/internal/semver" + "golang.org/x/tools/internal/gocommand" + "golang.org/x/xerrors" ) // debug controls verbose logging. @@ -42,16 +43,21 @@ type responseDeduper struct { dr *driverResponse } -// init fills in r with a driverResponse. -func (r *responseDeduper) init(dr *driverResponse) { - r.dr = dr - r.seenRoots = map[string]bool{} - r.seenPackages = map[string]*Package{} +func newDeduper() *responseDeduper { + return &responseDeduper{ + dr: &driverResponse{}, + seenRoots: map[string]bool{}, + seenPackages: map[string]*Package{}, + } +} + +// addAll fills in r with a driverResponse. +func (r *responseDeduper) addAll(dr *driverResponse) { for _, pkg := range dr.Packages { - r.seenPackages[pkg.ID] = pkg + r.addPackage(pkg) } for _, root := range dr.Roots { - r.seenRoots[root] = true + r.addRoot(root) } } @@ -71,24 +77,90 @@ func (r *responseDeduper) addRoot(id string) { r.dr.Roots = append(r.dr.Roots, id) } +type golistState struct { + cfg *Config + ctx context.Context + + envOnce sync.Once + goEnvError error + goEnv map[string]string + + rootsOnce sync.Once + rootDirsError error + rootDirs map[string]string + + goVersionOnce sync.Once + goVersionError error + goVersion string // third field of 'go version' + + // vendorDirs caches the (non)existence of vendor directories. + vendorDirs map[string]bool +} + +// getEnv returns Go environment variables. Only specific variables are +// populated -- computing all of them is slow. +func (state *golistState) getEnv() (map[string]string, error) { + state.envOnce.Do(func() { + var b *bytes.Buffer + b, state.goEnvError = state.invokeGo("env", "-json", "GOMOD", "GOPATH") + if state.goEnvError != nil { + return + } + + state.goEnv = make(map[string]string) + decoder := json.NewDecoder(b) + if state.goEnvError = decoder.Decode(&state.goEnv); state.goEnvError != nil { + return + } + }) + return state.goEnv, state.goEnvError +} + +// mustGetEnv is a convenience function that can be used if getEnv has already succeeded. +func (state *golistState) mustGetEnv() map[string]string { + env, err := state.getEnv() + if err != nil { + panic(fmt.Sprintf("mustGetEnv: %v", err)) + } + return env +} + // goListDriver uses the go list command to interpret the patterns and produce // the build system package structure. // See driver for more details. func goListDriver(cfg *Config, patterns ...string) (*driverResponse, error) { - var sizes types.Sizes + // Make sure that any asynchronous go commands are killed when we return. + parentCtx := cfg.Context + if parentCtx == nil { + parentCtx = context.Background() + } + ctx, cancel := context.WithCancel(parentCtx) + defer cancel() + + response := newDeduper() + + // Fill in response.Sizes asynchronously if necessary. var sizeserr error var sizeswg sync.WaitGroup if cfg.Mode&NeedTypesSizes != 0 || cfg.Mode&NeedTypes != 0 { sizeswg.Add(1) go func() { - sizes, sizeserr = getSizes(cfg) + var sizes types.Sizes + sizes, sizeserr = packagesdriver.GetSizesGolist(ctx, cfg.BuildFlags, cfg.Env, cfg.gocmdRunner, cfg.Dir) + // types.SizesFor always returns nil or a *types.StdSizes. + response.dr.Sizes, _ = sizes.(*types.StdSizes) sizeswg.Done() }() } + state := &golistState{ + cfg: cfg, + ctx: ctx, + vendorDirs: map[string]bool{}, + } + // Determine files requested in contains patterns var containFiles []string - var packagesNamed []string restPatterns := make([]string, 0, len(patterns)) // Extract file= and other [querytype]= patterns. Report an error if querytype // doesn't exist. @@ -104,8 +176,6 @@ extractQueries: containFiles = append(containFiles, value) case "pattern": restPatterns = append(restPatterns, value) - case "iamashamedtousethedisabledqueryname": - packagesNamed = append(packagesNamed, value) case "": // not a reserved query restPatterns = append(restPatterns, pattern) default: @@ -121,58 +191,52 @@ extractQueries: } } - response := &responseDeduper{} - var err error - // See if we have any patterns to pass through to go list. Zero initial // patterns also requires a go list call, since it's the equivalent of // ".". if len(restPatterns) > 0 || len(patterns) == 0 { - dr, err := golistDriver(cfg, restPatterns...) + dr, err := state.createDriverResponse(restPatterns...) if err != nil { return nil, err } - response.init(dr) - } else { - response.init(&driverResponse{}) + response.addAll(dr) } - sizeswg.Wait() - if sizeserr != nil { - return nil, sizeserr - } - // types.SizesFor always returns nil or a *types.StdSizes - response.dr.Sizes, _ = sizes.(*types.StdSizes) - - var containsCandidates []string - if len(containFiles) != 0 { - if err := runContainsQueries(cfg, golistDriver, response, containFiles); err != nil { + if err := state.runContainsQueries(response, containFiles); err != nil { return nil, err } } - if len(packagesNamed) != 0 { - if err := runNamedQueries(cfg, golistDriver, response, packagesNamed); err != nil { - return nil, err - } - } - - modifiedPkgs, needPkgs, err := processGolistOverlay(cfg, response.dr) + modifiedPkgs, needPkgs, err := state.processGolistOverlay(response) if err != nil { return nil, err } + + var containsCandidates []string if len(containFiles) > 0 { containsCandidates = append(containsCandidates, modifiedPkgs...) containsCandidates = append(containsCandidates, needPkgs...) } - if err := addNeededOverlayPackages(cfg, golistDriver, response, needPkgs); err != nil { + if err := state.addNeededOverlayPackages(response, needPkgs); err != nil { return nil, err } // Check candidate packages for containFiles. if len(containFiles) > 0 { for _, id := range containsCandidates { - pkg := response.seenPackages[id] + pkg, ok := response.seenPackages[id] + if !ok { + response.addPackage(&Package{ + ID: id, + Errors: []Error{ + { + Kind: ListError, + Msg: fmt.Sprintf("package %s expected but not seen", id), + }, + }, + }) + continue + } for _, f := range containFiles { for _, g := range pkg.GoFiles { if sameFile(f, g) { @@ -182,30 +246,48 @@ extractQueries: } } } + // Add root for any package that matches a pattern. This applies only to + // packages that are modified by overlays, since they are not added as + // roots automatically. + for _, pattern := range restPatterns { + match := matchPattern(pattern) + for _, pkgID := range modifiedPkgs { + pkg, ok := response.seenPackages[pkgID] + if !ok { + continue + } + if match(pkg.PkgPath) { + response.addRoot(pkg.ID) + } + } + } + sizeswg.Wait() + if sizeserr != nil { + return nil, sizeserr + } return response.dr, nil } -func addNeededOverlayPackages(cfg *Config, driver driver, response *responseDeduper, pkgs []string) error { +func (state *golistState) addNeededOverlayPackages(response *responseDeduper, pkgs []string) error { if len(pkgs) == 0 { return nil } - dr, err := driver(cfg, pkgs...) + dr, err := state.createDriverResponse(pkgs...) if err != nil { return err } for _, pkg := range dr.Packages { response.addPackage(pkg) } - _, needPkgs, err := processGolistOverlay(cfg, response.dr) + _, needPkgs, err := state.processGolistOverlay(response) if err != nil { return err } - addNeededOverlayPackages(cfg, driver, response, needPkgs) - return nil + return state.addNeededOverlayPackages(response, needPkgs) } -func runContainsQueries(cfg *Config, driver driver, response *responseDeduper, queries []string) error { +func (state *golistState) runContainsQueries(response *responseDeduper, queries []string) error { for _, query := range queries { // TODO(matloob): Do only one query per directory. fdir := filepath.Dir(query) @@ -215,9 +297,18 @@ func runContainsQueries(cfg *Config, driver driver, response *responseDeduper, q if err != nil { return fmt.Errorf("could not determine absolute path of file= query path %q: %v", query, err) } - dirResponse, err := driver(cfg, pattern) - if err != nil { - return err + dirResponse, err := state.createDriverResponse(pattern) + + // If there was an error loading the package, or the package is returned + // with errors, try to load the file as an ad-hoc package. + // Usually the error will appear in a returned package, but may not if we're + // in module mode and the ad-hoc is located outside a module. + if err != nil || len(dirResponse.Packages) == 1 && len(dirResponse.Packages[0].GoFiles) == 0 && + len(dirResponse.Packages[0].Errors) == 1 { + var queryErr error + if dirResponse, queryErr = state.adhocPackage(pattern, query); queryErr != nil { + return err // return the original error + } } isRoot := make(map[string]bool, len(dirResponse.Roots)) for _, root := range dirResponse.Roots { @@ -244,262 +335,47 @@ func runContainsQueries(cfg *Config, driver driver, response *responseDeduper, q return nil } -// modCacheRegexp splits a path in a module cache into module, module version, and package. -var modCacheRegexp = regexp.MustCompile(`(.*)@([^/\\]*)(.*)`) - -func runNamedQueries(cfg *Config, driver driver, response *responseDeduper, queries []string) error { - // calling `go env` isn't free; bail out if there's nothing to do. - if len(queries) == 0 { - return nil - } - // Determine which directories are relevant to scan. - roots, modRoot, err := roots(cfg) +// adhocPackage attempts to load or construct an ad-hoc package for a given +// query, if the original call to the driver produced inadequate results. +func (state *golistState) adhocPackage(pattern, query string) (*driverResponse, error) { + response, err := state.createDriverResponse(query) if err != nil { - return err + return nil, err } - - // Scan the selected directories. Simple matches, from GOPATH/GOROOT - // or the local module, can simply be "go list"ed. Matches from the - // module cache need special treatment. - var matchesMu sync.Mutex - var simpleMatches, modCacheMatches []string - add := func(root gopathwalk.Root, dir string) { - // Walk calls this concurrently; protect the result slices. - matchesMu.Lock() - defer matchesMu.Unlock() - - path := dir - if dir != root.Path { - path = dir[len(root.Path)+1:] - } - if pathMatchesQueries(path, queries) { - switch root.Type { - case gopathwalk.RootModuleCache: - modCacheMatches = append(modCacheMatches, path) - case gopathwalk.RootCurrentModule: - // We'd need to read go.mod to find the full - // import path. Relative's easier. - rel, err := filepath.Rel(cfg.Dir, dir) - if err != nil { - // This ought to be impossible, since - // we found dir in the current module. - panic(err) - } - simpleMatches = append(simpleMatches, "./"+rel) - case gopathwalk.RootGOPATH, gopathwalk.RootGOROOT: - simpleMatches = append(simpleMatches, path) - } - } + // If we get nothing back from `go list`, + // try to make this file into its own ad-hoc package. + // TODO(rstambler): Should this check against the original response? + if len(response.Packages) == 0 { + response.Packages = append(response.Packages, &Package{ + ID: "command-line-arguments", + PkgPath: query, + GoFiles: []string{query}, + CompiledGoFiles: []string{query}, + Imports: make(map[string]*Package), + }) + response.Roots = append(response.Roots, "command-line-arguments") } - - startWalk := time.Now() - gopathwalk.Walk(roots, add, gopathwalk.Options{ModulesEnabled: modRoot != "", Debug: debug}) - if debug { - log.Printf("%v for walk", time.Since(startWalk)) - } - - // Weird special case: the top-level package in a module will be in - // whatever directory the user checked the repository out into. It's - // more reasonable for that to not match the package name. So, if there - // are any Go files in the mod root, query it just to be safe. - if modRoot != "" { - rel, err := filepath.Rel(cfg.Dir, modRoot) - if err != nil { - panic(err) // See above. - } - - files, err := ioutil.ReadDir(modRoot) - for _, f := range files { - if strings.HasSuffix(f.Name(), ".go") { - simpleMatches = append(simpleMatches, rel) - break - } - } - } - - addResponse := func(r *driverResponse) { - for _, pkg := range r.Packages { - response.addPackage(pkg) - for _, name := range queries { - if pkg.Name == name { - response.addRoot(pkg.ID) - break + // Handle special cases. + if len(response.Packages) == 1 { + // golang/go#33482: If this is a file= query for ad-hoc packages where + // the file only exists on an overlay, and exists outside of a module, + // add the file to the package and remove the errors. + if response.Packages[0].ID == "command-line-arguments" || + filepath.ToSlash(response.Packages[0].PkgPath) == filepath.ToSlash(query) { + if len(response.Packages[0].GoFiles) == 0 { + filename := filepath.Join(pattern, filepath.Base(query)) // avoid recomputing abspath + // TODO(matloob): check if the file is outside of a root dir? + for path := range state.cfg.Overlay { + if path == filename { + response.Packages[0].Errors = nil + response.Packages[0].GoFiles = []string{path} + response.Packages[0].CompiledGoFiles = []string{path} + } } } } } - - if len(simpleMatches) != 0 { - resp, err := driver(cfg, simpleMatches...) - if err != nil { - return err - } - addResponse(resp) - } - - // Module cache matches are tricky. We want to avoid downloading new - // versions of things, so we need to use the ones present in the cache. - // go list doesn't accept version specifiers, so we have to write out a - // temporary module, and do the list in that module. - if len(modCacheMatches) != 0 { - // Collect all the matches, deduplicating by major version - // and preferring the newest. - type modInfo struct { - mod string - major string - } - mods := make(map[modInfo]string) - var imports []string - for _, modPath := range modCacheMatches { - matches := modCacheRegexp.FindStringSubmatch(modPath) - mod, ver := filepath.ToSlash(matches[1]), matches[2] - importPath := filepath.ToSlash(filepath.Join(matches[1], matches[3])) - - major := semver.Major(ver) - if prevVer, ok := mods[modInfo{mod, major}]; !ok || semver.Compare(ver, prevVer) > 0 { - mods[modInfo{mod, major}] = ver - } - - imports = append(imports, importPath) - } - - // Build the temporary module. - var gomod bytes.Buffer - gomod.WriteString("module modquery\nrequire (\n") - for mod, version := range mods { - gomod.WriteString("\t" + mod.mod + " " + version + "\n") - } - gomod.WriteString(")\n") - - tmpCfg := *cfg - - // We're only trying to look at stuff in the module cache, so - // disable the network. This should speed things up, and has - // prevented errors in at least one case, #28518. - tmpCfg.Env = append(append([]string{"GOPROXY=off"}, cfg.Env...)) - - var err error - tmpCfg.Dir, err = ioutil.TempDir("", "gopackages-modquery") - if err != nil { - return err - } - defer os.RemoveAll(tmpCfg.Dir) - - if err := ioutil.WriteFile(filepath.Join(tmpCfg.Dir, "go.mod"), gomod.Bytes(), 0777); err != nil { - return fmt.Errorf("writing go.mod for module cache query: %v", err) - } - - // Run the query, using the import paths calculated from the matches above. - resp, err := driver(&tmpCfg, imports...) - if err != nil { - return fmt.Errorf("querying module cache matches: %v", err) - } - addResponse(resp) - } - - return nil -} - -func getSizes(cfg *Config) (types.Sizes, error) { - return packagesdriver.GetSizesGolist(cfg.Context, cfg.BuildFlags, cfg.Env, cfg.Dir, usesExportData(cfg)) -} - -// roots selects the appropriate paths to walk based on the passed-in configuration, -// particularly the environment and the presence of a go.mod in cfg.Dir's parents. -func roots(cfg *Config) ([]gopathwalk.Root, string, error) { - stdout, err := invokeGo(cfg, "env", "GOROOT", "GOPATH", "GOMOD") - if err != nil { - return nil, "", err - } - - fields := strings.Split(stdout.String(), "\n") - if len(fields) != 4 || len(fields[3]) != 0 { - return nil, "", fmt.Errorf("go env returned unexpected output: %q", stdout.String()) - } - goroot, gopath, gomod := fields[0], filepath.SplitList(fields[1]), fields[2] - var modDir string - if gomod != "" { - modDir = filepath.Dir(gomod) - } - - var roots []gopathwalk.Root - // Always add GOROOT. - roots = append(roots, gopathwalk.Root{filepath.Join(goroot, "/src"), gopathwalk.RootGOROOT}) - // If modules are enabled, scan the module dir. - if modDir != "" { - roots = append(roots, gopathwalk.Root{modDir, gopathwalk.RootCurrentModule}) - } - // Add either GOPATH/src or GOPATH/pkg/mod, depending on module mode. - for _, p := range gopath { - if modDir != "" { - roots = append(roots, gopathwalk.Root{filepath.Join(p, "/pkg/mod"), gopathwalk.RootModuleCache}) - } else { - roots = append(roots, gopathwalk.Root{filepath.Join(p, "/src"), gopathwalk.RootGOPATH}) - } - } - - return roots, modDir, nil -} - -// These functions were copied from goimports. See further documentation there. - -// pathMatchesQueries is adapted from pkgIsCandidate. -// TODO: is it reasonable to do Contains here, rather than an exact match on a path component? -func pathMatchesQueries(path string, queries []string) bool { - lastTwo := lastTwoComponents(path) - for _, query := range queries { - if strings.Contains(lastTwo, query) { - return true - } - if hasHyphenOrUpperASCII(lastTwo) && !hasHyphenOrUpperASCII(query) { - lastTwo = lowerASCIIAndRemoveHyphen(lastTwo) - if strings.Contains(lastTwo, query) { - return true - } - } - } - return false -} - -// lastTwoComponents returns at most the last two path components -// of v, using either / or \ as the path separator. -func lastTwoComponents(v string) string { - nslash := 0 - for i := len(v) - 1; i >= 0; i-- { - if v[i] == '/' || v[i] == '\\' { - nslash++ - if nslash == 2 { - return v[i:] - } - } - } - return v -} - -func hasHyphenOrUpperASCII(s string) bool { - for i := 0; i < len(s); i++ { - b := s[i] - if b == '-' || ('A' <= b && b <= 'Z') { - return true - } - } - return false -} - -func lowerASCIIAndRemoveHyphen(s string) (ret string) { - buf := make([]byte, 0, len(s)) - for i := 0; i < len(s); i++ { - b := s[i] - switch { - case b == '-': - continue - case 'A' <= b && b <= 'Z': - buf = append(buf, b+('a'-'A')) - default: - buf = append(buf, b) - } - } - return string(buf) + return response, nil } // Fields must match go list; @@ -524,6 +400,7 @@ type jsonPackage struct { Imports []string ImportMap map[string]string Deps []string + Module *Module TestGoFiles []string TestImports []string XTestGoFiles []string @@ -544,16 +421,15 @@ func otherFiles(p *jsonPackage) [][]string { return [][]string{p.CFiles, p.CXXFiles, p.MFiles, p.HFiles, p.FFiles, p.SFiles, p.SwigFiles, p.SwigCXXFiles, p.SysoFiles} } -// golistDriver uses the "go list" command to expand the pattern -// words and return metadata for the specified packages. dir may be -// "" and env may be nil, as per os/exec.Command. -func golistDriver(cfg *Config, words ...string) (*driverResponse, error) { +// createDriverResponse uses the "go list" command to expand the pattern +// words and return a response for the specified packages. +func (state *golistState) createDriverResponse(words ...string) (*driverResponse, error) { // go list uses the following identifiers in ImportPath and Imports: // // "p" -- importable package or main (command) - // "q.test" -- q's test executable + // "q.test" -- q's test executable // "p [q.test]" -- variant of p as built for q's test executable - // "q_test [q.test]" -- q's external test package + // "q_test [q.test]" -- q's external test package // // The packages p that are built differently for a test q.test // are q itself, plus any helpers used by the external test q_test, @@ -561,11 +437,13 @@ func golistDriver(cfg *Config, words ...string) (*driverResponse, error) { // Run "go list" for complete // information on the specified packages. - buf, err := invokeGo(cfg, golistargs(cfg, words)...) + buf, err := state.invokeGo("list", golistargs(state.cfg, words)...) if err != nil { return nil, err } seen := make(map[string]*jsonPackage) + pkgs := make(map[string]*Package) + additionalErrors := make(map[string][]Error) // Decode the JSON and convert it to Package form. var response driverResponse for dec := json.NewDecoder(buf); dec.More(); { @@ -588,12 +466,89 @@ func golistDriver(cfg *Config, words ...string) (*driverResponse, error) { return nil, fmt.Errorf("package missing import path: %+v", p) } - if old, found := seen[p.ImportPath]; found { - if !reflect.DeepEqual(p, old) { - return nil, fmt.Errorf("internal error: go list gives conflicting information for package %v", p.ImportPath) + // Work around https://golang.org/issue/33157: + // go list -e, when given an absolute path, will find the package contained at + // that directory. But when no package exists there, it will return a fake package + // with an error and the ImportPath set to the absolute path provided to go list. + // Try to convert that absolute path to what its package path would be if it's + // contained in a known module or GOPATH entry. This will allow the package to be + // properly "reclaimed" when overlays are processed. + if filepath.IsAbs(p.ImportPath) && p.Error != nil { + pkgPath, ok, err := state.getPkgPath(p.ImportPath) + if err != nil { + return nil, err } - // skip the duplicate - continue + if ok { + p.ImportPath = pkgPath + } + } + + if old, found := seen[p.ImportPath]; found { + // If one version of the package has an error, and the other doesn't, assume + // that this is a case where go list is reporting a fake dependency variant + // of the imported package: When a package tries to invalidly import another + // package, go list emits a variant of the imported package (with the same + // import path, but with an error on it, and the package will have a + // DepError set on it). An example of when this can happen is for imports of + // main packages: main packages can not be imported, but they may be + // separately matched and listed by another pattern. + // See golang.org/issue/36188 for more details. + + // The plan is that eventually, hopefully in Go 1.15, the error will be + // reported on the importing package rather than the duplicate "fake" + // version of the imported package. Once all supported versions of Go + // have the new behavior this logic can be deleted. + // TODO(matloob): delete the workaround logic once all supported versions of + // Go return the errors on the proper package. + + // There should be exactly one version of a package that doesn't have an + // error. + if old.Error == nil && p.Error == nil { + if !reflect.DeepEqual(p, old) { + return nil, fmt.Errorf("internal error: go list gives conflicting information for package %v", p.ImportPath) + } + continue + } + + // Determine if this package's error needs to be bubbled up. + // This is a hack, and we expect for go list to eventually set the error + // on the package. + if old.Error != nil { + var errkind string + if strings.Contains(old.Error.Err, "not an importable package") { + errkind = "not an importable package" + } else if strings.Contains(old.Error.Err, "use of internal package") && strings.Contains(old.Error.Err, "not allowed") { + errkind = "use of internal package not allowed" + } + if errkind != "" { + if len(old.Error.ImportStack) < 1 { + return nil, fmt.Errorf(`internal error: go list gave a %q error with empty import stack`, errkind) + } + importingPkg := old.Error.ImportStack[len(old.Error.ImportStack)-1] + if importingPkg == old.ImportPath { + // Using an older version of Go which put this package itself on top of import + // stack, instead of the importer. Look for importer in second from top + // position. + if len(old.Error.ImportStack) < 2 { + return nil, fmt.Errorf(`internal error: go list gave a %q error with an import stack without importing package`, errkind) + } + importingPkg = old.Error.ImportStack[len(old.Error.ImportStack)-2] + } + additionalErrors[importingPkg] = append(additionalErrors[importingPkg], Error{ + Pos: old.Error.Pos, + Msg: old.Error.Err, + Kind: ListError, + }) + } + } + + // Make sure that if there's a version of the package without an error, + // that's the one reported to the user. + if old.Error == nil { + continue + } + + // This package will replace the old one at the end of the loop. } seen[p.ImportPath] = p @@ -603,6 +558,27 @@ func golistDriver(cfg *Config, words ...string) (*driverResponse, error) { GoFiles: absJoin(p.Dir, p.GoFiles, p.CgoFiles), CompiledGoFiles: absJoin(p.Dir, p.CompiledGoFiles), OtherFiles: absJoin(p.Dir, otherFiles(p)...), + forTest: p.ForTest, + Module: p.Module, + } + + if (state.cfg.Mode&typecheckCgo) != 0 && len(p.CgoFiles) != 0 { + if len(p.CompiledGoFiles) > len(p.GoFiles) { + // We need the cgo definitions, which are in the first + // CompiledGoFile after the non-cgo ones. This is a hack but there + // isn't currently a better way to find it. We also need the pure + // Go files and unprocessed cgo files, all of which are already + // in pkg.GoFiles. + cgoTypes := p.CompiledGoFiles[len(p.GoFiles)] + pkg.CompiledGoFiles = append([]string{cgoTypes}, pkg.GoFiles...) + } else { + // golang/go#38990: go list silently fails to do cgo processing + pkg.CompiledGoFiles = nil + pkg.Errors = append(pkg.Errors, Error{ + Msg: "go list failed to return CompiledGoFiles; https://golang.org/issue/38990?", + Kind: ListError, + }) + } } // Work around https://golang.org/issue/28749: @@ -678,19 +654,157 @@ func golistDriver(cfg *Config, words ...string) (*driverResponse, error) { pkg.CompiledGoFiles = pkg.GoFiles } + // Temporary work-around for golang/go#39986. Parse filenames out of + // error messages. This happens if there are unrecoverable syntax + // errors in the source, so we can't match on a specific error message. + if err := p.Error; err != nil && state.shouldAddFilenameFromError(p) { + addFilenameFromPos := func(pos string) bool { + split := strings.Split(pos, ":") + if len(split) < 1 { + return false + } + filename := strings.TrimSpace(split[0]) + if filename == "" { + return false + } + if !filepath.IsAbs(filename) { + filename = filepath.Join(state.cfg.Dir, filename) + } + info, _ := os.Stat(filename) + if info == nil { + return false + } + pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, filename) + pkg.GoFiles = append(pkg.GoFiles, filename) + return true + } + found := addFilenameFromPos(err.Pos) + // In some cases, go list only reports the error position in the + // error text, not the error position. One such case is when the + // file's package name is a keyword (see golang.org/issue/39763). + if !found { + addFilenameFromPos(err.Err) + } + } + if p.Error != nil { + msg := strings.TrimSpace(p.Error.Err) // Trim to work around golang.org/issue/32363. + // Address golang.org/issue/35964 by appending import stack to error message. + if msg == "import cycle not allowed" && len(p.Error.ImportStack) != 0 { + msg += fmt.Sprintf(": import stack: %v", p.Error.ImportStack) + } pkg.Errors = append(pkg.Errors, Error{ - Pos: p.Error.Pos, - Msg: p.Error.Err, + Pos: p.Error.Pos, + Msg: msg, + Kind: ListError, }) } - response.Packages = append(response.Packages, pkg) + pkgs[pkg.ID] = pkg } + for id, errs := range additionalErrors { + if p, ok := pkgs[id]; ok { + p.Errors = append(p.Errors, errs...) + } + } + for _, pkg := range pkgs { + response.Packages = append(response.Packages, pkg) + } + sort.Slice(response.Packages, func(i, j int) bool { return response.Packages[i].ID < response.Packages[j].ID }) + return &response, nil } +func (state *golistState) shouldAddFilenameFromError(p *jsonPackage) bool { + if len(p.GoFiles) > 0 || len(p.CompiledGoFiles) > 0 { + return false + } + + goV, err := state.getGoVersion() + if err != nil { + return false + } + + // On Go 1.14 and earlier, only add filenames from errors if the import stack is empty. + // The import stack behaves differently for these versions than newer Go versions. + if strings.HasPrefix(goV, "go1.13") || strings.HasPrefix(goV, "go1.14") { + return len(p.Error.ImportStack) == 0 + } + + // On Go 1.15 and later, only parse filenames out of error if there's no import stack, + // or the current package is at the top of the import stack. This is not guaranteed + // to work perfectly, but should avoid some cases where files in errors don't belong to this + // package. + return len(p.Error.ImportStack) == 0 || p.Error.ImportStack[len(p.Error.ImportStack)-1] == p.ImportPath +} + +func (state *golistState) getGoVersion() (string, error) { + state.goVersionOnce.Do(func() { + var b *bytes.Buffer + // Invoke go version. Don't use invokeGo because it will supply build flags, and + // go version doesn't expect build flags. + inv := gocommand.Invocation{ + Verb: "version", + Env: state.cfg.Env, + Logf: state.cfg.Logf, + } + gocmdRunner := state.cfg.gocmdRunner + if gocmdRunner == nil { + gocmdRunner = &gocommand.Runner{} + } + b, _, _, state.goVersionError = gocmdRunner.RunRaw(state.cfg.Context, inv) + if state.goVersionError != nil { + return + } + + sp := strings.Split(b.String(), " ") + if len(sp) < 3 { + state.goVersionError = fmt.Errorf("go version output: expected 'go version ', got '%s'", b.String()) + return + } + state.goVersion = sp[2] + }) + return state.goVersion, state.goVersionError +} + +// getPkgPath finds the package path of a directory if it's relative to a root +// directory. +func (state *golistState) getPkgPath(dir string) (string, bool, error) { + absDir, err := filepath.Abs(dir) + if err != nil { + return "", false, err + } + roots, err := state.determineRootDirs() + if err != nil { + return "", false, err + } + + for rdir, rpath := range roots { + // Make sure that the directory is in the module, + // to avoid creating a path relative to another module. + if !strings.HasPrefix(absDir, rdir) { + continue + } + // TODO(matloob): This doesn't properly handle symlinks. + r, err := filepath.Rel(rdir, dir) + if err != nil { + continue + } + if rpath != "" { + // We choose only one root even though the directory even it can belong in multiple modules + // or GOPATH entries. This is okay because we only need to work with absolute dirs when a + // file is missing from disk, for instance when gopls calls go/packages in an overlay. + // Once the file is saved, gopls, or the next invocation of the tool will get the correct + // result straight from golist. + // TODO(matloob): Implement module tiebreaking? + return path.Join(rpath, filepath.ToSlash(r)), true, nil + } + return filepath.ToSlash(r), true, nil + } + return "", false, nil +} + // absJoin absolutizes and flattens the lists of files. func absJoin(dir string, fileses ...[]string) (res []string) { for _, files := range fileses { @@ -707,11 +821,11 @@ func absJoin(dir string, fileses ...[]string) (res []string) { func golistargs(cfg *Config, words []string) []string { const findFlags = NeedImports | NeedTypes | NeedSyntax | NeedTypesInfo fullargs := []string{ - "list", "-e", "-json", - fmt.Sprintf("-compiled=%t", cfg.Mode&(NeedCompiledGoFiles|NeedSyntax|NeedTypesInfo|NeedTypesSizes) != 0), + "-e", "-json", + fmt.Sprintf("-compiled=%t", cfg.Mode&(NeedCompiledGoFiles|NeedSyntax|NeedTypes|NeedTypesInfo|NeedTypesSizes) != 0), fmt.Sprintf("-test=%t", cfg.Tests), fmt.Sprintf("-export=%t", usesExportData(cfg)), - fmt.Sprintf("-deps=%t", cfg.Mode&NeedDeps != 0), + fmt.Sprintf("-deps=%t", cfg.Mode&NeedImports != 0), // go list doesn't let you pass -test and -find together, // probably because you'd just get the TestMain. fmt.Sprintf("-find=%t", !cfg.Tests && cfg.Mode&findFlags == 0), @@ -723,27 +837,23 @@ func golistargs(cfg *Config, words []string) []string { } // invokeGo returns the stdout of a go command invocation. -func invokeGo(cfg *Config, args ...string) (*bytes.Buffer, error) { - stdout := new(bytes.Buffer) - stderr := new(bytes.Buffer) - cmd := exec.CommandContext(cfg.Context, "go", args...) - // On darwin the cwd gets resolved to the real path, which breaks anything that - // expects the working directory to keep the original path, including the - // go command when dealing with modules. - // The Go stdlib has a special feature where if the cwd and the PWD are the - // same node then it trusts the PWD, so by setting it in the env for the child - // process we fix up all the paths returned by the go command. - cmd.Env = append(append([]string{}, cfg.Env...), "PWD="+cfg.Dir) - cmd.Dir = cfg.Dir - cmd.Stdout = stdout - cmd.Stderr = stderr - if debug { - defer func(start time.Time) { - log.Printf("%s for %v, stderr: <<%s>>\n", time.Since(start), cmdDebugStr(cmd, args...), stderr) - }(time.Now()) - } +func (state *golistState) invokeGo(verb string, args ...string) (*bytes.Buffer, error) { + cfg := state.cfg - if err := cmd.Run(); err != nil { + inv := gocommand.Invocation{ + Verb: verb, + Args: args, + BuildFlags: cfg.BuildFlags, + Env: cfg.Env, + Logf: cfg.Logf, + WorkingDir: cfg.Dir, + } + gocmdRunner := cfg.gocmdRunner + if gocmdRunner == nil { + gocmdRunner = &gocommand.Runner{} + } + stdout, stderr, _, err := gocmdRunner.RunRaw(cfg.Context, inv) + if err != nil { // Check for 'go' executable not being found. if ee, ok := err.(*exec.Error); ok && ee.Err == exec.ErrNotFound { return nil, fmt.Errorf("'go list' driver requires 'go', but %s", exec.ErrNotFound) @@ -753,7 +863,7 @@ func invokeGo(cfg *Config, args ...string) (*bytes.Buffer, error) { if !ok { // Catastrophic error: // - context cancellation - return nil, fmt.Errorf("couldn't exec 'go %v': %s %T", args, err, err) + return nil, xerrors.Errorf("couldn't run 'go': %w", err) } // Old go version? @@ -761,6 +871,35 @@ func invokeGo(cfg *Config, args ...string) (*bytes.Buffer, error) { return nil, goTooOldError{fmt.Errorf("unsupported version of go: %s: %s", exitErr, stderr)} } + // Related to #24854 + if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "unexpected directory layout") { + return nil, fmt.Errorf("%s", stderr.String()) + } + + // Is there an error running the C compiler in cgo? This will be reported in the "Error" field + // and should be suppressed by go list -e. + // + // This condition is not perfect yet because the error message can include other error messages than runtime/cgo. + isPkgPathRune := func(r rune) bool { + // From https://golang.org/ref/spec#Import_declarations: + // Implementation restriction: A compiler may restrict ImportPaths to non-empty strings + // using only characters belonging to Unicode's L, M, N, P, and S general categories + // (the Graphic characters without spaces) and may also exclude the + // characters !"#$%&'()*,:;<=>?[\]^`{|} and the Unicode replacement character U+FFFD. + return unicode.IsOneOf([]*unicode.RangeTable{unicode.L, unicode.M, unicode.N, unicode.P, unicode.S}, r) && + !strings.ContainsRune("!\"#$%&'()*,:;<=>?[\\]^`{|}\uFFFD", r) + } + if len(stderr.String()) > 0 && strings.HasPrefix(stderr.String(), "# ") { + msg := stderr.String()[len("# "):] + if strings.HasPrefix(strings.TrimLeftFunc(msg, isPkgPathRune), "\n") { + return stdout, nil + } + // Treat pkg-config errors as a special case (golang.org/issue/36770). + if strings.HasPrefix(msg, "pkg-config") { + return stdout, nil + } + } + // This error only appears in stderr. See golang.org/cl/166398 for a fix in go list to show // the error in the Err section of stdout in case -e option is provided. // This fix is provided for backwards compatibility. @@ -770,13 +909,70 @@ func invokeGo(cfg *Config, args ...string) (*bytes.Buffer, error) { return bytes.NewBufferString(output), nil } + // Similar to the previous error, but currently lacks a fix in Go. + if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "named files must all be in one directory") { + output := fmt.Sprintf(`{"ImportPath": "command-line-arguments","Incomplete": true,"Error": {"Pos": "","Err": %q}}`, + strings.Trim(stderr.String(), "\n")) + return bytes.NewBufferString(output), nil + } + + // Backwards compatibility for Go 1.11 because 1.12 and 1.13 put the directory in the ImportPath. + // If the package doesn't exist, put the absolute path of the directory into the error message, + // as Go 1.13 list does. + const noSuchDirectory = "no such directory" + if len(stderr.String()) > 0 && strings.Contains(stderr.String(), noSuchDirectory) { + errstr := stderr.String() + abspath := strings.TrimSpace(errstr[strings.Index(errstr, noSuchDirectory)+len(noSuchDirectory):]) + output := fmt.Sprintf(`{"ImportPath": %q,"Incomplete": true,"Error": {"Pos": "","Err": %q}}`, + abspath, strings.Trim(stderr.String(), "\n")) + return bytes.NewBufferString(output), nil + } + // Workaround for #29280: go list -e has incorrect behavior when an ad-hoc package doesn't exist. + // Note that the error message we look for in this case is different that the one looked for above. if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "no such file or directory") { output := fmt.Sprintf(`{"ImportPath": "command-line-arguments","Incomplete": true,"Error": {"Pos": "","Err": %q}}`, strings.Trim(stderr.String(), "\n")) return bytes.NewBufferString(output), nil } + // Workaround for #34273. go list -e with GO111MODULE=on has incorrect behavior when listing a + // directory outside any module. + if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "outside available modules") { + output := fmt.Sprintf(`{"ImportPath": %q,"Incomplete": true,"Error": {"Pos": "","Err": %q}}`, + // TODO(matloob): command-line-arguments isn't correct here. + "command-line-arguments", strings.Trim(stderr.String(), "\n")) + return bytes.NewBufferString(output), nil + } + + // Another variation of the previous error + if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "outside module root") { + output := fmt.Sprintf(`{"ImportPath": %q,"Incomplete": true,"Error": {"Pos": "","Err": %q}}`, + // TODO(matloob): command-line-arguments isn't correct here. + "command-line-arguments", strings.Trim(stderr.String(), "\n")) + return bytes.NewBufferString(output), nil + } + + // Workaround for an instance of golang.org/issue/26755: go list -e will return a non-zero exit + // status if there's a dependency on a package that doesn't exist. But it should return + // a zero exit status and set an error on that package. + if len(stderr.String()) > 0 && strings.Contains(stderr.String(), "no Go files in") { + // Don't clobber stdout if `go list` actually returned something. + if len(stdout.String()) > 0 { + return stdout, nil + } + // try to extract package name from string + stderrStr := stderr.String() + var importPath string + colon := strings.Index(stderrStr, ":") + if colon > 0 && strings.HasPrefix(stderrStr, "go build ") { + importPath = stderrStr[len("go build "):colon] + } + output := fmt.Sprintf(`{"ImportPath": %q,"Incomplete": true,"Error": {"Pos": "","Err": %q}}`, + importPath, strings.Trim(stderrStr, "\n")) + return bytes.NewBufferString(output), nil + } + // Export mode entails a build. // If that build fails, errors appear on stderr // (despite the -e flag) and the Export field is blank. @@ -788,22 +984,6 @@ func invokeGo(cfg *Config, args ...string) (*bytes.Buffer, error) { return nil, fmt.Errorf("go %v: %s: %s", args, exitErr, stderr) } } - - // As of writing, go list -export prints some non-fatal compilation - // errors to stderr, even with -e set. We would prefer that it put - // them in the Package.Error JSON (see https://golang.org/issue/26319). - // In the meantime, there's nowhere good to put them, but they can - // be useful for debugging. Print them if $GOPACKAGESPRINTGOLISTERRORS - // is set. - if len(stderr.Bytes()) != 0 && os.Getenv("GOPACKAGESPRINTGOLISTERRORS") != "" { - fmt.Fprintf(os.Stderr, "%s stderr: <<%s>>\n", cmdDebugStr(cmd, args...), stderr) - } - - // debugging - if false { - fmt.Fprintf(os.Stderr, "%s stdout: <<%s>>\n", cmdDebugStr(cmd, args...), stdout) - } - return stdout, nil } diff --git a/vendor/golang.org/x/tools/go/packages/golist_overlay.go b/vendor/golang.org/x/tools/go/packages/golist_overlay.go index 33a0a28f2..874f90134 100644 --- a/vendor/golang.org/x/tools/go/packages/golist_overlay.go +++ b/vendor/golang.org/x/tools/go/packages/golist_overlay.go @@ -1,85 +1,250 @@ package packages import ( + "encoding/json" + "fmt" "go/parser" "go/token" + "log" + "os" "path/filepath" + "regexp" + "sort" "strconv" "strings" + + "golang.org/x/tools/internal/gocommand" ) // processGolistOverlay provides rudimentary support for adding // files that don't exist on disk to an overlay. The results can be // sometimes incorrect. // TODO(matloob): Handle unsupported cases, including the following: -// - test files -// - adding test and non-test files to test variants of packages // - determining the correct package to add given a new import path -// - creating packages that don't exist -func processGolistOverlay(cfg *Config, response *driverResponse) (modifiedPkgs, needPkgs []string, err error) { +func (state *golistState) processGolistOverlay(response *responseDeduper) (modifiedPkgs, needPkgs []string, err error) { havePkgs := make(map[string]string) // importPath -> non-test package ID needPkgsSet := make(map[string]bool) modifiedPkgsSet := make(map[string]bool) - for _, pkg := range response.Packages { + pkgOfDir := make(map[string][]*Package) + for _, pkg := range response.dr.Packages { // This is an approximation of import path to id. This can be // wrong for tests, vendored packages, and a number of other cases. havePkgs[pkg.PkgPath] = pkg.ID + x := commonDir(pkg.GoFiles) + if x != "" { + pkgOfDir[x] = append(pkgOfDir[x], pkg) + } } -outer: - for path, contents := range cfg.Overlay { - base := filepath.Base(path) - if strings.HasSuffix(path, "_test.go") { - // Overlays don't support adding new test files yet. - // TODO(matloob): support adding new test files. + // If no new imports are added, it is safe to avoid loading any needPkgs. + // Otherwise, it's hard to tell which package is actually being loaded + // (due to vendoring) and whether any modified package will show up + // in the transitive set of dependencies (because new imports are added, + // potentially modifying the transitive set of dependencies). + var overlayAddsImports bool + + // If both a package and its test package are created by the overlay, we + // need the real package first. Process all non-test files before test + // files, and make the whole process deterministic while we're at it. + var overlayFiles []string + for opath := range state.cfg.Overlay { + overlayFiles = append(overlayFiles, opath) + } + sort.Slice(overlayFiles, func(i, j int) bool { + iTest := strings.HasSuffix(overlayFiles[i], "_test.go") + jTest := strings.HasSuffix(overlayFiles[j], "_test.go") + if iTest != jTest { + return !iTest // non-tests are before tests. + } + return overlayFiles[i] < overlayFiles[j] + }) + for _, opath := range overlayFiles { + contents := state.cfg.Overlay[opath] + base := filepath.Base(opath) + dir := filepath.Dir(opath) + var pkg *Package // if opath belongs to both a package and its test variant, this will be the test variant + var testVariantOf *Package // if opath is a test file, this is the package it is testing + var fileExists bool + isTestFile := strings.HasSuffix(opath, "_test.go") + pkgName, ok := extractPackageName(opath, contents) + if !ok { + // Don't bother adding a file that doesn't even have a parsable package statement + // to the overlay. continue } - dir := filepath.Dir(path) - for _, pkg := range response.Packages { - var dirContains, fileExists bool - for _, f := range pkg.GoFiles { - if sameFile(filepath.Dir(f), dir) { - dirContains = true + // If all the overlay files belong to a different package, change the + // package name to that package. + maybeFixPackageName(pkgName, isTestFile, pkgOfDir[dir]) + nextPackage: + for _, p := range response.dr.Packages { + if pkgName != p.Name && p.ID != "command-line-arguments" { + continue + } + for _, f := range p.GoFiles { + if !sameFile(filepath.Dir(f), dir) { + continue } + // Make sure to capture information on the package's test variant, if needed. + if isTestFile && !hasTestFiles(p) { + // TODO(matloob): Are there packages other than the 'production' variant + // of a package that this can match? This shouldn't match the test main package + // because the file is generated in another directory. + testVariantOf = p + continue nextPackage + } else if !isTestFile && hasTestFiles(p) { + // We're examining a test variant, but the overlaid file is + // a non-test file. Because the overlay implementation + // (currently) only adds a file to one package, skip this + // package, so that we can add the file to the production + // variant of the package. (https://golang.org/issue/36857 + // tracks handling overlays on both the production and test + // variant of a package). + continue nextPackage + } + if pkg != nil && p != pkg && pkg.PkgPath == p.PkgPath { + // We have already seen the production version of the + // for which p is a test variant. + if hasTestFiles(p) { + testVariantOf = pkg + } + } + pkg = p if filepath.Base(f) == base { fileExists = true } } - // The overlay could have included an entirely new package. - isNewPackage := extractPackage(pkg, path, contents) - if dirContains || isNewPackage { - if !fileExists { - pkg.GoFiles = append(pkg.GoFiles, path) // TODO(matloob): should the file just be added to GoFiles? - pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, path) - modifiedPkgsSet[pkg.ID] = true + } + // The overlay could have included an entirely new package or an + // ad-hoc package. An ad-hoc package is one that we have manually + // constructed from inadequate `go list` results for a file= query. + // It will have the ID command-line-arguments. + if pkg == nil || pkg.ID == "command-line-arguments" { + // Try to find the module or gopath dir the file is contained in. + // Then for modules, add the module opath to the beginning. + pkgPath, ok, err := state.getPkgPath(dir) + if err != nil { + return nil, nil, err + } + if !ok { + break + } + var forTest string // only set for x tests + isXTest := strings.HasSuffix(pkgName, "_test") + if isXTest { + forTest = pkgPath + pkgPath += "_test" + } + id := pkgPath + if isTestFile { + if isXTest { + id = fmt.Sprintf("%s [%s.test]", pkgPath, forTest) + } else { + id = fmt.Sprintf("%s [%s.test]", pkgPath, pkgPath) } - imports, err := extractImports(path, contents) - if err != nil { - // Let the parser or type checker report errors later. - continue outer - } - for _, imp := range imports { - _, found := pkg.Imports[imp] - if !found { - needPkgsSet[imp] = true - // TODO(matloob): Handle cases when the following block isn't correct. - // These include imports of test variants, imports of vendored packages, etc. - id, ok := havePkgs[imp] - if !ok { - id = imp - } - pkg.Imports[imp] = &Package{ID: id} + } + if pkg != nil { + // TODO(rstambler): We should change the package's path and ID + // here. The only issue is that this messes with the roots. + } else { + // Try to reclaim a package with the same ID, if it exists in the response. + for _, p := range response.dr.Packages { + if reclaimPackage(p, id, opath, contents) { + pkg = p + break } } - continue outer + // Otherwise, create a new package. + if pkg == nil { + pkg = &Package{ + PkgPath: pkgPath, + ID: id, + Name: pkgName, + Imports: make(map[string]*Package), + } + response.addPackage(pkg) + havePkgs[pkg.PkgPath] = id + // Add the production package's sources for a test variant. + if isTestFile && !isXTest && testVariantOf != nil { + pkg.GoFiles = append(pkg.GoFiles, testVariantOf.GoFiles...) + pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, testVariantOf.CompiledGoFiles...) + // Add the package under test and its imports to the test variant. + pkg.forTest = testVariantOf.PkgPath + for k, v := range testVariantOf.Imports { + pkg.Imports[k] = &Package{ID: v.ID} + } + } + if isXTest { + pkg.forTest = forTest + } + } + } + } + if !fileExists { + pkg.GoFiles = append(pkg.GoFiles, opath) + // TODO(matloob): Adding the file to CompiledGoFiles can exhibit the wrong behavior + // if the file will be ignored due to its build tags. + pkg.CompiledGoFiles = append(pkg.CompiledGoFiles, opath) + modifiedPkgsSet[pkg.ID] = true + } + imports, err := extractImports(opath, contents) + if err != nil { + // Let the parser or type checker report errors later. + continue + } + for _, imp := range imports { + // TODO(rstambler): If the package is an x test and the import has + // a test variant, make sure to replace it. + if _, found := pkg.Imports[imp]; found { + continue + } + overlayAddsImports = true + id, ok := havePkgs[imp] + if !ok { + var err error + id, err = state.resolveImport(dir, imp) + if err != nil { + return nil, nil, err + } + } + pkg.Imports[imp] = &Package{ID: id} + // Add dependencies to the non-test variant version of this package as well. + if testVariantOf != nil { + testVariantOf.Imports[imp] = &Package{ID: id} } } } - needPkgs = make([]string, 0, len(needPkgsSet)) - for pkg := range needPkgsSet { - needPkgs = append(needPkgs, pkg) + // toPkgPath guesses the package path given the id. + toPkgPath := func(sourceDir, id string) (string, error) { + if i := strings.IndexByte(id, ' '); i >= 0 { + return state.resolveImport(sourceDir, id[:i]) + } + return state.resolveImport(sourceDir, id) + } + + // Now that new packages have been created, do another pass to determine + // the new set of missing packages. + for _, pkg := range response.dr.Packages { + for _, imp := range pkg.Imports { + if len(pkg.GoFiles) == 0 { + return nil, nil, fmt.Errorf("cannot resolve imports for package %q with no Go files", pkg.PkgPath) + } + pkgPath, err := toPkgPath(filepath.Dir(pkg.GoFiles[0]), imp.ID) + if err != nil { + return nil, nil, err + } + if _, ok := havePkgs[pkgPath]; !ok { + needPkgsSet[pkgPath] = true + } + } + } + + if overlayAddsImports { + needPkgs = make([]string, 0, len(needPkgsSet)) + for pkg := range needPkgsSet { + needPkgs = append(needPkgs, pkg) + } } modifiedPkgs = make([]string, 0, len(modifiedPkgsSet)) for pkg := range modifiedPkgsSet { @@ -88,6 +253,132 @@ outer: return modifiedPkgs, needPkgs, err } +// resolveImport finds the the ID of a package given its import path. +// In particular, it will find the right vendored copy when in GOPATH mode. +func (state *golistState) resolveImport(sourceDir, importPath string) (string, error) { + env, err := state.getEnv() + if err != nil { + return "", err + } + if env["GOMOD"] != "" { + return importPath, nil + } + + searchDir := sourceDir + for { + vendorDir := filepath.Join(searchDir, "vendor") + exists, ok := state.vendorDirs[vendorDir] + if !ok { + info, err := os.Stat(vendorDir) + exists = err == nil && info.IsDir() + state.vendorDirs[vendorDir] = exists + } + + if exists { + vendoredPath := filepath.Join(vendorDir, importPath) + if info, err := os.Stat(vendoredPath); err == nil && info.IsDir() { + // We should probably check for .go files here, but shame on anyone who fools us. + path, ok, err := state.getPkgPath(vendoredPath) + if err != nil { + return "", err + } + if ok { + return path, nil + } + } + } + + // We know we've hit the top of the filesystem when we Dir / and get /, + // or C:\ and get C:\, etc. + next := filepath.Dir(searchDir) + if next == searchDir { + break + } + searchDir = next + } + return importPath, nil +} + +func hasTestFiles(p *Package) bool { + for _, f := range p.GoFiles { + if strings.HasSuffix(f, "_test.go") { + return true + } + } + return false +} + +// determineRootDirs returns a mapping from absolute directories that could +// contain code to their corresponding import path prefixes. +func (state *golistState) determineRootDirs() (map[string]string, error) { + env, err := state.getEnv() + if err != nil { + return nil, err + } + if env["GOMOD"] != "" { + state.rootsOnce.Do(func() { + state.rootDirs, state.rootDirsError = state.determineRootDirsModules() + }) + } else { + state.rootsOnce.Do(func() { + state.rootDirs, state.rootDirsError = state.determineRootDirsGOPATH() + }) + } + return state.rootDirs, state.rootDirsError +} + +func (state *golistState) determineRootDirsModules() (map[string]string, error) { + // List all of the modules--the first will be the directory for the main + // module. Any replaced modules will also need to be treated as roots. + // Editing files in the module cache isn't a great idea, so we don't + // plan to ever support that. + out, err := state.invokeGo("list", "-m", "-json", "all") + if err != nil { + // 'go list all' will fail if we're outside of a module and + // GO111MODULE=on. Try falling back without 'all'. + var innerErr error + out, innerErr = state.invokeGo("list", "-m", "-json") + if innerErr != nil { + return nil, err + } + } + roots := map[string]string{} + modules := map[string]string{} + var i int + for dec := json.NewDecoder(out); dec.More(); { + mod := new(gocommand.ModuleJSON) + if err := dec.Decode(mod); err != nil { + return nil, err + } + if mod.Dir != "" && mod.Path != "" { + // This is a valid module; add it to the map. + absDir, err := filepath.Abs(mod.Dir) + if err != nil { + return nil, err + } + modules[absDir] = mod.Path + // The first result is the main module. + if i == 0 || mod.Replace != nil && mod.Replace.Path != "" { + roots[absDir] = mod.Path + } + } + i++ + } + return roots, nil +} + +func (state *golistState) determineRootDirsGOPATH() (map[string]string, error) { + m := map[string]string{} + for _, dir := range filepath.SplitList(state.mustGetEnv()["GOPATH"]) { + absDir, err := filepath.Abs(dir) + if err != nil { + return nil, err + } + m[filepath.Join(absDir, "src")] = "" + } + return m, nil +} + func extractImports(filename string, contents []byte) ([]string, error) { f, err := parser.ParseFile(token.NewFileSet(), filename, contents, parser.ImportsOnly) // TODO(matloob): reuse fileset? if err != nil { @@ -105,13 +396,16 @@ func extractImports(filename string, contents []byte) ([]string, error) { return res, nil } -// extractPackage attempts to extract a package defined in an overlay. +// reclaimPackage attempts to reuse a package that failed to load in an overlay. // // If the package has errors and has no Name, GoFiles, or Imports, // then it's possible that it doesn't yet exist on disk. -func extractPackage(pkg *Package, filename string, contents []byte) bool { +func reclaimPackage(pkg *Package, id string, filename string, contents []byte) bool { // TODO(rstambler): Check the message of the actual error? // It differs between $GOPATH and module mode. + if pkg.ID != id { + return false + } if len(pkg.Errors) != 1 { return false } @@ -124,15 +418,151 @@ func extractPackage(pkg *Package, filename string, contents []byte) bool { if len(pkg.Imports) > 0 { return false } - f, err := parser.ParseFile(token.NewFileSet(), filename, contents, parser.PackageClauseOnly) // TODO(matloob): reuse fileset? - if err != nil { + pkgName, ok := extractPackageName(filename, contents) + if !ok { return false } - // TODO(rstambler): This doesn't work for main packages. - if filepath.Base(pkg.PkgPath) != f.Name.Name { - return false - } - pkg.Name = f.Name.Name + pkg.Name = pkgName pkg.Errors = nil return true } + +func extractPackageName(filename string, contents []byte) (string, bool) { + // TODO(rstambler): Check the message of the actual error? + // It differs between $GOPATH and module mode. + f, err := parser.ParseFile(token.NewFileSet(), filename, contents, parser.PackageClauseOnly) // TODO(matloob): reuse fileset? + if err != nil { + return "", false + } + return f.Name.Name, true +} + +func commonDir(a []string) string { + seen := make(map[string]bool) + x := append([]string{}, a...) + for _, f := range x { + seen[filepath.Dir(f)] = true + } + if len(seen) > 1 { + log.Fatalf("commonDir saw %v for %v", seen, x) + } + for k := range seen { + // len(seen) == 1 + return k + } + return "" // no files +} + +// It is possible that the files in the disk directory dir have a different package +// name from newName, which is deduced from the overlays. If they all have a different +// package name, and they all have the same package name, then that name becomes +// the package name. +// It returns true if it changes the package name, false otherwise. +func maybeFixPackageName(newName string, isTestFile bool, pkgsOfDir []*Package) { + names := make(map[string]int) + for _, p := range pkgsOfDir { + names[p.Name]++ + } + if len(names) != 1 { + // some files are in different packages + return + } + var oldName string + for k := range names { + oldName = k + } + if newName == oldName { + return + } + // We might have a case where all of the package names in the directory are + // the same, but the overlay file is for an x test, which belongs to its + // own package. If the x test does not yet exist on disk, we may not yet + // have its package name on disk, but we should not rename the packages. + // + // We use a heuristic to determine if this file belongs to an x test: + // The test file should have a package name whose package name has a _test + // suffix or looks like "newName_test". + maybeXTest := strings.HasPrefix(oldName+"_test", newName) || strings.HasSuffix(newName, "_test") + if isTestFile && maybeXTest { + return + } + for _, p := range pkgsOfDir { + p.Name = newName + } +} + +// This function is copy-pasted from +// https://github.com/golang/go/blob/9706f510a5e2754595d716bd64be8375997311fb/src/cmd/go/internal/search/search.go#L360. +// It should be deleted when we remove support for overlays from go/packages. +// +// NOTE: This does not handle any ./... or ./ style queries, as this function +// doesn't know the working directory. +// +// matchPattern(pattern)(name) reports whether +// name matches pattern. Pattern is a limited glob +// pattern in which '...' means 'any string' and there +// is no other special syntax. +// Unfortunately, there are two special cases. Quoting "go help packages": +// +// First, /... at the end of the pattern can match an empty string, +// so that net/... matches both net and packages in its subdirectories, like net/http. +// Second, any slash-separated pattern element containing a wildcard never +// participates in a match of the "vendor" element in the path of a vendored +// package, so that ./... does not match packages in subdirectories of +// ./vendor or ./mycode/vendor, but ./vendor/... and ./mycode/vendor/... do. +// Note, however, that a directory named vendor that itself contains code +// is not a vendored package: cmd/vendor would be a command named vendor, +// and the pattern cmd/... matches it. +func matchPattern(pattern string) func(name string) bool { + // Convert pattern to regular expression. + // The strategy for the trailing /... is to nest it in an explicit ? expression. + // The strategy for the vendor exclusion is to change the unmatchable + // vendor strings to a disallowed code point (vendorChar) and to use + // "(anything but that codepoint)*" as the implementation of the ... wildcard. + // This is a bit complicated but the obvious alternative, + // namely a hand-written search like in most shell glob matchers, + // is too easy to make accidentally exponential. + // Using package regexp guarantees linear-time matching. + + const vendorChar = "\x00" + + if strings.Contains(pattern, vendorChar) { + return func(name string) bool { return false } + } + + re := regexp.QuoteMeta(pattern) + re = replaceVendor(re, vendorChar) + switch { + case strings.HasSuffix(re, `/`+vendorChar+`/\.\.\.`): + re = strings.TrimSuffix(re, `/`+vendorChar+`/\.\.\.`) + `(/vendor|/` + vendorChar + `/\.\.\.)` + case re == vendorChar+`/\.\.\.`: + re = `(/vendor|/` + vendorChar + `/\.\.\.)` + case strings.HasSuffix(re, `/\.\.\.`): + re = strings.TrimSuffix(re, `/\.\.\.`) + `(/\.\.\.)?` + } + re = strings.ReplaceAll(re, `\.\.\.`, `[^`+vendorChar+`]*`) + + reg := regexp.MustCompile(`^` + re + `$`) + + return func(name string) bool { + if strings.Contains(name, vendorChar) { + return false + } + return reg.MatchString(replaceVendor(name, vendorChar)) + } +} + +// replaceVendor returns the result of replacing +// non-trailing vendor path elements in x with repl. +func replaceVendor(x, repl string) string { + if !strings.Contains(x, "vendor") { + return x + } + elem := strings.Split(x, "/") + for i := 0; i < len(elem)-1; i++ { + if elem[i] == "vendor" { + elem[i] = repl + } + } + return strings.Join(elem, "/") +} diff --git a/vendor/golang.org/x/tools/go/packages/loadmode_string.go b/vendor/golang.org/x/tools/go/packages/loadmode_string.go new file mode 100644 index 000000000..7ea37e7ee --- /dev/null +++ b/vendor/golang.org/x/tools/go/packages/loadmode_string.go @@ -0,0 +1,57 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package packages + +import ( + "fmt" + "strings" +) + +var allModes = []LoadMode{ + NeedName, + NeedFiles, + NeedCompiledGoFiles, + NeedImports, + NeedDeps, + NeedExportsFile, + NeedTypes, + NeedSyntax, + NeedTypesInfo, + NeedTypesSizes, +} + +var modeStrings = []string{ + "NeedName", + "NeedFiles", + "NeedCompiledGoFiles", + "NeedImports", + "NeedDeps", + "NeedExportsFile", + "NeedTypes", + "NeedSyntax", + "NeedTypesInfo", + "NeedTypesSizes", +} + +func (mod LoadMode) String() string { + m := mod + if m == 0 { + return "LoadMode(0)" + } + var out []string + for i, x := range allModes { + if x > m { + break + } + if (m & x) != 0 { + out = append(out, modeStrings[i]) + m = m ^ x + } + } + if m != 0 { + out = append(out, "Unknown") + } + return fmt.Sprintf("LoadMode(%s)", strings.Join(out, "|")) +} diff --git a/vendor/golang.org/x/tools/go/packages/packages.go b/vendor/golang.org/x/tools/go/packages/packages.go index eedd43bb6..04053f1e7 100644 --- a/vendor/golang.org/x/tools/go/packages/packages.go +++ b/vendor/golang.org/x/tools/go/packages/packages.go @@ -21,28 +21,27 @@ import ( "path/filepath" "strings" "sync" + "time" "golang.org/x/tools/go/gcexportdata" + "golang.org/x/tools/internal/gocommand" + "golang.org/x/tools/internal/packagesinternal" + "golang.org/x/tools/internal/typesinternal" ) -// A LoadMode specifies the amount of detail to return when loading. -// Higher-numbered modes cause Load to return more information, -// but may be slower. Load may return more information than requested. +// A LoadMode controls the amount of detail to return when loading. +// The bits below can be combined to specify which fields should be +// filled in the result packages. +// The zero value is a special case, equivalent to combining +// the NeedName, NeedFiles, and NeedCompiledGoFiles bits. +// ID and Errors (if present) will always be filled. +// Load may return more information than requested. type LoadMode int +// TODO(matloob): When a V2 of go/packages is released, rename NeedExportsFile to +// NeedExportFile to make it consistent with the Package field it's adding. + const ( - // The following constants are used to specify which fields of the Package - // should be filled when loading is done. As a special case to provide - // backwards compatibility, a LoadMode of 0 is equivalent to LoadFiles. - // For all other LoadModes, the bits below specify which fields will be filled - // in the result packages. - // WARNING: This part of the go/packages API is EXPERIMENTAL. It might - // be changed or removed up until April 15 2019. After that date it will - // be frozen. - // TODO(matloob): Remove this comment on April 15. - - // ID and Errors (if present) will always be filled. - // NeedName adds Name and PkgPath. NeedName LoadMode = 1 << iota @@ -56,11 +55,10 @@ const ( // "placeholder" Packages with only the ID set. NeedImports - // NeedDeps adds the fields requested by the LoadMode in the packages in Imports. If NeedImports - // is not set NeedDeps has no effect. + // NeedDeps adds the fields requested by the LoadMode in the packages in Imports. NeedDeps - // NeedExportsFile adds ExportsFile. + // NeedExportsFile adds ExportFile. NeedExportsFile // NeedTypes adds Types, Fset, and IllTyped. @@ -74,34 +72,35 @@ const ( // NeedTypesSizes adds TypesSizes. NeedTypesSizes + + // typecheckCgo enables full support for type checking cgo. Requires Go 1.15+. + // Modifies CompiledGoFiles and Types, and has no effect on its own. + typecheckCgo + + // NeedModule adds Module. + NeedModule ) const ( - // LoadFiles finds the packages and computes their source file lists. - // Package fields: ID, Name, Errors, GoFiles, CompiledGoFiles, and OtherFiles. + // Deprecated: LoadFiles exists for historical compatibility + // and should not be used. Please directly specify the needed fields using the Need values. LoadFiles = NeedName | NeedFiles | NeedCompiledGoFiles - // LoadImports adds import information for each package - // and its dependencies. - // Package fields added: Imports. - LoadImports = LoadFiles | NeedImports | NeedDeps + // Deprecated: LoadImports exists for historical compatibility + // and should not be used. Please directly specify the needed fields using the Need values. + LoadImports = LoadFiles | NeedImports - // LoadTypes adds type information for package-level - // declarations in the packages matching the patterns. - // Package fields added: Types, TypesSizes, Fset, and IllTyped. - // This mode uses type information provided by the build system when - // possible, and may fill in the ExportFile field. + // Deprecated: LoadTypes exists for historical compatibility + // and should not be used. Please directly specify the needed fields using the Need values. LoadTypes = LoadImports | NeedTypes | NeedTypesSizes - // LoadSyntax adds typed syntax trees for the packages matching the patterns. - // Package fields added: Syntax, and TypesInfo, for direct pattern matches only. + // Deprecated: LoadSyntax exists for historical compatibility + // and should not be used. Please directly specify the needed fields using the Need values. LoadSyntax = LoadTypes | NeedSyntax | NeedTypesInfo - // LoadAllSyntax adds typed syntax trees for the packages matching the patterns - // and all dependencies. - // Package fields added: Types, Fset, IllTyped, Syntax, and TypesInfo, - // for all packages in the import graph. - LoadAllSyntax = LoadSyntax + // Deprecated: LoadAllSyntax exists for historical compatibility + // and should not be used. Please directly specify the needed fields using the Need values. + LoadAllSyntax = LoadSyntax | NeedDeps ) // A Config specifies details about how packages should be loaded. @@ -117,6 +116,12 @@ type Config struct { // If Context is nil, the load cannot be cancelled. Context context.Context + // Logf is the logger for the config. + // If the user provides a logger, debug logging is enabled. + // If the GOPACKAGESDEBUG environment variable is set to true, + // but the logger is nil, default to log.Printf. + Logf func(format string, args ...interface{}) + // Dir is the directory in which to run the build system's query tool // that provides information about the packages. // If Dir is empty, the tool is run in the current directory. @@ -132,6 +137,9 @@ type Config struct { // Env []string + // gocmdRunner guards go command calls from concurrency errors. + gocmdRunner *gocommand.Runner + // BuildFlags is a list of command-line flags to be passed through to // the build system's query tool. BuildFlags []string @@ -169,7 +177,7 @@ type Config struct { Tests bool // Overlay provides a mapping of absolute file paths to file contents. - // If the file with the given path already exists, the parser will use the + // If the file with the given path already exists, the parser will use the // alternative file contents provided by the map. // // Overlays provide incomplete support for when a given file doesn't @@ -183,6 +191,13 @@ type driver func(cfg *Config, patterns ...string) (*driverResponse, error) // driverResponse contains the results for a driver query. type driverResponse struct { + // NotHandled is returned if the request can't be handled by the current + // driver. If an external driver returns a response with NotHandled, the + // rest of the driverResponse is ignored, and go/packages will fallback + // to the next driver. If go/packages is extended in the future to support + // lists of multiple drivers, go/packages will fall back to the next driver. + NotHandled bool + // Sizes, if not nil, is the types.Sizes to use when type checking. Sizes *types.StdSizes @@ -224,14 +239,22 @@ func Load(cfg *Config, patterns ...string) ([]*Package, error) { return l.refine(response.Roots, response.Packages...) } -// defaultDriver is a driver that looks for an external driver binary, and if -// it does not find it falls back to the built in go list driver. +// defaultDriver is a driver that implements go/packages' fallback behavior. +// It will try to request to an external driver, if one exists. If there's +// no external driver, or the driver returns a response with NotHandled set, +// defaultDriver will fall back to the go list driver. func defaultDriver(cfg *Config, patterns ...string) (*driverResponse, error) { driver := findExternalDriver(cfg) if driver == nil { driver = goListDriver } - return driver(cfg, patterns...) + response, err := driver(cfg, patterns...) + if err != nil { + return response, err + } else if response.NotHandled { + return goListDriver(cfg, patterns...) + } + return response, nil } // A Package describes a loaded Go package. @@ -258,7 +281,7 @@ type Package struct { GoFiles []string // CompiledGoFiles lists the absolute file paths of the package's source - // files that were presented to the compiler. + // files that are suitable for type checking. // This may differ from GoFiles if files are processed before compilation. CompiledGoFiles []string @@ -275,9 +298,9 @@ type Package struct { Imports map[string]*Package // Types provides type information for the package. - // Modes LoadTypes and above set this field for packages matching the - // patterns; type information for dependencies may be missing or incomplete. - // Mode LoadAllSyntax sets this field for all packages, including dependencies. + // The NeedTypes LoadMode bit sets this field for packages matching the + // patterns; type information for dependencies may be missing or incomplete, + // unless NeedDeps and NeedImports are also set. Types *types.Package // Fset provides position information for Types, TypesInfo, and Syntax. @@ -290,8 +313,9 @@ type Package struct { // Syntax is the package's syntax trees, for the files listed in CompiledGoFiles. // - // Mode LoadSyntax sets this field for packages matching the patterns. - // Mode LoadAllSyntax sets this field for all packages, including dependencies. + // The NeedSyntax LoadMode bit populates this field for packages matching the patterns. + // If NeedDeps and NeedImports are also set, this field will also be populated + // for dependencies. Syntax []*ast.File // TypesInfo provides type information about the package's syntax trees. @@ -300,6 +324,44 @@ type Package struct { // TypesSizes provides the effective size function for types in TypesInfo. TypesSizes types.Sizes + + // forTest is the package under test, if any. + forTest string + + // module is the module information for the package if it exists. + Module *Module +} + +// Module provides module information for a package. +type Module struct { + Path string // module path + Version string // module version + Replace *Module // replaced by this module + Time *time.Time // time version was created + Main bool // is this the main module? + Indirect bool // is this module only an indirect dependency of main module? + Dir string // directory holding files for this module, if any + GoMod string // path to go.mod file used when loading this module, if any + GoVersion string // go version used in module + Error *ModuleError // error loading module +} + +// ModuleError holds errors loading a module. +type ModuleError struct { + Err string // the error itself +} + +func init() { + packagesinternal.GetForTest = func(p interface{}) string { + return p.(*Package).forTest + } + packagesinternal.GetGoCmdRunner = func(config interface{}) *gocommand.Runner { + return config.(*Config).gocmdRunner + } + packagesinternal.SetGoCmdRunner = func(config interface{}, runner *gocommand.Runner) { + config.(*Config).gocmdRunner = runner + } + packagesinternal.TypecheckCgo = int(typecheckCgo) } // An Error describes a problem with a package's metadata, syntax, or types. @@ -423,11 +485,13 @@ type loader struct { parseCacheMu sync.Mutex exportMu sync.Mutex // enforces mutual exclusion of exportdata operations - // TODO(matloob): Add an implied mode here and use that instead of mode. - // Implied mode would contain all the fields we need the data for so we can - // get the actually requested fields. We'll zero them out before returning - // packages to the user. This will make it easier for us to get the conditions - // where we need certain modes right. + // Config.Mode contains the implied mode (see impliedLoadMode). + // Implied mode contains all the fields we need the data for. + // In requestedMode there are the actually requested fields. + // We'll zero them out before returning packages to the user. + // This makes it easier for us to get the conditions where + // we need certain modes right. + requestedMode LoadMode } type parseValue struct { @@ -442,13 +506,27 @@ func newLoader(cfg *Config) *loader { } if cfg != nil { ld.Config = *cfg + // If the user has provided a logger, use it. + ld.Config.Logf = cfg.Logf + } + if ld.Config.Logf == nil { + // If the GOPACKAGESDEBUG environment variable is set to true, + // but the user has not provided a logger, default to log.Printf. + if debug { + ld.Config.Logf = log.Printf + } else { + ld.Config.Logf = func(format string, args ...interface{}) {} + } } if ld.Config.Mode == 0 { - ld.Config.Mode = LoadFiles // Preserve zero behavior of Mode for backwards compatibility. + ld.Config.Mode = NeedName | NeedFiles | NeedCompiledGoFiles // Preserve zero behavior of Mode for backwards compatibility. } if ld.Config.Env == nil { ld.Config.Env = os.Environ() } + if ld.Config.gocmdRunner == nil { + ld.Config.gocmdRunner = &gocommand.Runner{} + } if ld.Context == nil { ld.Context = context.Background() } @@ -458,7 +536,11 @@ func newLoader(cfg *Config) *loader { } } - if ld.Mode&NeedTypes != 0 { + // Save the actually requested fields. We'll zero them out before returning packages to the user. + ld.requestedMode = ld.Mode + ld.Mode = impliedLoadMode(ld.Mode) + + if ld.Mode&NeedTypes != 0 || ld.Mode&NeedSyntax != 0 { if ld.Fset == nil { ld.Fset = token.NewFileSet() } @@ -472,6 +554,7 @@ func newLoader(cfg *Config) *loader { } } } + return ld } @@ -490,12 +573,23 @@ func (ld *loader) refine(roots []string, list ...*Package) ([]*Package, error) { if i, found := rootMap[pkg.ID]; found { rootIndex = i } + + // Overlays can invalidate export data. + // TODO(matloob): make this check fine-grained based on dependencies on overlaid files + exportDataInvalid := len(ld.Overlay) > 0 || pkg.ExportFile == "" && pkg.PkgPath != "unsafe" + // This package needs type information if the caller requested types and the package is + // either a root, or it's a non-root and the user requested dependencies ... + needtypes := (ld.Mode&NeedTypes|NeedTypesInfo != 0 && (rootIndex >= 0 || ld.Mode&NeedDeps != 0)) + // This package needs source if the call requested source (or types info, which implies source) + // and the package is either a root, or itas a non- root and the user requested dependencies... + needsrc := ((ld.Mode&(NeedSyntax|NeedTypesInfo) != 0 && (rootIndex >= 0 || ld.Mode&NeedDeps != 0)) || + // ... or if we need types and the exportData is invalid. We fall back to (incompletely) + // typechecking packages from source if they fail to compile. + (ld.Mode&NeedTypes|NeedTypesInfo != 0 && exportDataInvalid)) && pkg.PkgPath != "unsafe" lpkg := &loaderPackage{ Package: pkg, - needtypes: (ld.Mode&(NeedTypes|NeedTypesInfo) != 0 && rootIndex < 0) || rootIndex >= 0, - needsrc: (ld.Mode&(NeedSyntax|NeedTypesInfo) != 0 && rootIndex < 0) || rootIndex >= 0 || - len(ld.Overlay) > 0 || // Overlays can invalidate export data. TODO(matloob): make this check fine-grained based on dependencies on overlaid files - pkg.ExportFile == "" && pkg.PkgPath != "unsafe", + needtypes: needtypes, + needsrc: needsrc, } ld.pkgs[lpkg.ID] = lpkg if rootIndex >= 0 { @@ -540,28 +634,31 @@ func (ld *loader) refine(roots []string, list ...*Package) ([]*Package, error) { lpkg.color = grey stack = append(stack, lpkg) // push stubs := lpkg.Imports // the structure form has only stubs with the ID in the Imports - lpkg.Imports = make(map[string]*Package, len(stubs)) - for importPath, ipkg := range stubs { - var importErr error - imp := ld.pkgs[ipkg.ID] - if imp == nil { - // (includes package "C" when DisableCgo) - importErr = fmt.Errorf("missing package: %q", ipkg.ID) - } else if imp.color == grey { - importErr = fmt.Errorf("import cycle: %s", stack) - } - if importErr != nil { - if lpkg.importErrors == nil { - lpkg.importErrors = make(map[string]error) + // If NeedImports isn't set, the imports fields will all be zeroed out. + if ld.Mode&NeedImports != 0 { + lpkg.Imports = make(map[string]*Package, len(stubs)) + for importPath, ipkg := range stubs { + var importErr error + imp := ld.pkgs[ipkg.ID] + if imp == nil { + // (includes package "C" when DisableCgo) + importErr = fmt.Errorf("missing package: %q", ipkg.ID) + } else if imp.color == grey { + importErr = fmt.Errorf("import cycle: %s", stack) + } + if importErr != nil { + if lpkg.importErrors == nil { + lpkg.importErrors = make(map[string]error) + } + lpkg.importErrors[importPath] = importErr + continue } - lpkg.importErrors[importPath] = importErr - continue - } - if visit(imp) { - lpkg.needsrc = true + if visit(imp) { + lpkg.needsrc = true + } + lpkg.Imports[importPath] = imp.Package } - lpkg.Imports[importPath] = imp.Package } if lpkg.needsrc { srcPkgs = append(srcPkgs, lpkg) @@ -575,7 +672,7 @@ func (ld *loader) refine(roots []string, list ...*Package) ([]*Package, error) { return lpkg.needsrc } - if ld.Mode&(NeedImports|NeedDeps) == 0 { + if ld.Mode&NeedImports == 0 { // We do this to drop the stub import packages that we are not even going to try to resolve. for _, lpkg := range initial { lpkg.Imports = nil @@ -586,7 +683,7 @@ func (ld *loader) refine(roots []string, list ...*Package) ([]*Package, error) { visit(lpkg) } } - if ld.Mode&NeedDeps != 0 { // TODO(matloob): This is only the case if NeedTypes is also set, right? + if ld.Mode&NeedImports != 0 && ld.Mode&NeedTypes != 0 { for _, lpkg := range srcPkgs { // Complete type information is required for the // immediate dependencies of each source package. @@ -596,9 +693,9 @@ func (ld *loader) refine(roots []string, list ...*Package) ([]*Package, error) { } } } - // Load type data if needed, starting at + // Load type data and syntax if needed, starting at // the initial packages (roots of the import DAG). - if ld.Mode&NeedTypes != 0 { + if ld.Mode&NeedTypes != 0 || ld.Mode&NeedSyntax != 0 { var wg sync.WaitGroup for _, lpkg := range initial { wg.Add(1) @@ -611,54 +708,47 @@ func (ld *loader) refine(roots []string, list ...*Package) ([]*Package, error) { } result := make([]*Package, len(initial)) - importPlaceholders := make(map[string]*Package) for i, lpkg := range initial { result[i] = lpkg.Package } for i := range ld.pkgs { // Clear all unrequested fields, for extra de-Hyrum-ization. - if ld.Mode&NeedName == 0 { + if ld.requestedMode&NeedName == 0 { ld.pkgs[i].Name = "" ld.pkgs[i].PkgPath = "" } - if ld.Mode&NeedFiles == 0 { + if ld.requestedMode&NeedFiles == 0 { ld.pkgs[i].GoFiles = nil ld.pkgs[i].OtherFiles = nil } - if ld.Mode&NeedCompiledGoFiles == 0 { + if ld.requestedMode&NeedCompiledGoFiles == 0 { ld.pkgs[i].CompiledGoFiles = nil } - if ld.Mode&NeedImports == 0 { + if ld.requestedMode&NeedImports == 0 { ld.pkgs[i].Imports = nil } - if ld.Mode&NeedExportsFile == 0 { + if ld.requestedMode&NeedExportsFile == 0 { ld.pkgs[i].ExportFile = "" } - if ld.Mode&NeedTypes == 0 { + if ld.requestedMode&NeedTypes == 0 { ld.pkgs[i].Types = nil ld.pkgs[i].Fset = nil ld.pkgs[i].IllTyped = false } - if ld.Mode&NeedSyntax == 0 { + if ld.requestedMode&NeedSyntax == 0 { ld.pkgs[i].Syntax = nil } - if ld.Mode&NeedTypesInfo == 0 { + if ld.requestedMode&NeedTypesInfo == 0 { ld.pkgs[i].TypesInfo = nil } - if ld.Mode&NeedTypesSizes == 0 { + if ld.requestedMode&NeedTypesSizes == 0 { ld.pkgs[i].TypesSizes = nil } - if ld.Mode&NeedDeps == 0 { - for j, pkg := range ld.pkgs[i].Imports { - ph, ok := importPlaceholders[pkg.ID] - if !ok { - ph = &Package{ID: pkg.ID} - importPlaceholders[pkg.ID] = ph - } - ld.pkgs[i].Imports[j] = ph - } + if ld.requestedMode&NeedModule == 0 { + ld.pkgs[i].Module = nil } } + return result, nil } @@ -679,7 +769,6 @@ func (ld *loader) loadRecursive(lpkg *loaderPackage) { }(imp) } wg.Wait() - ld.loadPackage(lpkg) }) } @@ -687,7 +776,7 @@ func (ld *loader) loadRecursive(lpkg *loaderPackage) { // loadPackage loads the specified package. // It must be called only once per Package, // after immediate dependencies are loaded. -// Precondition: ld.Mode >= LoadTypes. +// Precondition: ld.Mode & NeedTypes. func (ld *loader) loadPackage(lpkg *loaderPackage) { if lpkg.PkgPath == "unsafe" { // Fill in the blanks to avoid surprises. @@ -711,7 +800,7 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) { // which would then require that such created packages be explicitly // inserted back into the Import graph as a final step after export data loading. // The Diamond test exercises this case. - if !lpkg.needtypes { + if !lpkg.needtypes && !lpkg.needsrc { return } if !lpkg.needsrc { @@ -768,12 +857,23 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) { lpkg.Errors = append(lpkg.Errors, errs...) } + if ld.Config.Mode&NeedTypes != 0 && len(lpkg.CompiledGoFiles) == 0 && lpkg.ExportFile != "" { + // The config requested loading sources and types, but sources are missing. + // Add an error to the package and fall back to loading from export data. + appendError(Error{"-", fmt.Sprintf("sources missing for package %s", lpkg.ID), ParseError}) + ld.loadFromExportData(lpkg) + return // can't get syntax trees for this package + } + files, errs := ld.parseFiles(lpkg.CompiledGoFiles) for _, err := range errs { appendError(err) } lpkg.Syntax = files + if ld.Config.Mode&NeedTypes == 0 { + return + } lpkg.TypesInfo = &types.Info{ Types: make(map[ast.Expr]types.TypeAndValue), @@ -806,7 +906,7 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) { if ipkg.Types != nil && ipkg.Types.Complete() { return ipkg.Types, nil } - log.Fatalf("internal error: nil Pkg importing %q from %q", path, lpkg) + log.Fatalf("internal error: package %q without types was imported from %q", path, lpkg) panic("unreachable") }) @@ -817,11 +917,20 @@ func (ld *loader) loadPackage(lpkg *loaderPackage) { // Type-check bodies of functions only in non-initial packages. // Example: for import graph A->B->C and initial packages {A,C}, // we can ignore function bodies in B. - IgnoreFuncBodies: (ld.Mode&(NeedDeps|NeedTypesInfo) == 0) && !lpkg.initial, + IgnoreFuncBodies: ld.Mode&NeedDeps == 0 && !lpkg.initial, Error: appendError, Sizes: ld.sizes, } + if (ld.Mode & typecheckCgo) != 0 { + if !typesinternal.SetUsesCgo(tc) { + appendError(Error{ + Msg: "typecheckCgo requires Go 1.15+", + Kind: ListError, + }) + return + } + } types.NewChecker(tc, ld.Fset, lpkg.Types, lpkg.TypesInfo).Files(lpkg.Syntax) lpkg.importErrors = nil // no longer needed @@ -1079,6 +1188,25 @@ func (ld *loader) loadFromExportData(lpkg *loaderPackage) (*types.Package, error return tpkg, nil } -func usesExportData(cfg *Config) bool { - return cfg.Mode&NeedExportsFile != 0 || cfg.Mode&NeedTypes != 0 && cfg.Mode&NeedTypesInfo == 0 +// impliedLoadMode returns loadMode with its dependencies. +func impliedLoadMode(loadMode LoadMode) LoadMode { + if loadMode&NeedTypesInfo != 0 && loadMode&NeedImports == 0 { + // If NeedTypesInfo, go/packages needs to do typechecking itself so it can + // associate type info with the AST. To do so, we need the export data + // for dependencies, which means we need to ask for the direct dependencies. + // NeedImports is used to ask for the direct dependencies. + loadMode |= NeedImports + } + + if loadMode&NeedDeps != 0 && loadMode&NeedImports == 0 { + // With NeedDeps we need to load at least direct dependencies. + // NeedImports is used to ask for the direct dependencies. + loadMode |= NeedImports + } + + return loadMode +} + +func usesExportData(cfg *Config) bool { + return cfg.Mode&NeedExportsFile != 0 || cfg.Mode&NeedTypes != 0 && cfg.Mode&NeedDeps == 0 } diff --git a/vendor/golang.org/x/tools/imports/fix.go b/vendor/golang.org/x/tools/imports/fix.go deleted file mode 100644 index 777d28ccd..000000000 --- a/vendor/golang.org/x/tools/imports/fix.go +++ /dev/null @@ -1,1259 +0,0 @@ -// Copyright 2013 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package imports - -import ( - "bytes" - "context" - "fmt" - "go/ast" - "go/build" - "go/parser" - "go/token" - "io/ioutil" - "log" - "os" - "os/exec" - "path" - "path/filepath" - "sort" - "strconv" - "strings" - "sync" - "time" - "unicode" - "unicode/utf8" - - "golang.org/x/tools/go/ast/astutil" - "golang.org/x/tools/go/packages" - "golang.org/x/tools/internal/gopathwalk" -) - -// Debug controls verbose logging. -var Debug = false - -// LocalPrefix is a comma-separated string of import path prefixes, which, if -// set, instructs Process to sort the import paths with the given prefixes -// into another group after 3rd-party packages. -var LocalPrefix string - -func localPrefixes() []string { - if LocalPrefix != "" { - return strings.Split(LocalPrefix, ",") - } - return nil -} - -// importToGroup is a list of functions which map from an import path to -// a group number. -var importToGroup = []func(importPath string) (num int, ok bool){ - func(importPath string) (num int, ok bool) { - for _, p := range localPrefixes() { - if strings.HasPrefix(importPath, p) || strings.TrimSuffix(p, "/") == importPath { - return 3, true - } - } - return - }, - func(importPath string) (num int, ok bool) { - if strings.HasPrefix(importPath, "appengine") { - return 2, true - } - return - }, - func(importPath string) (num int, ok bool) { - if strings.Contains(importPath, ".") { - return 1, true - } - return - }, -} - -func importGroup(importPath string) int { - for _, fn := range importToGroup { - if n, ok := fn(importPath); ok { - return n - } - } - return 0 -} - -// An importInfo represents a single import statement. -type importInfo struct { - importPath string // import path, e.g. "crypto/rand". - name string // import name, e.g. "crand", or "" if none. -} - -// A packageInfo represents what's known about a package. -type packageInfo struct { - name string // real package name, if known. - exports map[string]bool // known exports. -} - -// parseOtherFiles parses all the Go files in srcDir except filename, including -// test files if filename looks like a test. -func parseOtherFiles(fset *token.FileSet, srcDir, filename string) []*ast.File { - // This could use go/packages but it doesn't buy much, and it fails - // with https://golang.org/issue/26296 in LoadFiles mode in some cases. - considerTests := strings.HasSuffix(filename, "_test.go") - - fileBase := filepath.Base(filename) - packageFileInfos, err := ioutil.ReadDir(srcDir) - if err != nil { - return nil - } - - var files []*ast.File - for _, fi := range packageFileInfos { - if fi.Name() == fileBase || !strings.HasSuffix(fi.Name(), ".go") { - continue - } - if !considerTests && strings.HasSuffix(fi.Name(), "_test.go") { - continue - } - - f, err := parser.ParseFile(fset, filepath.Join(srcDir, fi.Name()), nil, 0) - if err != nil { - continue - } - - files = append(files, f) - } - - return files -} - -// addGlobals puts the names of package vars into the provided map. -func addGlobals(f *ast.File, globals map[string]bool) { - for _, decl := range f.Decls { - genDecl, ok := decl.(*ast.GenDecl) - if !ok { - continue - } - - for _, spec := range genDecl.Specs { - valueSpec, ok := spec.(*ast.ValueSpec) - if !ok { - continue - } - globals[valueSpec.Names[0].Name] = true - } - } -} - -// collectReferences builds a map of selector expressions, from -// left hand side (X) to a set of right hand sides (Sel). -func collectReferences(f *ast.File) references { - refs := references{} - - var visitor visitFn - visitor = func(node ast.Node) ast.Visitor { - if node == nil { - return visitor - } - switch v := node.(type) { - case *ast.SelectorExpr: - xident, ok := v.X.(*ast.Ident) - if !ok { - break - } - if xident.Obj != nil { - // If the parser can resolve it, it's not a package ref. - break - } - if !ast.IsExported(v.Sel.Name) { - // Whatever this is, it's not exported from a package. - break - } - pkgName := xident.Name - r := refs[pkgName] - if r == nil { - r = make(map[string]bool) - refs[pkgName] = r - } - r[v.Sel.Name] = true - } - return visitor - } - ast.Walk(visitor, f) - return refs -} - -// collectImports returns all the imports in f, keyed by their package name as -// determined by pathToName. Unnamed imports (., _) and "C" are ignored. -func collectImports(f *ast.File) []*importInfo { - var imports []*importInfo - for _, imp := range f.Imports { - var name string - if imp.Name != nil { - name = imp.Name.Name - } - if imp.Path.Value == `"C"` || name == "_" || name == "." { - continue - } - path := strings.Trim(imp.Path.Value, `"`) - imports = append(imports, &importInfo{ - name: name, - importPath: path, - }) - } - return imports -} - -// findMissingImport searches pass's candidates for an import that provides -// pkg, containing all of syms. -func (p *pass) findMissingImport(pkg string, syms map[string]bool) *importInfo { - for _, candidate := range p.candidates { - pkgInfo, ok := p.knownPackages[candidate.importPath] - if !ok { - continue - } - if p.importIdentifier(candidate) != pkg { - continue - } - - allFound := true - for right := range syms { - if !pkgInfo.exports[right] { - allFound = false - break - } - } - - if allFound { - return candidate - } - } - return nil -} - -// references is set of references found in a Go file. The first map key is the -// left hand side of a selector expression, the second key is the right hand -// side, and the value should always be true. -type references map[string]map[string]bool - -// A pass contains all the inputs and state necessary to fix a file's imports. -// It can be modified in some ways during use; see comments below. -type pass struct { - // Inputs. These must be set before a call to load, and not modified after. - fset *token.FileSet // fset used to parse f and its siblings. - f *ast.File // the file being fixed. - srcDir string // the directory containing f. - fixEnv *fixEnv // the environment to use for go commands, etc. - loadRealPackageNames bool // if true, load package names from disk rather than guessing them. - otherFiles []*ast.File // sibling files. - - // Intermediate state, generated by load. - existingImports map[string]*importInfo - allRefs references - missingRefs references - - // Inputs to fix. These can be augmented between successive fix calls. - lastTry bool // indicates that this is the last call and fix should clean up as best it can. - candidates []*importInfo // candidate imports in priority order. - knownPackages map[string]*packageInfo // information about all known packages. -} - -// loadPackageNames saves the package names for everything referenced by imports. -func (p *pass) loadPackageNames(imports []*importInfo) error { - var unknown []string - for _, imp := range imports { - if _, ok := p.knownPackages[imp.importPath]; ok { - continue - } - unknown = append(unknown, imp.importPath) - } - - names, err := p.fixEnv.getResolver().loadPackageNames(unknown, p.srcDir) - if err != nil { - return err - } - - for path, name := range names { - p.knownPackages[path] = &packageInfo{ - name: name, - exports: map[string]bool{}, - } - } - return nil -} - -// importIdentifier returns the identifier that imp will introduce. It will -// guess if the package name has not been loaded, e.g. because the source -// is not available. -func (p *pass) importIdentifier(imp *importInfo) string { - if imp.name != "" { - return imp.name - } - known := p.knownPackages[imp.importPath] - if known != nil && known.name != "" { - return known.name - } - return importPathToAssumedName(imp.importPath) -} - -// load reads in everything necessary to run a pass, and reports whether the -// file already has all the imports it needs. It fills in p.missingRefs with the -// file's missing symbols, if any, or removes unused imports if not. -func (p *pass) load() bool { - p.knownPackages = map[string]*packageInfo{} - p.missingRefs = references{} - p.existingImports = map[string]*importInfo{} - - // Load basic information about the file in question. - p.allRefs = collectReferences(p.f) - - // Load stuff from other files in the same package: - // global variables so we know they don't need resolving, and imports - // that we might want to mimic. - globals := map[string]bool{} - for _, otherFile := range p.otherFiles { - // Don't load globals from files that are in the same directory - // but a different package. Using them to suggest imports is OK. - if p.f.Name.Name == otherFile.Name.Name { - addGlobals(otherFile, globals) - } - p.candidates = append(p.candidates, collectImports(otherFile)...) - } - - // Resolve all the import paths we've seen to package names, and store - // f's imports by the identifier they introduce. - imports := collectImports(p.f) - if p.loadRealPackageNames { - err := p.loadPackageNames(append(imports, p.candidates...)) - if err != nil { - if Debug { - log.Printf("loading package names: %v", err) - } - return false - } - } - for _, imp := range imports { - p.existingImports[p.importIdentifier(imp)] = imp - } - - // Find missing references. - for left, rights := range p.allRefs { - if globals[left] { - continue - } - _, ok := p.existingImports[left] - if !ok { - p.missingRefs[left] = rights - continue - } - } - if len(p.missingRefs) != 0 { - return false - } - - return p.fix() -} - -// fix attempts to satisfy missing imports using p.candidates. If it finds -// everything, or if p.lastTry is true, it adds the imports it found, -// removes anything unused, and returns true. -func (p *pass) fix() bool { - // Find missing imports. - var selected []*importInfo - for left, rights := range p.missingRefs { - if imp := p.findMissingImport(left, rights); imp != nil { - selected = append(selected, imp) - } - } - - if !p.lastTry && len(selected) != len(p.missingRefs) { - return false - } - - // Found everything, or giving up. Add the new imports and remove any unused. - for _, imp := range p.existingImports { - // We deliberately ignore globals here, because we can't be sure - // they're in the same package. People do things like put multiple - // main packages in the same directory, and we don't want to - // remove imports if they happen to have the same name as a var in - // a different package. - if _, ok := p.allRefs[p.importIdentifier(imp)]; !ok { - astutil.DeleteNamedImport(p.fset, p.f, imp.name, imp.importPath) - } - } - - for _, imp := range selected { - astutil.AddNamedImport(p.fset, p.f, imp.name, imp.importPath) - } - - if p.loadRealPackageNames { - for _, imp := range p.f.Imports { - if imp.Name != nil { - continue - } - path := strings.Trim(imp.Path.Value, `""`) - ident := p.importIdentifier(&importInfo{importPath: path}) - if ident != importPathToAssumedName(path) { - imp.Name = &ast.Ident{Name: ident, NamePos: imp.Pos()} - } - } - } - - return true -} - -// assumeSiblingImportsValid assumes that siblings' use of packages is valid, -// adding the exports they use. -func (p *pass) assumeSiblingImportsValid() { - for _, f := range p.otherFiles { - refs := collectReferences(f) - imports := collectImports(f) - importsByName := map[string]*importInfo{} - for _, imp := range imports { - importsByName[p.importIdentifier(imp)] = imp - } - for left, rights := range refs { - if imp, ok := importsByName[left]; ok { - if _, ok := stdlib[imp.importPath]; ok { - // We have the stdlib in memory; no need to guess. - rights = stdlib[imp.importPath] - } - p.addCandidate(imp, &packageInfo{ - // no name; we already know it. - exports: rights, - }) - } - } - } -} - -// addCandidate adds a candidate import to p, and merges in the information -// in pkg. -func (p *pass) addCandidate(imp *importInfo, pkg *packageInfo) { - p.candidates = append(p.candidates, imp) - if existing, ok := p.knownPackages[imp.importPath]; ok { - if existing.name == "" { - existing.name = pkg.name - } - for export := range pkg.exports { - existing.exports[export] = true - } - } else { - p.knownPackages[imp.importPath] = pkg - } -} - -// fixImports adds and removes imports from f so that all its references are -// satisfied and there are no unused imports. -// -// This is declared as a variable rather than a function so goimports can -// easily be extended by adding a file with an init function. -var fixImports = fixImportsDefault - -func fixImportsDefault(fset *token.FileSet, f *ast.File, filename string, env *fixEnv) error { - abs, err := filepath.Abs(filename) - if err != nil { - return err - } - srcDir := filepath.Dir(abs) - if Debug { - log.Printf("fixImports(filename=%q), abs=%q, srcDir=%q ...", filename, abs, srcDir) - } - - // First pass: looking only at f, and using the naive algorithm to - // derive package names from import paths, see if the file is already - // complete. We can't add any imports yet, because we don't know - // if missing references are actually package vars. - p := &pass{fset: fset, f: f, srcDir: srcDir} - if p.load() { - return nil - } - - otherFiles := parseOtherFiles(fset, srcDir, filename) - - // Second pass: add information from other files in the same package, - // like their package vars and imports. - p.otherFiles = otherFiles - if p.load() { - return nil - } - - // Now we can try adding imports from the stdlib. - p.assumeSiblingImportsValid() - addStdlibCandidates(p, p.missingRefs) - if p.fix() { - return nil - } - - // Third pass: get real package names where we had previously used - // the naive algorithm. This is the first step that will use the - // environment, so we provide it here for the first time. - p = &pass{fset: fset, f: f, srcDir: srcDir, fixEnv: env} - p.loadRealPackageNames = true - p.otherFiles = otherFiles - if p.load() { - return nil - } - - addStdlibCandidates(p, p.missingRefs) - p.assumeSiblingImportsValid() - if p.fix() { - return nil - } - - // Go look for candidates in $GOPATH, etc. We don't necessarily load - // the real exports of sibling imports, so keep assuming their contents. - if err := addExternalCandidates(p, p.missingRefs, filename); err != nil { - return err - } - - p.lastTry = true - p.fix() - return nil -} - -// fixEnv contains environment variables and settings that affect the use of -// the go command, the go/build package, etc. -type fixEnv struct { - // If non-empty, these will be used instead of the - // process-wide values. - GOPATH, GOROOT, GO111MODULE, GOPROXY, GOFLAGS string - WorkingDir string - - // If true, use go/packages regardless of the environment. - ForceGoPackages bool - - resolver resolver -} - -func (e *fixEnv) env() []string { - env := os.Environ() - add := func(k, v string) { - if v != "" { - env = append(env, k+"="+v) - } - } - add("GOPATH", e.GOPATH) - add("GOROOT", e.GOROOT) - add("GO111MODULE", e.GO111MODULE) - add("GOPROXY", e.GOPROXY) - add("GOFLAGS", e.GOFLAGS) - if e.WorkingDir != "" { - add("PWD", e.WorkingDir) - } - return env -} - -func (e *fixEnv) getResolver() resolver { - if e.resolver != nil { - return e.resolver - } - if e.ForceGoPackages { - return &goPackagesResolver{env: e} - } - - out, err := e.invokeGo("env", "GOMOD") - if err != nil || len(bytes.TrimSpace(out.Bytes())) == 0 { - return &gopathResolver{env: e} - } - return &moduleResolver{env: e} -} - -func (e *fixEnv) newPackagesConfig(mode packages.LoadMode) *packages.Config { - return &packages.Config{ - Mode: mode, - Dir: e.WorkingDir, - Env: e.env(), - } -} - -func (e *fixEnv) buildContext() *build.Context { - ctx := build.Default - ctx.GOROOT = e.GOROOT - ctx.GOPATH = e.GOPATH - return &ctx -} - -func (e *fixEnv) invokeGo(args ...string) (*bytes.Buffer, error) { - cmd := exec.Command("go", args...) - stdout := &bytes.Buffer{} - stderr := &bytes.Buffer{} - cmd.Stdout = stdout - cmd.Stderr = stderr - cmd.Env = e.env() - cmd.Dir = e.WorkingDir - - if Debug { - defer func(start time.Time) { log.Printf("%s for %v", time.Since(start), cmdDebugStr(cmd)) }(time.Now()) - } - if err := cmd.Run(); err != nil { - return nil, fmt.Errorf("running go: %v (stderr:\n%s)", err, stderr) - } - return stdout, nil -} - -func cmdDebugStr(cmd *exec.Cmd) string { - env := make(map[string]string) - for _, kv := range cmd.Env { - split := strings.Split(kv, "=") - k, v := split[0], split[1] - env[k] = v - } - - return fmt.Sprintf("GOROOT=%v GOPATH=%v GO111MODULE=%v GOPROXY=%v PWD=%v go %v", env["GOROOT"], env["GOPATH"], env["GO111MODULE"], env["GOPROXY"], env["PWD"], cmd.Args) -} - -func addStdlibCandidates(pass *pass, refs references) { - add := func(pkg string) { - pass.addCandidate( - &importInfo{importPath: pkg}, - &packageInfo{name: path.Base(pkg), exports: stdlib[pkg]}) - } - for left := range refs { - if left == "rand" { - // Make sure we try crypto/rand before math/rand. - add("crypto/rand") - add("math/rand") - continue - } - for importPath := range stdlib { - if path.Base(importPath) == left { - add(importPath) - } - } - } -} - -// A resolver does the build-system-specific parts of goimports. -type resolver interface { - // loadPackageNames loads the package names in importPaths. - loadPackageNames(importPaths []string, srcDir string) (map[string]string, error) - // scan finds (at least) the packages satisfying refs. The returned slice is unordered. - scan(refs references) ([]*pkg, error) -} - -// gopathResolver implements resolver for GOPATH and module workspaces using go/packages. -type goPackagesResolver struct { - env *fixEnv -} - -func (r *goPackagesResolver) loadPackageNames(importPaths []string, srcDir string) (map[string]string, error) { - cfg := r.env.newPackagesConfig(packages.LoadFiles) - pkgs, err := packages.Load(cfg, importPaths...) - if err != nil { - return nil, err - } - names := map[string]string{} - for _, pkg := range pkgs { - names[VendorlessPath(pkg.PkgPath)] = pkg.Name - } - // We may not have found all the packages. Guess the rest. - for _, path := range importPaths { - if _, ok := names[path]; ok { - continue - } - names[path] = importPathToAssumedName(path) - } - return names, nil - -} - -func (r *goPackagesResolver) scan(refs references) ([]*pkg, error) { - var loadQueries []string - for pkgName := range refs { - loadQueries = append(loadQueries, "iamashamedtousethedisabledqueryname="+pkgName) - } - sort.Strings(loadQueries) - cfg := r.env.newPackagesConfig(packages.LoadFiles) - goPackages, err := packages.Load(cfg, loadQueries...) - if err != nil { - return nil, err - } - - var scan []*pkg - for _, goPackage := range goPackages { - scan = append(scan, &pkg{ - dir: filepath.Dir(goPackage.CompiledGoFiles[0]), - importPathShort: VendorlessPath(goPackage.PkgPath), - goPackage: goPackage, - }) - } - return scan, nil -} - -func addExternalCandidates(pass *pass, refs references, filename string) error { - dirScan, err := pass.fixEnv.getResolver().scan(refs) - if err != nil { - return err - } - - // Search for imports matching potential package references. - type result struct { - imp *importInfo - pkg *packageInfo - } - results := make(chan result, len(refs)) - - ctx, cancel := context.WithCancel(context.TODO()) - var wg sync.WaitGroup - defer func() { - cancel() - wg.Wait() - }() - var ( - firstErr error - firstErrOnce sync.Once - ) - for pkgName, symbols := range refs { - wg.Add(1) - go func(pkgName string, symbols map[string]bool) { - defer wg.Done() - - found, err := findImport(ctx, pass.fixEnv, dirScan, pkgName, symbols, filename) - - if err != nil { - firstErrOnce.Do(func() { - firstErr = err - cancel() - }) - return - } - - if found == nil { - return // No matching package. - } - - imp := &importInfo{ - importPath: found.importPathShort, - } - - pkg := &packageInfo{ - name: pkgName, - exports: symbols, - } - results <- result{imp, pkg} - }(pkgName, symbols) - } - go func() { - wg.Wait() - close(results) - }() - - for result := range results { - pass.addCandidate(result.imp, result.pkg) - } - return firstErr -} - -// notIdentifier reports whether ch is an invalid identifier character. -func notIdentifier(ch rune) bool { - return !('a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || - '0' <= ch && ch <= '9' || - ch == '_' || - ch >= utf8.RuneSelf && (unicode.IsLetter(ch) || unicode.IsDigit(ch))) -} - -// importPathToAssumedName returns the assumed package name of an import path. -// It does this using only string parsing of the import path. -// It picks the last element of the path that does not look like a major -// version, and then picks the valid identifier off the start of that element. -// It is used to determine if a local rename should be added to an import for -// clarity. -// This function could be moved to a standard package and exported if we want -// for use in other tools. -func importPathToAssumedName(importPath string) string { - base := path.Base(importPath) - if strings.HasPrefix(base, "v") { - if _, err := strconv.Atoi(base[1:]); err == nil { - dir := path.Dir(importPath) - if dir != "." { - base = path.Base(dir) - } - } - } - base = strings.TrimPrefix(base, "go-") - if i := strings.IndexFunc(base, notIdentifier); i >= 0 { - base = base[:i] - } - return base -} - -// gopathResolver implements resolver for GOPATH workspaces. -type gopathResolver struct { - env *fixEnv -} - -func (r *gopathResolver) loadPackageNames(importPaths []string, srcDir string) (map[string]string, error) { - names := map[string]string{} - for _, path := range importPaths { - names[path] = importPathToName(r.env, path, srcDir) - } - return names, nil -} - -// importPathToNameGoPath finds out the actual package name, as declared in its .go files. -// If there's a problem, it returns "". -func importPathToName(env *fixEnv, importPath, srcDir string) (packageName string) { - // Fast path for standard library without going to disk. - if _, ok := stdlib[importPath]; ok { - return path.Base(importPath) // stdlib packages always match their paths. - } - - buildPkg, err := env.buildContext().Import(importPath, srcDir, build.FindOnly) - if err != nil { - return "" - } - pkgName, err := packageDirToName(buildPkg.Dir) - if err != nil { - return "" - } - return pkgName -} - -// packageDirToName is a faster version of build.Import if -// the only thing desired is the package name. It uses build.FindOnly -// to find the directory and then only parses one file in the package, -// trusting that the files in the directory are consistent. -func packageDirToName(dir string) (packageName string, err error) { - d, err := os.Open(dir) - if err != nil { - return "", err - } - names, err := d.Readdirnames(-1) - d.Close() - if err != nil { - return "", err - } - sort.Strings(names) // to have predictable behavior - var lastErr error - var nfile int - for _, name := range names { - if !strings.HasSuffix(name, ".go") { - continue - } - if strings.HasSuffix(name, "_test.go") { - continue - } - nfile++ - fullFile := filepath.Join(dir, name) - - fset := token.NewFileSet() - f, err := parser.ParseFile(fset, fullFile, nil, parser.PackageClauseOnly) - if err != nil { - lastErr = err - continue - } - pkgName := f.Name.Name - if pkgName == "documentation" { - // Special case from go/build.ImportDir, not - // handled by ctx.MatchFile. - continue - } - if pkgName == "main" { - // Also skip package main, assuming it's a +build ignore generator or example. - // Since you can't import a package main anyway, there's no harm here. - continue - } - return pkgName, nil - } - if lastErr != nil { - return "", lastErr - } - return "", fmt.Errorf("no importable package found in %d Go files", nfile) -} - -type pkg struct { - goPackage *packages.Package - dir string // absolute file path to pkg directory ("/usr/lib/go/src/net/http") - importPathShort string // vendorless import path ("net/http", "a/b") -} - -type pkgDistance struct { - pkg *pkg - distance int // relative distance to target -} - -// byDistanceOrImportPathShortLength sorts by relative distance breaking ties -// on the short import path length and then the import string itself. -type byDistanceOrImportPathShortLength []pkgDistance - -func (s byDistanceOrImportPathShortLength) Len() int { return len(s) } -func (s byDistanceOrImportPathShortLength) Less(i, j int) bool { - di, dj := s[i].distance, s[j].distance - if di == -1 { - return false - } - if dj == -1 { - return true - } - if di != dj { - return di < dj - } - - vi, vj := s[i].pkg.importPathShort, s[j].pkg.importPathShort - if len(vi) != len(vj) { - return len(vi) < len(vj) - } - return vi < vj -} -func (s byDistanceOrImportPathShortLength) Swap(i, j int) { s[i], s[j] = s[j], s[i] } - -func distance(basepath, targetpath string) int { - p, err := filepath.Rel(basepath, targetpath) - if err != nil { - return -1 - } - if p == "." { - return 0 - } - return strings.Count(p, string(filepath.Separator)) + 1 -} - -func (r *gopathResolver) scan(_ references) ([]*pkg, error) { - dupCheck := make(map[string]bool) - var result []*pkg - - var mu sync.Mutex - - add := func(root gopathwalk.Root, dir string) { - mu.Lock() - defer mu.Unlock() - - if _, dup := dupCheck[dir]; dup { - return - } - dupCheck[dir] = true - importpath := filepath.ToSlash(dir[len(root.Path)+len("/"):]) - result = append(result, &pkg{ - importPathShort: VendorlessPath(importpath), - dir: dir, - }) - } - gopathwalk.Walk(gopathwalk.SrcDirsRoots(r.env.buildContext()), add, gopathwalk.Options{Debug: Debug, ModulesEnabled: false}) - return result, nil -} - -// VendorlessPath returns the devendorized version of the import path ipath. -// For example, VendorlessPath("foo/bar/vendor/a/b") returns "a/b". -func VendorlessPath(ipath string) string { - // Devendorize for use in import statement. - if i := strings.LastIndex(ipath, "/vendor/"); i >= 0 { - return ipath[i+len("/vendor/"):] - } - if strings.HasPrefix(ipath, "vendor/") { - return ipath[len("vendor/"):] - } - return ipath -} - -// loadExports returns the set of exported symbols in the package at dir. -// It returns nil on error or if the package name in dir does not match expectPackage. -func loadExports(ctx context.Context, env *fixEnv, expectPackage string, pkg *pkg) (map[string]bool, error) { - if Debug { - log.Printf("loading exports in dir %s (seeking package %s)", pkg.dir, expectPackage) - } - if pkg.goPackage != nil { - exports := map[string]bool{} - fset := token.NewFileSet() - for _, fname := range pkg.goPackage.CompiledGoFiles { - f, err := parser.ParseFile(fset, fname, nil, 0) - if err != nil { - return nil, fmt.Errorf("parsing %s: %v", fname, err) - } - for name := range f.Scope.Objects { - if ast.IsExported(name) { - exports[name] = true - } - } - } - return exports, nil - } - - exports := make(map[string]bool) - - // Look for non-test, buildable .go files which could provide exports. - all, err := ioutil.ReadDir(pkg.dir) - if err != nil { - return nil, err - } - var files []os.FileInfo - for _, fi := range all { - name := fi.Name() - if !strings.HasSuffix(name, ".go") || strings.HasSuffix(name, "_test.go") { - continue - } - match, err := env.buildContext().MatchFile(pkg.dir, fi.Name()) - if err != nil || !match { - continue - } - files = append(files, fi) - } - - if len(files) == 0 { - return nil, fmt.Errorf("dir %v contains no buildable, non-test .go files", pkg.dir) - } - - fset := token.NewFileSet() - for _, fi := range files { - select { - case <-ctx.Done(): - return nil, ctx.Err() - default: - } - - fullFile := filepath.Join(pkg.dir, fi.Name()) - f, err := parser.ParseFile(fset, fullFile, nil, 0) - if err != nil { - return nil, fmt.Errorf("parsing %s: %v", fullFile, err) - } - pkgName := f.Name.Name - if pkgName == "documentation" { - // Special case from go/build.ImportDir, not - // handled by MatchFile above. - continue - } - if pkgName != expectPackage { - return nil, fmt.Errorf("scan of dir %v is not expected package %v (actually %v)", pkg.dir, expectPackage, pkgName) - } - for name := range f.Scope.Objects { - if ast.IsExported(name) { - exports[name] = true - } - } - } - - if Debug { - exportList := make([]string, 0, len(exports)) - for k := range exports { - exportList = append(exportList, k) - } - sort.Strings(exportList) - log.Printf("loaded exports in dir %v (package %v): %v", pkg.dir, expectPackage, strings.Join(exportList, ", ")) - } - return exports, nil -} - -// findImport searches for a package with the given symbols. -// If no package is found, findImport returns ("", false, nil) -func findImport(ctx context.Context, env *fixEnv, dirScan []*pkg, pkgName string, symbols map[string]bool, filename string) (*pkg, error) { - pkgDir, err := filepath.Abs(filename) - if err != nil { - return nil, err - } - pkgDir = filepath.Dir(pkgDir) - - // Find candidate packages, looking only at their directory names first. - var candidates []pkgDistance - for _, pkg := range dirScan { - if pkg.dir != pkgDir && pkgIsCandidate(filename, pkgName, pkg) { - candidates = append(candidates, pkgDistance{ - pkg: pkg, - distance: distance(pkgDir, pkg.dir), - }) - } - } - - // Sort the candidates by their import package length, - // assuming that shorter package names are better than long - // ones. Note that this sorts by the de-vendored name, so - // there's no "penalty" for vendoring. - sort.Sort(byDistanceOrImportPathShortLength(candidates)) - if Debug { - for i, c := range candidates { - log.Printf("%s candidate %d/%d: %v in %v", pkgName, i+1, len(candidates), c.pkg.importPathShort, c.pkg.dir) - } - } - - // Collect exports for packages with matching names. - - rescv := make([]chan *pkg, len(candidates)) - for i := range candidates { - rescv[i] = make(chan *pkg, 1) - } - const maxConcurrentPackageImport = 4 - loadExportsSem := make(chan struct{}, maxConcurrentPackageImport) - - ctx, cancel := context.WithCancel(ctx) - var wg sync.WaitGroup - defer func() { - cancel() - wg.Wait() - }() - - wg.Add(1) - go func() { - defer wg.Done() - for i, c := range candidates { - select { - case loadExportsSem <- struct{}{}: - case <-ctx.Done(): - return - } - - wg.Add(1) - go func(c pkgDistance, resc chan<- *pkg) { - defer func() { - <-loadExportsSem - wg.Done() - }() - - exports, err := loadExports(ctx, env, pkgName, c.pkg) - if err != nil { - if Debug { - log.Printf("loading exports in dir %s (seeking package %s): %v", c.pkg.dir, pkgName, err) - } - resc <- nil - return - } - - // If it doesn't have the right - // symbols, send nil to mean no match. - for symbol := range symbols { - if !exports[symbol] { - resc <- nil - return - } - } - resc <- c.pkg - }(c, rescv[i]) - } - }() - - for _, resc := range rescv { - pkg := <-resc - if pkg == nil { - continue - } - return pkg, nil - } - return nil, nil -} - -// pkgIsCandidate reports whether pkg is a candidate for satisfying the -// finding which package pkgIdent in the file named by filename is trying -// to refer to. -// -// This check is purely lexical and is meant to be as fast as possible -// because it's run over all $GOPATH directories to filter out poor -// candidates in order to limit the CPU and I/O later parsing the -// exports in candidate packages. -// -// filename is the file being formatted. -// pkgIdent is the package being searched for, like "client" (if -// searching for "client.New") -func pkgIsCandidate(filename, pkgIdent string, pkg *pkg) bool { - // Check "internal" and "vendor" visibility: - if !canUse(filename, pkg.dir) { - return false - } - - // Speed optimization to minimize disk I/O: - // the last two components on disk must contain the - // package name somewhere. - // - // This permits mismatch naming like directory - // "go-foo" being package "foo", or "pkg.v3" being "pkg", - // or directory "google.golang.org/api/cloudbilling/v1" - // being package "cloudbilling", but doesn't - // permit a directory "foo" to be package - // "bar", which is strongly discouraged - // anyway. There's no reason goimports needs - // to be slow just to accommodate that. - lastTwo := lastTwoComponents(pkg.importPathShort) - if strings.Contains(lastTwo, pkgIdent) { - return true - } - if hasHyphenOrUpperASCII(lastTwo) && !hasHyphenOrUpperASCII(pkgIdent) { - lastTwo = lowerASCIIAndRemoveHyphen(lastTwo) - if strings.Contains(lastTwo, pkgIdent) { - return true - } - } - - return false -} - -func hasHyphenOrUpperASCII(s string) bool { - for i := 0; i < len(s); i++ { - b := s[i] - if b == '-' || ('A' <= b && b <= 'Z') { - return true - } - } - return false -} - -func lowerASCIIAndRemoveHyphen(s string) (ret string) { - buf := make([]byte, 0, len(s)) - for i := 0; i < len(s); i++ { - b := s[i] - switch { - case b == '-': - continue - case 'A' <= b && b <= 'Z': - buf = append(buf, b+('a'-'A')) - default: - buf = append(buf, b) - } - } - return string(buf) -} - -// canUse reports whether the package in dir is usable from filename, -// respecting the Go "internal" and "vendor" visibility rules. -func canUse(filename, dir string) bool { - // Fast path check, before any allocations. If it doesn't contain vendor - // or internal, it's not tricky: - // Note that this can false-negative on directories like "notinternal", - // but we check it correctly below. This is just a fast path. - if !strings.Contains(dir, "vendor") && !strings.Contains(dir, "internal") { - return true - } - - dirSlash := filepath.ToSlash(dir) - if !strings.Contains(dirSlash, "/vendor/") && !strings.Contains(dirSlash, "/internal/") && !strings.HasSuffix(dirSlash, "/internal") { - return true - } - // Vendor or internal directory only visible from children of parent. - // That means the path from the current directory to the target directory - // can contain ../vendor or ../internal but not ../foo/vendor or ../foo/internal - // or bar/vendor or bar/internal. - // After stripping all the leading ../, the only okay place to see vendor or internal - // is at the very beginning of the path. - absfile, err := filepath.Abs(filename) - if err != nil { - return false - } - absdir, err := filepath.Abs(dir) - if err != nil { - return false - } - rel, err := filepath.Rel(absfile, absdir) - if err != nil { - return false - } - relSlash := filepath.ToSlash(rel) - if i := strings.LastIndex(relSlash, "../"); i >= 0 { - relSlash = relSlash[i+len("../"):] - } - return !strings.Contains(relSlash, "/vendor/") && !strings.Contains(relSlash, "/internal/") && !strings.HasSuffix(relSlash, "/internal") -} - -// lastTwoComponents returns at most the last two path components -// of v, using either / or \ as the path separator. -func lastTwoComponents(v string) string { - nslash := 0 - for i := len(v) - 1; i >= 0; i-- { - if v[i] == '/' || v[i] == '\\' { - nslash++ - if nslash == 2 { - return v[i:] - } - } - } - return v -} - -type visitFn func(node ast.Node) ast.Visitor - -func (fn visitFn) Visit(node ast.Node) ast.Visitor { - return fn(node) -} diff --git a/vendor/golang.org/x/tools/imports/forward.go b/vendor/golang.org/x/tools/imports/forward.go new file mode 100644 index 000000000..a4e40adba --- /dev/null +++ b/vendor/golang.org/x/tools/imports/forward.go @@ -0,0 +1,73 @@ +// Package imports implements a Go pretty-printer (like package "go/format") +// that also adds or removes import statements as necessary. +package imports // import "golang.org/x/tools/imports" + +import ( + "io/ioutil" + "log" + + "golang.org/x/tools/internal/gocommand" + intimp "golang.org/x/tools/internal/imports" +) + +// Options specifies options for processing files. +type Options struct { + Fragment bool // Accept fragment of a source file (no package statement) + AllErrors bool // Report all errors (not just the first 10 on different lines) + + Comments bool // Print comments (true if nil *Options provided) + TabIndent bool // Use tabs for indent (true if nil *Options provided) + TabWidth int // Tab width (8 if nil *Options provided) + + FormatOnly bool // Disable the insertion and deletion of imports +} + +// Debug controls verbose logging. +var Debug = false + +// LocalPrefix is a comma-separated string of import path prefixes, which, if +// set, instructs Process to sort the import paths with the given prefixes +// into another group after 3rd-party packages. +var LocalPrefix string + +// Process formats and adjusts imports for the provided file. +// If opt is nil the defaults are used, and if src is nil the source +// is read from the filesystem. +// +// Note that filename's directory influences which imports can be chosen, +// so it is important that filename be accurate. +// To process data ``as if'' it were in filename, pass the data as a non-nil src. +func Process(filename string, src []byte, opt *Options) ([]byte, error) { + var err error + if src == nil { + src, err = ioutil.ReadFile(filename) + if err != nil { + return nil, err + } + } + if opt == nil { + opt = &Options{Comments: true, TabIndent: true, TabWidth: 8} + } + intopt := &intimp.Options{ + Env: &intimp.ProcessEnv{ + GocmdRunner: &gocommand.Runner{}, + }, + LocalPrefix: LocalPrefix, + AllErrors: opt.AllErrors, + Comments: opt.Comments, + FormatOnly: opt.FormatOnly, + Fragment: opt.Fragment, + TabIndent: opt.TabIndent, + TabWidth: opt.TabWidth, + } + if Debug { + intopt.Env.Logf = log.Printf + } + return intimp.Process(filename, src, intopt) +} + +// VendorlessPath returns the devendorized version of the import path ipath. +// For example, VendorlessPath("foo/bar/vendor/a/b") returns "a/b". +func VendorlessPath(ipath string) string { + return intimp.VendorlessPath(ipath) +} diff --git a/vendor/golang.org/x/tools/imports/mod.go b/vendor/golang.org/x/tools/imports/mod.go deleted file mode 100644 index 018c43ce8..000000000 --- a/vendor/golang.org/x/tools/imports/mod.go +++ /dev/null @@ -1,355 +0,0 @@ -package imports - -import ( - "bytes" - "encoding/json" - "io/ioutil" - "log" - "os" - "path" - "path/filepath" - "regexp" - "sort" - "strconv" - "strings" - "sync" - "time" - - "golang.org/x/tools/internal/gopathwalk" - "golang.org/x/tools/internal/module" -) - -// moduleResolver implements resolver for modules using the go command as little -// as feasible. -type moduleResolver struct { - env *fixEnv - - initialized bool - main *moduleJSON - modsByModPath []*moduleJSON // All modules, ordered by # of path components in module Path... - modsByDir []*moduleJSON // ...or Dir. -} - -type moduleJSON struct { - Path string // module path - Version string // module version - Versions []string // available module versions (with -versions) - Replace *moduleJSON // replaced by this module - Time *time.Time // time version was created - Update *moduleJSON // available update, if any (with -u) - Main bool // is this the main module? - Indirect bool // is this module only an indirect dependency of main module? - Dir string // directory holding files for this module, if any - GoMod string // path to go.mod file for this module, if any - Error *moduleErrorJSON // error loading module -} - -type moduleErrorJSON struct { - Err string // the error itself -} - -func (r *moduleResolver) init() error { - if r.initialized { - return nil - } - stdout, err := r.env.invokeGo("list", "-m", "-json", "...") - if err != nil { - return err - } - for dec := json.NewDecoder(stdout); dec.More(); { - mod := &moduleJSON{} - if err := dec.Decode(mod); err != nil { - return err - } - if mod.Dir == "" { - if Debug { - log.Printf("module %v has not been downloaded and will be ignored", mod.Path) - } - // Can't do anything with a module that's not downloaded. - continue - } - r.modsByModPath = append(r.modsByModPath, mod) - r.modsByDir = append(r.modsByDir, mod) - if mod.Main { - r.main = mod - } - } - - sort.Slice(r.modsByModPath, func(i, j int) bool { - count := func(x int) int { - return strings.Count(r.modsByModPath[x].Path, "/") - } - return count(j) < count(i) // descending order - }) - sort.Slice(r.modsByDir, func(i, j int) bool { - count := func(x int) int { - return strings.Count(r.modsByDir[x].Dir, "/") - } - return count(j) < count(i) // descending order - }) - - r.initialized = true - return nil -} - -// findPackage returns the module and directory that contains the package at -// the given import path, or returns nil, "" if no module is in scope. -func (r *moduleResolver) findPackage(importPath string) (*moduleJSON, string) { - for _, m := range r.modsByModPath { - if !strings.HasPrefix(importPath, m.Path) { - continue - } - pathInModule := importPath[len(m.Path):] - pkgDir := filepath.Join(m.Dir, pathInModule) - if dirIsNestedModule(pkgDir, m) { - continue - } - - pkgFiles, err := ioutil.ReadDir(pkgDir) - if err != nil { - continue - } - - // A module only contains a package if it has buildable go - // files in that directory. If not, it could be provided by an - // outer module. See #29736. - for _, fi := range pkgFiles { - if ok, _ := r.env.buildContext().MatchFile(pkgDir, fi.Name()); ok { - return m, pkgDir - } - } - } - return nil, "" -} - -// findModuleByDir returns the module that contains dir, or nil if no such -// module is in scope. -func (r *moduleResolver) findModuleByDir(dir string) *moduleJSON { - // This is quite tricky and may not be correct. dir could be: - // - a package in the main module. - // - a replace target underneath the main module's directory. - // - a nested module in the above. - // - a replace target somewhere totally random. - // - a nested module in the above. - // - in the mod cache. - // - in /vendor/ in -mod=vendor mode. - // - nested module? Dunno. - // Rumor has it that replace targets cannot contain other replace targets. - for _, m := range r.modsByDir { - if !strings.HasPrefix(dir, m.Dir) { - continue - } - - if dirIsNestedModule(dir, m) { - continue - } - - return m - } - return nil -} - -// dirIsNestedModule reports if dir is contained in a nested module underneath -// mod, not actually in mod. -func dirIsNestedModule(dir string, mod *moduleJSON) bool { - if !strings.HasPrefix(dir, mod.Dir) { - return false - } - mf := findModFile(dir) - if mf == "" { - return false - } - return filepath.Dir(mf) != mod.Dir -} - -func findModFile(dir string) string { - for { - f := filepath.Join(dir, "go.mod") - info, err := os.Stat(f) - if err == nil && !info.IsDir() { - return f - } - d := filepath.Dir(dir) - if len(d) >= len(dir) { - return "" // reached top of file system, no go.mod - } - dir = d - } -} - -func (r *moduleResolver) loadPackageNames(importPaths []string, srcDir string) (map[string]string, error) { - if err := r.init(); err != nil { - return nil, err - } - names := map[string]string{} - for _, path := range importPaths { - _, packageDir := r.findPackage(path) - if packageDir == "" { - continue - } - name, err := packageDirToName(packageDir) - if err != nil { - continue - } - names[path] = name - } - return names, nil -} - -func (r *moduleResolver) scan(_ references) ([]*pkg, error) { - if err := r.init(); err != nil { - return nil, err - } - - // Walk GOROOT, GOPATH/pkg/mod, and the main module. - roots := []gopathwalk.Root{ - {filepath.Join(r.env.GOROOT, "/src"), gopathwalk.RootGOROOT}, - } - if r.main != nil { - roots = append(roots, gopathwalk.Root{r.main.Dir, gopathwalk.RootCurrentModule}) - } - for _, p := range filepath.SplitList(r.env.GOPATH) { - roots = append(roots, gopathwalk.Root{filepath.Join(p, "/pkg/mod"), gopathwalk.RootModuleCache}) - } - - // Walk replace targets, just in case they're not in any of the above. - for _, mod := range r.modsByModPath { - if mod.Replace != nil { - roots = append(roots, gopathwalk.Root{mod.Dir, gopathwalk.RootOther}) - } - } - - var result []*pkg - dupCheck := make(map[string]bool) - var mu sync.Mutex - - gopathwalk.Walk(roots, func(root gopathwalk.Root, dir string) { - mu.Lock() - defer mu.Unlock() - - if _, dup := dupCheck[dir]; dup { - return - } - - dupCheck[dir] = true - - subdir := "" - if dir != root.Path { - subdir = dir[len(root.Path)+len("/"):] - } - importPath := filepath.ToSlash(subdir) - if strings.HasPrefix(importPath, "vendor/") { - // Ignore vendor dirs. If -mod=vendor is on, then things - // should mostly just work, but when it's not vendor/ - // is a mess. There's no easy way to tell if it's on. - // We can still find things in the mod cache and - // map them into /vendor when -mod=vendor is on. - return - } - switch root.Type { - case gopathwalk.RootCurrentModule: - importPath = path.Join(r.main.Path, filepath.ToSlash(subdir)) - case gopathwalk.RootModuleCache: - matches := modCacheRegexp.FindStringSubmatch(subdir) - modPath, err := module.DecodePath(filepath.ToSlash(matches[1])) - if err != nil { - if Debug { - log.Printf("decoding module cache path %q: %v", subdir, err) - } - return - } - importPath = path.Join(modPath, filepath.ToSlash(matches[3])) - case gopathwalk.RootGOROOT: - importPath = subdir - } - - // Check if the directory is underneath a module that's in scope. - if mod := r.findModuleByDir(dir); mod != nil { - // It is. If dir is the target of a replace directive, - // our guessed import path is wrong. Use the real one. - if mod.Dir == dir { - importPath = mod.Path - } else { - dirInMod := dir[len(mod.Dir)+len("/"):] - importPath = path.Join(mod.Path, filepath.ToSlash(dirInMod)) - } - } else { - // The package is in an unknown module. Check that it's - // not obviously impossible to import. - var modFile string - switch root.Type { - case gopathwalk.RootModuleCache: - matches := modCacheRegexp.FindStringSubmatch(subdir) - modFile = filepath.Join(matches[1], "@", matches[2], "go.mod") - default: - modFile = findModFile(dir) - } - - modBytes, err := ioutil.ReadFile(modFile) - if err == nil && !strings.HasPrefix(importPath, modulePath(modBytes)) { - // The module's declared path does not match - // its expected path. It probably needs a - // replace directive we don't have. - return - } - } - // We may have discovered a package that has a different version - // in scope already. Canonicalize to that one if possible. - if _, canonicalDir := r.findPackage(importPath); canonicalDir != "" { - dir = canonicalDir - } - - result = append(result, &pkg{ - importPathShort: VendorlessPath(importPath), - dir: dir, - }) - }, gopathwalk.Options{Debug: Debug, ModulesEnabled: true}) - return result, nil -} - -// modCacheRegexp splits a path in a module cache into module, module version, and package. -var modCacheRegexp = regexp.MustCompile(`(.*)@([^/\\]*)(.*)`) - -var ( - slashSlash = []byte("//") - moduleStr = []byte("module") -) - -// modulePath returns the module path from the gomod file text. -// If it cannot find a module path, it returns an empty string. -// It is tolerant of unrelated problems in the go.mod file. -// -// Copied from cmd/go/internal/modfile. -func modulePath(mod []byte) string { - for len(mod) > 0 { - line := mod - mod = nil - if i := bytes.IndexByte(line, '\n'); i >= 0 { - line, mod = line[:i], line[i+1:] - } - if i := bytes.Index(line, slashSlash); i >= 0 { - line = line[:i] - } - line = bytes.TrimSpace(line) - if !bytes.HasPrefix(line, moduleStr) { - continue - } - line = line[len(moduleStr):] - n := len(line) - line = bytes.TrimSpace(line) - if len(line) == n || len(line) == 0 { - continue - } - - if line[0] == '"' || line[0] == '`' { - p, err := strconv.Unquote(string(line)) - if err != nil { - return "" // malformed quoted string or multiline module path - } - return p - } - - return string(line) - } - return "" // missing module path -} diff --git a/vendor/golang.org/x/tools/imports/zstdlib.go b/vendor/golang.org/x/tools/imports/zstdlib.go deleted file mode 100644 index d81b8c530..000000000 --- a/vendor/golang.org/x/tools/imports/zstdlib.go +++ /dev/null @@ -1,10325 +0,0 @@ -// Code generated by mkstdlib.go. DO NOT EDIT. - -package imports - -var stdlib = map[string]map[string]bool{ - "archive/tar": map[string]bool{ - "ErrFieldTooLong": true, - "ErrHeader": true, - "ErrWriteAfterClose": true, - "ErrWriteTooLong": true, - "FileInfoHeader": true, - "Format": true, - "FormatGNU": true, - "FormatPAX": true, - "FormatUSTAR": true, - "FormatUnknown": true, - "Header": true, - "NewReader": true, - "NewWriter": true, - "Reader": true, - "TypeBlock": true, - "TypeChar": true, - "TypeCont": true, - "TypeDir": true, - "TypeFifo": true, - "TypeGNULongLink": true, - "TypeGNULongName": true, - "TypeGNUSparse": true, - "TypeLink": true, - "TypeReg": true, - "TypeRegA": true, - "TypeSymlink": true, - "TypeXGlobalHeader": true, - "TypeXHeader": true, - "Writer": true, - }, - "archive/zip": map[string]bool{ - "Compressor": true, - "Decompressor": true, - "Deflate": true, - "ErrAlgorithm": true, - "ErrChecksum": true, - "ErrFormat": true, - "File": true, - "FileHeader": true, - "FileInfoHeader": true, - "NewReader": true, - "NewWriter": true, - "OpenReader": true, - "ReadCloser": true, - "Reader": true, - "RegisterCompressor": true, - "RegisterDecompressor": true, - "Store": true, - "Writer": true, - }, - "bufio": map[string]bool{ - "ErrAdvanceTooFar": true, - "ErrBufferFull": true, - "ErrFinalToken": true, - "ErrInvalidUnreadByte": true, - "ErrInvalidUnreadRune": true, - "ErrNegativeAdvance": true, - "ErrNegativeCount": true, - "ErrTooLong": true, - "MaxScanTokenSize": true, - "NewReadWriter": true, - "NewReader": true, - "NewReaderSize": true, - "NewScanner": true, - "NewWriter": true, - "NewWriterSize": true, - "ReadWriter": true, - "Reader": true, - "ScanBytes": true, - "ScanLines": true, - "ScanRunes": true, - "ScanWords": true, - "Scanner": true, - "SplitFunc": true, - "Writer": true, - }, - "bytes": map[string]bool{ - "Buffer": true, - "Compare": true, - "Contains": true, - "ContainsAny": true, - "ContainsRune": true, - "Count": true, - "Equal": true, - "EqualFold": true, - "ErrTooLarge": true, - "Fields": true, - "FieldsFunc": true, - "HasPrefix": true, - "HasSuffix": true, - "Index": true, - "IndexAny": true, - "IndexByte": true, - "IndexFunc": true, - "IndexRune": true, - "Join": true, - "LastIndex": true, - "LastIndexAny": true, - "LastIndexByte": true, - "LastIndexFunc": true, - "Map": true, - "MinRead": true, - "NewBuffer": true, - "NewBufferString": true, - "NewReader": true, - "Reader": true, - "Repeat": true, - "Replace": true, - "ReplaceAll": true, - "Runes": true, - "Split": true, - "SplitAfter": true, - "SplitAfterN": true, - "SplitN": true, - "Title": true, - "ToLower": true, - "ToLowerSpecial": true, - "ToTitle": true, - "ToTitleSpecial": true, - "ToUpper": true, - "ToUpperSpecial": true, - "Trim": true, - "TrimFunc": true, - "TrimLeft": true, - "TrimLeftFunc": true, - "TrimPrefix": true, - "TrimRight": true, - "TrimRightFunc": true, - "TrimSpace": true, - "TrimSuffix": true, - }, - "compress/bzip2": map[string]bool{ - "NewReader": true, - "StructuralError": true, - }, - "compress/flate": map[string]bool{ - "BestCompression": true, - "BestSpeed": true, - "CorruptInputError": true, - "DefaultCompression": true, - "HuffmanOnly": true, - "InternalError": true, - "NewReader": true, - "NewReaderDict": true, - "NewWriter": true, - "NewWriterDict": true, - "NoCompression": true, - "ReadError": true, - "Reader": true, - "Resetter": true, - "WriteError": true, - "Writer": true, - }, - "compress/gzip": map[string]bool{ - "BestCompression": true, - "BestSpeed": true, - "DefaultCompression": true, - "ErrChecksum": true, - "ErrHeader": true, - "Header": true, - "HuffmanOnly": true, - "NewReader": true, - "NewWriter": true, - "NewWriterLevel": true, - "NoCompression": true, - "Reader": true, - "Writer": true, - }, - "compress/lzw": map[string]bool{ - "LSB": true, - "MSB": true, - "NewReader": true, - "NewWriter": true, - "Order": true, - }, - "compress/zlib": map[string]bool{ - "BestCompression": true, - "BestSpeed": true, - "DefaultCompression": true, - "ErrChecksum": true, - "ErrDictionary": true, - "ErrHeader": true, - "HuffmanOnly": true, - "NewReader": true, - "NewReaderDict": true, - "NewWriter": true, - "NewWriterLevel": true, - "NewWriterLevelDict": true, - "NoCompression": true, - "Resetter": true, - "Writer": true, - }, - "container/heap": map[string]bool{ - "Fix": true, - "Init": true, - "Interface": true, - "Pop": true, - "Push": true, - "Remove": true, - }, - "container/list": map[string]bool{ - "Element": true, - "List": true, - "New": true, - }, - "container/ring": map[string]bool{ - "New": true, - "Ring": true, - }, - "context": map[string]bool{ - "Background": true, - "CancelFunc": true, - "Canceled": true, - "Context": true, - "DeadlineExceeded": true, - "TODO": true, - "WithCancel": true, - "WithDeadline": true, - "WithTimeout": true, - "WithValue": true, - }, - "crypto": map[string]bool{ - "BLAKE2b_256": true, - "BLAKE2b_384": true, - "BLAKE2b_512": true, - "BLAKE2s_256": true, - "Decrypter": true, - "DecrypterOpts": true, - "Hash": true, - "MD4": true, - "MD5": true, - "MD5SHA1": true, - "PrivateKey": true, - "PublicKey": true, - "RIPEMD160": true, - "RegisterHash": true, - "SHA1": true, - "SHA224": true, - "SHA256": true, - "SHA384": true, - "SHA3_224": true, - "SHA3_256": true, - "SHA3_384": true, - "SHA3_512": true, - "SHA512": true, - "SHA512_224": true, - "SHA512_256": true, - "Signer": true, - "SignerOpts": true, - }, - "crypto/aes": map[string]bool{ - "BlockSize": true, - "KeySizeError": true, - "NewCipher": true, - }, - "crypto/cipher": map[string]bool{ - "AEAD": true, - "Block": true, - "BlockMode": true, - "NewCBCDecrypter": true, - "NewCBCEncrypter": true, - "NewCFBDecrypter": true, - "NewCFBEncrypter": true, - "NewCTR": true, - "NewGCM": true, - "NewGCMWithNonceSize": true, - "NewGCMWithTagSize": true, - "NewOFB": true, - "Stream": true, - "StreamReader": true, - "StreamWriter": true, - }, - "crypto/des": map[string]bool{ - "BlockSize": true, - "KeySizeError": true, - "NewCipher": true, - "NewTripleDESCipher": true, - }, - "crypto/dsa": map[string]bool{ - "ErrInvalidPublicKey": true, - "GenerateKey": true, - "GenerateParameters": true, - "L1024N160": true, - "L2048N224": true, - "L2048N256": true, - "L3072N256": true, - "ParameterSizes": true, - "Parameters": true, - "PrivateKey": true, - "PublicKey": true, - "Sign": true, - "Verify": true, - }, - "crypto/ecdsa": map[string]bool{ - "GenerateKey": true, - "PrivateKey": true, - "PublicKey": true, - "Sign": true, - "Verify": true, - }, - "crypto/elliptic": map[string]bool{ - "Curve": true, - "CurveParams": true, - "GenerateKey": true, - "Marshal": true, - "P224": true, - "P256": true, - "P384": true, - "P521": true, - "Unmarshal": true, - }, - "crypto/hmac": map[string]bool{ - "Equal": true, - "New": true, - }, - "crypto/md5": map[string]bool{ - "BlockSize": true, - "New": true, - "Size": true, - "Sum": true, - }, - "crypto/rand": map[string]bool{ - "Int": true, - "Prime": true, - "Read": true, - "Reader": true, - }, - "crypto/rc4": map[string]bool{ - "Cipher": true, - "KeySizeError": true, - "NewCipher": true, - }, - "crypto/rsa": map[string]bool{ - "CRTValue": true, - "DecryptOAEP": true, - "DecryptPKCS1v15": true, - "DecryptPKCS1v15SessionKey": true, - "EncryptOAEP": true, - "EncryptPKCS1v15": true, - "ErrDecryption": true, - "ErrMessageTooLong": true, - "ErrVerification": true, - "GenerateKey": true, - "GenerateMultiPrimeKey": true, - "OAEPOptions": true, - "PKCS1v15DecryptOptions": true, - "PSSOptions": true, - "PSSSaltLengthAuto": true, - "PSSSaltLengthEqualsHash": true, - "PrecomputedValues": true, - "PrivateKey": true, - "PublicKey": true, - "SignPKCS1v15": true, - "SignPSS": true, - "VerifyPKCS1v15": true, - "VerifyPSS": true, - }, - "crypto/sha1": map[string]bool{ - "BlockSize": true, - "New": true, - "Size": true, - "Sum": true, - }, - "crypto/sha256": map[string]bool{ - "BlockSize": true, - "New": true, - "New224": true, - "Size": true, - "Size224": true, - "Sum224": true, - "Sum256": true, - }, - "crypto/sha512": map[string]bool{ - "BlockSize": true, - "New": true, - "New384": true, - "New512_224": true, - "New512_256": true, - "Size": true, - "Size224": true, - "Size256": true, - "Size384": true, - "Sum384": true, - "Sum512": true, - "Sum512_224": true, - "Sum512_256": true, - }, - "crypto/subtle": map[string]bool{ - "ConstantTimeByteEq": true, - "ConstantTimeCompare": true, - "ConstantTimeCopy": true, - "ConstantTimeEq": true, - "ConstantTimeLessOrEq": true, - "ConstantTimeSelect": true, - }, - "crypto/tls": map[string]bool{ - "Certificate": true, - "CertificateRequestInfo": true, - "Client": true, - "ClientAuthType": true, - "ClientHelloInfo": true, - "ClientSessionCache": true, - "ClientSessionState": true, - "Config": true, - "Conn": true, - "ConnectionState": true, - "CurveID": true, - "CurveP256": true, - "CurveP384": true, - "CurveP521": true, - "Dial": true, - "DialWithDialer": true, - "ECDSAWithP256AndSHA256": true, - "ECDSAWithP384AndSHA384": true, - "ECDSAWithP521AndSHA512": true, - "ECDSAWithSHA1": true, - "Listen": true, - "LoadX509KeyPair": true, - "NewLRUClientSessionCache": true, - "NewListener": true, - "NoClientCert": true, - "PKCS1WithSHA1": true, - "PKCS1WithSHA256": true, - "PKCS1WithSHA384": true, - "PKCS1WithSHA512": true, - "PSSWithSHA256": true, - "PSSWithSHA384": true, - "PSSWithSHA512": true, - "RecordHeaderError": true, - "RenegotiateFreelyAsClient": true, - "RenegotiateNever": true, - "RenegotiateOnceAsClient": true, - "RenegotiationSupport": true, - "RequestClientCert": true, - "RequireAndVerifyClientCert": true, - "RequireAnyClientCert": true, - "Server": true, - "SignatureScheme": true, - "TLS_AES_128_GCM_SHA256": true, - "TLS_AES_256_GCM_SHA384": true, - "TLS_CHACHA20_POLY1305_SHA256": true, - "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA": true, - "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": true, - "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256": true, - "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA": true, - "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384": true, - "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305": true, - "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA": true, - "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA": true, - "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA": true, - "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256": true, - "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256": true, - "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA": true, - "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384": true, - "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305": true, - "TLS_ECDHE_RSA_WITH_RC4_128_SHA": true, - "TLS_FALLBACK_SCSV": true, - "TLS_RSA_WITH_3DES_EDE_CBC_SHA": true, - "TLS_RSA_WITH_AES_128_CBC_SHA": true, - "TLS_RSA_WITH_AES_128_CBC_SHA256": true, - "TLS_RSA_WITH_AES_128_GCM_SHA256": true, - "TLS_RSA_WITH_AES_256_CBC_SHA": true, - "TLS_RSA_WITH_AES_256_GCM_SHA384": true, - "TLS_RSA_WITH_RC4_128_SHA": true, - "VerifyClientCertIfGiven": true, - "VersionSSL30": true, - "VersionTLS10": true, - "VersionTLS11": true, - "VersionTLS12": true, - "VersionTLS13": true, - "X25519": true, - "X509KeyPair": true, - }, - "crypto/x509": map[string]bool{ - "CANotAuthorizedForExtKeyUsage": true, - "CANotAuthorizedForThisName": true, - "CertPool": true, - "Certificate": true, - "CertificateInvalidError": true, - "CertificateRequest": true, - "ConstraintViolationError": true, - "CreateCertificate": true, - "CreateCertificateRequest": true, - "DSA": true, - "DSAWithSHA1": true, - "DSAWithSHA256": true, - "DecryptPEMBlock": true, - "ECDSA": true, - "ECDSAWithSHA1": true, - "ECDSAWithSHA256": true, - "ECDSAWithSHA384": true, - "ECDSAWithSHA512": true, - "EncryptPEMBlock": true, - "ErrUnsupportedAlgorithm": true, - "Expired": true, - "ExtKeyUsage": true, - "ExtKeyUsageAny": true, - "ExtKeyUsageClientAuth": true, - "ExtKeyUsageCodeSigning": true, - "ExtKeyUsageEmailProtection": true, - "ExtKeyUsageIPSECEndSystem": true, - "ExtKeyUsageIPSECTunnel": true, - "ExtKeyUsageIPSECUser": true, - "ExtKeyUsageMicrosoftCommercialCodeSigning": true, - "ExtKeyUsageMicrosoftKernelCodeSigning": true, - "ExtKeyUsageMicrosoftServerGatedCrypto": true, - "ExtKeyUsageNetscapeServerGatedCrypto": true, - "ExtKeyUsageOCSPSigning": true, - "ExtKeyUsageServerAuth": true, - "ExtKeyUsageTimeStamping": true, - "HostnameError": true, - "IncompatibleUsage": true, - "IncorrectPasswordError": true, - "InsecureAlgorithmError": true, - "InvalidReason": true, - "IsEncryptedPEMBlock": true, - "KeyUsage": true, - "KeyUsageCRLSign": true, - "KeyUsageCertSign": true, - "KeyUsageContentCommitment": true, - "KeyUsageDataEncipherment": true, - "KeyUsageDecipherOnly": true, - "KeyUsageDigitalSignature": true, - "KeyUsageEncipherOnly": true, - "KeyUsageKeyAgreement": true, - "KeyUsageKeyEncipherment": true, - "MD2WithRSA": true, - "MD5WithRSA": true, - "MarshalECPrivateKey": true, - "MarshalPKCS1PrivateKey": true, - "MarshalPKCS1PublicKey": true, - "MarshalPKCS8PrivateKey": true, - "MarshalPKIXPublicKey": true, - "NameConstraintsWithoutSANs": true, - "NameMismatch": true, - "NewCertPool": true, - "NotAuthorizedToSign": true, - "PEMCipher": true, - "PEMCipher3DES": true, - "PEMCipherAES128": true, - "PEMCipherAES192": true, - "PEMCipherAES256": true, - "PEMCipherDES": true, - "ParseCRL": true, - "ParseCertificate": true, - "ParseCertificateRequest": true, - "ParseCertificates": true, - "ParseDERCRL": true, - "ParseECPrivateKey": true, - "ParsePKCS1PrivateKey": true, - "ParsePKCS1PublicKey": true, - "ParsePKCS8PrivateKey": true, - "ParsePKIXPublicKey": true, - "PublicKeyAlgorithm": true, - "RSA": true, - "SHA1WithRSA": true, - "SHA256WithRSA": true, - "SHA256WithRSAPSS": true, - "SHA384WithRSA": true, - "SHA384WithRSAPSS": true, - "SHA512WithRSA": true, - "SHA512WithRSAPSS": true, - "SignatureAlgorithm": true, - "SystemCertPool": true, - "SystemRootsError": true, - "TooManyConstraints": true, - "TooManyIntermediates": true, - "UnconstrainedName": true, - "UnhandledCriticalExtension": true, - "UnknownAuthorityError": true, - "UnknownPublicKeyAlgorithm": true, - "UnknownSignatureAlgorithm": true, - "VerifyOptions": true, - }, - "crypto/x509/pkix": map[string]bool{ - "AlgorithmIdentifier": true, - "AttributeTypeAndValue": true, - "AttributeTypeAndValueSET": true, - "CertificateList": true, - "Extension": true, - "Name": true, - "RDNSequence": true, - "RelativeDistinguishedNameSET": true, - "RevokedCertificate": true, - "TBSCertificateList": true, - }, - "database/sql": map[string]bool{ - "ColumnType": true, - "Conn": true, - "DB": true, - "DBStats": true, - "Drivers": true, - "ErrConnDone": true, - "ErrNoRows": true, - "ErrTxDone": true, - "IsolationLevel": true, - "LevelDefault": true, - "LevelLinearizable": true, - "LevelReadCommitted": true, - "LevelReadUncommitted": true, - "LevelRepeatableRead": true, - "LevelSerializable": true, - "LevelSnapshot": true, - "LevelWriteCommitted": true, - "Named": true, - "NamedArg": true, - "NullBool": true, - "NullFloat64": true, - "NullInt64": true, - "NullString": true, - "Open": true, - "OpenDB": true, - "Out": true, - "RawBytes": true, - "Register": true, - "Result": true, - "Row": true, - "Rows": true, - "Scanner": true, - "Stmt": true, - "Tx": true, - "TxOptions": true, - }, - "database/sql/driver": map[string]bool{ - "Bool": true, - "ColumnConverter": true, - "Conn": true, - "ConnBeginTx": true, - "ConnPrepareContext": true, - "Connector": true, - "DefaultParameterConverter": true, - "Driver": true, - "DriverContext": true, - "ErrBadConn": true, - "ErrRemoveArgument": true, - "ErrSkip": true, - "Execer": true, - "ExecerContext": true, - "Int32": true, - "IsScanValue": true, - "IsValue": true, - "IsolationLevel": true, - "NamedValue": true, - "NamedValueChecker": true, - "NotNull": true, - "Null": true, - "Pinger": true, - "Queryer": true, - "QueryerContext": true, - "Result": true, - "ResultNoRows": true, - "Rows": true, - "RowsAffected": true, - "RowsColumnTypeDatabaseTypeName": true, - "RowsColumnTypeLength": true, - "RowsColumnTypeNullable": true, - "RowsColumnTypePrecisionScale": true, - "RowsColumnTypeScanType": true, - "RowsNextResultSet": true, - "SessionResetter": true, - "Stmt": true, - "StmtExecContext": true, - "StmtQueryContext": true, - "String": true, - "Tx": true, - "TxOptions": true, - "Value": true, - "ValueConverter": true, - "Valuer": true, - }, - "debug/dwarf": map[string]bool{ - "AddrType": true, - "ArrayType": true, - "Attr": true, - "AttrAbstractOrigin": true, - "AttrAccessibility": true, - "AttrAddrClass": true, - "AttrAllocated": true, - "AttrArtificial": true, - "AttrAssociated": true, - "AttrBaseTypes": true, - "AttrBitOffset": true, - "AttrBitSize": true, - "AttrByteSize": true, - "AttrCallColumn": true, - "AttrCallFile": true, - "AttrCallLine": true, - "AttrCalling": true, - "AttrCommonRef": true, - "AttrCompDir": true, - "AttrConstValue": true, - "AttrContainingType": true, - "AttrCount": true, - "AttrDataLocation": true, - "AttrDataMemberLoc": true, - "AttrDeclColumn": true, - "AttrDeclFile": true, - "AttrDeclLine": true, - "AttrDeclaration": true, - "AttrDefaultValue": true, - "AttrDescription": true, - "AttrDiscr": true, - "AttrDiscrList": true, - "AttrDiscrValue": true, - "AttrEncoding": true, - "AttrEntrypc": true, - "AttrExtension": true, - "AttrExternal": true, - "AttrFrameBase": true, - "AttrFriend": true, - "AttrHighpc": true, - "AttrIdentifierCase": true, - "AttrImport": true, - "AttrInline": true, - "AttrIsOptional": true, - "AttrLanguage": true, - "AttrLocation": true, - "AttrLowerBound": true, - "AttrLowpc": true, - "AttrMacroInfo": true, - "AttrName": true, - "AttrNamelistItem": true, - "AttrOrdering": true, - "AttrPriority": true, - "AttrProducer": true, - "AttrPrototyped": true, - "AttrRanges": true, - "AttrReturnAddr": true, - "AttrSegment": true, - "AttrSibling": true, - "AttrSpecification": true, - "AttrStartScope": true, - "AttrStaticLink": true, - "AttrStmtList": true, - "AttrStride": true, - "AttrStrideSize": true, - "AttrStringLength": true, - "AttrTrampoline": true, - "AttrType": true, - "AttrUpperBound": true, - "AttrUseLocation": true, - "AttrUseUTF8": true, - "AttrVarParam": true, - "AttrVirtuality": true, - "AttrVisibility": true, - "AttrVtableElemLoc": true, - "BasicType": true, - "BoolType": true, - "CharType": true, - "Class": true, - "ClassAddress": true, - "ClassBlock": true, - "ClassConstant": true, - "ClassExprLoc": true, - "ClassFlag": true, - "ClassLinePtr": true, - "ClassLocListPtr": true, - "ClassMacPtr": true, - "ClassRangeListPtr": true, - "ClassReference": true, - "ClassReferenceAlt": true, - "ClassReferenceSig": true, - "ClassString": true, - "ClassStringAlt": true, - "ClassUnknown": true, - "CommonType": true, - "ComplexType": true, - "Data": true, - "DecodeError": true, - "DotDotDotType": true, - "Entry": true, - "EnumType": true, - "EnumValue": true, - "ErrUnknownPC": true, - "Field": true, - "FloatType": true, - "FuncType": true, - "IntType": true, - "LineEntry": true, - "LineFile": true, - "LineReader": true, - "LineReaderPos": true, - "New": true, - "Offset": true, - "PtrType": true, - "QualType": true, - "Reader": true, - "StructField": true, - "StructType": true, - "Tag": true, - "TagAccessDeclaration": true, - "TagArrayType": true, - "TagBaseType": true, - "TagCatchDwarfBlock": true, - "TagClassType": true, - "TagCommonDwarfBlock": true, - "TagCommonInclusion": true, - "TagCompileUnit": true, - "TagCondition": true, - "TagConstType": true, - "TagConstant": true, - "TagDwarfProcedure": true, - "TagEntryPoint": true, - "TagEnumerationType": true, - "TagEnumerator": true, - "TagFileType": true, - "TagFormalParameter": true, - "TagFriend": true, - "TagImportedDeclaration": true, - "TagImportedModule": true, - "TagImportedUnit": true, - "TagInheritance": true, - "TagInlinedSubroutine": true, - "TagInterfaceType": true, - "TagLabel": true, - "TagLexDwarfBlock": true, - "TagMember": true, - "TagModule": true, - "TagMutableType": true, - "TagNamelist": true, - "TagNamelistItem": true, - "TagNamespace": true, - "TagPackedType": true, - "TagPartialUnit": true, - "TagPointerType": true, - "TagPtrToMemberType": true, - "TagReferenceType": true, - "TagRestrictType": true, - "TagRvalueReferenceType": true, - "TagSetType": true, - "TagSharedType": true, - "TagStringType": true, - "TagStructType": true, - "TagSubprogram": true, - "TagSubrangeType": true, - "TagSubroutineType": true, - "TagTemplateAlias": true, - "TagTemplateTypeParameter": true, - "TagTemplateValueParameter": true, - "TagThrownType": true, - "TagTryDwarfBlock": true, - "TagTypeUnit": true, - "TagTypedef": true, - "TagUnionType": true, - "TagUnspecifiedParameters": true, - "TagUnspecifiedType": true, - "TagVariable": true, - "TagVariant": true, - "TagVariantPart": true, - "TagVolatileType": true, - "TagWithStmt": true, - "Type": true, - "TypedefType": true, - "UcharType": true, - "UintType": true, - "UnspecifiedType": true, - "VoidType": true, - }, - "debug/elf": map[string]bool{ - "ARM_MAGIC_TRAMP_NUMBER": true, - "COMPRESS_HIOS": true, - "COMPRESS_HIPROC": true, - "COMPRESS_LOOS": true, - "COMPRESS_LOPROC": true, - "COMPRESS_ZLIB": true, - "Chdr32": true, - "Chdr64": true, - "Class": true, - "CompressionType": true, - "DF_BIND_NOW": true, - "DF_ORIGIN": true, - "DF_STATIC_TLS": true, - "DF_SYMBOLIC": true, - "DF_TEXTREL": true, - "DT_BIND_NOW": true, - "DT_DEBUG": true, - "DT_ENCODING": true, - "DT_FINI": true, - "DT_FINI_ARRAY": true, - "DT_FINI_ARRAYSZ": true, - "DT_FLAGS": true, - "DT_HASH": true, - "DT_HIOS": true, - "DT_HIPROC": true, - "DT_INIT": true, - "DT_INIT_ARRAY": true, - "DT_INIT_ARRAYSZ": true, - "DT_JMPREL": true, - "DT_LOOS": true, - "DT_LOPROC": true, - "DT_NEEDED": true, - "DT_NULL": true, - "DT_PLTGOT": true, - "DT_PLTREL": true, - "DT_PLTRELSZ": true, - "DT_PREINIT_ARRAY": true, - "DT_PREINIT_ARRAYSZ": true, - "DT_REL": true, - "DT_RELA": true, - "DT_RELAENT": true, - "DT_RELASZ": true, - "DT_RELENT": true, - "DT_RELSZ": true, - "DT_RPATH": true, - "DT_RUNPATH": true, - "DT_SONAME": true, - "DT_STRSZ": true, - "DT_STRTAB": true, - "DT_SYMBOLIC": true, - "DT_SYMENT": true, - "DT_SYMTAB": true, - "DT_TEXTREL": true, - "DT_VERNEED": true, - "DT_VERNEEDNUM": true, - "DT_VERSYM": true, - "Data": true, - "Dyn32": true, - "Dyn64": true, - "DynFlag": true, - "DynTag": true, - "EI_ABIVERSION": true, - "EI_CLASS": true, - "EI_DATA": true, - "EI_NIDENT": true, - "EI_OSABI": true, - "EI_PAD": true, - "EI_VERSION": true, - "ELFCLASS32": true, - "ELFCLASS64": true, - "ELFCLASSNONE": true, - "ELFDATA2LSB": true, - "ELFDATA2MSB": true, - "ELFDATANONE": true, - "ELFMAG": true, - "ELFOSABI_86OPEN": true, - "ELFOSABI_AIX": true, - "ELFOSABI_ARM": true, - "ELFOSABI_AROS": true, - "ELFOSABI_CLOUDABI": true, - "ELFOSABI_FENIXOS": true, - "ELFOSABI_FREEBSD": true, - "ELFOSABI_HPUX": true, - "ELFOSABI_HURD": true, - "ELFOSABI_IRIX": true, - "ELFOSABI_LINUX": true, - "ELFOSABI_MODESTO": true, - "ELFOSABI_NETBSD": true, - "ELFOSABI_NONE": true, - "ELFOSABI_NSK": true, - "ELFOSABI_OPENBSD": true, - "ELFOSABI_OPENVMS": true, - "ELFOSABI_SOLARIS": true, - "ELFOSABI_STANDALONE": true, - "ELFOSABI_TRU64": true, - "EM_386": true, - "EM_486": true, - "EM_56800EX": true, - "EM_68HC05": true, - "EM_68HC08": true, - "EM_68HC11": true, - "EM_68HC12": true, - "EM_68HC16": true, - "EM_68K": true, - "EM_78KOR": true, - "EM_8051": true, - "EM_860": true, - "EM_88K": true, - "EM_960": true, - "EM_AARCH64": true, - "EM_ALPHA": true, - "EM_ALPHA_STD": true, - "EM_ALTERA_NIOS2": true, - "EM_AMDGPU": true, - "EM_ARC": true, - "EM_ARCA": true, - "EM_ARC_COMPACT": true, - "EM_ARC_COMPACT2": true, - "EM_ARM": true, - "EM_AVR": true, - "EM_AVR32": true, - "EM_BA1": true, - "EM_BA2": true, - "EM_BLACKFIN": true, - "EM_BPF": true, - "EM_C166": true, - "EM_CDP": true, - "EM_CE": true, - "EM_CLOUDSHIELD": true, - "EM_COGE": true, - "EM_COLDFIRE": true, - "EM_COOL": true, - "EM_COREA_1ST": true, - "EM_COREA_2ND": true, - "EM_CR": true, - "EM_CR16": true, - "EM_CRAYNV2": true, - "EM_CRIS": true, - "EM_CRX": true, - "EM_CSR_KALIMBA": true, - "EM_CUDA": true, - "EM_CYPRESS_M8C": true, - "EM_D10V": true, - "EM_D30V": true, - "EM_DSP24": true, - "EM_DSPIC30F": true, - "EM_DXP": true, - "EM_ECOG1": true, - "EM_ECOG16": true, - "EM_ECOG1X": true, - "EM_ECOG2": true, - "EM_ETPU": true, - "EM_EXCESS": true, - "EM_F2MC16": true, - "EM_FIREPATH": true, - "EM_FR20": true, - "EM_FR30": true, - "EM_FT32": true, - "EM_FX66": true, - "EM_H8S": true, - "EM_H8_300": true, - "EM_H8_300H": true, - "EM_H8_500": true, - "EM_HUANY": true, - "EM_IA_64": true, - "EM_INTEL205": true, - "EM_INTEL206": true, - "EM_INTEL207": true, - "EM_INTEL208": true, - "EM_INTEL209": true, - "EM_IP2K": true, - "EM_JAVELIN": true, - "EM_K10M": true, - "EM_KM32": true, - "EM_KMX16": true, - "EM_KMX32": true, - "EM_KMX8": true, - "EM_KVARC": true, - "EM_L10M": true, - "EM_LANAI": true, - "EM_LATTICEMICO32": true, - "EM_M16C": true, - "EM_M32": true, - "EM_M32C": true, - "EM_M32R": true, - "EM_MANIK": true, - "EM_MAX": true, - "EM_MAXQ30": true, - "EM_MCHP_PIC": true, - "EM_MCST_ELBRUS": true, - "EM_ME16": true, - "EM_METAG": true, - "EM_MICROBLAZE": true, - "EM_MIPS": true, - "EM_MIPS_RS3_LE": true, - "EM_MIPS_RS4_BE": true, - "EM_MIPS_X": true, - "EM_MMA": true, - "EM_MMDSP_PLUS": true, - "EM_MMIX": true, - "EM_MN10200": true, - "EM_MN10300": true, - "EM_MOXIE": true, - "EM_MSP430": true, - "EM_NCPU": true, - "EM_NDR1": true, - "EM_NDS32": true, - "EM_NONE": true, - "EM_NORC": true, - "EM_NS32K": true, - "EM_OPEN8": true, - "EM_OPENRISC": true, - "EM_PARISC": true, - "EM_PCP": true, - "EM_PDP10": true, - "EM_PDP11": true, - "EM_PDSP": true, - "EM_PJ": true, - "EM_PPC": true, - "EM_PPC64": true, - "EM_PRISM": true, - "EM_QDSP6": true, - "EM_R32C": true, - "EM_RCE": true, - "EM_RH32": true, - "EM_RISCV": true, - "EM_RL78": true, - "EM_RS08": true, - "EM_RX": true, - "EM_S370": true, - "EM_S390": true, - "EM_SCORE7": true, - "EM_SEP": true, - "EM_SE_C17": true, - "EM_SE_C33": true, - "EM_SH": true, - "EM_SHARC": true, - "EM_SLE9X": true, - "EM_SNP1K": true, - "EM_SPARC": true, - "EM_SPARC32PLUS": true, - "EM_SPARCV9": true, - "EM_ST100": true, - "EM_ST19": true, - "EM_ST200": true, - "EM_ST7": true, - "EM_ST9PLUS": true, - "EM_STARCORE": true, - "EM_STM8": true, - "EM_STXP7X": true, - "EM_SVX": true, - "EM_TILE64": true, - "EM_TILEGX": true, - "EM_TILEPRO": true, - "EM_TINYJ": true, - "EM_TI_ARP32": true, - "EM_TI_C2000": true, - "EM_TI_C5500": true, - "EM_TI_C6000": true, - "EM_TI_PRU": true, - "EM_TMM_GPP": true, - "EM_TPC": true, - "EM_TRICORE": true, - "EM_TRIMEDIA": true, - "EM_TSK3000": true, - "EM_UNICORE": true, - "EM_V800": true, - "EM_V850": true, - "EM_VAX": true, - "EM_VIDEOCORE": true, - "EM_VIDEOCORE3": true, - "EM_VIDEOCORE5": true, - "EM_VISIUM": true, - "EM_VPP500": true, - "EM_X86_64": true, - "EM_XCORE": true, - "EM_XGATE": true, - "EM_XIMO16": true, - "EM_XTENSA": true, - "EM_Z80": true, - "EM_ZSP": true, - "ET_CORE": true, - "ET_DYN": true, - "ET_EXEC": true, - "ET_HIOS": true, - "ET_HIPROC": true, - "ET_LOOS": true, - "ET_LOPROC": true, - "ET_NONE": true, - "ET_REL": true, - "EV_CURRENT": true, - "EV_NONE": true, - "ErrNoSymbols": true, - "File": true, - "FileHeader": true, - "FormatError": true, - "Header32": true, - "Header64": true, - "ImportedSymbol": true, - "Machine": true, - "NT_FPREGSET": true, - "NT_PRPSINFO": true, - "NT_PRSTATUS": true, - "NType": true, - "NewFile": true, - "OSABI": true, - "Open": true, - "PF_MASKOS": true, - "PF_MASKPROC": true, - "PF_R": true, - "PF_W": true, - "PF_X": true, - "PT_DYNAMIC": true, - "PT_HIOS": true, - "PT_HIPROC": true, - "PT_INTERP": true, - "PT_LOAD": true, - "PT_LOOS": true, - "PT_LOPROC": true, - "PT_NOTE": true, - "PT_NULL": true, - "PT_PHDR": true, - "PT_SHLIB": true, - "PT_TLS": true, - "Prog": true, - "Prog32": true, - "Prog64": true, - "ProgFlag": true, - "ProgHeader": true, - "ProgType": true, - "R_386": true, - "R_386_16": true, - "R_386_32": true, - "R_386_32PLT": true, - "R_386_8": true, - "R_386_COPY": true, - "R_386_GLOB_DAT": true, - "R_386_GOT32": true, - "R_386_GOT32X": true, - "R_386_GOTOFF": true, - "R_386_GOTPC": true, - "R_386_IRELATIVE": true, - "R_386_JMP_SLOT": true, - "R_386_NONE": true, - "R_386_PC16": true, - "R_386_PC32": true, - "R_386_PC8": true, - "R_386_PLT32": true, - "R_386_RELATIVE": true, - "R_386_SIZE32": true, - "R_386_TLS_DESC": true, - "R_386_TLS_DESC_CALL": true, - "R_386_TLS_DTPMOD32": true, - "R_386_TLS_DTPOFF32": true, - "R_386_TLS_GD": true, - "R_386_TLS_GD_32": true, - "R_386_TLS_GD_CALL": true, - "R_386_TLS_GD_POP": true, - "R_386_TLS_GD_PUSH": true, - "R_386_TLS_GOTDESC": true, - "R_386_TLS_GOTIE": true, - "R_386_TLS_IE": true, - "R_386_TLS_IE_32": true, - "R_386_TLS_LDM": true, - "R_386_TLS_LDM_32": true, - "R_386_TLS_LDM_CALL": true, - "R_386_TLS_LDM_POP": true, - "R_386_TLS_LDM_PUSH": true, - "R_386_TLS_LDO_32": true, - "R_386_TLS_LE": true, - "R_386_TLS_LE_32": true, - "R_386_TLS_TPOFF": true, - "R_386_TLS_TPOFF32": true, - "R_390": true, - "R_390_12": true, - "R_390_16": true, - "R_390_20": true, - "R_390_32": true, - "R_390_64": true, - "R_390_8": true, - "R_390_COPY": true, - "R_390_GLOB_DAT": true, - "R_390_GOT12": true, - "R_390_GOT16": true, - "R_390_GOT20": true, - "R_390_GOT32": true, - "R_390_GOT64": true, - "R_390_GOTENT": true, - "R_390_GOTOFF": true, - "R_390_GOTOFF16": true, - "R_390_GOTOFF64": true, - "R_390_GOTPC": true, - "R_390_GOTPCDBL": true, - "R_390_GOTPLT12": true, - "R_390_GOTPLT16": true, - "R_390_GOTPLT20": true, - "R_390_GOTPLT32": true, - "R_390_GOTPLT64": true, - "R_390_GOTPLTENT": true, - "R_390_GOTPLTOFF16": true, - "R_390_GOTPLTOFF32": true, - "R_390_GOTPLTOFF64": true, - "R_390_JMP_SLOT": true, - "R_390_NONE": true, - "R_390_PC16": true, - "R_390_PC16DBL": true, - "R_390_PC32": true, - "R_390_PC32DBL": true, - "R_390_PC64": true, - "R_390_PLT16DBL": true, - "R_390_PLT32": true, - "R_390_PLT32DBL": true, - "R_390_PLT64": true, - "R_390_RELATIVE": true, - "R_390_TLS_DTPMOD": true, - "R_390_TLS_DTPOFF": true, - "R_390_TLS_GD32": true, - "R_390_TLS_GD64": true, - "R_390_TLS_GDCALL": true, - "R_390_TLS_GOTIE12": true, - "R_390_TLS_GOTIE20": true, - "R_390_TLS_GOTIE32": true, - "R_390_TLS_GOTIE64": true, - "R_390_TLS_IE32": true, - "R_390_TLS_IE64": true, - "R_390_TLS_IEENT": true, - "R_390_TLS_LDCALL": true, - "R_390_TLS_LDM32": true, - "R_390_TLS_LDM64": true, - "R_390_TLS_LDO32": true, - "R_390_TLS_LDO64": true, - "R_390_TLS_LE32": true, - "R_390_TLS_LE64": true, - "R_390_TLS_LOAD": true, - "R_390_TLS_TPOFF": true, - "R_AARCH64": true, - "R_AARCH64_ABS16": true, - "R_AARCH64_ABS32": true, - "R_AARCH64_ABS64": true, - "R_AARCH64_ADD_ABS_LO12_NC": true, - "R_AARCH64_ADR_GOT_PAGE": true, - "R_AARCH64_ADR_PREL_LO21": true, - "R_AARCH64_ADR_PREL_PG_HI21": true, - "R_AARCH64_ADR_PREL_PG_HI21_NC": true, - "R_AARCH64_CALL26": true, - "R_AARCH64_CONDBR19": true, - "R_AARCH64_COPY": true, - "R_AARCH64_GLOB_DAT": true, - "R_AARCH64_GOT_LD_PREL19": true, - "R_AARCH64_IRELATIVE": true, - "R_AARCH64_JUMP26": true, - "R_AARCH64_JUMP_SLOT": true, - "R_AARCH64_LD64_GOTOFF_LO15": true, - "R_AARCH64_LD64_GOTPAGE_LO15": true, - "R_AARCH64_LD64_GOT_LO12_NC": true, - "R_AARCH64_LDST128_ABS_LO12_NC": true, - "R_AARCH64_LDST16_ABS_LO12_NC": true, - "R_AARCH64_LDST32_ABS_LO12_NC": true, - "R_AARCH64_LDST64_ABS_LO12_NC": true, - "R_AARCH64_LDST8_ABS_LO12_NC": true, - "R_AARCH64_LD_PREL_LO19": true, - "R_AARCH64_MOVW_SABS_G0": true, - "R_AARCH64_MOVW_SABS_G1": true, - "R_AARCH64_MOVW_SABS_G2": true, - "R_AARCH64_MOVW_UABS_G0": true, - "R_AARCH64_MOVW_UABS_G0_NC": true, - "R_AARCH64_MOVW_UABS_G1": true, - "R_AARCH64_MOVW_UABS_G1_NC": true, - "R_AARCH64_MOVW_UABS_G2": true, - "R_AARCH64_MOVW_UABS_G2_NC": true, - "R_AARCH64_MOVW_UABS_G3": true, - "R_AARCH64_NONE": true, - "R_AARCH64_NULL": true, - "R_AARCH64_P32_ABS16": true, - "R_AARCH64_P32_ABS32": true, - "R_AARCH64_P32_ADD_ABS_LO12_NC": true, - "R_AARCH64_P32_ADR_GOT_PAGE": true, - "R_AARCH64_P32_ADR_PREL_LO21": true, - "R_AARCH64_P32_ADR_PREL_PG_HI21": true, - "R_AARCH64_P32_CALL26": true, - "R_AARCH64_P32_CONDBR19": true, - "R_AARCH64_P32_COPY": true, - "R_AARCH64_P32_GLOB_DAT": true, - "R_AARCH64_P32_GOT_LD_PREL19": true, - "R_AARCH64_P32_IRELATIVE": true, - "R_AARCH64_P32_JUMP26": true, - "R_AARCH64_P32_JUMP_SLOT": true, - "R_AARCH64_P32_LD32_GOT_LO12_NC": true, - "R_AARCH64_P32_LDST128_ABS_LO12_NC": true, - "R_AARCH64_P32_LDST16_ABS_LO12_NC": true, - "R_AARCH64_P32_LDST32_ABS_LO12_NC": true, - "R_AARCH64_P32_LDST64_ABS_LO12_NC": true, - "R_AARCH64_P32_LDST8_ABS_LO12_NC": true, - "R_AARCH64_P32_LD_PREL_LO19": true, - "R_AARCH64_P32_MOVW_SABS_G0": true, - "R_AARCH64_P32_MOVW_UABS_G0": true, - "R_AARCH64_P32_MOVW_UABS_G0_NC": true, - "R_AARCH64_P32_MOVW_UABS_G1": true, - "R_AARCH64_P32_PREL16": true, - "R_AARCH64_P32_PREL32": true, - "R_AARCH64_P32_RELATIVE": true, - "R_AARCH64_P32_TLSDESC": true, - "R_AARCH64_P32_TLSDESC_ADD_LO12_NC": true, - "R_AARCH64_P32_TLSDESC_ADR_PAGE21": true, - "R_AARCH64_P32_TLSDESC_ADR_PREL21": true, - "R_AARCH64_P32_TLSDESC_CALL": true, - "R_AARCH64_P32_TLSDESC_LD32_LO12_NC": true, - "R_AARCH64_P32_TLSDESC_LD_PREL19": true, - "R_AARCH64_P32_TLSGD_ADD_LO12_NC": true, - "R_AARCH64_P32_TLSGD_ADR_PAGE21": true, - "R_AARCH64_P32_TLSIE_ADR_GOTTPREL_PAGE21": true, - "R_AARCH64_P32_TLSIE_LD32_GOTTPREL_LO12_NC": true, - "R_AARCH64_P32_TLSIE_LD_GOTTPREL_PREL19": true, - "R_AARCH64_P32_TLSLE_ADD_TPREL_HI12": true, - "R_AARCH64_P32_TLSLE_ADD_TPREL_LO12": true, - "R_AARCH64_P32_TLSLE_ADD_TPREL_LO12_NC": true, - "R_AARCH64_P32_TLSLE_MOVW_TPREL_G0": true, - "R_AARCH64_P32_TLSLE_MOVW_TPREL_G0_NC": true, - "R_AARCH64_P32_TLSLE_MOVW_TPREL_G1": true, - "R_AARCH64_P32_TLS_DTPMOD": true, - "R_AARCH64_P32_TLS_DTPREL": true, - "R_AARCH64_P32_TLS_TPREL": true, - "R_AARCH64_P32_TSTBR14": true, - "R_AARCH64_PREL16": true, - "R_AARCH64_PREL32": true, - "R_AARCH64_PREL64": true, - "R_AARCH64_RELATIVE": true, - "R_AARCH64_TLSDESC": true, - "R_AARCH64_TLSDESC_ADD": true, - "R_AARCH64_TLSDESC_ADD_LO12_NC": true, - "R_AARCH64_TLSDESC_ADR_PAGE21": true, - "R_AARCH64_TLSDESC_ADR_PREL21": true, - "R_AARCH64_TLSDESC_CALL": true, - "R_AARCH64_TLSDESC_LD64_LO12_NC": true, - "R_AARCH64_TLSDESC_LDR": true, - "R_AARCH64_TLSDESC_LD_PREL19": true, - "R_AARCH64_TLSDESC_OFF_G0_NC": true, - "R_AARCH64_TLSDESC_OFF_G1": true, - "R_AARCH64_TLSGD_ADD_LO12_NC": true, - "R_AARCH64_TLSGD_ADR_PAGE21": true, - "R_AARCH64_TLSGD_ADR_PREL21": true, - "R_AARCH64_TLSGD_MOVW_G0_NC": true, - "R_AARCH64_TLSGD_MOVW_G1": true, - "R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21": true, - "R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC": true, - "R_AARCH64_TLSIE_LD_GOTTPREL_PREL19": true, - "R_AARCH64_TLSIE_MOVW_GOTTPREL_G0_NC": true, - "R_AARCH64_TLSIE_MOVW_GOTTPREL_G1": true, - "R_AARCH64_TLSLD_ADR_PAGE21": true, - "R_AARCH64_TLSLD_ADR_PREL21": true, - "R_AARCH64_TLSLD_LDST128_DTPREL_LO12": true, - "R_AARCH64_TLSLD_LDST128_DTPREL_LO12_NC": true, - "R_AARCH64_TLSLE_ADD_TPREL_HI12": true, - "R_AARCH64_TLSLE_ADD_TPREL_LO12": true, - "R_AARCH64_TLSLE_ADD_TPREL_LO12_NC": true, - "R_AARCH64_TLSLE_LDST128_TPREL_LO12": true, - "R_AARCH64_TLSLE_LDST128_TPREL_LO12_NC": true, - "R_AARCH64_TLSLE_MOVW_TPREL_G0": true, - "R_AARCH64_TLSLE_MOVW_TPREL_G0_NC": true, - "R_AARCH64_TLSLE_MOVW_TPREL_G1": true, - "R_AARCH64_TLSLE_MOVW_TPREL_G1_NC": true, - "R_AARCH64_TLSLE_MOVW_TPREL_G2": true, - "R_AARCH64_TLS_DTPMOD64": true, - "R_AARCH64_TLS_DTPREL64": true, - "R_AARCH64_TLS_TPREL64": true, - "R_AARCH64_TSTBR14": true, - "R_ALPHA": true, - "R_ALPHA_BRADDR": true, - "R_ALPHA_COPY": true, - "R_ALPHA_GLOB_DAT": true, - "R_ALPHA_GPDISP": true, - "R_ALPHA_GPREL32": true, - "R_ALPHA_GPRELHIGH": true, - "R_ALPHA_GPRELLOW": true, - "R_ALPHA_GPVALUE": true, - "R_ALPHA_HINT": true, - "R_ALPHA_IMMED_BR_HI32": true, - "R_ALPHA_IMMED_GP_16": true, - "R_ALPHA_IMMED_GP_HI32": true, - "R_ALPHA_IMMED_LO32": true, - "R_ALPHA_IMMED_SCN_HI32": true, - "R_ALPHA_JMP_SLOT": true, - "R_ALPHA_LITERAL": true, - "R_ALPHA_LITUSE": true, - "R_ALPHA_NONE": true, - "R_ALPHA_OP_PRSHIFT": true, - "R_ALPHA_OP_PSUB": true, - "R_ALPHA_OP_PUSH": true, - "R_ALPHA_OP_STORE": true, - "R_ALPHA_REFLONG": true, - "R_ALPHA_REFQUAD": true, - "R_ALPHA_RELATIVE": true, - "R_ALPHA_SREL16": true, - "R_ALPHA_SREL32": true, - "R_ALPHA_SREL64": true, - "R_ARM": true, - "R_ARM_ABS12": true, - "R_ARM_ABS16": true, - "R_ARM_ABS32": true, - "R_ARM_ABS32_NOI": true, - "R_ARM_ABS8": true, - "R_ARM_ALU_PCREL_15_8": true, - "R_ARM_ALU_PCREL_23_15": true, - "R_ARM_ALU_PCREL_7_0": true, - "R_ARM_ALU_PC_G0": true, - "R_ARM_ALU_PC_G0_NC": true, - "R_ARM_ALU_PC_G1": true, - "R_ARM_ALU_PC_G1_NC": true, - "R_ARM_ALU_PC_G2": true, - "R_ARM_ALU_SBREL_19_12_NC": true, - "R_ARM_ALU_SBREL_27_20_CK": true, - "R_ARM_ALU_SB_G0": true, - "R_ARM_ALU_SB_G0_NC": true, - "R_ARM_ALU_SB_G1": true, - "R_ARM_ALU_SB_G1_NC": true, - "R_ARM_ALU_SB_G2": true, - "R_ARM_AMP_VCALL9": true, - "R_ARM_BASE_ABS": true, - "R_ARM_CALL": true, - "R_ARM_COPY": true, - "R_ARM_GLOB_DAT": true, - "R_ARM_GNU_VTENTRY": true, - "R_ARM_GNU_VTINHERIT": true, - "R_ARM_GOT32": true, - "R_ARM_GOTOFF": true, - "R_ARM_GOTOFF12": true, - "R_ARM_GOTPC": true, - "R_ARM_GOTRELAX": true, - "R_ARM_GOT_ABS": true, - "R_ARM_GOT_BREL12": true, - "R_ARM_GOT_PREL": true, - "R_ARM_IRELATIVE": true, - "R_ARM_JUMP24": true, - "R_ARM_JUMP_SLOT": true, - "R_ARM_LDC_PC_G0": true, - "R_ARM_LDC_PC_G1": true, - "R_ARM_LDC_PC_G2": true, - "R_ARM_LDC_SB_G0": true, - "R_ARM_LDC_SB_G1": true, - "R_ARM_LDC_SB_G2": true, - "R_ARM_LDRS_PC_G0": true, - "R_ARM_LDRS_PC_G1": true, - "R_ARM_LDRS_PC_G2": true, - "R_ARM_LDRS_SB_G0": true, - "R_ARM_LDRS_SB_G1": true, - "R_ARM_LDRS_SB_G2": true, - "R_ARM_LDR_PC_G1": true, - "R_ARM_LDR_PC_G2": true, - "R_ARM_LDR_SBREL_11_10_NC": true, - "R_ARM_LDR_SB_G0": true, - "R_ARM_LDR_SB_G1": true, - "R_ARM_LDR_SB_G2": true, - "R_ARM_ME_TOO": true, - "R_ARM_MOVT_ABS": true, - "R_ARM_MOVT_BREL": true, - "R_ARM_MOVT_PREL": true, - "R_ARM_MOVW_ABS_NC": true, - "R_ARM_MOVW_BREL": true, - "R_ARM_MOVW_BREL_NC": true, - "R_ARM_MOVW_PREL_NC": true, - "R_ARM_NONE": true, - "R_ARM_PC13": true, - "R_ARM_PC24": true, - "R_ARM_PLT32": true, - "R_ARM_PLT32_ABS": true, - "R_ARM_PREL31": true, - "R_ARM_PRIVATE_0": true, - "R_ARM_PRIVATE_1": true, - "R_ARM_PRIVATE_10": true, - "R_ARM_PRIVATE_11": true, - "R_ARM_PRIVATE_12": true, - "R_ARM_PRIVATE_13": true, - "R_ARM_PRIVATE_14": true, - "R_ARM_PRIVATE_15": true, - "R_ARM_PRIVATE_2": true, - "R_ARM_PRIVATE_3": true, - "R_ARM_PRIVATE_4": true, - "R_ARM_PRIVATE_5": true, - "R_ARM_PRIVATE_6": true, - "R_ARM_PRIVATE_7": true, - "R_ARM_PRIVATE_8": true, - "R_ARM_PRIVATE_9": true, - "R_ARM_RABS32": true, - "R_ARM_RBASE": true, - "R_ARM_REL32": true, - "R_ARM_REL32_NOI": true, - "R_ARM_RELATIVE": true, - "R_ARM_RPC24": true, - "R_ARM_RREL32": true, - "R_ARM_RSBREL32": true, - "R_ARM_RXPC25": true, - "R_ARM_SBREL31": true, - "R_ARM_SBREL32": true, - "R_ARM_SWI24": true, - "R_ARM_TARGET1": true, - "R_ARM_TARGET2": true, - "R_ARM_THM_ABS5": true, - "R_ARM_THM_ALU_ABS_G0_NC": true, - "R_ARM_THM_ALU_ABS_G1_NC": true, - "R_ARM_THM_ALU_ABS_G2_NC": true, - "R_ARM_THM_ALU_ABS_G3": true, - "R_ARM_THM_ALU_PREL_11_0": true, - "R_ARM_THM_GOT_BREL12": true, - "R_ARM_THM_JUMP11": true, - "R_ARM_THM_JUMP19": true, - "R_ARM_THM_JUMP24": true, - "R_ARM_THM_JUMP6": true, - "R_ARM_THM_JUMP8": true, - "R_ARM_THM_MOVT_ABS": true, - "R_ARM_THM_MOVT_BREL": true, - "R_ARM_THM_MOVT_PREL": true, - "R_ARM_THM_MOVW_ABS_NC": true, - "R_ARM_THM_MOVW_BREL": true, - "R_ARM_THM_MOVW_BREL_NC": true, - "R_ARM_THM_MOVW_PREL_NC": true, - "R_ARM_THM_PC12": true, - "R_ARM_THM_PC22": true, - "R_ARM_THM_PC8": true, - "R_ARM_THM_RPC22": true, - "R_ARM_THM_SWI8": true, - "R_ARM_THM_TLS_CALL": true, - "R_ARM_THM_TLS_DESCSEQ16": true, - "R_ARM_THM_TLS_DESCSEQ32": true, - "R_ARM_THM_XPC22": true, - "R_ARM_TLS_CALL": true, - "R_ARM_TLS_DESCSEQ": true, - "R_ARM_TLS_DTPMOD32": true, - "R_ARM_TLS_DTPOFF32": true, - "R_ARM_TLS_GD32": true, - "R_ARM_TLS_GOTDESC": true, - "R_ARM_TLS_IE12GP": true, - "R_ARM_TLS_IE32": true, - "R_ARM_TLS_LDM32": true, - "R_ARM_TLS_LDO12": true, - "R_ARM_TLS_LDO32": true, - "R_ARM_TLS_LE12": true, - "R_ARM_TLS_LE32": true, - "R_ARM_TLS_TPOFF32": true, - "R_ARM_V4BX": true, - "R_ARM_XPC25": true, - "R_INFO": true, - "R_INFO32": true, - "R_MIPS": true, - "R_MIPS_16": true, - "R_MIPS_26": true, - "R_MIPS_32": true, - "R_MIPS_64": true, - "R_MIPS_ADD_IMMEDIATE": true, - "R_MIPS_CALL16": true, - "R_MIPS_CALL_HI16": true, - "R_MIPS_CALL_LO16": true, - "R_MIPS_DELETE": true, - "R_MIPS_GOT16": true, - "R_MIPS_GOT_DISP": true, - "R_MIPS_GOT_HI16": true, - "R_MIPS_GOT_LO16": true, - "R_MIPS_GOT_OFST": true, - "R_MIPS_GOT_PAGE": true, - "R_MIPS_GPREL16": true, - "R_MIPS_GPREL32": true, - "R_MIPS_HI16": true, - "R_MIPS_HIGHER": true, - "R_MIPS_HIGHEST": true, - "R_MIPS_INSERT_A": true, - "R_MIPS_INSERT_B": true, - "R_MIPS_JALR": true, - "R_MIPS_LITERAL": true, - "R_MIPS_LO16": true, - "R_MIPS_NONE": true, - "R_MIPS_PC16": true, - "R_MIPS_PJUMP": true, - "R_MIPS_REL16": true, - "R_MIPS_REL32": true, - "R_MIPS_RELGOT": true, - "R_MIPS_SCN_DISP": true, - "R_MIPS_SHIFT5": true, - "R_MIPS_SHIFT6": true, - "R_MIPS_SUB": true, - "R_MIPS_TLS_DTPMOD32": true, - "R_MIPS_TLS_DTPMOD64": true, - "R_MIPS_TLS_DTPREL32": true, - "R_MIPS_TLS_DTPREL64": true, - "R_MIPS_TLS_DTPREL_HI16": true, - "R_MIPS_TLS_DTPREL_LO16": true, - "R_MIPS_TLS_GD": true, - "R_MIPS_TLS_GOTTPREL": true, - "R_MIPS_TLS_LDM": true, - "R_MIPS_TLS_TPREL32": true, - "R_MIPS_TLS_TPREL64": true, - "R_MIPS_TLS_TPREL_HI16": true, - "R_MIPS_TLS_TPREL_LO16": true, - "R_PPC": true, - "R_PPC64": true, - "R_PPC64_ADDR14": true, - "R_PPC64_ADDR14_BRNTAKEN": true, - "R_PPC64_ADDR14_BRTAKEN": true, - "R_PPC64_ADDR16": true, - "R_PPC64_ADDR16_DS": true, - "R_PPC64_ADDR16_HA": true, - "R_PPC64_ADDR16_HI": true, - "R_PPC64_ADDR16_HIGH": true, - "R_PPC64_ADDR16_HIGHA": true, - "R_PPC64_ADDR16_HIGHER": true, - "R_PPC64_ADDR16_HIGHERA": true, - "R_PPC64_ADDR16_HIGHEST": true, - "R_PPC64_ADDR16_HIGHESTA": true, - "R_PPC64_ADDR16_LO": true, - "R_PPC64_ADDR16_LO_DS": true, - "R_PPC64_ADDR24": true, - "R_PPC64_ADDR32": true, - "R_PPC64_ADDR64": true, - "R_PPC64_ADDR64_LOCAL": true, - "R_PPC64_DTPMOD64": true, - "R_PPC64_DTPREL16": true, - "R_PPC64_DTPREL16_DS": true, - "R_PPC64_DTPREL16_HA": true, - "R_PPC64_DTPREL16_HI": true, - "R_PPC64_DTPREL16_HIGH": true, - "R_PPC64_DTPREL16_HIGHA": true, - "R_PPC64_DTPREL16_HIGHER": true, - "R_PPC64_DTPREL16_HIGHERA": true, - "R_PPC64_DTPREL16_HIGHEST": true, - "R_PPC64_DTPREL16_HIGHESTA": true, - "R_PPC64_DTPREL16_LO": true, - "R_PPC64_DTPREL16_LO_DS": true, - "R_PPC64_DTPREL64": true, - "R_PPC64_ENTRY": true, - "R_PPC64_GOT16": true, - "R_PPC64_GOT16_DS": true, - "R_PPC64_GOT16_HA": true, - "R_PPC64_GOT16_HI": true, - "R_PPC64_GOT16_LO": true, - "R_PPC64_GOT16_LO_DS": true, - "R_PPC64_GOT_DTPREL16_DS": true, - "R_PPC64_GOT_DTPREL16_HA": true, - "R_PPC64_GOT_DTPREL16_HI": true, - "R_PPC64_GOT_DTPREL16_LO_DS": true, - "R_PPC64_GOT_TLSGD16": true, - "R_PPC64_GOT_TLSGD16_HA": true, - "R_PPC64_GOT_TLSGD16_HI": true, - "R_PPC64_GOT_TLSGD16_LO": true, - "R_PPC64_GOT_TLSLD16": true, - "R_PPC64_GOT_TLSLD16_HA": true, - "R_PPC64_GOT_TLSLD16_HI": true, - "R_PPC64_GOT_TLSLD16_LO": true, - "R_PPC64_GOT_TPREL16_DS": true, - "R_PPC64_GOT_TPREL16_HA": true, - "R_PPC64_GOT_TPREL16_HI": true, - "R_PPC64_GOT_TPREL16_LO_DS": true, - "R_PPC64_IRELATIVE": true, - "R_PPC64_JMP_IREL": true, - "R_PPC64_JMP_SLOT": true, - "R_PPC64_NONE": true, - "R_PPC64_PLT16_LO_DS": true, - "R_PPC64_PLTGOT16": true, - "R_PPC64_PLTGOT16_DS": true, - "R_PPC64_PLTGOT16_HA": true, - "R_PPC64_PLTGOT16_HI": true, - "R_PPC64_PLTGOT16_LO": true, - "R_PPC64_PLTGOT_LO_DS": true, - "R_PPC64_REL14": true, - "R_PPC64_REL14_BRNTAKEN": true, - "R_PPC64_REL14_BRTAKEN": true, - "R_PPC64_REL16": true, - "R_PPC64_REL16DX_HA": true, - "R_PPC64_REL16_HA": true, - "R_PPC64_REL16_HI": true, - "R_PPC64_REL16_LO": true, - "R_PPC64_REL24": true, - "R_PPC64_REL24_NOTOC": true, - "R_PPC64_REL32": true, - "R_PPC64_REL64": true, - "R_PPC64_SECTOFF_DS": true, - "R_PPC64_SECTOFF_LO_DS": true, - "R_PPC64_TLS": true, - "R_PPC64_TLSGD": true, - "R_PPC64_TLSLD": true, - "R_PPC64_TOC": true, - "R_PPC64_TOC16": true, - "R_PPC64_TOC16_DS": true, - "R_PPC64_TOC16_HA": true, - "R_PPC64_TOC16_HI": true, - "R_PPC64_TOC16_LO": true, - "R_PPC64_TOC16_LO_DS": true, - "R_PPC64_TOCSAVE": true, - "R_PPC64_TPREL16": true, - "R_PPC64_TPREL16_DS": true, - "R_PPC64_TPREL16_HA": true, - "R_PPC64_TPREL16_HI": true, - "R_PPC64_TPREL16_HIGH": true, - "R_PPC64_TPREL16_HIGHA": true, - "R_PPC64_TPREL16_HIGHER": true, - "R_PPC64_TPREL16_HIGHERA": true, - "R_PPC64_TPREL16_HIGHEST": true, - "R_PPC64_TPREL16_HIGHESTA": true, - "R_PPC64_TPREL16_LO": true, - "R_PPC64_TPREL16_LO_DS": true, - "R_PPC64_TPREL64": true, - "R_PPC_ADDR14": true, - "R_PPC_ADDR14_BRNTAKEN": true, - "R_PPC_ADDR14_BRTAKEN": true, - "R_PPC_ADDR16": true, - "R_PPC_ADDR16_HA": true, - "R_PPC_ADDR16_HI": true, - "R_PPC_ADDR16_LO": true, - "R_PPC_ADDR24": true, - "R_PPC_ADDR32": true, - "R_PPC_COPY": true, - "R_PPC_DTPMOD32": true, - "R_PPC_DTPREL16": true, - "R_PPC_DTPREL16_HA": true, - "R_PPC_DTPREL16_HI": true, - "R_PPC_DTPREL16_LO": true, - "R_PPC_DTPREL32": true, - "R_PPC_EMB_BIT_FLD": true, - "R_PPC_EMB_MRKREF": true, - "R_PPC_EMB_NADDR16": true, - "R_PPC_EMB_NADDR16_HA": true, - "R_PPC_EMB_NADDR16_HI": true, - "R_PPC_EMB_NADDR16_LO": true, - "R_PPC_EMB_NADDR32": true, - "R_PPC_EMB_RELSDA": true, - "R_PPC_EMB_RELSEC16": true, - "R_PPC_EMB_RELST_HA": true, - "R_PPC_EMB_RELST_HI": true, - "R_PPC_EMB_RELST_LO": true, - "R_PPC_EMB_SDA21": true, - "R_PPC_EMB_SDA2I16": true, - "R_PPC_EMB_SDA2REL": true, - "R_PPC_EMB_SDAI16": true, - "R_PPC_GLOB_DAT": true, - "R_PPC_GOT16": true, - "R_PPC_GOT16_HA": true, - "R_PPC_GOT16_HI": true, - "R_PPC_GOT16_LO": true, - "R_PPC_GOT_TLSGD16": true, - "R_PPC_GOT_TLSGD16_HA": true, - "R_PPC_GOT_TLSGD16_HI": true, - "R_PPC_GOT_TLSGD16_LO": true, - "R_PPC_GOT_TLSLD16": true, - "R_PPC_GOT_TLSLD16_HA": true, - "R_PPC_GOT_TLSLD16_HI": true, - "R_PPC_GOT_TLSLD16_LO": true, - "R_PPC_GOT_TPREL16": true, - "R_PPC_GOT_TPREL16_HA": true, - "R_PPC_GOT_TPREL16_HI": true, - "R_PPC_GOT_TPREL16_LO": true, - "R_PPC_JMP_SLOT": true, - "R_PPC_LOCAL24PC": true, - "R_PPC_NONE": true, - "R_PPC_PLT16_HA": true, - "R_PPC_PLT16_HI": true, - "R_PPC_PLT16_LO": true, - "R_PPC_PLT32": true, - "R_PPC_PLTREL24": true, - "R_PPC_PLTREL32": true, - "R_PPC_REL14": true, - "R_PPC_REL14_BRNTAKEN": true, - "R_PPC_REL14_BRTAKEN": true, - "R_PPC_REL24": true, - "R_PPC_REL32": true, - "R_PPC_RELATIVE": true, - "R_PPC_SDAREL16": true, - "R_PPC_SECTOFF": true, - "R_PPC_SECTOFF_HA": true, - "R_PPC_SECTOFF_HI": true, - "R_PPC_SECTOFF_LO": true, - "R_PPC_TLS": true, - "R_PPC_TPREL16": true, - "R_PPC_TPREL16_HA": true, - "R_PPC_TPREL16_HI": true, - "R_PPC_TPREL16_LO": true, - "R_PPC_TPREL32": true, - "R_PPC_UADDR16": true, - "R_PPC_UADDR32": true, - "R_RISCV": true, - "R_RISCV_32": true, - "R_RISCV_32_PCREL": true, - "R_RISCV_64": true, - "R_RISCV_ADD16": true, - "R_RISCV_ADD32": true, - "R_RISCV_ADD64": true, - "R_RISCV_ADD8": true, - "R_RISCV_ALIGN": true, - "R_RISCV_BRANCH": true, - "R_RISCV_CALL": true, - "R_RISCV_CALL_PLT": true, - "R_RISCV_COPY": true, - "R_RISCV_GNU_VTENTRY": true, - "R_RISCV_GNU_VTINHERIT": true, - "R_RISCV_GOT_HI20": true, - "R_RISCV_GPREL_I": true, - "R_RISCV_GPREL_S": true, - "R_RISCV_HI20": true, - "R_RISCV_JAL": true, - "R_RISCV_JUMP_SLOT": true, - "R_RISCV_LO12_I": true, - "R_RISCV_LO12_S": true, - "R_RISCV_NONE": true, - "R_RISCV_PCREL_HI20": true, - "R_RISCV_PCREL_LO12_I": true, - "R_RISCV_PCREL_LO12_S": true, - "R_RISCV_RELATIVE": true, - "R_RISCV_RELAX": true, - "R_RISCV_RVC_BRANCH": true, - "R_RISCV_RVC_JUMP": true, - "R_RISCV_RVC_LUI": true, - "R_RISCV_SET16": true, - "R_RISCV_SET32": true, - "R_RISCV_SET6": true, - "R_RISCV_SET8": true, - "R_RISCV_SUB16": true, - "R_RISCV_SUB32": true, - "R_RISCV_SUB6": true, - "R_RISCV_SUB64": true, - "R_RISCV_SUB8": true, - "R_RISCV_TLS_DTPMOD32": true, - "R_RISCV_TLS_DTPMOD64": true, - "R_RISCV_TLS_DTPREL32": true, - "R_RISCV_TLS_DTPREL64": true, - "R_RISCV_TLS_GD_HI20": true, - "R_RISCV_TLS_GOT_HI20": true, - "R_RISCV_TLS_TPREL32": true, - "R_RISCV_TLS_TPREL64": true, - "R_RISCV_TPREL_ADD": true, - "R_RISCV_TPREL_HI20": true, - "R_RISCV_TPREL_I": true, - "R_RISCV_TPREL_LO12_I": true, - "R_RISCV_TPREL_LO12_S": true, - "R_RISCV_TPREL_S": true, - "R_SPARC": true, - "R_SPARC_10": true, - "R_SPARC_11": true, - "R_SPARC_13": true, - "R_SPARC_16": true, - "R_SPARC_22": true, - "R_SPARC_32": true, - "R_SPARC_5": true, - "R_SPARC_6": true, - "R_SPARC_64": true, - "R_SPARC_7": true, - "R_SPARC_8": true, - "R_SPARC_COPY": true, - "R_SPARC_DISP16": true, - "R_SPARC_DISP32": true, - "R_SPARC_DISP64": true, - "R_SPARC_DISP8": true, - "R_SPARC_GLOB_DAT": true, - "R_SPARC_GLOB_JMP": true, - "R_SPARC_GOT10": true, - "R_SPARC_GOT13": true, - "R_SPARC_GOT22": true, - "R_SPARC_H44": true, - "R_SPARC_HH22": true, - "R_SPARC_HI22": true, - "R_SPARC_HIPLT22": true, - "R_SPARC_HIX22": true, - "R_SPARC_HM10": true, - "R_SPARC_JMP_SLOT": true, - "R_SPARC_L44": true, - "R_SPARC_LM22": true, - "R_SPARC_LO10": true, - "R_SPARC_LOPLT10": true, - "R_SPARC_LOX10": true, - "R_SPARC_M44": true, - "R_SPARC_NONE": true, - "R_SPARC_OLO10": true, - "R_SPARC_PC10": true, - "R_SPARC_PC22": true, - "R_SPARC_PCPLT10": true, - "R_SPARC_PCPLT22": true, - "R_SPARC_PCPLT32": true, - "R_SPARC_PC_HH22": true, - "R_SPARC_PC_HM10": true, - "R_SPARC_PC_LM22": true, - "R_SPARC_PLT32": true, - "R_SPARC_PLT64": true, - "R_SPARC_REGISTER": true, - "R_SPARC_RELATIVE": true, - "R_SPARC_UA16": true, - "R_SPARC_UA32": true, - "R_SPARC_UA64": true, - "R_SPARC_WDISP16": true, - "R_SPARC_WDISP19": true, - "R_SPARC_WDISP22": true, - "R_SPARC_WDISP30": true, - "R_SPARC_WPLT30": true, - "R_SYM32": true, - "R_SYM64": true, - "R_TYPE32": true, - "R_TYPE64": true, - "R_X86_64": true, - "R_X86_64_16": true, - "R_X86_64_32": true, - "R_X86_64_32S": true, - "R_X86_64_64": true, - "R_X86_64_8": true, - "R_X86_64_COPY": true, - "R_X86_64_DTPMOD64": true, - "R_X86_64_DTPOFF32": true, - "R_X86_64_DTPOFF64": true, - "R_X86_64_GLOB_DAT": true, - "R_X86_64_GOT32": true, - "R_X86_64_GOT64": true, - "R_X86_64_GOTOFF64": true, - "R_X86_64_GOTPC32": true, - "R_X86_64_GOTPC32_TLSDESC": true, - "R_X86_64_GOTPC64": true, - "R_X86_64_GOTPCREL": true, - "R_X86_64_GOTPCREL64": true, - "R_X86_64_GOTPCRELX": true, - "R_X86_64_GOTPLT64": true, - "R_X86_64_GOTTPOFF": true, - "R_X86_64_IRELATIVE": true, - "R_X86_64_JMP_SLOT": true, - "R_X86_64_NONE": true, - "R_X86_64_PC16": true, - "R_X86_64_PC32": true, - "R_X86_64_PC32_BND": true, - "R_X86_64_PC64": true, - "R_X86_64_PC8": true, - "R_X86_64_PLT32": true, - "R_X86_64_PLT32_BND": true, - "R_X86_64_PLTOFF64": true, - "R_X86_64_RELATIVE": true, - "R_X86_64_RELATIVE64": true, - "R_X86_64_REX_GOTPCRELX": true, - "R_X86_64_SIZE32": true, - "R_X86_64_SIZE64": true, - "R_X86_64_TLSDESC": true, - "R_X86_64_TLSDESC_CALL": true, - "R_X86_64_TLSGD": true, - "R_X86_64_TLSLD": true, - "R_X86_64_TPOFF32": true, - "R_X86_64_TPOFF64": true, - "Rel32": true, - "Rel64": true, - "Rela32": true, - "Rela64": true, - "SHF_ALLOC": true, - "SHF_COMPRESSED": true, - "SHF_EXECINSTR": true, - "SHF_GROUP": true, - "SHF_INFO_LINK": true, - "SHF_LINK_ORDER": true, - "SHF_MASKOS": true, - "SHF_MASKPROC": true, - "SHF_MERGE": true, - "SHF_OS_NONCONFORMING": true, - "SHF_STRINGS": true, - "SHF_TLS": true, - "SHF_WRITE": true, - "SHN_ABS": true, - "SHN_COMMON": true, - "SHN_HIOS": true, - "SHN_HIPROC": true, - "SHN_HIRESERVE": true, - "SHN_LOOS": true, - "SHN_LOPROC": true, - "SHN_LORESERVE": true, - "SHN_UNDEF": true, - "SHN_XINDEX": true, - "SHT_DYNAMIC": true, - "SHT_DYNSYM": true, - "SHT_FINI_ARRAY": true, - "SHT_GNU_ATTRIBUTES": true, - "SHT_GNU_HASH": true, - "SHT_GNU_LIBLIST": true, - "SHT_GNU_VERDEF": true, - "SHT_GNU_VERNEED": true, - "SHT_GNU_VERSYM": true, - "SHT_GROUP": true, - "SHT_HASH": true, - "SHT_HIOS": true, - "SHT_HIPROC": true, - "SHT_HIUSER": true, - "SHT_INIT_ARRAY": true, - "SHT_LOOS": true, - "SHT_LOPROC": true, - "SHT_LOUSER": true, - "SHT_NOBITS": true, - "SHT_NOTE": true, - "SHT_NULL": true, - "SHT_PREINIT_ARRAY": true, - "SHT_PROGBITS": true, - "SHT_REL": true, - "SHT_RELA": true, - "SHT_SHLIB": true, - "SHT_STRTAB": true, - "SHT_SYMTAB": true, - "SHT_SYMTAB_SHNDX": true, - "STB_GLOBAL": true, - "STB_HIOS": true, - "STB_HIPROC": true, - "STB_LOCAL": true, - "STB_LOOS": true, - "STB_LOPROC": true, - "STB_WEAK": true, - "STT_COMMON": true, - "STT_FILE": true, - "STT_FUNC": true, - "STT_HIOS": true, - "STT_HIPROC": true, - "STT_LOOS": true, - "STT_LOPROC": true, - "STT_NOTYPE": true, - "STT_OBJECT": true, - "STT_SECTION": true, - "STT_TLS": true, - "STV_DEFAULT": true, - "STV_HIDDEN": true, - "STV_INTERNAL": true, - "STV_PROTECTED": true, - "ST_BIND": true, - "ST_INFO": true, - "ST_TYPE": true, - "ST_VISIBILITY": true, - "Section": true, - "Section32": true, - "Section64": true, - "SectionFlag": true, - "SectionHeader": true, - "SectionIndex": true, - "SectionType": true, - "Sym32": true, - "Sym32Size": true, - "Sym64": true, - "Sym64Size": true, - "SymBind": true, - "SymType": true, - "SymVis": true, - "Symbol": true, - "Type": true, - "Version": true, - }, - "debug/gosym": map[string]bool{ - "DecodingError": true, - "Func": true, - "LineTable": true, - "NewLineTable": true, - "NewTable": true, - "Obj": true, - "Sym": true, - "Table": true, - "UnknownFileError": true, - "UnknownLineError": true, - }, - "debug/macho": map[string]bool{ - "ARM64_RELOC_ADDEND": true, - "ARM64_RELOC_BRANCH26": true, - "ARM64_RELOC_GOT_LOAD_PAGE21": true, - "ARM64_RELOC_GOT_LOAD_PAGEOFF12": true, - "ARM64_RELOC_PAGE21": true, - "ARM64_RELOC_PAGEOFF12": true, - "ARM64_RELOC_POINTER_TO_GOT": true, - "ARM64_RELOC_SUBTRACTOR": true, - "ARM64_RELOC_TLVP_LOAD_PAGE21": true, - "ARM64_RELOC_TLVP_LOAD_PAGEOFF12": true, - "ARM64_RELOC_UNSIGNED": true, - "ARM_RELOC_BR24": true, - "ARM_RELOC_HALF": true, - "ARM_RELOC_HALF_SECTDIFF": true, - "ARM_RELOC_LOCAL_SECTDIFF": true, - "ARM_RELOC_PAIR": true, - "ARM_RELOC_PB_LA_PTR": true, - "ARM_RELOC_SECTDIFF": true, - "ARM_RELOC_VANILLA": true, - "ARM_THUMB_32BIT_BRANCH": true, - "ARM_THUMB_RELOC_BR22": true, - "Cpu": true, - "Cpu386": true, - "CpuAmd64": true, - "CpuArm": true, - "CpuArm64": true, - "CpuPpc": true, - "CpuPpc64": true, - "Dylib": true, - "DylibCmd": true, - "Dysymtab": true, - "DysymtabCmd": true, - "ErrNotFat": true, - "FatArch": true, - "FatArchHeader": true, - "FatFile": true, - "File": true, - "FileHeader": true, - "FlagAllModsBound": true, - "FlagAllowStackExecution": true, - "FlagAppExtensionSafe": true, - "FlagBindAtLoad": true, - "FlagBindsToWeak": true, - "FlagCanonical": true, - "FlagDeadStrippableDylib": true, - "FlagDyldLink": true, - "FlagForceFlat": true, - "FlagHasTLVDescriptors": true, - "FlagIncrLink": true, - "FlagLazyInit": true, - "FlagNoFixPrebinding": true, - "FlagNoHeapExecution": true, - "FlagNoMultiDefs": true, - "FlagNoReexportedDylibs": true, - "FlagNoUndefs": true, - "FlagPIE": true, - "FlagPrebindable": true, - "FlagPrebound": true, - "FlagRootSafe": true, - "FlagSetuidSafe": true, - "FlagSplitSegs": true, - "FlagSubsectionsViaSymbols": true, - "FlagTwoLevel": true, - "FlagWeakDefines": true, - "FormatError": true, - "GENERIC_RELOC_LOCAL_SECTDIFF": true, - "GENERIC_RELOC_PAIR": true, - "GENERIC_RELOC_PB_LA_PTR": true, - "GENERIC_RELOC_SECTDIFF": true, - "GENERIC_RELOC_TLV": true, - "GENERIC_RELOC_VANILLA": true, - "Load": true, - "LoadBytes": true, - "LoadCmd": true, - "LoadCmdDylib": true, - "LoadCmdDylinker": true, - "LoadCmdDysymtab": true, - "LoadCmdRpath": true, - "LoadCmdSegment": true, - "LoadCmdSegment64": true, - "LoadCmdSymtab": true, - "LoadCmdThread": true, - "LoadCmdUnixThread": true, - "Magic32": true, - "Magic64": true, - "MagicFat": true, - "NewFatFile": true, - "NewFile": true, - "Nlist32": true, - "Nlist64": true, - "Open": true, - "OpenFat": true, - "Regs386": true, - "RegsAMD64": true, - "Reloc": true, - "RelocTypeARM": true, - "RelocTypeARM64": true, - "RelocTypeGeneric": true, - "RelocTypeX86_64": true, - "Rpath": true, - "RpathCmd": true, - "Section": true, - "Section32": true, - "Section64": true, - "SectionHeader": true, - "Segment": true, - "Segment32": true, - "Segment64": true, - "SegmentHeader": true, - "Symbol": true, - "Symtab": true, - "SymtabCmd": true, - "Thread": true, - "Type": true, - "TypeBundle": true, - "TypeDylib": true, - "TypeExec": true, - "TypeObj": true, - "X86_64_RELOC_BRANCH": true, - "X86_64_RELOC_GOT": true, - "X86_64_RELOC_GOT_LOAD": true, - "X86_64_RELOC_SIGNED": true, - "X86_64_RELOC_SIGNED_1": true, - "X86_64_RELOC_SIGNED_2": true, - "X86_64_RELOC_SIGNED_4": true, - "X86_64_RELOC_SUBTRACTOR": true, - "X86_64_RELOC_TLV": true, - "X86_64_RELOC_UNSIGNED": true, - }, - "debug/pe": map[string]bool{ - "COFFSymbol": true, - "COFFSymbolSize": true, - "DataDirectory": true, - "File": true, - "FileHeader": true, - "FormatError": true, - "IMAGE_DIRECTORY_ENTRY_ARCHITECTURE": true, - "IMAGE_DIRECTORY_ENTRY_BASERELOC": true, - "IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT": true, - "IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR": true, - "IMAGE_DIRECTORY_ENTRY_DEBUG": true, - "IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT": true, - "IMAGE_DIRECTORY_ENTRY_EXCEPTION": true, - "IMAGE_DIRECTORY_ENTRY_EXPORT": true, - "IMAGE_DIRECTORY_ENTRY_GLOBALPTR": true, - "IMAGE_DIRECTORY_ENTRY_IAT": true, - "IMAGE_DIRECTORY_ENTRY_IMPORT": true, - "IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG": true, - "IMAGE_DIRECTORY_ENTRY_RESOURCE": true, - "IMAGE_DIRECTORY_ENTRY_SECURITY": true, - "IMAGE_DIRECTORY_ENTRY_TLS": true, - "IMAGE_FILE_MACHINE_AM33": true, - "IMAGE_FILE_MACHINE_AMD64": true, - "IMAGE_FILE_MACHINE_ARM": true, - "IMAGE_FILE_MACHINE_ARM64": true, - "IMAGE_FILE_MACHINE_ARMNT": true, - "IMAGE_FILE_MACHINE_EBC": true, - "IMAGE_FILE_MACHINE_I386": true, - "IMAGE_FILE_MACHINE_IA64": true, - "IMAGE_FILE_MACHINE_M32R": true, - "IMAGE_FILE_MACHINE_MIPS16": true, - "IMAGE_FILE_MACHINE_MIPSFPU": true, - "IMAGE_FILE_MACHINE_MIPSFPU16": true, - "IMAGE_FILE_MACHINE_POWERPC": true, - "IMAGE_FILE_MACHINE_POWERPCFP": true, - "IMAGE_FILE_MACHINE_R4000": true, - "IMAGE_FILE_MACHINE_SH3": true, - "IMAGE_FILE_MACHINE_SH3DSP": true, - "IMAGE_FILE_MACHINE_SH4": true, - "IMAGE_FILE_MACHINE_SH5": true, - "IMAGE_FILE_MACHINE_THUMB": true, - "IMAGE_FILE_MACHINE_UNKNOWN": true, - "IMAGE_FILE_MACHINE_WCEMIPSV2": true, - "ImportDirectory": true, - "NewFile": true, - "Open": true, - "OptionalHeader32": true, - "OptionalHeader64": true, - "Reloc": true, - "Section": true, - "SectionHeader": true, - "SectionHeader32": true, - "StringTable": true, - "Symbol": true, - }, - "debug/plan9obj": map[string]bool{ - "File": true, - "FileHeader": true, - "Magic386": true, - "Magic64": true, - "MagicAMD64": true, - "MagicARM": true, - "NewFile": true, - "Open": true, - "Section": true, - "SectionHeader": true, - "Sym": true, - }, - "encoding": map[string]bool{ - "BinaryMarshaler": true, - "BinaryUnmarshaler": true, - "TextMarshaler": true, - "TextUnmarshaler": true, - }, - "encoding/ascii85": map[string]bool{ - "CorruptInputError": true, - "Decode": true, - "Encode": true, - "MaxEncodedLen": true, - "NewDecoder": true, - "NewEncoder": true, - }, - "encoding/asn1": map[string]bool{ - "BitString": true, - "ClassApplication": true, - "ClassContextSpecific": true, - "ClassPrivate": true, - "ClassUniversal": true, - "Enumerated": true, - "Flag": true, - "Marshal": true, - "MarshalWithParams": true, - "NullBytes": true, - "NullRawValue": true, - "ObjectIdentifier": true, - "RawContent": true, - "RawValue": true, - "StructuralError": true, - "SyntaxError": true, - "TagBitString": true, - "TagBoolean": true, - "TagEnum": true, - "TagGeneralString": true, - "TagGeneralizedTime": true, - "TagIA5String": true, - "TagInteger": true, - "TagNull": true, - "TagNumericString": true, - "TagOID": true, - "TagOctetString": true, - "TagPrintableString": true, - "TagSequence": true, - "TagSet": true, - "TagT61String": true, - "TagUTCTime": true, - "TagUTF8String": true, - "Unmarshal": true, - "UnmarshalWithParams": true, - }, - "encoding/base32": map[string]bool{ - "CorruptInputError": true, - "Encoding": true, - "HexEncoding": true, - "NewDecoder": true, - "NewEncoder": true, - "NewEncoding": true, - "NoPadding": true, - "StdEncoding": true, - "StdPadding": true, - }, - "encoding/base64": map[string]bool{ - "CorruptInputError": true, - "Encoding": true, - "NewDecoder": true, - "NewEncoder": true, - "NewEncoding": true, - "NoPadding": true, - "RawStdEncoding": true, - "RawURLEncoding": true, - "StdEncoding": true, - "StdPadding": true, - "URLEncoding": true, - }, - "encoding/binary": map[string]bool{ - "BigEndian": true, - "ByteOrder": true, - "LittleEndian": true, - "MaxVarintLen16": true, - "MaxVarintLen32": true, - "MaxVarintLen64": true, - "PutUvarint": true, - "PutVarint": true, - "Read": true, - "ReadUvarint": true, - "ReadVarint": true, - "Size": true, - "Uvarint": true, - "Varint": true, - "Write": true, - }, - "encoding/csv": map[string]bool{ - "ErrBareQuote": true, - "ErrFieldCount": true, - "ErrQuote": true, - "ErrTrailingComma": true, - "NewReader": true, - "NewWriter": true, - "ParseError": true, - "Reader": true, - "Writer": true, - }, - "encoding/gob": map[string]bool{ - "CommonType": true, - "Decoder": true, - "Encoder": true, - "GobDecoder": true, - "GobEncoder": true, - "NewDecoder": true, - "NewEncoder": true, - "Register": true, - "RegisterName": true, - }, - "encoding/hex": map[string]bool{ - "Decode": true, - "DecodeString": true, - "DecodedLen": true, - "Dump": true, - "Dumper": true, - "Encode": true, - "EncodeToString": true, - "EncodedLen": true, - "ErrLength": true, - "InvalidByteError": true, - "NewDecoder": true, - "NewEncoder": true, - }, - "encoding/json": map[string]bool{ - "Compact": true, - "Decoder": true, - "Delim": true, - "Encoder": true, - "HTMLEscape": true, - "Indent": true, - "InvalidUTF8Error": true, - "InvalidUnmarshalError": true, - "Marshal": true, - "MarshalIndent": true, - "Marshaler": true, - "MarshalerError": true, - "NewDecoder": true, - "NewEncoder": true, - "Number": true, - "RawMessage": true, - "SyntaxError": true, - "Token": true, - "Unmarshal": true, - "UnmarshalFieldError": true, - "UnmarshalTypeError": true, - "Unmarshaler": true, - "UnsupportedTypeError": true, - "UnsupportedValueError": true, - "Valid": true, - }, - "encoding/pem": map[string]bool{ - "Block": true, - "Decode": true, - "Encode": true, - "EncodeToMemory": true, - }, - "encoding/xml": map[string]bool{ - "Attr": true, - "CharData": true, - "Comment": true, - "CopyToken": true, - "Decoder": true, - "Directive": true, - "Encoder": true, - "EndElement": true, - "Escape": true, - "EscapeText": true, - "HTMLAutoClose": true, - "HTMLEntity": true, - "Header": true, - "Marshal": true, - "MarshalIndent": true, - "Marshaler": true, - "MarshalerAttr": true, - "Name": true, - "NewDecoder": true, - "NewEncoder": true, - "NewTokenDecoder": true, - "ProcInst": true, - "StartElement": true, - "SyntaxError": true, - "TagPathError": true, - "Token": true, - "TokenReader": true, - "Unmarshal": true, - "UnmarshalError": true, - "Unmarshaler": true, - "UnmarshalerAttr": true, - "UnsupportedTypeError": true, - }, - "errors": map[string]bool{ - "New": true, - }, - "expvar": map[string]bool{ - "Do": true, - "Float": true, - "Func": true, - "Get": true, - "Handler": true, - "Int": true, - "KeyValue": true, - "Map": true, - "NewFloat": true, - "NewInt": true, - "NewMap": true, - "NewString": true, - "Publish": true, - "String": true, - "Var": true, - }, - "flag": map[string]bool{ - "Arg": true, - "Args": true, - "Bool": true, - "BoolVar": true, - "CommandLine": true, - "ContinueOnError": true, - "Duration": true, - "DurationVar": true, - "ErrHelp": true, - "ErrorHandling": true, - "ExitOnError": true, - "Flag": true, - "FlagSet": true, - "Float64": true, - "Float64Var": true, - "Getter": true, - "Int": true, - "Int64": true, - "Int64Var": true, - "IntVar": true, - "Lookup": true, - "NArg": true, - "NFlag": true, - "NewFlagSet": true, - "PanicOnError": true, - "Parse": true, - "Parsed": true, - "PrintDefaults": true, - "Set": true, - "String": true, - "StringVar": true, - "Uint": true, - "Uint64": true, - "Uint64Var": true, - "UintVar": true, - "UnquoteUsage": true, - "Usage": true, - "Value": true, - "Var": true, - "Visit": true, - "VisitAll": true, - }, - "fmt": map[string]bool{ - "Errorf": true, - "Formatter": true, - "Fprint": true, - "Fprintf": true, - "Fprintln": true, - "Fscan": true, - "Fscanf": true, - "Fscanln": true, - "GoStringer": true, - "Print": true, - "Printf": true, - "Println": true, - "Scan": true, - "ScanState": true, - "Scanf": true, - "Scanln": true, - "Scanner": true, - "Sprint": true, - "Sprintf": true, - "Sprintln": true, - "Sscan": true, - "Sscanf": true, - "Sscanln": true, - "State": true, - "Stringer": true, - }, - "go/ast": map[string]bool{ - "ArrayType": true, - "AssignStmt": true, - "Bad": true, - "BadDecl": true, - "BadExpr": true, - "BadStmt": true, - "BasicLit": true, - "BinaryExpr": true, - "BlockStmt": true, - "BranchStmt": true, - "CallExpr": true, - "CaseClause": true, - "ChanDir": true, - "ChanType": true, - "CommClause": true, - "Comment": true, - "CommentGroup": true, - "CommentMap": true, - "CompositeLit": true, - "Con": true, - "DeclStmt": true, - "DeferStmt": true, - "Ellipsis": true, - "EmptyStmt": true, - "ExprStmt": true, - "Field": true, - "FieldFilter": true, - "FieldList": true, - "File": true, - "FileExports": true, - "Filter": true, - "FilterDecl": true, - "FilterFile": true, - "FilterFuncDuplicates": true, - "FilterImportDuplicates": true, - "FilterPackage": true, - "FilterUnassociatedComments": true, - "ForStmt": true, - "Fprint": true, - "Fun": true, - "FuncDecl": true, - "FuncLit": true, - "FuncType": true, - "GenDecl": true, - "GoStmt": true, - "Ident": true, - "IfStmt": true, - "ImportSpec": true, - "Importer": true, - "IncDecStmt": true, - "IndexExpr": true, - "Inspect": true, - "InterfaceType": true, - "IsExported": true, - "KeyValueExpr": true, - "LabeledStmt": true, - "Lbl": true, - "MapType": true, - "MergeMode": true, - "MergePackageFiles": true, - "NewCommentMap": true, - "NewIdent": true, - "NewObj": true, - "NewPackage": true, - "NewScope": true, - "Node": true, - "NotNilFilter": true, - "ObjKind": true, - "Object": true, - "Package": true, - "PackageExports": true, - "ParenExpr": true, - "Pkg": true, - "Print": true, - "RECV": true, - "RangeStmt": true, - "ReturnStmt": true, - "SEND": true, - "Scope": true, - "SelectStmt": true, - "SelectorExpr": true, - "SendStmt": true, - "SliceExpr": true, - "SortImports": true, - "StarExpr": true, - "StructType": true, - "SwitchStmt": true, - "Typ": true, - "TypeAssertExpr": true, - "TypeSpec": true, - "TypeSwitchStmt": true, - "UnaryExpr": true, - "ValueSpec": true, - "Var": true, - "Visitor": true, - "Walk": true, - }, - "go/build": map[string]bool{ - "AllowBinary": true, - "ArchChar": true, - "Context": true, - "Default": true, - "FindOnly": true, - "IgnoreVendor": true, - "Import": true, - "ImportComment": true, - "ImportDir": true, - "ImportMode": true, - "IsLocalImport": true, - "MultiplePackageError": true, - "NoGoError": true, - "Package": true, - "ToolDir": true, - }, - "go/constant": map[string]bool{ - "BinaryOp": true, - "BitLen": true, - "Bool": true, - "BoolVal": true, - "Bytes": true, - "Compare": true, - "Complex": true, - "Denom": true, - "Float": true, - "Float32Val": true, - "Float64Val": true, - "Imag": true, - "Int": true, - "Int64Val": true, - "Kind": true, - "MakeBool": true, - "MakeFloat64": true, - "MakeFromBytes": true, - "MakeFromLiteral": true, - "MakeImag": true, - "MakeInt64": true, - "MakeString": true, - "MakeUint64": true, - "MakeUnknown": true, - "Num": true, - "Real": true, - "Shift": true, - "Sign": true, - "String": true, - "StringVal": true, - "ToComplex": true, - "ToFloat": true, - "ToInt": true, - "Uint64Val": true, - "UnaryOp": true, - "Unknown": true, - }, - "go/doc": map[string]bool{ - "AllDecls": true, - "AllMethods": true, - "Example": true, - "Examples": true, - "Filter": true, - "Func": true, - "IllegalPrefixes": true, - "IsPredeclared": true, - "Mode": true, - "New": true, - "Note": true, - "Package": true, - "PreserveAST": true, - "Synopsis": true, - "ToHTML": true, - "ToText": true, - "Type": true, - "Value": true, - }, - "go/format": map[string]bool{ - "Node": true, - "Source": true, - }, - "go/importer": map[string]bool{ - "Default": true, - "For": true, - "ForCompiler": true, - "Lookup": true, - }, - "go/parser": map[string]bool{ - "AllErrors": true, - "DeclarationErrors": true, - "ImportsOnly": true, - "Mode": true, - "PackageClauseOnly": true, - "ParseComments": true, - "ParseDir": true, - "ParseExpr": true, - "ParseExprFrom": true, - "ParseFile": true, - "SpuriousErrors": true, - "Trace": true, - }, - "go/printer": map[string]bool{ - "CommentedNode": true, - "Config": true, - "Fprint": true, - "Mode": true, - "RawFormat": true, - "SourcePos": true, - "TabIndent": true, - "UseSpaces": true, - }, - "go/scanner": map[string]bool{ - "Error": true, - "ErrorHandler": true, - "ErrorList": true, - "Mode": true, - "PrintError": true, - "ScanComments": true, - "Scanner": true, - }, - "go/token": map[string]bool{ - "ADD": true, - "ADD_ASSIGN": true, - "AND": true, - "AND_ASSIGN": true, - "AND_NOT": true, - "AND_NOT_ASSIGN": true, - "ARROW": true, - "ASSIGN": true, - "BREAK": true, - "CASE": true, - "CHAN": true, - "CHAR": true, - "COLON": true, - "COMMA": true, - "COMMENT": true, - "CONST": true, - "CONTINUE": true, - "DEC": true, - "DEFAULT": true, - "DEFER": true, - "DEFINE": true, - "ELLIPSIS": true, - "ELSE": true, - "EOF": true, - "EQL": true, - "FALLTHROUGH": true, - "FLOAT": true, - "FOR": true, - "FUNC": true, - "File": true, - "FileSet": true, - "GEQ": true, - "GO": true, - "GOTO": true, - "GTR": true, - "HighestPrec": true, - "IDENT": true, - "IF": true, - "ILLEGAL": true, - "IMAG": true, - "IMPORT": true, - "INC": true, - "INT": true, - "INTERFACE": true, - "LAND": true, - "LBRACE": true, - "LBRACK": true, - "LEQ": true, - "LOR": true, - "LPAREN": true, - "LSS": true, - "Lookup": true, - "LowestPrec": true, - "MAP": true, - "MUL": true, - "MUL_ASSIGN": true, - "NEQ": true, - "NOT": true, - "NewFileSet": true, - "NoPos": true, - "OR": true, - "OR_ASSIGN": true, - "PACKAGE": true, - "PERIOD": true, - "Pos": true, - "Position": true, - "QUO": true, - "QUO_ASSIGN": true, - "RANGE": true, - "RBRACE": true, - "RBRACK": true, - "REM": true, - "REM_ASSIGN": true, - "RETURN": true, - "RPAREN": true, - "SELECT": true, - "SEMICOLON": true, - "SHL": true, - "SHL_ASSIGN": true, - "SHR": true, - "SHR_ASSIGN": true, - "STRING": true, - "STRUCT": true, - "SUB": true, - "SUB_ASSIGN": true, - "SWITCH": true, - "TYPE": true, - "Token": true, - "UnaryPrec": true, - "VAR": true, - "XOR": true, - "XOR_ASSIGN": true, - }, - "go/types": map[string]bool{ - "Array": true, - "AssertableTo": true, - "AssignableTo": true, - "Basic": true, - "BasicInfo": true, - "BasicKind": true, - "Bool": true, - "Builtin": true, - "Byte": true, - "Chan": true, - "ChanDir": true, - "Checker": true, - "Comparable": true, - "Complex128": true, - "Complex64": true, - "Config": true, - "Const": true, - "ConvertibleTo": true, - "DefPredeclaredTestFuncs": true, - "Default": true, - "Error": true, - "Eval": true, - "ExprString": true, - "FieldVal": true, - "Float32": true, - "Float64": true, - "Func": true, - "Id": true, - "Identical": true, - "IdenticalIgnoreTags": true, - "Implements": true, - "ImportMode": true, - "Importer": true, - "ImporterFrom": true, - "Info": true, - "Initializer": true, - "Int": true, - "Int16": true, - "Int32": true, - "Int64": true, - "Int8": true, - "Interface": true, - "Invalid": true, - "IsBoolean": true, - "IsComplex": true, - "IsConstType": true, - "IsFloat": true, - "IsInteger": true, - "IsInterface": true, - "IsNumeric": true, - "IsOrdered": true, - "IsString": true, - "IsUnsigned": true, - "IsUntyped": true, - "Label": true, - "LookupFieldOrMethod": true, - "Map": true, - "MethodExpr": true, - "MethodSet": true, - "MethodVal": true, - "MissingMethod": true, - "Named": true, - "NewArray": true, - "NewChan": true, - "NewChecker": true, - "NewConst": true, - "NewField": true, - "NewFunc": true, - "NewInterface": true, - "NewInterfaceType": true, - "NewLabel": true, - "NewMap": true, - "NewMethodSet": true, - "NewNamed": true, - "NewPackage": true, - "NewParam": true, - "NewPkgName": true, - "NewPointer": true, - "NewScope": true, - "NewSignature": true, - "NewSlice": true, - "NewStruct": true, - "NewTuple": true, - "NewTypeName": true, - "NewVar": true, - "Nil": true, - "ObjectString": true, - "Package": true, - "PkgName": true, - "Pointer": true, - "Qualifier": true, - "RecvOnly": true, - "RelativeTo": true, - "Rune": true, - "Scope": true, - "Selection": true, - "SelectionKind": true, - "SelectionString": true, - "SendOnly": true, - "SendRecv": true, - "Signature": true, - "Sizes": true, - "SizesFor": true, - "Slice": true, - "StdSizes": true, - "String": true, - "Struct": true, - "Tuple": true, - "Typ": true, - "Type": true, - "TypeAndValue": true, - "TypeName": true, - "TypeString": true, - "Uint": true, - "Uint16": true, - "Uint32": true, - "Uint64": true, - "Uint8": true, - "Uintptr": true, - "Universe": true, - "Unsafe": true, - "UnsafePointer": true, - "UntypedBool": true, - "UntypedComplex": true, - "UntypedFloat": true, - "UntypedInt": true, - "UntypedNil": true, - "UntypedRune": true, - "UntypedString": true, - "Var": true, - "WriteExpr": true, - "WriteSignature": true, - "WriteType": true, - }, - "hash": map[string]bool{ - "Hash": true, - "Hash32": true, - "Hash64": true, - }, - "hash/adler32": map[string]bool{ - "Checksum": true, - "New": true, - "Size": true, - }, - "hash/crc32": map[string]bool{ - "Castagnoli": true, - "Checksum": true, - "ChecksumIEEE": true, - "IEEE": true, - "IEEETable": true, - "Koopman": true, - "MakeTable": true, - "New": true, - "NewIEEE": true, - "Size": true, - "Table": true, - "Update": true, - }, - "hash/crc64": map[string]bool{ - "Checksum": true, - "ECMA": true, - "ISO": true, - "MakeTable": true, - "New": true, - "Size": true, - "Table": true, - "Update": true, - }, - "hash/fnv": map[string]bool{ - "New128": true, - "New128a": true, - "New32": true, - "New32a": true, - "New64": true, - "New64a": true, - }, - "html": map[string]bool{ - "EscapeString": true, - "UnescapeString": true, - }, - "html/template": map[string]bool{ - "CSS": true, - "ErrAmbigContext": true, - "ErrBadHTML": true, - "ErrBranchEnd": true, - "ErrEndContext": true, - "ErrNoSuchTemplate": true, - "ErrOutputContext": true, - "ErrPartialCharset": true, - "ErrPartialEscape": true, - "ErrPredefinedEscaper": true, - "ErrRangeLoopReentry": true, - "ErrSlashAmbig": true, - "Error": true, - "ErrorCode": true, - "FuncMap": true, - "HTML": true, - "HTMLAttr": true, - "HTMLEscape": true, - "HTMLEscapeString": true, - "HTMLEscaper": true, - "IsTrue": true, - "JS": true, - "JSEscape": true, - "JSEscapeString": true, - "JSEscaper": true, - "JSStr": true, - "Must": true, - "New": true, - "OK": true, - "ParseFiles": true, - "ParseGlob": true, - "Srcset": true, - "Template": true, - "URL": true, - "URLQueryEscaper": true, - }, - "image": map[string]bool{ - "Alpha": true, - "Alpha16": true, - "Black": true, - "CMYK": true, - "Config": true, - "Decode": true, - "DecodeConfig": true, - "ErrFormat": true, - "Gray": true, - "Gray16": true, - "Image": true, - "NRGBA": true, - "NRGBA64": true, - "NYCbCrA": true, - "NewAlpha": true, - "NewAlpha16": true, - "NewCMYK": true, - "NewGray": true, - "NewGray16": true, - "NewNRGBA": true, - "NewNRGBA64": true, - "NewNYCbCrA": true, - "NewPaletted": true, - "NewRGBA": true, - "NewRGBA64": true, - "NewUniform": true, - "NewYCbCr": true, - "Opaque": true, - "Paletted": true, - "PalettedImage": true, - "Point": true, - "Pt": true, - "RGBA": true, - "RGBA64": true, - "Rect": true, - "Rectangle": true, - "RegisterFormat": true, - "Transparent": true, - "Uniform": true, - "White": true, - "YCbCr": true, - "YCbCrSubsampleRatio": true, - "YCbCrSubsampleRatio410": true, - "YCbCrSubsampleRatio411": true, - "YCbCrSubsampleRatio420": true, - "YCbCrSubsampleRatio422": true, - "YCbCrSubsampleRatio440": true, - "YCbCrSubsampleRatio444": true, - "ZP": true, - "ZR": true, - }, - "image/color": map[string]bool{ - "Alpha": true, - "Alpha16": true, - "Alpha16Model": true, - "AlphaModel": true, - "Black": true, - "CMYK": true, - "CMYKModel": true, - "CMYKToRGB": true, - "Color": true, - "Gray": true, - "Gray16": true, - "Gray16Model": true, - "GrayModel": true, - "Model": true, - "ModelFunc": true, - "NRGBA": true, - "NRGBA64": true, - "NRGBA64Model": true, - "NRGBAModel": true, - "NYCbCrA": true, - "NYCbCrAModel": true, - "Opaque": true, - "Palette": true, - "RGBA": true, - "RGBA64": true, - "RGBA64Model": true, - "RGBAModel": true, - "RGBToCMYK": true, - "RGBToYCbCr": true, - "Transparent": true, - "White": true, - "YCbCr": true, - "YCbCrModel": true, - "YCbCrToRGB": true, - }, - "image/color/palette": map[string]bool{ - "Plan9": true, - "WebSafe": true, - }, - "image/draw": map[string]bool{ - "Draw": true, - "DrawMask": true, - "Drawer": true, - "FloydSteinberg": true, - "Image": true, - "Op": true, - "Over": true, - "Quantizer": true, - "Src": true, - }, - "image/gif": map[string]bool{ - "Decode": true, - "DecodeAll": true, - "DecodeConfig": true, - "DisposalBackground": true, - "DisposalNone": true, - "DisposalPrevious": true, - "Encode": true, - "EncodeAll": true, - "GIF": true, - "Options": true, - }, - "image/jpeg": map[string]bool{ - "Decode": true, - "DecodeConfig": true, - "DefaultQuality": true, - "Encode": true, - "FormatError": true, - "Options": true, - "Reader": true, - "UnsupportedError": true, - }, - "image/png": map[string]bool{ - "BestCompression": true, - "BestSpeed": true, - "CompressionLevel": true, - "Decode": true, - "DecodeConfig": true, - "DefaultCompression": true, - "Encode": true, - "Encoder": true, - "EncoderBuffer": true, - "EncoderBufferPool": true, - "FormatError": true, - "NoCompression": true, - "UnsupportedError": true, - }, - "index/suffixarray": map[string]bool{ - "Index": true, - "New": true, - }, - "io": map[string]bool{ - "ByteReader": true, - "ByteScanner": true, - "ByteWriter": true, - "Closer": true, - "Copy": true, - "CopyBuffer": true, - "CopyN": true, - "EOF": true, - "ErrClosedPipe": true, - "ErrNoProgress": true, - "ErrShortBuffer": true, - "ErrShortWrite": true, - "ErrUnexpectedEOF": true, - "LimitReader": true, - "LimitedReader": true, - "MultiReader": true, - "MultiWriter": true, - "NewSectionReader": true, - "Pipe": true, - "PipeReader": true, - "PipeWriter": true, - "ReadAtLeast": true, - "ReadCloser": true, - "ReadFull": true, - "ReadSeeker": true, - "ReadWriteCloser": true, - "ReadWriteSeeker": true, - "ReadWriter": true, - "Reader": true, - "ReaderAt": true, - "ReaderFrom": true, - "RuneReader": true, - "RuneScanner": true, - "SectionReader": true, - "SeekCurrent": true, - "SeekEnd": true, - "SeekStart": true, - "Seeker": true, - "StringWriter": true, - "TeeReader": true, - "WriteCloser": true, - "WriteSeeker": true, - "WriteString": true, - "Writer": true, - "WriterAt": true, - "WriterTo": true, - }, - "io/ioutil": map[string]bool{ - "Discard": true, - "NopCloser": true, - "ReadAll": true, - "ReadDir": true, - "ReadFile": true, - "TempDir": true, - "TempFile": true, - "WriteFile": true, - }, - "log": map[string]bool{ - "Fatal": true, - "Fatalf": true, - "Fatalln": true, - "Flags": true, - "LUTC": true, - "Ldate": true, - "Llongfile": true, - "Lmicroseconds": true, - "Logger": true, - "Lshortfile": true, - "LstdFlags": true, - "Ltime": true, - "New": true, - "Output": true, - "Panic": true, - "Panicf": true, - "Panicln": true, - "Prefix": true, - "Print": true, - "Printf": true, - "Println": true, - "SetFlags": true, - "SetOutput": true, - "SetPrefix": true, - }, - "log/syslog": map[string]bool{ - "Dial": true, - "LOG_ALERT": true, - "LOG_AUTH": true, - "LOG_AUTHPRIV": true, - "LOG_CRIT": true, - "LOG_CRON": true, - "LOG_DAEMON": true, - "LOG_DEBUG": true, - "LOG_EMERG": true, - "LOG_ERR": true, - "LOG_FTP": true, - "LOG_INFO": true, - "LOG_KERN": true, - "LOG_LOCAL0": true, - "LOG_LOCAL1": true, - "LOG_LOCAL2": true, - "LOG_LOCAL3": true, - "LOG_LOCAL4": true, - "LOG_LOCAL5": true, - "LOG_LOCAL6": true, - "LOG_LOCAL7": true, - "LOG_LPR": true, - "LOG_MAIL": true, - "LOG_NEWS": true, - "LOG_NOTICE": true, - "LOG_SYSLOG": true, - "LOG_USER": true, - "LOG_UUCP": true, - "LOG_WARNING": true, - "New": true, - "NewLogger": true, - "Priority": true, - "Writer": true, - }, - "math": map[string]bool{ - "Abs": true, - "Acos": true, - "Acosh": true, - "Asin": true, - "Asinh": true, - "Atan": true, - "Atan2": true, - "Atanh": true, - "Cbrt": true, - "Ceil": true, - "Copysign": true, - "Cos": true, - "Cosh": true, - "Dim": true, - "E": true, - "Erf": true, - "Erfc": true, - "Erfcinv": true, - "Erfinv": true, - "Exp": true, - "Exp2": true, - "Expm1": true, - "Float32bits": true, - "Float32frombits": true, - "Float64bits": true, - "Float64frombits": true, - "Floor": true, - "Frexp": true, - "Gamma": true, - "Hypot": true, - "Ilogb": true, - "Inf": true, - "IsInf": true, - "IsNaN": true, - "J0": true, - "J1": true, - "Jn": true, - "Ldexp": true, - "Lgamma": true, - "Ln10": true, - "Ln2": true, - "Log": true, - "Log10": true, - "Log10E": true, - "Log1p": true, - "Log2": true, - "Log2E": true, - "Logb": true, - "Max": true, - "MaxFloat32": true, - "MaxFloat64": true, - "MaxInt16": true, - "MaxInt32": true, - "MaxInt64": true, - "MaxInt8": true, - "MaxUint16": true, - "MaxUint32": true, - "MaxUint64": true, - "MaxUint8": true, - "Min": true, - "MinInt16": true, - "MinInt32": true, - "MinInt64": true, - "MinInt8": true, - "Mod": true, - "Modf": true, - "NaN": true, - "Nextafter": true, - "Nextafter32": true, - "Phi": true, - "Pi": true, - "Pow": true, - "Pow10": true, - "Remainder": true, - "Round": true, - "RoundToEven": true, - "Signbit": true, - "Sin": true, - "Sincos": true, - "Sinh": true, - "SmallestNonzeroFloat32": true, - "SmallestNonzeroFloat64": true, - "Sqrt": true, - "Sqrt2": true, - "SqrtE": true, - "SqrtPhi": true, - "SqrtPi": true, - "Tan": true, - "Tanh": true, - "Trunc": true, - "Y0": true, - "Y1": true, - "Yn": true, - }, - "math/big": map[string]bool{ - "Above": true, - "Accuracy": true, - "AwayFromZero": true, - "Below": true, - "ErrNaN": true, - "Exact": true, - "Float": true, - "Int": true, - "Jacobi": true, - "MaxBase": true, - "MaxExp": true, - "MaxPrec": true, - "MinExp": true, - "NewFloat": true, - "NewInt": true, - "NewRat": true, - "ParseFloat": true, - "Rat": true, - "RoundingMode": true, - "ToNearestAway": true, - "ToNearestEven": true, - "ToNegativeInf": true, - "ToPositiveInf": true, - "ToZero": true, - "Word": true, - }, - "math/bits": map[string]bool{ - "Add": true, - "Add32": true, - "Add64": true, - "Div": true, - "Div32": true, - "Div64": true, - "LeadingZeros": true, - "LeadingZeros16": true, - "LeadingZeros32": true, - "LeadingZeros64": true, - "LeadingZeros8": true, - "Len": true, - "Len16": true, - "Len32": true, - "Len64": true, - "Len8": true, - "Mul": true, - "Mul32": true, - "Mul64": true, - "OnesCount": true, - "OnesCount16": true, - "OnesCount32": true, - "OnesCount64": true, - "OnesCount8": true, - "Reverse": true, - "Reverse16": true, - "Reverse32": true, - "Reverse64": true, - "Reverse8": true, - "ReverseBytes": true, - "ReverseBytes16": true, - "ReverseBytes32": true, - "ReverseBytes64": true, - "RotateLeft": true, - "RotateLeft16": true, - "RotateLeft32": true, - "RotateLeft64": true, - "RotateLeft8": true, - "Sub": true, - "Sub32": true, - "Sub64": true, - "TrailingZeros": true, - "TrailingZeros16": true, - "TrailingZeros32": true, - "TrailingZeros64": true, - "TrailingZeros8": true, - "UintSize": true, - }, - "math/cmplx": map[string]bool{ - "Abs": true, - "Acos": true, - "Acosh": true, - "Asin": true, - "Asinh": true, - "Atan": true, - "Atanh": true, - "Conj": true, - "Cos": true, - "Cosh": true, - "Cot": true, - "Exp": true, - "Inf": true, - "IsInf": true, - "IsNaN": true, - "Log": true, - "Log10": true, - "NaN": true, - "Phase": true, - "Polar": true, - "Pow": true, - "Rect": true, - "Sin": true, - "Sinh": true, - "Sqrt": true, - "Tan": true, - "Tanh": true, - }, - "math/rand": map[string]bool{ - "ExpFloat64": true, - "Float32": true, - "Float64": true, - "Int": true, - "Int31": true, - "Int31n": true, - "Int63": true, - "Int63n": true, - "Intn": true, - "New": true, - "NewSource": true, - "NewZipf": true, - "NormFloat64": true, - "Perm": true, - "Rand": true, - "Read": true, - "Seed": true, - "Shuffle": true, - "Source": true, - "Source64": true, - "Uint32": true, - "Uint64": true, - "Zipf": true, - }, - "mime": map[string]bool{ - "AddExtensionType": true, - "BEncoding": true, - "ErrInvalidMediaParameter": true, - "ExtensionsByType": true, - "FormatMediaType": true, - "ParseMediaType": true, - "QEncoding": true, - "TypeByExtension": true, - "WordDecoder": true, - "WordEncoder": true, - }, - "mime/multipart": map[string]bool{ - "ErrMessageTooLarge": true, - "File": true, - "FileHeader": true, - "Form": true, - "NewReader": true, - "NewWriter": true, - "Part": true, - "Reader": true, - "Writer": true, - }, - "mime/quotedprintable": map[string]bool{ - "NewReader": true, - "NewWriter": true, - "Reader": true, - "Writer": true, - }, - "net": map[string]bool{ - "Addr": true, - "AddrError": true, - "Buffers": true, - "CIDRMask": true, - "Conn": true, - "DNSConfigError": true, - "DNSError": true, - "DefaultResolver": true, - "Dial": true, - "DialIP": true, - "DialTCP": true, - "DialTimeout": true, - "DialUDP": true, - "DialUnix": true, - "Dialer": true, - "ErrWriteToConnected": true, - "Error": true, - "FileConn": true, - "FileListener": true, - "FilePacketConn": true, - "FlagBroadcast": true, - "FlagLoopback": true, - "FlagMulticast": true, - "FlagPointToPoint": true, - "FlagUp": true, - "Flags": true, - "HardwareAddr": true, - "IP": true, - "IPAddr": true, - "IPConn": true, - "IPMask": true, - "IPNet": true, - "IPv4": true, - "IPv4Mask": true, - "IPv4allrouter": true, - "IPv4allsys": true, - "IPv4bcast": true, - "IPv4len": true, - "IPv4zero": true, - "IPv6interfacelocalallnodes": true, - "IPv6len": true, - "IPv6linklocalallnodes": true, - "IPv6linklocalallrouters": true, - "IPv6loopback": true, - "IPv6unspecified": true, - "IPv6zero": true, - "Interface": true, - "InterfaceAddrs": true, - "InterfaceByIndex": true, - "InterfaceByName": true, - "Interfaces": true, - "InvalidAddrError": true, - "JoinHostPort": true, - "Listen": true, - "ListenConfig": true, - "ListenIP": true, - "ListenMulticastUDP": true, - "ListenPacket": true, - "ListenTCP": true, - "ListenUDP": true, - "ListenUnix": true, - "ListenUnixgram": true, - "Listener": true, - "LookupAddr": true, - "LookupCNAME": true, - "LookupHost": true, - "LookupIP": true, - "LookupMX": true, - "LookupNS": true, - "LookupPort": true, - "LookupSRV": true, - "LookupTXT": true, - "MX": true, - "NS": true, - "OpError": true, - "PacketConn": true, - "ParseCIDR": true, - "ParseError": true, - "ParseIP": true, - "ParseMAC": true, - "Pipe": true, - "ResolveIPAddr": true, - "ResolveTCPAddr": true, - "ResolveUDPAddr": true, - "ResolveUnixAddr": true, - "Resolver": true, - "SRV": true, - "SplitHostPort": true, - "TCPAddr": true, - "TCPConn": true, - "TCPListener": true, - "UDPAddr": true, - "UDPConn": true, - "UnixAddr": true, - "UnixConn": true, - "UnixListener": true, - "UnknownNetworkError": true, - }, - "net/http": map[string]bool{ - "CanonicalHeaderKey": true, - "Client": true, - "CloseNotifier": true, - "ConnState": true, - "Cookie": true, - "CookieJar": true, - "DefaultClient": true, - "DefaultMaxHeaderBytes": true, - "DefaultMaxIdleConnsPerHost": true, - "DefaultServeMux": true, - "DefaultTransport": true, - "DetectContentType": true, - "Dir": true, - "ErrAbortHandler": true, - "ErrBodyNotAllowed": true, - "ErrBodyReadAfterClose": true, - "ErrContentLength": true, - "ErrHandlerTimeout": true, - "ErrHeaderTooLong": true, - "ErrHijacked": true, - "ErrLineTooLong": true, - "ErrMissingBoundary": true, - "ErrMissingContentLength": true, - "ErrMissingFile": true, - "ErrNoCookie": true, - "ErrNoLocation": true, - "ErrNotMultipart": true, - "ErrNotSupported": true, - "ErrServerClosed": true, - "ErrShortBody": true, - "ErrSkipAltProtocol": true, - "ErrUnexpectedTrailer": true, - "ErrUseLastResponse": true, - "ErrWriteAfterFlush": true, - "Error": true, - "File": true, - "FileServer": true, - "FileSystem": true, - "Flusher": true, - "Get": true, - "Handle": true, - "HandleFunc": true, - "Handler": true, - "HandlerFunc": true, - "Head": true, - "Header": true, - "Hijacker": true, - "ListenAndServe": true, - "ListenAndServeTLS": true, - "LocalAddrContextKey": true, - "MaxBytesReader": true, - "MethodConnect": true, - "MethodDelete": true, - "MethodGet": true, - "MethodHead": true, - "MethodOptions": true, - "MethodPatch": true, - "MethodPost": true, - "MethodPut": true, - "MethodTrace": true, - "NewFileTransport": true, - "NewRequest": true, - "NewServeMux": true, - "NoBody": true, - "NotFound": true, - "NotFoundHandler": true, - "ParseHTTPVersion": true, - "ParseTime": true, - "Post": true, - "PostForm": true, - "ProtocolError": true, - "ProxyFromEnvironment": true, - "ProxyURL": true, - "PushOptions": true, - "Pusher": true, - "ReadRequest": true, - "ReadResponse": true, - "Redirect": true, - "RedirectHandler": true, - "Request": true, - "Response": true, - "ResponseWriter": true, - "RoundTripper": true, - "SameSite": true, - "SameSiteDefaultMode": true, - "SameSiteLaxMode": true, - "SameSiteStrictMode": true, - "Serve": true, - "ServeContent": true, - "ServeFile": true, - "ServeMux": true, - "ServeTLS": true, - "Server": true, - "ServerContextKey": true, - "SetCookie": true, - "StateActive": true, - "StateClosed": true, - "StateHijacked": true, - "StateIdle": true, - "StateNew": true, - "StatusAccepted": true, - "StatusAlreadyReported": true, - "StatusBadGateway": true, - "StatusBadRequest": true, - "StatusConflict": true, - "StatusContinue": true, - "StatusCreated": true, - "StatusExpectationFailed": true, - "StatusFailedDependency": true, - "StatusForbidden": true, - "StatusFound": true, - "StatusGatewayTimeout": true, - "StatusGone": true, - "StatusHTTPVersionNotSupported": true, - "StatusIMUsed": true, - "StatusInsufficientStorage": true, - "StatusInternalServerError": true, - "StatusLengthRequired": true, - "StatusLocked": true, - "StatusLoopDetected": true, - "StatusMethodNotAllowed": true, - "StatusMisdirectedRequest": true, - "StatusMovedPermanently": true, - "StatusMultiStatus": true, - "StatusMultipleChoices": true, - "StatusNetworkAuthenticationRequired": true, - "StatusNoContent": true, - "StatusNonAuthoritativeInfo": true, - "StatusNotAcceptable": true, - "StatusNotExtended": true, - "StatusNotFound": true, - "StatusNotImplemented": true, - "StatusNotModified": true, - "StatusOK": true, - "StatusPartialContent": true, - "StatusPaymentRequired": true, - "StatusPermanentRedirect": true, - "StatusPreconditionFailed": true, - "StatusPreconditionRequired": true, - "StatusProcessing": true, - "StatusProxyAuthRequired": true, - "StatusRequestEntityTooLarge": true, - "StatusRequestHeaderFieldsTooLarge": true, - "StatusRequestTimeout": true, - "StatusRequestURITooLong": true, - "StatusRequestedRangeNotSatisfiable": true, - "StatusResetContent": true, - "StatusSeeOther": true, - "StatusServiceUnavailable": true, - "StatusSwitchingProtocols": true, - "StatusTeapot": true, - "StatusTemporaryRedirect": true, - "StatusText": true, - "StatusTooEarly": true, - "StatusTooManyRequests": true, - "StatusUnauthorized": true, - "StatusUnavailableForLegalReasons": true, - "StatusUnprocessableEntity": true, - "StatusUnsupportedMediaType": true, - "StatusUpgradeRequired": true, - "StatusUseProxy": true, - "StatusVariantAlsoNegotiates": true, - "StripPrefix": true, - "TimeFormat": true, - "TimeoutHandler": true, - "TrailerPrefix": true, - "Transport": true, - }, - "net/http/cgi": map[string]bool{ - "Handler": true, - "Request": true, - "RequestFromMap": true, - "Serve": true, - }, - "net/http/cookiejar": map[string]bool{ - "Jar": true, - "New": true, - "Options": true, - "PublicSuffixList": true, - }, - "net/http/fcgi": map[string]bool{ - "ErrConnClosed": true, - "ErrRequestAborted": true, - "ProcessEnv": true, - "Serve": true, - }, - "net/http/httptest": map[string]bool{ - "DefaultRemoteAddr": true, - "NewRecorder": true, - "NewRequest": true, - "NewServer": true, - "NewTLSServer": true, - "NewUnstartedServer": true, - "ResponseRecorder": true, - "Server": true, - }, - "net/http/httptrace": map[string]bool{ - "ClientTrace": true, - "ContextClientTrace": true, - "DNSDoneInfo": true, - "DNSStartInfo": true, - "GotConnInfo": true, - "WithClientTrace": true, - "WroteRequestInfo": true, - }, - "net/http/httputil": map[string]bool{ - "BufferPool": true, - "ClientConn": true, - "DumpRequest": true, - "DumpRequestOut": true, - "DumpResponse": true, - "ErrClosed": true, - "ErrLineTooLong": true, - "ErrPersistEOF": true, - "ErrPipeline": true, - "NewChunkedReader": true, - "NewChunkedWriter": true, - "NewClientConn": true, - "NewProxyClientConn": true, - "NewServerConn": true, - "NewSingleHostReverseProxy": true, - "ReverseProxy": true, - "ServerConn": true, - }, - "net/http/pprof": map[string]bool{ - "Cmdline": true, - "Handler": true, - "Index": true, - "Profile": true, - "Symbol": true, - "Trace": true, - }, - "net/mail": map[string]bool{ - "Address": true, - "AddressParser": true, - "ErrHeaderNotPresent": true, - "Header": true, - "Message": true, - "ParseAddress": true, - "ParseAddressList": true, - "ParseDate": true, - "ReadMessage": true, - }, - "net/rpc": map[string]bool{ - "Accept": true, - "Call": true, - "Client": true, - "ClientCodec": true, - "DefaultDebugPath": true, - "DefaultRPCPath": true, - "DefaultServer": true, - "Dial": true, - "DialHTTP": true, - "DialHTTPPath": true, - "ErrShutdown": true, - "HandleHTTP": true, - "NewClient": true, - "NewClientWithCodec": true, - "NewServer": true, - "Register": true, - "RegisterName": true, - "Request": true, - "Response": true, - "ServeCodec": true, - "ServeConn": true, - "ServeRequest": true, - "Server": true, - "ServerCodec": true, - "ServerError": true, - }, - "net/rpc/jsonrpc": map[string]bool{ - "Dial": true, - "NewClient": true, - "NewClientCodec": true, - "NewServerCodec": true, - "ServeConn": true, - }, - "net/smtp": map[string]bool{ - "Auth": true, - "CRAMMD5Auth": true, - "Client": true, - "Dial": true, - "NewClient": true, - "PlainAuth": true, - "SendMail": true, - "ServerInfo": true, - }, - "net/textproto": map[string]bool{ - "CanonicalMIMEHeaderKey": true, - "Conn": true, - "Dial": true, - "Error": true, - "MIMEHeader": true, - "NewConn": true, - "NewReader": true, - "NewWriter": true, - "Pipeline": true, - "ProtocolError": true, - "Reader": true, - "TrimBytes": true, - "TrimString": true, - "Writer": true, - }, - "net/url": map[string]bool{ - "Error": true, - "EscapeError": true, - "InvalidHostError": true, - "Parse": true, - "ParseQuery": true, - "ParseRequestURI": true, - "PathEscape": true, - "PathUnescape": true, - "QueryEscape": true, - "QueryUnescape": true, - "URL": true, - "User": true, - "UserPassword": true, - "Userinfo": true, - "Values": true, - }, - "os": map[string]bool{ - "Args": true, - "Chdir": true, - "Chmod": true, - "Chown": true, - "Chtimes": true, - "Clearenv": true, - "Create": true, - "DevNull": true, - "Environ": true, - "ErrClosed": true, - "ErrExist": true, - "ErrInvalid": true, - "ErrNoDeadline": true, - "ErrNotExist": true, - "ErrPermission": true, - "Executable": true, - "Exit": true, - "Expand": true, - "ExpandEnv": true, - "File": true, - "FileInfo": true, - "FileMode": true, - "FindProcess": true, - "Getegid": true, - "Getenv": true, - "Geteuid": true, - "Getgid": true, - "Getgroups": true, - "Getpagesize": true, - "Getpid": true, - "Getppid": true, - "Getuid": true, - "Getwd": true, - "Hostname": true, - "Interrupt": true, - "IsExist": true, - "IsNotExist": true, - "IsPathSeparator": true, - "IsPermission": true, - "IsTimeout": true, - "Kill": true, - "Lchown": true, - "Link": true, - "LinkError": true, - "LookupEnv": true, - "Lstat": true, - "Mkdir": true, - "MkdirAll": true, - "ModeAppend": true, - "ModeCharDevice": true, - "ModeDevice": true, - "ModeDir": true, - "ModeExclusive": true, - "ModeIrregular": true, - "ModeNamedPipe": true, - "ModePerm": true, - "ModeSetgid": true, - "ModeSetuid": true, - "ModeSocket": true, - "ModeSticky": true, - "ModeSymlink": true, - "ModeTemporary": true, - "ModeType": true, - "NewFile": true, - "NewSyscallError": true, - "O_APPEND": true, - "O_CREATE": true, - "O_EXCL": true, - "O_RDONLY": true, - "O_RDWR": true, - "O_SYNC": true, - "O_TRUNC": true, - "O_WRONLY": true, - "Open": true, - "OpenFile": true, - "PathError": true, - "PathListSeparator": true, - "PathSeparator": true, - "Pipe": true, - "ProcAttr": true, - "Process": true, - "ProcessState": true, - "Readlink": true, - "Remove": true, - "RemoveAll": true, - "Rename": true, - "SEEK_CUR": true, - "SEEK_END": true, - "SEEK_SET": true, - "SameFile": true, - "Setenv": true, - "Signal": true, - "StartProcess": true, - "Stat": true, - "Stderr": true, - "Stdin": true, - "Stdout": true, - "Symlink": true, - "SyscallError": true, - "TempDir": true, - "Truncate": true, - "Unsetenv": true, - "UserCacheDir": true, - "UserHomeDir": true, - }, - "os/exec": map[string]bool{ - "Cmd": true, - "Command": true, - "CommandContext": true, - "ErrNotFound": true, - "Error": true, - "ExitError": true, - "LookPath": true, - }, - "os/signal": map[string]bool{ - "Ignore": true, - "Ignored": true, - "Notify": true, - "Reset": true, - "Stop": true, - }, - "os/user": map[string]bool{ - "Current": true, - "Group": true, - "Lookup": true, - "LookupGroup": true, - "LookupGroupId": true, - "LookupId": true, - "UnknownGroupError": true, - "UnknownGroupIdError": true, - "UnknownUserError": true, - "UnknownUserIdError": true, - "User": true, - }, - "path": map[string]bool{ - "Base": true, - "Clean": true, - "Dir": true, - "ErrBadPattern": true, - "Ext": true, - "IsAbs": true, - "Join": true, - "Match": true, - "Split": true, - }, - "path/filepath": map[string]bool{ - "Abs": true, - "Base": true, - "Clean": true, - "Dir": true, - "ErrBadPattern": true, - "EvalSymlinks": true, - "Ext": true, - "FromSlash": true, - "Glob": true, - "HasPrefix": true, - "IsAbs": true, - "Join": true, - "ListSeparator": true, - "Match": true, - "Rel": true, - "Separator": true, - "SkipDir": true, - "Split": true, - "SplitList": true, - "ToSlash": true, - "VolumeName": true, - "Walk": true, - "WalkFunc": true, - }, - "plugin": map[string]bool{ - "Open": true, - "Plugin": true, - "Symbol": true, - }, - "reflect": map[string]bool{ - "Append": true, - "AppendSlice": true, - "Array": true, - "ArrayOf": true, - "Bool": true, - "BothDir": true, - "Chan": true, - "ChanDir": true, - "ChanOf": true, - "Complex128": true, - "Complex64": true, - "Copy": true, - "DeepEqual": true, - "Float32": true, - "Float64": true, - "Func": true, - "FuncOf": true, - "Indirect": true, - "Int": true, - "Int16": true, - "Int32": true, - "Int64": true, - "Int8": true, - "Interface": true, - "Invalid": true, - "Kind": true, - "MakeChan": true, - "MakeFunc": true, - "MakeMap": true, - "MakeMapWithSize": true, - "MakeSlice": true, - "Map": true, - "MapIter": true, - "MapOf": true, - "Method": true, - "New": true, - "NewAt": true, - "Ptr": true, - "PtrTo": true, - "RecvDir": true, - "Select": true, - "SelectCase": true, - "SelectDefault": true, - "SelectDir": true, - "SelectRecv": true, - "SelectSend": true, - "SendDir": true, - "Slice": true, - "SliceHeader": true, - "SliceOf": true, - "String": true, - "StringHeader": true, - "Struct": true, - "StructField": true, - "StructOf": true, - "StructTag": true, - "Swapper": true, - "TypeOf": true, - "Uint": true, - "Uint16": true, - "Uint32": true, - "Uint64": true, - "Uint8": true, - "Uintptr": true, - "UnsafePointer": true, - "Value": true, - "ValueError": true, - "ValueOf": true, - "Zero": true, - }, - "regexp": map[string]bool{ - "Compile": true, - "CompilePOSIX": true, - "Match": true, - "MatchReader": true, - "MatchString": true, - "MustCompile": true, - "MustCompilePOSIX": true, - "QuoteMeta": true, - "Regexp": true, - }, - "regexp/syntax": map[string]bool{ - "ClassNL": true, - "Compile": true, - "DotNL": true, - "EmptyBeginLine": true, - "EmptyBeginText": true, - "EmptyEndLine": true, - "EmptyEndText": true, - "EmptyNoWordBoundary": true, - "EmptyOp": true, - "EmptyOpContext": true, - "EmptyWordBoundary": true, - "ErrInternalError": true, - "ErrInvalidCharClass": true, - "ErrInvalidCharRange": true, - "ErrInvalidEscape": true, - "ErrInvalidNamedCapture": true, - "ErrInvalidPerlOp": true, - "ErrInvalidRepeatOp": true, - "ErrInvalidRepeatSize": true, - "ErrInvalidUTF8": true, - "ErrMissingBracket": true, - "ErrMissingParen": true, - "ErrMissingRepeatArgument": true, - "ErrTrailingBackslash": true, - "ErrUnexpectedParen": true, - "Error": true, - "ErrorCode": true, - "Flags": true, - "FoldCase": true, - "Inst": true, - "InstAlt": true, - "InstAltMatch": true, - "InstCapture": true, - "InstEmptyWidth": true, - "InstFail": true, - "InstMatch": true, - "InstNop": true, - "InstOp": true, - "InstRune": true, - "InstRune1": true, - "InstRuneAny": true, - "InstRuneAnyNotNL": true, - "IsWordChar": true, - "Literal": true, - "MatchNL": true, - "NonGreedy": true, - "OneLine": true, - "Op": true, - "OpAlternate": true, - "OpAnyChar": true, - "OpAnyCharNotNL": true, - "OpBeginLine": true, - "OpBeginText": true, - "OpCapture": true, - "OpCharClass": true, - "OpConcat": true, - "OpEmptyMatch": true, - "OpEndLine": true, - "OpEndText": true, - "OpLiteral": true, - "OpNoMatch": true, - "OpNoWordBoundary": true, - "OpPlus": true, - "OpQuest": true, - "OpRepeat": true, - "OpStar": true, - "OpWordBoundary": true, - "POSIX": true, - "Parse": true, - "Perl": true, - "PerlX": true, - "Prog": true, - "Regexp": true, - "Simple": true, - "UnicodeGroups": true, - "WasDollar": true, - }, - "runtime": map[string]bool{ - "BlockProfile": true, - "BlockProfileRecord": true, - "Breakpoint": true, - "CPUProfile": true, - "Caller": true, - "Callers": true, - "CallersFrames": true, - "Compiler": true, - "Error": true, - "Frame": true, - "Frames": true, - "Func": true, - "FuncForPC": true, - "GC": true, - "GOARCH": true, - "GOMAXPROCS": true, - "GOOS": true, - "GOROOT": true, - "Goexit": true, - "GoroutineProfile": true, - "Gosched": true, - "KeepAlive": true, - "LockOSThread": true, - "MemProfile": true, - "MemProfileRate": true, - "MemProfileRecord": true, - "MemStats": true, - "MutexProfile": true, - "NumCPU": true, - "NumCgoCall": true, - "NumGoroutine": true, - "ReadMemStats": true, - "ReadTrace": true, - "SetBlockProfileRate": true, - "SetCPUProfileRate": true, - "SetCgoTraceback": true, - "SetFinalizer": true, - "SetMutexProfileFraction": true, - "Stack": true, - "StackRecord": true, - "StartTrace": true, - "StopTrace": true, - "ThreadCreateProfile": true, - "TypeAssertionError": true, - "UnlockOSThread": true, - "Version": true, - }, - "runtime/debug": map[string]bool{ - "BuildInfo": true, - "FreeOSMemory": true, - "GCStats": true, - "Module": true, - "PrintStack": true, - "ReadBuildInfo": true, - "ReadGCStats": true, - "SetGCPercent": true, - "SetMaxStack": true, - "SetMaxThreads": true, - "SetPanicOnFault": true, - "SetTraceback": true, - "Stack": true, - "WriteHeapDump": true, - }, - "runtime/pprof": map[string]bool{ - "Do": true, - "ForLabels": true, - "Label": true, - "LabelSet": true, - "Labels": true, - "Lookup": true, - "NewProfile": true, - "Profile": true, - "Profiles": true, - "SetGoroutineLabels": true, - "StartCPUProfile": true, - "StopCPUProfile": true, - "WithLabels": true, - "WriteHeapProfile": true, - }, - "runtime/trace": map[string]bool{ - "IsEnabled": true, - "Log": true, - "Logf": true, - "NewTask": true, - "Region": true, - "Start": true, - "StartRegion": true, - "Stop": true, - "Task": true, - "WithRegion": true, - }, - "sort": map[string]bool{ - "Float64Slice": true, - "Float64s": true, - "Float64sAreSorted": true, - "IntSlice": true, - "Interface": true, - "Ints": true, - "IntsAreSorted": true, - "IsSorted": true, - "Reverse": true, - "Search": true, - "SearchFloat64s": true, - "SearchInts": true, - "SearchStrings": true, - "Slice": true, - "SliceIsSorted": true, - "SliceStable": true, - "Sort": true, - "Stable": true, - "StringSlice": true, - "Strings": true, - "StringsAreSorted": true, - }, - "strconv": map[string]bool{ - "AppendBool": true, - "AppendFloat": true, - "AppendInt": true, - "AppendQuote": true, - "AppendQuoteRune": true, - "AppendQuoteRuneToASCII": true, - "AppendQuoteRuneToGraphic": true, - "AppendQuoteToASCII": true, - "AppendQuoteToGraphic": true, - "AppendUint": true, - "Atoi": true, - "CanBackquote": true, - "ErrRange": true, - "ErrSyntax": true, - "FormatBool": true, - "FormatFloat": true, - "FormatInt": true, - "FormatUint": true, - "IntSize": true, - "IsGraphic": true, - "IsPrint": true, - "Itoa": true, - "NumError": true, - "ParseBool": true, - "ParseFloat": true, - "ParseInt": true, - "ParseUint": true, - "Quote": true, - "QuoteRune": true, - "QuoteRuneToASCII": true, - "QuoteRuneToGraphic": true, - "QuoteToASCII": true, - "QuoteToGraphic": true, - "Unquote": true, - "UnquoteChar": true, - }, - "strings": map[string]bool{ - "Builder": true, - "Compare": true, - "Contains": true, - "ContainsAny": true, - "ContainsRune": true, - "Count": true, - "EqualFold": true, - "Fields": true, - "FieldsFunc": true, - "HasPrefix": true, - "HasSuffix": true, - "Index": true, - "IndexAny": true, - "IndexByte": true, - "IndexFunc": true, - "IndexRune": true, - "Join": true, - "LastIndex": true, - "LastIndexAny": true, - "LastIndexByte": true, - "LastIndexFunc": true, - "Map": true, - "NewReader": true, - "NewReplacer": true, - "Reader": true, - "Repeat": true, - "Replace": true, - "ReplaceAll": true, - "Replacer": true, - "Split": true, - "SplitAfter": true, - "SplitAfterN": true, - "SplitN": true, - "Title": true, - "ToLower": true, - "ToLowerSpecial": true, - "ToTitle": true, - "ToTitleSpecial": true, - "ToUpper": true, - "ToUpperSpecial": true, - "Trim": true, - "TrimFunc": true, - "TrimLeft": true, - "TrimLeftFunc": true, - "TrimPrefix": true, - "TrimRight": true, - "TrimRightFunc": true, - "TrimSpace": true, - "TrimSuffix": true, - }, - "sync": map[string]bool{ - "Cond": true, - "Locker": true, - "Map": true, - "Mutex": true, - "NewCond": true, - "Once": true, - "Pool": true, - "RWMutex": true, - "WaitGroup": true, - }, - "sync/atomic": map[string]bool{ - "AddInt32": true, - "AddInt64": true, - "AddUint32": true, - "AddUint64": true, - "AddUintptr": true, - "CompareAndSwapInt32": true, - "CompareAndSwapInt64": true, - "CompareAndSwapPointer": true, - "CompareAndSwapUint32": true, - "CompareAndSwapUint64": true, - "CompareAndSwapUintptr": true, - "LoadInt32": true, - "LoadInt64": true, - "LoadPointer": true, - "LoadUint32": true, - "LoadUint64": true, - "LoadUintptr": true, - "StoreInt32": true, - "StoreInt64": true, - "StorePointer": true, - "StoreUint32": true, - "StoreUint64": true, - "StoreUintptr": true, - "SwapInt32": true, - "SwapInt64": true, - "SwapPointer": true, - "SwapUint32": true, - "SwapUint64": true, - "SwapUintptr": true, - "Value": true, - }, - "syscall": map[string]bool{ - "AF_ALG": true, - "AF_APPLETALK": true, - "AF_ARP": true, - "AF_ASH": true, - "AF_ATM": true, - "AF_ATMPVC": true, - "AF_ATMSVC": true, - "AF_AX25": true, - "AF_BLUETOOTH": true, - "AF_BRIDGE": true, - "AF_CAIF": true, - "AF_CAN": true, - "AF_CCITT": true, - "AF_CHAOS": true, - "AF_CNT": true, - "AF_COIP": true, - "AF_DATAKIT": true, - "AF_DECnet": true, - "AF_DLI": true, - "AF_E164": true, - "AF_ECMA": true, - "AF_ECONET": true, - "AF_ENCAP": true, - "AF_FILE": true, - "AF_HYLINK": true, - "AF_IEEE80211": true, - "AF_IEEE802154": true, - "AF_IMPLINK": true, - "AF_INET": true, - "AF_INET6": true, - "AF_INET6_SDP": true, - "AF_INET_SDP": true, - "AF_IPX": true, - "AF_IRDA": true, - "AF_ISDN": true, - "AF_ISO": true, - "AF_IUCV": true, - "AF_KEY": true, - "AF_LAT": true, - "AF_LINK": true, - "AF_LLC": true, - "AF_LOCAL": true, - "AF_MAX": true, - "AF_MPLS": true, - "AF_NATM": true, - "AF_NDRV": true, - "AF_NETBEUI": true, - "AF_NETBIOS": true, - "AF_NETGRAPH": true, - "AF_NETLINK": true, - "AF_NETROM": true, - "AF_NS": true, - "AF_OROUTE": true, - "AF_OSI": true, - "AF_PACKET": true, - "AF_PHONET": true, - "AF_PPP": true, - "AF_PPPOX": true, - "AF_PUP": true, - "AF_RDS": true, - "AF_RESERVED_36": true, - "AF_ROSE": true, - "AF_ROUTE": true, - "AF_RXRPC": true, - "AF_SCLUSTER": true, - "AF_SECURITY": true, - "AF_SIP": true, - "AF_SLOW": true, - "AF_SNA": true, - "AF_SYSTEM": true, - "AF_TIPC": true, - "AF_UNIX": true, - "AF_UNSPEC": true, - "AF_VENDOR00": true, - "AF_VENDOR01": true, - "AF_VENDOR02": true, - "AF_VENDOR03": true, - "AF_VENDOR04": true, - "AF_VENDOR05": true, - "AF_VENDOR06": true, - "AF_VENDOR07": true, - "AF_VENDOR08": true, - "AF_VENDOR09": true, - "AF_VENDOR10": true, - "AF_VENDOR11": true, - "AF_VENDOR12": true, - "AF_VENDOR13": true, - "AF_VENDOR14": true, - "AF_VENDOR15": true, - "AF_VENDOR16": true, - "AF_VENDOR17": true, - "AF_VENDOR18": true, - "AF_VENDOR19": true, - "AF_VENDOR20": true, - "AF_VENDOR21": true, - "AF_VENDOR22": true, - "AF_VENDOR23": true, - "AF_VENDOR24": true, - "AF_VENDOR25": true, - "AF_VENDOR26": true, - "AF_VENDOR27": true, - "AF_VENDOR28": true, - "AF_VENDOR29": true, - "AF_VENDOR30": true, - "AF_VENDOR31": true, - "AF_VENDOR32": true, - "AF_VENDOR33": true, - "AF_VENDOR34": true, - "AF_VENDOR35": true, - "AF_VENDOR36": true, - "AF_VENDOR37": true, - "AF_VENDOR38": true, - "AF_VENDOR39": true, - "AF_VENDOR40": true, - "AF_VENDOR41": true, - "AF_VENDOR42": true, - "AF_VENDOR43": true, - "AF_VENDOR44": true, - "AF_VENDOR45": true, - "AF_VENDOR46": true, - "AF_VENDOR47": true, - "AF_WANPIPE": true, - "AF_X25": true, - "AI_CANONNAME": true, - "AI_NUMERICHOST": true, - "AI_PASSIVE": true, - "APPLICATION_ERROR": true, - "ARPHRD_ADAPT": true, - "ARPHRD_APPLETLK": true, - "ARPHRD_ARCNET": true, - "ARPHRD_ASH": true, - "ARPHRD_ATM": true, - "ARPHRD_AX25": true, - "ARPHRD_BIF": true, - "ARPHRD_CHAOS": true, - "ARPHRD_CISCO": true, - "ARPHRD_CSLIP": true, - "ARPHRD_CSLIP6": true, - "ARPHRD_DDCMP": true, - "ARPHRD_DLCI": true, - "ARPHRD_ECONET": true, - "ARPHRD_EETHER": true, - "ARPHRD_ETHER": true, - "ARPHRD_EUI64": true, - "ARPHRD_FCAL": true, - "ARPHRD_FCFABRIC": true, - "ARPHRD_FCPL": true, - "ARPHRD_FCPP": true, - "ARPHRD_FDDI": true, - "ARPHRD_FRAD": true, - "ARPHRD_FRELAY": true, - "ARPHRD_HDLC": true, - "ARPHRD_HIPPI": true, - "ARPHRD_HWX25": true, - "ARPHRD_IEEE1394": true, - "ARPHRD_IEEE802": true, - "ARPHRD_IEEE80211": true, - "ARPHRD_IEEE80211_PRISM": true, - "ARPHRD_IEEE80211_RADIOTAP": true, - "ARPHRD_IEEE802154": true, - "ARPHRD_IEEE802154_PHY": true, - "ARPHRD_IEEE802_TR": true, - "ARPHRD_INFINIBAND": true, - "ARPHRD_IPDDP": true, - "ARPHRD_IPGRE": true, - "ARPHRD_IRDA": true, - "ARPHRD_LAPB": true, - "ARPHRD_LOCALTLK": true, - "ARPHRD_LOOPBACK": true, - "ARPHRD_METRICOM": true, - "ARPHRD_NETROM": true, - "ARPHRD_NONE": true, - "ARPHRD_PIMREG": true, - "ARPHRD_PPP": true, - "ARPHRD_PRONET": true, - "ARPHRD_RAWHDLC": true, - "ARPHRD_ROSE": true, - "ARPHRD_RSRVD": true, - "ARPHRD_SIT": true, - "ARPHRD_SKIP": true, - "ARPHRD_SLIP": true, - "ARPHRD_SLIP6": true, - "ARPHRD_STRIP": true, - "ARPHRD_TUNNEL": true, - "ARPHRD_TUNNEL6": true, - "ARPHRD_VOID": true, - "ARPHRD_X25": true, - "AUTHTYPE_CLIENT": true, - "AUTHTYPE_SERVER": true, - "Accept": true, - "Accept4": true, - "AcceptEx": true, - "Access": true, - "Acct": true, - "AddrinfoW": true, - "Adjtime": true, - "Adjtimex": true, - "AttachLsf": true, - "B0": true, - "B1000000": true, - "B110": true, - "B115200": true, - "B1152000": true, - "B1200": true, - "B134": true, - "B14400": true, - "B150": true, - "B1500000": true, - "B1800": true, - "B19200": true, - "B200": true, - "B2000000": true, - "B230400": true, - "B2400": true, - "B2500000": true, - "B28800": true, - "B300": true, - "B3000000": true, - "B3500000": true, - "B38400": true, - "B4000000": true, - "B460800": true, - "B4800": true, - "B50": true, - "B500000": true, - "B57600": true, - "B576000": true, - "B600": true, - "B7200": true, - "B75": true, - "B76800": true, - "B921600": true, - "B9600": true, - "BASE_PROTOCOL": true, - "BIOCFEEDBACK": true, - "BIOCFLUSH": true, - "BIOCGBLEN": true, - "BIOCGDIRECTION": true, - "BIOCGDIRFILT": true, - "BIOCGDLT": true, - "BIOCGDLTLIST": true, - "BIOCGETBUFMODE": true, - "BIOCGETIF": true, - "BIOCGETZMAX": true, - "BIOCGFEEDBACK": true, - "BIOCGFILDROP": true, - "BIOCGHDRCMPLT": true, - "BIOCGRSIG": true, - "BIOCGRTIMEOUT": true, - "BIOCGSEESENT": true, - "BIOCGSTATS": true, - "BIOCGSTATSOLD": true, - "BIOCGTSTAMP": true, - "BIOCIMMEDIATE": true, - "BIOCLOCK": true, - "BIOCPROMISC": true, - "BIOCROTZBUF": true, - "BIOCSBLEN": true, - "BIOCSDIRECTION": true, - "BIOCSDIRFILT": true, - "BIOCSDLT": true, - "BIOCSETBUFMODE": true, - "BIOCSETF": true, - "BIOCSETFNR": true, - "BIOCSETIF": true, - "BIOCSETWF": true, - "BIOCSETZBUF": true, - "BIOCSFEEDBACK": true, - "BIOCSFILDROP": true, - "BIOCSHDRCMPLT": true, - "BIOCSRSIG": true, - "BIOCSRTIMEOUT": true, - "BIOCSSEESENT": true, - "BIOCSTCPF": true, - "BIOCSTSTAMP": true, - "BIOCSUDPF": true, - "BIOCVERSION": true, - "BPF_A": true, - "BPF_ABS": true, - "BPF_ADD": true, - "BPF_ALIGNMENT": true, - "BPF_ALIGNMENT32": true, - "BPF_ALU": true, - "BPF_AND": true, - "BPF_B": true, - "BPF_BUFMODE_BUFFER": true, - "BPF_BUFMODE_ZBUF": true, - "BPF_DFLTBUFSIZE": true, - "BPF_DIRECTION_IN": true, - "BPF_DIRECTION_OUT": true, - "BPF_DIV": true, - "BPF_H": true, - "BPF_IMM": true, - "BPF_IND": true, - "BPF_JA": true, - "BPF_JEQ": true, - "BPF_JGE": true, - "BPF_JGT": true, - "BPF_JMP": true, - "BPF_JSET": true, - "BPF_K": true, - "BPF_LD": true, - "BPF_LDX": true, - "BPF_LEN": true, - "BPF_LSH": true, - "BPF_MAJOR_VERSION": true, - "BPF_MAXBUFSIZE": true, - "BPF_MAXINSNS": true, - "BPF_MEM": true, - "BPF_MEMWORDS": true, - "BPF_MINBUFSIZE": true, - "BPF_MINOR_VERSION": true, - "BPF_MISC": true, - "BPF_MSH": true, - "BPF_MUL": true, - "BPF_NEG": true, - "BPF_OR": true, - "BPF_RELEASE": true, - "BPF_RET": true, - "BPF_RSH": true, - "BPF_ST": true, - "BPF_STX": true, - "BPF_SUB": true, - "BPF_TAX": true, - "BPF_TXA": true, - "BPF_T_BINTIME": true, - "BPF_T_BINTIME_FAST": true, - "BPF_T_BINTIME_MONOTONIC": true, - "BPF_T_BINTIME_MONOTONIC_FAST": true, - "BPF_T_FAST": true, - "BPF_T_FLAG_MASK": true, - "BPF_T_FORMAT_MASK": true, - "BPF_T_MICROTIME": true, - "BPF_T_MICROTIME_FAST": true, - "BPF_T_MICROTIME_MONOTONIC": true, - "BPF_T_MICROTIME_MONOTONIC_FAST": true, - "BPF_T_MONOTONIC": true, - "BPF_T_MONOTONIC_FAST": true, - "BPF_T_NANOTIME": true, - "BPF_T_NANOTIME_FAST": true, - "BPF_T_NANOTIME_MONOTONIC": true, - "BPF_T_NANOTIME_MONOTONIC_FAST": true, - "BPF_T_NONE": true, - "BPF_T_NORMAL": true, - "BPF_W": true, - "BPF_X": true, - "BRKINT": true, - "Bind": true, - "BindToDevice": true, - "BpfBuflen": true, - "BpfDatalink": true, - "BpfHdr": true, - "BpfHeadercmpl": true, - "BpfInsn": true, - "BpfInterface": true, - "BpfJump": true, - "BpfProgram": true, - "BpfStat": true, - "BpfStats": true, - "BpfStmt": true, - "BpfTimeout": true, - "BpfTimeval": true, - "BpfVersion": true, - "BpfZbuf": true, - "BpfZbufHeader": true, - "ByHandleFileInformation": true, - "BytePtrFromString": true, - "ByteSliceFromString": true, - "CCR0_FLUSH": true, - "CERT_CHAIN_POLICY_AUTHENTICODE": true, - "CERT_CHAIN_POLICY_AUTHENTICODE_TS": true, - "CERT_CHAIN_POLICY_BASE": true, - "CERT_CHAIN_POLICY_BASIC_CONSTRAINTS": true, - "CERT_CHAIN_POLICY_EV": true, - "CERT_CHAIN_POLICY_MICROSOFT_ROOT": true, - "CERT_CHAIN_POLICY_NT_AUTH": true, - "CERT_CHAIN_POLICY_SSL": true, - "CERT_E_CN_NO_MATCH": true, - "CERT_E_EXPIRED": true, - "CERT_E_PURPOSE": true, - "CERT_E_ROLE": true, - "CERT_E_UNTRUSTEDROOT": true, - "CERT_STORE_ADD_ALWAYS": true, - "CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG": true, - "CERT_STORE_PROV_MEMORY": true, - "CERT_TRUST_HAS_EXCLUDED_NAME_CONSTRAINT": true, - "CERT_TRUST_HAS_NOT_DEFINED_NAME_CONSTRAINT": true, - "CERT_TRUST_HAS_NOT_PERMITTED_NAME_CONSTRAINT": true, - "CERT_TRUST_HAS_NOT_SUPPORTED_CRITICAL_EXT": true, - "CERT_TRUST_HAS_NOT_SUPPORTED_NAME_CONSTRAINT": true, - "CERT_TRUST_INVALID_BASIC_CONSTRAINTS": true, - "CERT_TRUST_INVALID_EXTENSION": true, - "CERT_TRUST_INVALID_NAME_CONSTRAINTS": true, - "CERT_TRUST_INVALID_POLICY_CONSTRAINTS": true, - "CERT_TRUST_IS_CYCLIC": true, - "CERT_TRUST_IS_EXPLICIT_DISTRUST": true, - "CERT_TRUST_IS_NOT_SIGNATURE_VALID": true, - "CERT_TRUST_IS_NOT_TIME_VALID": true, - "CERT_TRUST_IS_NOT_VALID_FOR_USAGE": true, - "CERT_TRUST_IS_OFFLINE_REVOCATION": true, - "CERT_TRUST_IS_REVOKED": true, - "CERT_TRUST_IS_UNTRUSTED_ROOT": true, - "CERT_TRUST_NO_ERROR": true, - "CERT_TRUST_NO_ISSUANCE_CHAIN_POLICY": true, - "CERT_TRUST_REVOCATION_STATUS_UNKNOWN": true, - "CFLUSH": true, - "CLOCAL": true, - "CLONE_CHILD_CLEARTID": true, - "CLONE_CHILD_SETTID": true, - "CLONE_CSIGNAL": true, - "CLONE_DETACHED": true, - "CLONE_FILES": true, - "CLONE_FS": true, - "CLONE_IO": true, - "CLONE_NEWIPC": true, - "CLONE_NEWNET": true, - "CLONE_NEWNS": true, - "CLONE_NEWPID": true, - "CLONE_NEWUSER": true, - "CLONE_NEWUTS": true, - "CLONE_PARENT": true, - "CLONE_PARENT_SETTID": true, - "CLONE_PID": true, - "CLONE_PTRACE": true, - "CLONE_SETTLS": true, - "CLONE_SIGHAND": true, - "CLONE_SYSVSEM": true, - "CLONE_THREAD": true, - "CLONE_UNTRACED": true, - "CLONE_VFORK": true, - "CLONE_VM": true, - "CPUID_CFLUSH": true, - "CREAD": true, - "CREATE_ALWAYS": true, - "CREATE_NEW": true, - "CREATE_NEW_PROCESS_GROUP": true, - "CREATE_UNICODE_ENVIRONMENT": true, - "CRYPT_DEFAULT_CONTAINER_OPTIONAL": true, - "CRYPT_DELETEKEYSET": true, - "CRYPT_MACHINE_KEYSET": true, - "CRYPT_NEWKEYSET": true, - "CRYPT_SILENT": true, - "CRYPT_VERIFYCONTEXT": true, - "CS5": true, - "CS6": true, - "CS7": true, - "CS8": true, - "CSIZE": true, - "CSTART": true, - "CSTATUS": true, - "CSTOP": true, - "CSTOPB": true, - "CSUSP": true, - "CTL_MAXNAME": true, - "CTL_NET": true, - "CTL_QUERY": true, - "CTRL_BREAK_EVENT": true, - "CTRL_C_EVENT": true, - "CancelIo": true, - "CancelIoEx": true, - "CertAddCertificateContextToStore": true, - "CertChainContext": true, - "CertChainElement": true, - "CertChainPara": true, - "CertChainPolicyPara": true, - "CertChainPolicyStatus": true, - "CertCloseStore": true, - "CertContext": true, - "CertCreateCertificateContext": true, - "CertEnhKeyUsage": true, - "CertEnumCertificatesInStore": true, - "CertFreeCertificateChain": true, - "CertFreeCertificateContext": true, - "CertGetCertificateChain": true, - "CertInfo": true, - "CertOpenStore": true, - "CertOpenSystemStore": true, - "CertRevocationCrlInfo": true, - "CertRevocationInfo": true, - "CertSimpleChain": true, - "CertTrustListInfo": true, - "CertTrustStatus": true, - "CertUsageMatch": true, - "CertVerifyCertificateChainPolicy": true, - "Chdir": true, - "CheckBpfVersion": true, - "Chflags": true, - "Chmod": true, - "Chown": true, - "Chroot": true, - "Clearenv": true, - "Close": true, - "CloseHandle": true, - "CloseOnExec": true, - "Closesocket": true, - "CmsgLen": true, - "CmsgSpace": true, - "Cmsghdr": true, - "CommandLineToArgv": true, - "ComputerName": true, - "Conn": true, - "Connect": true, - "ConnectEx": true, - "ConvertSidToStringSid": true, - "ConvertStringSidToSid": true, - "CopySid": true, - "Creat": true, - "CreateDirectory": true, - "CreateFile": true, - "CreateFileMapping": true, - "CreateHardLink": true, - "CreateIoCompletionPort": true, - "CreatePipe": true, - "CreateProcess": true, - "CreateProcessAsUser": true, - "CreateSymbolicLink": true, - "CreateToolhelp32Snapshot": true, - "Credential": true, - "CryptAcquireContext": true, - "CryptGenRandom": true, - "CryptReleaseContext": true, - "DIOCBSFLUSH": true, - "DIOCOSFPFLUSH": true, - "DLL": true, - "DLLError": true, - "DLT_A429": true, - "DLT_A653_ICM": true, - "DLT_AIRONET_HEADER": true, - "DLT_AOS": true, - "DLT_APPLE_IP_OVER_IEEE1394": true, - "DLT_ARCNET": true, - "DLT_ARCNET_LINUX": true, - "DLT_ATM_CLIP": true, - "DLT_ATM_RFC1483": true, - "DLT_AURORA": true, - "DLT_AX25": true, - "DLT_AX25_KISS": true, - "DLT_BACNET_MS_TP": true, - "DLT_BLUETOOTH_HCI_H4": true, - "DLT_BLUETOOTH_HCI_H4_WITH_PHDR": true, - "DLT_CAN20B": true, - "DLT_CAN_SOCKETCAN": true, - "DLT_CHAOS": true, - "DLT_CHDLC": true, - "DLT_CISCO_IOS": true, - "DLT_C_HDLC": true, - "DLT_C_HDLC_WITH_DIR": true, - "DLT_DBUS": true, - "DLT_DECT": true, - "DLT_DOCSIS": true, - "DLT_DVB_CI": true, - "DLT_ECONET": true, - "DLT_EN10MB": true, - "DLT_EN3MB": true, - "DLT_ENC": true, - "DLT_ERF": true, - "DLT_ERF_ETH": true, - "DLT_ERF_POS": true, - "DLT_FC_2": true, - "DLT_FC_2_WITH_FRAME_DELIMS": true, - "DLT_FDDI": true, - "DLT_FLEXRAY": true, - "DLT_FRELAY": true, - "DLT_FRELAY_WITH_DIR": true, - "DLT_GCOM_SERIAL": true, - "DLT_GCOM_T1E1": true, - "DLT_GPF_F": true, - "DLT_GPF_T": true, - "DLT_GPRS_LLC": true, - "DLT_GSMTAP_ABIS": true, - "DLT_GSMTAP_UM": true, - "DLT_HDLC": true, - "DLT_HHDLC": true, - "DLT_HIPPI": true, - "DLT_IBM_SN": true, - "DLT_IBM_SP": true, - "DLT_IEEE802": true, - "DLT_IEEE802_11": true, - "DLT_IEEE802_11_RADIO": true, - "DLT_IEEE802_11_RADIO_AVS": true, - "DLT_IEEE802_15_4": true, - "DLT_IEEE802_15_4_LINUX": true, - "DLT_IEEE802_15_4_NOFCS": true, - "DLT_IEEE802_15_4_NONASK_PHY": true, - "DLT_IEEE802_16_MAC_CPS": true, - "DLT_IEEE802_16_MAC_CPS_RADIO": true, - "DLT_IPFILTER": true, - "DLT_IPMB": true, - "DLT_IPMB_LINUX": true, - "DLT_IPNET": true, - "DLT_IPOIB": true, - "DLT_IPV4": true, - "DLT_IPV6": true, - "DLT_IP_OVER_FC": true, - "DLT_JUNIPER_ATM1": true, - "DLT_JUNIPER_ATM2": true, - "DLT_JUNIPER_ATM_CEMIC": true, - "DLT_JUNIPER_CHDLC": true, - "DLT_JUNIPER_ES": true, - "DLT_JUNIPER_ETHER": true, - "DLT_JUNIPER_FIBRECHANNEL": true, - "DLT_JUNIPER_FRELAY": true, - "DLT_JUNIPER_GGSN": true, - "DLT_JUNIPER_ISM": true, - "DLT_JUNIPER_MFR": true, - "DLT_JUNIPER_MLFR": true, - "DLT_JUNIPER_MLPPP": true, - "DLT_JUNIPER_MONITOR": true, - "DLT_JUNIPER_PIC_PEER": true, - "DLT_JUNIPER_PPP": true, - "DLT_JUNIPER_PPPOE": true, - "DLT_JUNIPER_PPPOE_ATM": true, - "DLT_JUNIPER_SERVICES": true, - "DLT_JUNIPER_SRX_E2E": true, - "DLT_JUNIPER_ST": true, - "DLT_JUNIPER_VP": true, - "DLT_JUNIPER_VS": true, - "DLT_LAPB_WITH_DIR": true, - "DLT_LAPD": true, - "DLT_LIN": true, - "DLT_LINUX_EVDEV": true, - "DLT_LINUX_IRDA": true, - "DLT_LINUX_LAPD": true, - "DLT_LINUX_PPP_WITHDIRECTION": true, - "DLT_LINUX_SLL": true, - "DLT_LOOP": true, - "DLT_LTALK": true, - "DLT_MATCHING_MAX": true, - "DLT_MATCHING_MIN": true, - "DLT_MFR": true, - "DLT_MOST": true, - "DLT_MPEG_2_TS": true, - "DLT_MPLS": true, - "DLT_MTP2": true, - "DLT_MTP2_WITH_PHDR": true, - "DLT_MTP3": true, - "DLT_MUX27010": true, - "DLT_NETANALYZER": true, - "DLT_NETANALYZER_TRANSPARENT": true, - "DLT_NFC_LLCP": true, - "DLT_NFLOG": true, - "DLT_NG40": true, - "DLT_NULL": true, - "DLT_PCI_EXP": true, - "DLT_PFLOG": true, - "DLT_PFSYNC": true, - "DLT_PPI": true, - "DLT_PPP": true, - "DLT_PPP_BSDOS": true, - "DLT_PPP_ETHER": true, - "DLT_PPP_PPPD": true, - "DLT_PPP_SERIAL": true, - "DLT_PPP_WITH_DIR": true, - "DLT_PPP_WITH_DIRECTION": true, - "DLT_PRISM_HEADER": true, - "DLT_PRONET": true, - "DLT_RAIF1": true, - "DLT_RAW": true, - "DLT_RAWAF_MASK": true, - "DLT_RIO": true, - "DLT_SCCP": true, - "DLT_SITA": true, - "DLT_SLIP": true, - "DLT_SLIP_BSDOS": true, - "DLT_STANAG_5066_D_PDU": true, - "DLT_SUNATM": true, - "DLT_SYMANTEC_FIREWALL": true, - "DLT_TZSP": true, - "DLT_USB": true, - "DLT_USB_LINUX": true, - "DLT_USB_LINUX_MMAPPED": true, - "DLT_USER0": true, - "DLT_USER1": true, - "DLT_USER10": true, - "DLT_USER11": true, - "DLT_USER12": true, - "DLT_USER13": true, - "DLT_USER14": true, - "DLT_USER15": true, - "DLT_USER2": true, - "DLT_USER3": true, - "DLT_USER4": true, - "DLT_USER5": true, - "DLT_USER6": true, - "DLT_USER7": true, - "DLT_USER8": true, - "DLT_USER9": true, - "DLT_WIHART": true, - "DLT_X2E_SERIAL": true, - "DLT_X2E_XORAYA": true, - "DNSMXData": true, - "DNSPTRData": true, - "DNSRecord": true, - "DNSSRVData": true, - "DNSTXTData": true, - "DNS_INFO_NO_RECORDS": true, - "DNS_TYPE_A": true, - "DNS_TYPE_A6": true, - "DNS_TYPE_AAAA": true, - "DNS_TYPE_ADDRS": true, - "DNS_TYPE_AFSDB": true, - "DNS_TYPE_ALL": true, - "DNS_TYPE_ANY": true, - "DNS_TYPE_ATMA": true, - "DNS_TYPE_AXFR": true, - "DNS_TYPE_CERT": true, - "DNS_TYPE_CNAME": true, - "DNS_TYPE_DHCID": true, - "DNS_TYPE_DNAME": true, - "DNS_TYPE_DNSKEY": true, - "DNS_TYPE_DS": true, - "DNS_TYPE_EID": true, - "DNS_TYPE_GID": true, - "DNS_TYPE_GPOS": true, - "DNS_TYPE_HINFO": true, - "DNS_TYPE_ISDN": true, - "DNS_TYPE_IXFR": true, - "DNS_TYPE_KEY": true, - "DNS_TYPE_KX": true, - "DNS_TYPE_LOC": true, - "DNS_TYPE_MAILA": true, - "DNS_TYPE_MAILB": true, - "DNS_TYPE_MB": true, - "DNS_TYPE_MD": true, - "DNS_TYPE_MF": true, - "DNS_TYPE_MG": true, - "DNS_TYPE_MINFO": true, - "DNS_TYPE_MR": true, - "DNS_TYPE_MX": true, - "DNS_TYPE_NAPTR": true, - "DNS_TYPE_NBSTAT": true, - "DNS_TYPE_NIMLOC": true, - "DNS_TYPE_NS": true, - "DNS_TYPE_NSAP": true, - "DNS_TYPE_NSAPPTR": true, - "DNS_TYPE_NSEC": true, - "DNS_TYPE_NULL": true, - "DNS_TYPE_NXT": true, - "DNS_TYPE_OPT": true, - "DNS_TYPE_PTR": true, - "DNS_TYPE_PX": true, - "DNS_TYPE_RP": true, - "DNS_TYPE_RRSIG": true, - "DNS_TYPE_RT": true, - "DNS_TYPE_SIG": true, - "DNS_TYPE_SINK": true, - "DNS_TYPE_SOA": true, - "DNS_TYPE_SRV": true, - "DNS_TYPE_TEXT": true, - "DNS_TYPE_TKEY": true, - "DNS_TYPE_TSIG": true, - "DNS_TYPE_UID": true, - "DNS_TYPE_UINFO": true, - "DNS_TYPE_UNSPEC": true, - "DNS_TYPE_WINS": true, - "DNS_TYPE_WINSR": true, - "DNS_TYPE_WKS": true, - "DNS_TYPE_X25": true, - "DT_BLK": true, - "DT_CHR": true, - "DT_DIR": true, - "DT_FIFO": true, - "DT_LNK": true, - "DT_REG": true, - "DT_SOCK": true, - "DT_UNKNOWN": true, - "DT_WHT": true, - "DUPLICATE_CLOSE_SOURCE": true, - "DUPLICATE_SAME_ACCESS": true, - "DeleteFile": true, - "DetachLsf": true, - "DeviceIoControl": true, - "Dirent": true, - "DnsNameCompare": true, - "DnsQuery": true, - "DnsRecordListFree": true, - "DnsSectionAdditional": true, - "DnsSectionAnswer": true, - "DnsSectionAuthority": true, - "DnsSectionQuestion": true, - "Dup": true, - "Dup2": true, - "Dup3": true, - "DuplicateHandle": true, - "E2BIG": true, - "EACCES": true, - "EADDRINUSE": true, - "EADDRNOTAVAIL": true, - "EADV": true, - "EAFNOSUPPORT": true, - "EAGAIN": true, - "EALREADY": true, - "EAUTH": true, - "EBADARCH": true, - "EBADE": true, - "EBADEXEC": true, - "EBADF": true, - "EBADFD": true, - "EBADMACHO": true, - "EBADMSG": true, - "EBADR": true, - "EBADRPC": true, - "EBADRQC": true, - "EBADSLT": true, - "EBFONT": true, - "EBUSY": true, - "ECANCELED": true, - "ECAPMODE": true, - "ECHILD": true, - "ECHO": true, - "ECHOCTL": true, - "ECHOE": true, - "ECHOK": true, - "ECHOKE": true, - "ECHONL": true, - "ECHOPRT": true, - "ECHRNG": true, - "ECOMM": true, - "ECONNABORTED": true, - "ECONNREFUSED": true, - "ECONNRESET": true, - "EDEADLK": true, - "EDEADLOCK": true, - "EDESTADDRREQ": true, - "EDEVERR": true, - "EDOM": true, - "EDOOFUS": true, - "EDOTDOT": true, - "EDQUOT": true, - "EEXIST": true, - "EFAULT": true, - "EFBIG": true, - "EFER_LMA": true, - "EFER_LME": true, - "EFER_NXE": true, - "EFER_SCE": true, - "EFTYPE": true, - "EHOSTDOWN": true, - "EHOSTUNREACH": true, - "EHWPOISON": true, - "EIDRM": true, - "EILSEQ": true, - "EINPROGRESS": true, - "EINTR": true, - "EINVAL": true, - "EIO": true, - "EIPSEC": true, - "EISCONN": true, - "EISDIR": true, - "EISNAM": true, - "EKEYEXPIRED": true, - "EKEYREJECTED": true, - "EKEYREVOKED": true, - "EL2HLT": true, - "EL2NSYNC": true, - "EL3HLT": true, - "EL3RST": true, - "ELAST": true, - "ELF_NGREG": true, - "ELF_PRARGSZ": true, - "ELIBACC": true, - "ELIBBAD": true, - "ELIBEXEC": true, - "ELIBMAX": true, - "ELIBSCN": true, - "ELNRNG": true, - "ELOOP": true, - "EMEDIUMTYPE": true, - "EMFILE": true, - "EMLINK": true, - "EMSGSIZE": true, - "EMT_TAGOVF": true, - "EMULTIHOP": true, - "EMUL_ENABLED": true, - "EMUL_LINUX": true, - "EMUL_LINUX32": true, - "EMUL_MAXID": true, - "EMUL_NATIVE": true, - "ENAMETOOLONG": true, - "ENAVAIL": true, - "ENDRUNDISC": true, - "ENEEDAUTH": true, - "ENETDOWN": true, - "ENETRESET": true, - "ENETUNREACH": true, - "ENFILE": true, - "ENOANO": true, - "ENOATTR": true, - "ENOBUFS": true, - "ENOCSI": true, - "ENODATA": true, - "ENODEV": true, - "ENOENT": true, - "ENOEXEC": true, - "ENOKEY": true, - "ENOLCK": true, - "ENOLINK": true, - "ENOMEDIUM": true, - "ENOMEM": true, - "ENOMSG": true, - "ENONET": true, - "ENOPKG": true, - "ENOPOLICY": true, - "ENOPROTOOPT": true, - "ENOSPC": true, - "ENOSR": true, - "ENOSTR": true, - "ENOSYS": true, - "ENOTBLK": true, - "ENOTCAPABLE": true, - "ENOTCONN": true, - "ENOTDIR": true, - "ENOTEMPTY": true, - "ENOTNAM": true, - "ENOTRECOVERABLE": true, - "ENOTSOCK": true, - "ENOTSUP": true, - "ENOTTY": true, - "ENOTUNIQ": true, - "ENXIO": true, - "EN_SW_CTL_INF": true, - "EN_SW_CTL_PREC": true, - "EN_SW_CTL_ROUND": true, - "EN_SW_DATACHAIN": true, - "EN_SW_DENORM": true, - "EN_SW_INVOP": true, - "EN_SW_OVERFLOW": true, - "EN_SW_PRECLOSS": true, - "EN_SW_UNDERFLOW": true, - "EN_SW_ZERODIV": true, - "EOPNOTSUPP": true, - "EOVERFLOW": true, - "EOWNERDEAD": true, - "EPERM": true, - "EPFNOSUPPORT": true, - "EPIPE": true, - "EPOLLERR": true, - "EPOLLET": true, - "EPOLLHUP": true, - "EPOLLIN": true, - "EPOLLMSG": true, - "EPOLLONESHOT": true, - "EPOLLOUT": true, - "EPOLLPRI": true, - "EPOLLRDBAND": true, - "EPOLLRDHUP": true, - "EPOLLRDNORM": true, - "EPOLLWRBAND": true, - "EPOLLWRNORM": true, - "EPOLL_CLOEXEC": true, - "EPOLL_CTL_ADD": true, - "EPOLL_CTL_DEL": true, - "EPOLL_CTL_MOD": true, - "EPOLL_NONBLOCK": true, - "EPROCLIM": true, - "EPROCUNAVAIL": true, - "EPROGMISMATCH": true, - "EPROGUNAVAIL": true, - "EPROTO": true, - "EPROTONOSUPPORT": true, - "EPROTOTYPE": true, - "EPWROFF": true, - "ERANGE": true, - "EREMCHG": true, - "EREMOTE": true, - "EREMOTEIO": true, - "ERESTART": true, - "ERFKILL": true, - "EROFS": true, - "ERPCMISMATCH": true, - "ERROR_ACCESS_DENIED": true, - "ERROR_ALREADY_EXISTS": true, - "ERROR_BROKEN_PIPE": true, - "ERROR_BUFFER_OVERFLOW": true, - "ERROR_DIR_NOT_EMPTY": true, - "ERROR_ENVVAR_NOT_FOUND": true, - "ERROR_FILE_EXISTS": true, - "ERROR_FILE_NOT_FOUND": true, - "ERROR_HANDLE_EOF": true, - "ERROR_INSUFFICIENT_BUFFER": true, - "ERROR_IO_PENDING": true, - "ERROR_MOD_NOT_FOUND": true, - "ERROR_MORE_DATA": true, - "ERROR_NETNAME_DELETED": true, - "ERROR_NOT_FOUND": true, - "ERROR_NO_MORE_FILES": true, - "ERROR_OPERATION_ABORTED": true, - "ERROR_PATH_NOT_FOUND": true, - "ERROR_PRIVILEGE_NOT_HELD": true, - "ERROR_PROC_NOT_FOUND": true, - "ESHLIBVERS": true, - "ESHUTDOWN": true, - "ESOCKTNOSUPPORT": true, - "ESPIPE": true, - "ESRCH": true, - "ESRMNT": true, - "ESTALE": true, - "ESTRPIPE": true, - "ETHERCAP_JUMBO_MTU": true, - "ETHERCAP_VLAN_HWTAGGING": true, - "ETHERCAP_VLAN_MTU": true, - "ETHERMIN": true, - "ETHERMTU": true, - "ETHERMTU_JUMBO": true, - "ETHERTYPE_8023": true, - "ETHERTYPE_AARP": true, - "ETHERTYPE_ACCTON": true, - "ETHERTYPE_AEONIC": true, - "ETHERTYPE_ALPHA": true, - "ETHERTYPE_AMBER": true, - "ETHERTYPE_AMOEBA": true, - "ETHERTYPE_AOE": true, - "ETHERTYPE_APOLLO": true, - "ETHERTYPE_APOLLODOMAIN": true, - "ETHERTYPE_APPLETALK": true, - "ETHERTYPE_APPLITEK": true, - "ETHERTYPE_ARGONAUT": true, - "ETHERTYPE_ARP": true, - "ETHERTYPE_AT": true, - "ETHERTYPE_ATALK": true, - "ETHERTYPE_ATOMIC": true, - "ETHERTYPE_ATT": true, - "ETHERTYPE_ATTSTANFORD": true, - "ETHERTYPE_AUTOPHON": true, - "ETHERTYPE_AXIS": true, - "ETHERTYPE_BCLOOP": true, - "ETHERTYPE_BOFL": true, - "ETHERTYPE_CABLETRON": true, - "ETHERTYPE_CHAOS": true, - "ETHERTYPE_COMDESIGN": true, - "ETHERTYPE_COMPUGRAPHIC": true, - "ETHERTYPE_COUNTERPOINT": true, - "ETHERTYPE_CRONUS": true, - "ETHERTYPE_CRONUSVLN": true, - "ETHERTYPE_DCA": true, - "ETHERTYPE_DDE": true, - "ETHERTYPE_DEBNI": true, - "ETHERTYPE_DECAM": true, - "ETHERTYPE_DECCUST": true, - "ETHERTYPE_DECDIAG": true, - "ETHERTYPE_DECDNS": true, - "ETHERTYPE_DECDTS": true, - "ETHERTYPE_DECEXPER": true, - "ETHERTYPE_DECLAST": true, - "ETHERTYPE_DECLTM": true, - "ETHERTYPE_DECMUMPS": true, - "ETHERTYPE_DECNETBIOS": true, - "ETHERTYPE_DELTACON": true, - "ETHERTYPE_DIDDLE": true, - "ETHERTYPE_DLOG1": true, - "ETHERTYPE_DLOG2": true, - "ETHERTYPE_DN": true, - "ETHERTYPE_DOGFIGHT": true, - "ETHERTYPE_DSMD": true, - "ETHERTYPE_ECMA": true, - "ETHERTYPE_ENCRYPT": true, - "ETHERTYPE_ES": true, - "ETHERTYPE_EXCELAN": true, - "ETHERTYPE_EXPERDATA": true, - "ETHERTYPE_FLIP": true, - "ETHERTYPE_FLOWCONTROL": true, - "ETHERTYPE_FRARP": true, - "ETHERTYPE_GENDYN": true, - "ETHERTYPE_HAYES": true, - "ETHERTYPE_HIPPI_FP": true, - "ETHERTYPE_HITACHI": true, - "ETHERTYPE_HP": true, - "ETHERTYPE_IEEEPUP": true, - "ETHERTYPE_IEEEPUPAT": true, - "ETHERTYPE_IMLBL": true, - "ETHERTYPE_IMLBLDIAG": true, - "ETHERTYPE_IP": true, - "ETHERTYPE_IPAS": true, - "ETHERTYPE_IPV6": true, - "ETHERTYPE_IPX": true, - "ETHERTYPE_IPXNEW": true, - "ETHERTYPE_KALPANA": true, - "ETHERTYPE_LANBRIDGE": true, - "ETHERTYPE_LANPROBE": true, - "ETHERTYPE_LAT": true, - "ETHERTYPE_LBACK": true, - "ETHERTYPE_LITTLE": true, - "ETHERTYPE_LLDP": true, - "ETHERTYPE_LOGICRAFT": true, - "ETHERTYPE_LOOPBACK": true, - "ETHERTYPE_MATRA": true, - "ETHERTYPE_MAX": true, - "ETHERTYPE_MERIT": true, - "ETHERTYPE_MICP": true, - "ETHERTYPE_MOPDL": true, - "ETHERTYPE_MOPRC": true, - "ETHERTYPE_MOTOROLA": true, - "ETHERTYPE_MPLS": true, - "ETHERTYPE_MPLS_MCAST": true, - "ETHERTYPE_MUMPS": true, - "ETHERTYPE_NBPCC": true, - "ETHERTYPE_NBPCLAIM": true, - "ETHERTYPE_NBPCLREQ": true, - "ETHERTYPE_NBPCLRSP": true, - "ETHERTYPE_NBPCREQ": true, - "ETHERTYPE_NBPCRSP": true, - "ETHERTYPE_NBPDG": true, - "ETHERTYPE_NBPDGB": true, - "ETHERTYPE_NBPDLTE": true, - "ETHERTYPE_NBPRAR": true, - "ETHERTYPE_NBPRAS": true, - "ETHERTYPE_NBPRST": true, - "ETHERTYPE_NBPSCD": true, - "ETHERTYPE_NBPVCD": true, - "ETHERTYPE_NBS": true, - "ETHERTYPE_NCD": true, - "ETHERTYPE_NESTAR": true, - "ETHERTYPE_NETBEUI": true, - "ETHERTYPE_NOVELL": true, - "ETHERTYPE_NS": true, - "ETHERTYPE_NSAT": true, - "ETHERTYPE_NSCOMPAT": true, - "ETHERTYPE_NTRAILER": true, - "ETHERTYPE_OS9": true, - "ETHERTYPE_OS9NET": true, - "ETHERTYPE_PACER": true, - "ETHERTYPE_PAE": true, - "ETHERTYPE_PCS": true, - "ETHERTYPE_PLANNING": true, - "ETHERTYPE_PPP": true, - "ETHERTYPE_PPPOE": true, - "ETHERTYPE_PPPOEDISC": true, - "ETHERTYPE_PRIMENTS": true, - "ETHERTYPE_PUP": true, - "ETHERTYPE_PUPAT": true, - "ETHERTYPE_QINQ": true, - "ETHERTYPE_RACAL": true, - "ETHERTYPE_RATIONAL": true, - "ETHERTYPE_RAWFR": true, - "ETHERTYPE_RCL": true, - "ETHERTYPE_RDP": true, - "ETHERTYPE_RETIX": true, - "ETHERTYPE_REVARP": true, - "ETHERTYPE_SCA": true, - "ETHERTYPE_SECTRA": true, - "ETHERTYPE_SECUREDATA": true, - "ETHERTYPE_SGITW": true, - "ETHERTYPE_SG_BOUNCE": true, - "ETHERTYPE_SG_DIAG": true, - "ETHERTYPE_SG_NETGAMES": true, - "ETHERTYPE_SG_RESV": true, - "ETHERTYPE_SIMNET": true, - "ETHERTYPE_SLOW": true, - "ETHERTYPE_SLOWPROTOCOLS": true, - "ETHERTYPE_SNA": true, - "ETHERTYPE_SNMP": true, - "ETHERTYPE_SONIX": true, - "ETHERTYPE_SPIDER": true, - "ETHERTYPE_SPRITE": true, - "ETHERTYPE_STP": true, - "ETHERTYPE_TALARIS": true, - "ETHERTYPE_TALARISMC": true, - "ETHERTYPE_TCPCOMP": true, - "ETHERTYPE_TCPSM": true, - "ETHERTYPE_TEC": true, - "ETHERTYPE_TIGAN": true, - "ETHERTYPE_TRAIL": true, - "ETHERTYPE_TRANSETHER": true, - "ETHERTYPE_TYMSHARE": true, - "ETHERTYPE_UBBST": true, - "ETHERTYPE_UBDEBUG": true, - "ETHERTYPE_UBDIAGLOOP": true, - "ETHERTYPE_UBDL": true, - "ETHERTYPE_UBNIU": true, - "ETHERTYPE_UBNMC": true, - "ETHERTYPE_VALID": true, - "ETHERTYPE_VARIAN": true, - "ETHERTYPE_VAXELN": true, - "ETHERTYPE_VEECO": true, - "ETHERTYPE_VEXP": true, - "ETHERTYPE_VGLAB": true, - "ETHERTYPE_VINES": true, - "ETHERTYPE_VINESECHO": true, - "ETHERTYPE_VINESLOOP": true, - "ETHERTYPE_VITAL": true, - "ETHERTYPE_VLAN": true, - "ETHERTYPE_VLTLMAN": true, - "ETHERTYPE_VPROD": true, - "ETHERTYPE_VURESERVED": true, - "ETHERTYPE_WATERLOO": true, - "ETHERTYPE_WELLFLEET": true, - "ETHERTYPE_X25": true, - "ETHERTYPE_X75": true, - "ETHERTYPE_XNSSM": true, - "ETHERTYPE_XTP": true, - "ETHER_ADDR_LEN": true, - "ETHER_ALIGN": true, - "ETHER_CRC_LEN": true, - "ETHER_CRC_POLY_BE": true, - "ETHER_CRC_POLY_LE": true, - "ETHER_HDR_LEN": true, - "ETHER_MAX_DIX_LEN": true, - "ETHER_MAX_LEN": true, - "ETHER_MAX_LEN_JUMBO": true, - "ETHER_MIN_LEN": true, - "ETHER_PPPOE_ENCAP_LEN": true, - "ETHER_TYPE_LEN": true, - "ETHER_VLAN_ENCAP_LEN": true, - "ETH_P_1588": true, - "ETH_P_8021Q": true, - "ETH_P_802_2": true, - "ETH_P_802_3": true, - "ETH_P_AARP": true, - "ETH_P_ALL": true, - "ETH_P_AOE": true, - "ETH_P_ARCNET": true, - "ETH_P_ARP": true, - "ETH_P_ATALK": true, - "ETH_P_ATMFATE": true, - "ETH_P_ATMMPOA": true, - "ETH_P_AX25": true, - "ETH_P_BPQ": true, - "ETH_P_CAIF": true, - "ETH_P_CAN": true, - "ETH_P_CONTROL": true, - "ETH_P_CUST": true, - "ETH_P_DDCMP": true, - "ETH_P_DEC": true, - "ETH_P_DIAG": true, - "ETH_P_DNA_DL": true, - "ETH_P_DNA_RC": true, - "ETH_P_DNA_RT": true, - "ETH_P_DSA": true, - "ETH_P_ECONET": true, - "ETH_P_EDSA": true, - "ETH_P_FCOE": true, - "ETH_P_FIP": true, - "ETH_P_HDLC": true, - "ETH_P_IEEE802154": true, - "ETH_P_IEEEPUP": true, - "ETH_P_IEEEPUPAT": true, - "ETH_P_IP": true, - "ETH_P_IPV6": true, - "ETH_P_IPX": true, - "ETH_P_IRDA": true, - "ETH_P_LAT": true, - "ETH_P_LINK_CTL": true, - "ETH_P_LOCALTALK": true, - "ETH_P_LOOP": true, - "ETH_P_MOBITEX": true, - "ETH_P_MPLS_MC": true, - "ETH_P_MPLS_UC": true, - "ETH_P_PAE": true, - "ETH_P_PAUSE": true, - "ETH_P_PHONET": true, - "ETH_P_PPPTALK": true, - "ETH_P_PPP_DISC": true, - "ETH_P_PPP_MP": true, - "ETH_P_PPP_SES": true, - "ETH_P_PUP": true, - "ETH_P_PUPAT": true, - "ETH_P_RARP": true, - "ETH_P_SCA": true, - "ETH_P_SLOW": true, - "ETH_P_SNAP": true, - "ETH_P_TEB": true, - "ETH_P_TIPC": true, - "ETH_P_TRAILER": true, - "ETH_P_TR_802_2": true, - "ETH_P_WAN_PPP": true, - "ETH_P_WCCP": true, - "ETH_P_X25": true, - "ETIME": true, - "ETIMEDOUT": true, - "ETOOMANYREFS": true, - "ETXTBSY": true, - "EUCLEAN": true, - "EUNATCH": true, - "EUSERS": true, - "EVFILT_AIO": true, - "EVFILT_FS": true, - "EVFILT_LIO": true, - "EVFILT_MACHPORT": true, - "EVFILT_PROC": true, - "EVFILT_READ": true, - "EVFILT_SIGNAL": true, - "EVFILT_SYSCOUNT": true, - "EVFILT_THREADMARKER": true, - "EVFILT_TIMER": true, - "EVFILT_USER": true, - "EVFILT_VM": true, - "EVFILT_VNODE": true, - "EVFILT_WRITE": true, - "EV_ADD": true, - "EV_CLEAR": true, - "EV_DELETE": true, - "EV_DISABLE": true, - "EV_DISPATCH": true, - "EV_DROP": true, - "EV_ENABLE": true, - "EV_EOF": true, - "EV_ERROR": true, - "EV_FLAG0": true, - "EV_FLAG1": true, - "EV_ONESHOT": true, - "EV_OOBAND": true, - "EV_POLL": true, - "EV_RECEIPT": true, - "EV_SYSFLAGS": true, - "EWINDOWS": true, - "EWOULDBLOCK": true, - "EXDEV": true, - "EXFULL": true, - "EXTA": true, - "EXTB": true, - "EXTPROC": true, - "Environ": true, - "EpollCreate": true, - "EpollCreate1": true, - "EpollCtl": true, - "EpollEvent": true, - "EpollWait": true, - "Errno": true, - "EscapeArg": true, - "Exchangedata": true, - "Exec": true, - "Exit": true, - "ExitProcess": true, - "FD_CLOEXEC": true, - "FD_SETSIZE": true, - "FILE_ACTION_ADDED": true, - "FILE_ACTION_MODIFIED": true, - "FILE_ACTION_REMOVED": true, - "FILE_ACTION_RENAMED_NEW_NAME": true, - "FILE_ACTION_RENAMED_OLD_NAME": true, - "FILE_APPEND_DATA": true, - "FILE_ATTRIBUTE_ARCHIVE": true, - "FILE_ATTRIBUTE_DIRECTORY": true, - "FILE_ATTRIBUTE_HIDDEN": true, - "FILE_ATTRIBUTE_NORMAL": true, - "FILE_ATTRIBUTE_READONLY": true, - "FILE_ATTRIBUTE_REPARSE_POINT": true, - "FILE_ATTRIBUTE_SYSTEM": true, - "FILE_BEGIN": true, - "FILE_CURRENT": true, - "FILE_END": true, - "FILE_FLAG_BACKUP_SEMANTICS": true, - "FILE_FLAG_OPEN_REPARSE_POINT": true, - "FILE_FLAG_OVERLAPPED": true, - "FILE_LIST_DIRECTORY": true, - "FILE_MAP_COPY": true, - "FILE_MAP_EXECUTE": true, - "FILE_MAP_READ": true, - "FILE_MAP_WRITE": true, - "FILE_NOTIFY_CHANGE_ATTRIBUTES": true, - "FILE_NOTIFY_CHANGE_CREATION": true, - "FILE_NOTIFY_CHANGE_DIR_NAME": true, - "FILE_NOTIFY_CHANGE_FILE_NAME": true, - "FILE_NOTIFY_CHANGE_LAST_ACCESS": true, - "FILE_NOTIFY_CHANGE_LAST_WRITE": true, - "FILE_NOTIFY_CHANGE_SIZE": true, - "FILE_SHARE_DELETE": true, - "FILE_SHARE_READ": true, - "FILE_SHARE_WRITE": true, - "FILE_SKIP_COMPLETION_PORT_ON_SUCCESS": true, - "FILE_SKIP_SET_EVENT_ON_HANDLE": true, - "FILE_TYPE_CHAR": true, - "FILE_TYPE_DISK": true, - "FILE_TYPE_PIPE": true, - "FILE_TYPE_REMOTE": true, - "FILE_TYPE_UNKNOWN": true, - "FILE_WRITE_ATTRIBUTES": true, - "FLUSHO": true, - "FORMAT_MESSAGE_ALLOCATE_BUFFER": true, - "FORMAT_MESSAGE_ARGUMENT_ARRAY": true, - "FORMAT_MESSAGE_FROM_HMODULE": true, - "FORMAT_MESSAGE_FROM_STRING": true, - "FORMAT_MESSAGE_FROM_SYSTEM": true, - "FORMAT_MESSAGE_IGNORE_INSERTS": true, - "FORMAT_MESSAGE_MAX_WIDTH_MASK": true, - "FSCTL_GET_REPARSE_POINT": true, - "F_ADDFILESIGS": true, - "F_ADDSIGS": true, - "F_ALLOCATEALL": true, - "F_ALLOCATECONTIG": true, - "F_CANCEL": true, - "F_CHKCLEAN": true, - "F_CLOSEM": true, - "F_DUP2FD": true, - "F_DUP2FD_CLOEXEC": true, - "F_DUPFD": true, - "F_DUPFD_CLOEXEC": true, - "F_EXLCK": true, - "F_FLUSH_DATA": true, - "F_FREEZE_FS": true, - "F_FSCTL": true, - "F_FSDIRMASK": true, - "F_FSIN": true, - "F_FSINOUT": true, - "F_FSOUT": true, - "F_FSPRIV": true, - "F_FSVOID": true, - "F_FULLFSYNC": true, - "F_GETFD": true, - "F_GETFL": true, - "F_GETLEASE": true, - "F_GETLK": true, - "F_GETLK64": true, - "F_GETLKPID": true, - "F_GETNOSIGPIPE": true, - "F_GETOWN": true, - "F_GETOWN_EX": true, - "F_GETPATH": true, - "F_GETPATH_MTMINFO": true, - "F_GETPIPE_SZ": true, - "F_GETPROTECTIONCLASS": true, - "F_GETSIG": true, - "F_GLOBAL_NOCACHE": true, - "F_LOCK": true, - "F_LOG2PHYS": true, - "F_LOG2PHYS_EXT": true, - "F_MARKDEPENDENCY": true, - "F_MAXFD": true, - "F_NOCACHE": true, - "F_NODIRECT": true, - "F_NOTIFY": true, - "F_OGETLK": true, - "F_OK": true, - "F_OSETLK": true, - "F_OSETLKW": true, - "F_PARAM_MASK": true, - "F_PARAM_MAX": true, - "F_PATHPKG_CHECK": true, - "F_PEOFPOSMODE": true, - "F_PREALLOCATE": true, - "F_RDADVISE": true, - "F_RDAHEAD": true, - "F_RDLCK": true, - "F_READAHEAD": true, - "F_READBOOTSTRAP": true, - "F_SETBACKINGSTORE": true, - "F_SETFD": true, - "F_SETFL": true, - "F_SETLEASE": true, - "F_SETLK": true, - "F_SETLK64": true, - "F_SETLKW": true, - "F_SETLKW64": true, - "F_SETLK_REMOTE": true, - "F_SETNOSIGPIPE": true, - "F_SETOWN": true, - "F_SETOWN_EX": true, - "F_SETPIPE_SZ": true, - "F_SETPROTECTIONCLASS": true, - "F_SETSIG": true, - "F_SETSIZE": true, - "F_SHLCK": true, - "F_TEST": true, - "F_THAW_FS": true, - "F_TLOCK": true, - "F_ULOCK": true, - "F_UNLCK": true, - "F_UNLCKSYS": true, - "F_VOLPOSMODE": true, - "F_WRITEBOOTSTRAP": true, - "F_WRLCK": true, - "Faccessat": true, - "Fallocate": true, - "Fbootstraptransfer_t": true, - "Fchdir": true, - "Fchflags": true, - "Fchmod": true, - "Fchmodat": true, - "Fchown": true, - "Fchownat": true, - "FcntlFlock": true, - "FdSet": true, - "Fdatasync": true, - "FileNotifyInformation": true, - "Filetime": true, - "FindClose": true, - "FindFirstFile": true, - "FindNextFile": true, - "Flock": true, - "Flock_t": true, - "FlushBpf": true, - "FlushFileBuffers": true, - "FlushViewOfFile": true, - "ForkExec": true, - "ForkLock": true, - "FormatMessage": true, - "Fpathconf": true, - "FreeAddrInfoW": true, - "FreeEnvironmentStrings": true, - "FreeLibrary": true, - "Fsid": true, - "Fstat": true, - "Fstatat": true, - "Fstatfs": true, - "Fstore_t": true, - "Fsync": true, - "Ftruncate": true, - "FullPath": true, - "Futimes": true, - "Futimesat": true, - "GENERIC_ALL": true, - "GENERIC_EXECUTE": true, - "GENERIC_READ": true, - "GENERIC_WRITE": true, - "GUID": true, - "GetAcceptExSockaddrs": true, - "GetAdaptersInfo": true, - "GetAddrInfoW": true, - "GetCommandLine": true, - "GetComputerName": true, - "GetConsoleMode": true, - "GetCurrentDirectory": true, - "GetCurrentProcess": true, - "GetEnvironmentStrings": true, - "GetEnvironmentVariable": true, - "GetExitCodeProcess": true, - "GetFileAttributes": true, - "GetFileAttributesEx": true, - "GetFileExInfoStandard": true, - "GetFileExMaxInfoLevel": true, - "GetFileInformationByHandle": true, - "GetFileType": true, - "GetFullPathName": true, - "GetHostByName": true, - "GetIfEntry": true, - "GetLastError": true, - "GetLengthSid": true, - "GetLongPathName": true, - "GetProcAddress": true, - "GetProcessTimes": true, - "GetProtoByName": true, - "GetQueuedCompletionStatus": true, - "GetServByName": true, - "GetShortPathName": true, - "GetStartupInfo": true, - "GetStdHandle": true, - "GetSystemTimeAsFileTime": true, - "GetTempPath": true, - "GetTimeZoneInformation": true, - "GetTokenInformation": true, - "GetUserNameEx": true, - "GetUserProfileDirectory": true, - "GetVersion": true, - "Getcwd": true, - "Getdents": true, - "Getdirentries": true, - "Getdtablesize": true, - "Getegid": true, - "Getenv": true, - "Geteuid": true, - "Getfsstat": true, - "Getgid": true, - "Getgroups": true, - "Getpagesize": true, - "Getpeername": true, - "Getpgid": true, - "Getpgrp": true, - "Getpid": true, - "Getppid": true, - "Getpriority": true, - "Getrlimit": true, - "Getrusage": true, - "Getsid": true, - "Getsockname": true, - "Getsockopt": true, - "GetsockoptByte": true, - "GetsockoptICMPv6Filter": true, - "GetsockoptIPMreq": true, - "GetsockoptIPMreqn": true, - "GetsockoptIPv6MTUInfo": true, - "GetsockoptIPv6Mreq": true, - "GetsockoptInet4Addr": true, - "GetsockoptInt": true, - "GetsockoptUcred": true, - "Gettid": true, - "Gettimeofday": true, - "Getuid": true, - "Getwd": true, - "Getxattr": true, - "HANDLE_FLAG_INHERIT": true, - "HKEY_CLASSES_ROOT": true, - "HKEY_CURRENT_CONFIG": true, - "HKEY_CURRENT_USER": true, - "HKEY_DYN_DATA": true, - "HKEY_LOCAL_MACHINE": true, - "HKEY_PERFORMANCE_DATA": true, - "HKEY_USERS": true, - "HUPCL": true, - "Handle": true, - "Hostent": true, - "ICANON": true, - "ICMP6_FILTER": true, - "ICMPV6_FILTER": true, - "ICMPv6Filter": true, - "ICRNL": true, - "IEXTEN": true, - "IFAN_ARRIVAL": true, - "IFAN_DEPARTURE": true, - "IFA_ADDRESS": true, - "IFA_ANYCAST": true, - "IFA_BROADCAST": true, - "IFA_CACHEINFO": true, - "IFA_F_DADFAILED": true, - "IFA_F_DEPRECATED": true, - "IFA_F_HOMEADDRESS": true, - "IFA_F_NODAD": true, - "IFA_F_OPTIMISTIC": true, - "IFA_F_PERMANENT": true, - "IFA_F_SECONDARY": true, - "IFA_F_TEMPORARY": true, - "IFA_F_TENTATIVE": true, - "IFA_LABEL": true, - "IFA_LOCAL": true, - "IFA_MAX": true, - "IFA_MULTICAST": true, - "IFA_ROUTE": true, - "IFA_UNSPEC": true, - "IFF_ALLMULTI": true, - "IFF_ALTPHYS": true, - "IFF_AUTOMEDIA": true, - "IFF_BROADCAST": true, - "IFF_CANTCHANGE": true, - "IFF_CANTCONFIG": true, - "IFF_DEBUG": true, - "IFF_DRV_OACTIVE": true, - "IFF_DRV_RUNNING": true, - "IFF_DYING": true, - "IFF_DYNAMIC": true, - "IFF_LINK0": true, - "IFF_LINK1": true, - "IFF_LINK2": true, - "IFF_LOOPBACK": true, - "IFF_MASTER": true, - "IFF_MONITOR": true, - "IFF_MULTICAST": true, - "IFF_NOARP": true, - "IFF_NOTRAILERS": true, - "IFF_NO_PI": true, - "IFF_OACTIVE": true, - "IFF_ONE_QUEUE": true, - "IFF_POINTOPOINT": true, - "IFF_POINTTOPOINT": true, - "IFF_PORTSEL": true, - "IFF_PPROMISC": true, - "IFF_PROMISC": true, - "IFF_RENAMING": true, - "IFF_RUNNING": true, - "IFF_SIMPLEX": true, - "IFF_SLAVE": true, - "IFF_SMART": true, - "IFF_STATICARP": true, - "IFF_TAP": true, - "IFF_TUN": true, - "IFF_TUN_EXCL": true, - "IFF_UP": true, - "IFF_VNET_HDR": true, - "IFLA_ADDRESS": true, - "IFLA_BROADCAST": true, - "IFLA_COST": true, - "IFLA_IFALIAS": true, - "IFLA_IFNAME": true, - "IFLA_LINK": true, - "IFLA_LINKINFO": true, - "IFLA_LINKMODE": true, - "IFLA_MAP": true, - "IFLA_MASTER": true, - "IFLA_MAX": true, - "IFLA_MTU": true, - "IFLA_NET_NS_PID": true, - "IFLA_OPERSTATE": true, - "IFLA_PRIORITY": true, - "IFLA_PROTINFO": true, - "IFLA_QDISC": true, - "IFLA_STATS": true, - "IFLA_TXQLEN": true, - "IFLA_UNSPEC": true, - "IFLA_WEIGHT": true, - "IFLA_WIRELESS": true, - "IFNAMSIZ": true, - "IFT_1822": true, - "IFT_A12MPPSWITCH": true, - "IFT_AAL2": true, - "IFT_AAL5": true, - "IFT_ADSL": true, - "IFT_AFLANE8023": true, - "IFT_AFLANE8025": true, - "IFT_ARAP": true, - "IFT_ARCNET": true, - "IFT_ARCNETPLUS": true, - "IFT_ASYNC": true, - "IFT_ATM": true, - "IFT_ATMDXI": true, - "IFT_ATMFUNI": true, - "IFT_ATMIMA": true, - "IFT_ATMLOGICAL": true, - "IFT_ATMRADIO": true, - "IFT_ATMSUBINTERFACE": true, - "IFT_ATMVCIENDPT": true, - "IFT_ATMVIRTUAL": true, - "IFT_BGPPOLICYACCOUNTING": true, - "IFT_BLUETOOTH": true, - "IFT_BRIDGE": true, - "IFT_BSC": true, - "IFT_CARP": true, - "IFT_CCTEMUL": true, - "IFT_CELLULAR": true, - "IFT_CEPT": true, - "IFT_CES": true, - "IFT_CHANNEL": true, - "IFT_CNR": true, - "IFT_COFFEE": true, - "IFT_COMPOSITELINK": true, - "IFT_DCN": true, - "IFT_DIGITALPOWERLINE": true, - "IFT_DIGITALWRAPPEROVERHEADCHANNEL": true, - "IFT_DLSW": true, - "IFT_DOCSCABLEDOWNSTREAM": true, - "IFT_DOCSCABLEMACLAYER": true, - "IFT_DOCSCABLEUPSTREAM": true, - "IFT_DOCSCABLEUPSTREAMCHANNEL": true, - "IFT_DS0": true, - "IFT_DS0BUNDLE": true, - "IFT_DS1FDL": true, - "IFT_DS3": true, - "IFT_DTM": true, - "IFT_DUMMY": true, - "IFT_DVBASILN": true, - "IFT_DVBASIOUT": true, - "IFT_DVBRCCDOWNSTREAM": true, - "IFT_DVBRCCMACLAYER": true, - "IFT_DVBRCCUPSTREAM": true, - "IFT_ECONET": true, - "IFT_ENC": true, - "IFT_EON": true, - "IFT_EPLRS": true, - "IFT_ESCON": true, - "IFT_ETHER": true, - "IFT_FAITH": true, - "IFT_FAST": true, - "IFT_FASTETHER": true, - "IFT_FASTETHERFX": true, - "IFT_FDDI": true, - "IFT_FIBRECHANNEL": true, - "IFT_FRAMERELAYINTERCONNECT": true, - "IFT_FRAMERELAYMPI": true, - "IFT_FRDLCIENDPT": true, - "IFT_FRELAY": true, - "IFT_FRELAYDCE": true, - "IFT_FRF16MFRBUNDLE": true, - "IFT_FRFORWARD": true, - "IFT_G703AT2MB": true, - "IFT_G703AT64K": true, - "IFT_GIF": true, - "IFT_GIGABITETHERNET": true, - "IFT_GR303IDT": true, - "IFT_GR303RDT": true, - "IFT_H323GATEKEEPER": true, - "IFT_H323PROXY": true, - "IFT_HDH1822": true, - "IFT_HDLC": true, - "IFT_HDSL2": true, - "IFT_HIPERLAN2": true, - "IFT_HIPPI": true, - "IFT_HIPPIINTERFACE": true, - "IFT_HOSTPAD": true, - "IFT_HSSI": true, - "IFT_HY": true, - "IFT_IBM370PARCHAN": true, - "IFT_IDSL": true, - "IFT_IEEE1394": true, - "IFT_IEEE80211": true, - "IFT_IEEE80212": true, - "IFT_IEEE8023ADLAG": true, - "IFT_IFGSN": true, - "IFT_IMT": true, - "IFT_INFINIBAND": true, - "IFT_INTERLEAVE": true, - "IFT_IP": true, - "IFT_IPFORWARD": true, - "IFT_IPOVERATM": true, - "IFT_IPOVERCDLC": true, - "IFT_IPOVERCLAW": true, - "IFT_IPSWITCH": true, - "IFT_IPXIP": true, - "IFT_ISDN": true, - "IFT_ISDNBASIC": true, - "IFT_ISDNPRIMARY": true, - "IFT_ISDNS": true, - "IFT_ISDNU": true, - "IFT_ISO88022LLC": true, - "IFT_ISO88023": true, - "IFT_ISO88024": true, - "IFT_ISO88025": true, - "IFT_ISO88025CRFPINT": true, - "IFT_ISO88025DTR": true, - "IFT_ISO88025FIBER": true, - "IFT_ISO88026": true, - "IFT_ISUP": true, - "IFT_L2VLAN": true, - "IFT_L3IPVLAN": true, - "IFT_L3IPXVLAN": true, - "IFT_LAPB": true, - "IFT_LAPD": true, - "IFT_LAPF": true, - "IFT_LINEGROUP": true, - "IFT_LOCALTALK": true, - "IFT_LOOP": true, - "IFT_MEDIAMAILOVERIP": true, - "IFT_MFSIGLINK": true, - "IFT_MIOX25": true, - "IFT_MODEM": true, - "IFT_MPC": true, - "IFT_MPLS": true, - "IFT_MPLSTUNNEL": true, - "IFT_MSDSL": true, - "IFT_MVL": true, - "IFT_MYRINET": true, - "IFT_NFAS": true, - "IFT_NSIP": true, - "IFT_OPTICALCHANNEL": true, - "IFT_OPTICALTRANSPORT": true, - "IFT_OTHER": true, - "IFT_P10": true, - "IFT_P80": true, - "IFT_PARA": true, - "IFT_PDP": true, - "IFT_PFLOG": true, - "IFT_PFLOW": true, - "IFT_PFSYNC": true, - "IFT_PLC": true, - "IFT_PON155": true, - "IFT_PON622": true, - "IFT_POS": true, - "IFT_PPP": true, - "IFT_PPPMULTILINKBUNDLE": true, - "IFT_PROPATM": true, - "IFT_PROPBWAP2MP": true, - "IFT_PROPCNLS": true, - "IFT_PROPDOCSWIRELESSDOWNSTREAM": true, - "IFT_PROPDOCSWIRELESSMACLAYER": true, - "IFT_PROPDOCSWIRELESSUPSTREAM": true, - "IFT_PROPMUX": true, - "IFT_PROPVIRTUAL": true, - "IFT_PROPWIRELESSP2P": true, - "IFT_PTPSERIAL": true, - "IFT_PVC": true, - "IFT_Q2931": true, - "IFT_QLLC": true, - "IFT_RADIOMAC": true, - "IFT_RADSL": true, - "IFT_REACHDSL": true, - "IFT_RFC1483": true, - "IFT_RS232": true, - "IFT_RSRB": true, - "IFT_SDLC": true, - "IFT_SDSL": true, - "IFT_SHDSL": true, - "IFT_SIP": true, - "IFT_SIPSIG": true, - "IFT_SIPTG": true, - "IFT_SLIP": true, - "IFT_SMDSDXI": true, - "IFT_SMDSICIP": true, - "IFT_SONET": true, - "IFT_SONETOVERHEADCHANNEL": true, - "IFT_SONETPATH": true, - "IFT_SONETVT": true, - "IFT_SRP": true, - "IFT_SS7SIGLINK": true, - "IFT_STACKTOSTACK": true, - "IFT_STARLAN": true, - "IFT_STF": true, - "IFT_T1": true, - "IFT_TDLC": true, - "IFT_TELINK": true, - "IFT_TERMPAD": true, - "IFT_TR008": true, - "IFT_TRANSPHDLC": true, - "IFT_TUNNEL": true, - "IFT_ULTRA": true, - "IFT_USB": true, - "IFT_V11": true, - "IFT_V35": true, - "IFT_V36": true, - "IFT_V37": true, - "IFT_VDSL": true, - "IFT_VIRTUALIPADDRESS": true, - "IFT_VIRTUALTG": true, - "IFT_VOICEDID": true, - "IFT_VOICEEM": true, - "IFT_VOICEEMFGD": true, - "IFT_VOICEENCAP": true, - "IFT_VOICEFGDEANA": true, - "IFT_VOICEFXO": true, - "IFT_VOICEFXS": true, - "IFT_VOICEOVERATM": true, - "IFT_VOICEOVERCABLE": true, - "IFT_VOICEOVERFRAMERELAY": true, - "IFT_VOICEOVERIP": true, - "IFT_X213": true, - "IFT_X25": true, - "IFT_X25DDN": true, - "IFT_X25HUNTGROUP": true, - "IFT_X25MLP": true, - "IFT_X25PLE": true, - "IFT_XETHER": true, - "IGNBRK": true, - "IGNCR": true, - "IGNORE": true, - "IGNPAR": true, - "IMAXBEL": true, - "INFINITE": true, - "INLCR": true, - "INPCK": true, - "INVALID_FILE_ATTRIBUTES": true, - "IN_ACCESS": true, - "IN_ALL_EVENTS": true, - "IN_ATTRIB": true, - "IN_CLASSA_HOST": true, - "IN_CLASSA_MAX": true, - "IN_CLASSA_NET": true, - "IN_CLASSA_NSHIFT": true, - "IN_CLASSB_HOST": true, - "IN_CLASSB_MAX": true, - "IN_CLASSB_NET": true, - "IN_CLASSB_NSHIFT": true, - "IN_CLASSC_HOST": true, - "IN_CLASSC_NET": true, - "IN_CLASSC_NSHIFT": true, - "IN_CLASSD_HOST": true, - "IN_CLASSD_NET": true, - "IN_CLASSD_NSHIFT": true, - "IN_CLOEXEC": true, - "IN_CLOSE": true, - "IN_CLOSE_NOWRITE": true, - "IN_CLOSE_WRITE": true, - "IN_CREATE": true, - "IN_DELETE": true, - "IN_DELETE_SELF": true, - "IN_DONT_FOLLOW": true, - "IN_EXCL_UNLINK": true, - "IN_IGNORED": true, - "IN_ISDIR": true, - "IN_LINKLOCALNETNUM": true, - "IN_LOOPBACKNET": true, - "IN_MASK_ADD": true, - "IN_MODIFY": true, - "IN_MOVE": true, - "IN_MOVED_FROM": true, - "IN_MOVED_TO": true, - "IN_MOVE_SELF": true, - "IN_NONBLOCK": true, - "IN_ONESHOT": true, - "IN_ONLYDIR": true, - "IN_OPEN": true, - "IN_Q_OVERFLOW": true, - "IN_RFC3021_HOST": true, - "IN_RFC3021_MASK": true, - "IN_RFC3021_NET": true, - "IN_RFC3021_NSHIFT": true, - "IN_UNMOUNT": true, - "IOC_IN": true, - "IOC_INOUT": true, - "IOC_OUT": true, - "IOC_VENDOR": true, - "IOC_WS2": true, - "IO_REPARSE_TAG_SYMLINK": true, - "IPMreq": true, - "IPMreqn": true, - "IPPROTO_3PC": true, - "IPPROTO_ADFS": true, - "IPPROTO_AH": true, - "IPPROTO_AHIP": true, - "IPPROTO_APES": true, - "IPPROTO_ARGUS": true, - "IPPROTO_AX25": true, - "IPPROTO_BHA": true, - "IPPROTO_BLT": true, - "IPPROTO_BRSATMON": true, - "IPPROTO_CARP": true, - "IPPROTO_CFTP": true, - "IPPROTO_CHAOS": true, - "IPPROTO_CMTP": true, - "IPPROTO_COMP": true, - "IPPROTO_CPHB": true, - "IPPROTO_CPNX": true, - "IPPROTO_DCCP": true, - "IPPROTO_DDP": true, - "IPPROTO_DGP": true, - "IPPROTO_DIVERT": true, - "IPPROTO_DIVERT_INIT": true, - "IPPROTO_DIVERT_RESP": true, - "IPPROTO_DONE": true, - "IPPROTO_DSTOPTS": true, - "IPPROTO_EGP": true, - "IPPROTO_EMCON": true, - "IPPROTO_ENCAP": true, - "IPPROTO_EON": true, - "IPPROTO_ESP": true, - "IPPROTO_ETHERIP": true, - "IPPROTO_FRAGMENT": true, - "IPPROTO_GGP": true, - "IPPROTO_GMTP": true, - "IPPROTO_GRE": true, - "IPPROTO_HELLO": true, - "IPPROTO_HMP": true, - "IPPROTO_HOPOPTS": true, - "IPPROTO_ICMP": true, - "IPPROTO_ICMPV6": true, - "IPPROTO_IDP": true, - "IPPROTO_IDPR": true, - "IPPROTO_IDRP": true, - "IPPROTO_IGMP": true, - "IPPROTO_IGP": true, - "IPPROTO_IGRP": true, - "IPPROTO_IL": true, - "IPPROTO_INLSP": true, - "IPPROTO_INP": true, - "IPPROTO_IP": true, - "IPPROTO_IPCOMP": true, - "IPPROTO_IPCV": true, - "IPPROTO_IPEIP": true, - "IPPROTO_IPIP": true, - "IPPROTO_IPPC": true, - "IPPROTO_IPV4": true, - "IPPROTO_IPV6": true, - "IPPROTO_IPV6_ICMP": true, - "IPPROTO_IRTP": true, - "IPPROTO_KRYPTOLAN": true, - "IPPROTO_LARP": true, - "IPPROTO_LEAF1": true, - "IPPROTO_LEAF2": true, - "IPPROTO_MAX": true, - "IPPROTO_MAXID": true, - "IPPROTO_MEAS": true, - "IPPROTO_MH": true, - "IPPROTO_MHRP": true, - "IPPROTO_MICP": true, - "IPPROTO_MOBILE": true, - "IPPROTO_MPLS": true, - "IPPROTO_MTP": true, - "IPPROTO_MUX": true, - "IPPROTO_ND": true, - "IPPROTO_NHRP": true, - "IPPROTO_NONE": true, - "IPPROTO_NSP": true, - "IPPROTO_NVPII": true, - "IPPROTO_OLD_DIVERT": true, - "IPPROTO_OSPFIGP": true, - "IPPROTO_PFSYNC": true, - "IPPROTO_PGM": true, - "IPPROTO_PIGP": true, - "IPPROTO_PIM": true, - "IPPROTO_PRM": true, - "IPPROTO_PUP": true, - "IPPROTO_PVP": true, - "IPPROTO_RAW": true, - "IPPROTO_RCCMON": true, - "IPPROTO_RDP": true, - "IPPROTO_ROUTING": true, - "IPPROTO_RSVP": true, - "IPPROTO_RVD": true, - "IPPROTO_SATEXPAK": true, - "IPPROTO_SATMON": true, - "IPPROTO_SCCSP": true, - "IPPROTO_SCTP": true, - "IPPROTO_SDRP": true, - "IPPROTO_SEND": true, - "IPPROTO_SEP": true, - "IPPROTO_SKIP": true, - "IPPROTO_SPACER": true, - "IPPROTO_SRPC": true, - "IPPROTO_ST": true, - "IPPROTO_SVMTP": true, - "IPPROTO_SWIPE": true, - "IPPROTO_TCF": true, - "IPPROTO_TCP": true, - "IPPROTO_TLSP": true, - "IPPROTO_TP": true, - "IPPROTO_TPXX": true, - "IPPROTO_TRUNK1": true, - "IPPROTO_TRUNK2": true, - "IPPROTO_TTP": true, - "IPPROTO_UDP": true, - "IPPROTO_UDPLITE": true, - "IPPROTO_VINES": true, - "IPPROTO_VISA": true, - "IPPROTO_VMTP": true, - "IPPROTO_VRRP": true, - "IPPROTO_WBEXPAK": true, - "IPPROTO_WBMON": true, - "IPPROTO_WSN": true, - "IPPROTO_XNET": true, - "IPPROTO_XTP": true, - "IPV6_2292DSTOPTS": true, - "IPV6_2292HOPLIMIT": true, - "IPV6_2292HOPOPTS": true, - "IPV6_2292NEXTHOP": true, - "IPV6_2292PKTINFO": true, - "IPV6_2292PKTOPTIONS": true, - "IPV6_2292RTHDR": true, - "IPV6_ADDRFORM": true, - "IPV6_ADD_MEMBERSHIP": true, - "IPV6_AUTHHDR": true, - "IPV6_AUTH_LEVEL": true, - "IPV6_AUTOFLOWLABEL": true, - "IPV6_BINDANY": true, - "IPV6_BINDV6ONLY": true, - "IPV6_BOUND_IF": true, - "IPV6_CHECKSUM": true, - "IPV6_DEFAULT_MULTICAST_HOPS": true, - "IPV6_DEFAULT_MULTICAST_LOOP": true, - "IPV6_DEFHLIM": true, - "IPV6_DONTFRAG": true, - "IPV6_DROP_MEMBERSHIP": true, - "IPV6_DSTOPTS": true, - "IPV6_ESP_NETWORK_LEVEL": true, - "IPV6_ESP_TRANS_LEVEL": true, - "IPV6_FAITH": true, - "IPV6_FLOWINFO_MASK": true, - "IPV6_FLOWLABEL_MASK": true, - "IPV6_FRAGTTL": true, - "IPV6_FW_ADD": true, - "IPV6_FW_DEL": true, - "IPV6_FW_FLUSH": true, - "IPV6_FW_GET": true, - "IPV6_FW_ZERO": true, - "IPV6_HLIMDEC": true, - "IPV6_HOPLIMIT": true, - "IPV6_HOPOPTS": true, - "IPV6_IPCOMP_LEVEL": true, - "IPV6_IPSEC_POLICY": true, - "IPV6_JOIN_ANYCAST": true, - "IPV6_JOIN_GROUP": true, - "IPV6_LEAVE_ANYCAST": true, - "IPV6_LEAVE_GROUP": true, - "IPV6_MAXHLIM": true, - "IPV6_MAXOPTHDR": true, - "IPV6_MAXPACKET": true, - "IPV6_MAX_GROUP_SRC_FILTER": true, - "IPV6_MAX_MEMBERSHIPS": true, - "IPV6_MAX_SOCK_SRC_FILTER": true, - "IPV6_MIN_MEMBERSHIPS": true, - "IPV6_MMTU": true, - "IPV6_MSFILTER": true, - "IPV6_MTU": true, - "IPV6_MTU_DISCOVER": true, - "IPV6_MULTICAST_HOPS": true, - "IPV6_MULTICAST_IF": true, - "IPV6_MULTICAST_LOOP": true, - "IPV6_NEXTHOP": true, - "IPV6_OPTIONS": true, - "IPV6_PATHMTU": true, - "IPV6_PIPEX": true, - "IPV6_PKTINFO": true, - "IPV6_PMTUDISC_DO": true, - "IPV6_PMTUDISC_DONT": true, - "IPV6_PMTUDISC_PROBE": true, - "IPV6_PMTUDISC_WANT": true, - "IPV6_PORTRANGE": true, - "IPV6_PORTRANGE_DEFAULT": true, - "IPV6_PORTRANGE_HIGH": true, - "IPV6_PORTRANGE_LOW": true, - "IPV6_PREFER_TEMPADDR": true, - "IPV6_RECVDSTOPTS": true, - "IPV6_RECVDSTPORT": true, - "IPV6_RECVERR": true, - "IPV6_RECVHOPLIMIT": true, - "IPV6_RECVHOPOPTS": true, - "IPV6_RECVPATHMTU": true, - "IPV6_RECVPKTINFO": true, - "IPV6_RECVRTHDR": true, - "IPV6_RECVTCLASS": true, - "IPV6_ROUTER_ALERT": true, - "IPV6_RTABLE": true, - "IPV6_RTHDR": true, - "IPV6_RTHDRDSTOPTS": true, - "IPV6_RTHDR_LOOSE": true, - "IPV6_RTHDR_STRICT": true, - "IPV6_RTHDR_TYPE_0": true, - "IPV6_RXDSTOPTS": true, - "IPV6_RXHOPOPTS": true, - "IPV6_SOCKOPT_RESERVED1": true, - "IPV6_TCLASS": true, - "IPV6_UNICAST_HOPS": true, - "IPV6_USE_MIN_MTU": true, - "IPV6_V6ONLY": true, - "IPV6_VERSION": true, - "IPV6_VERSION_MASK": true, - "IPV6_XFRM_POLICY": true, - "IP_ADD_MEMBERSHIP": true, - "IP_ADD_SOURCE_MEMBERSHIP": true, - "IP_AUTH_LEVEL": true, - "IP_BINDANY": true, - "IP_BLOCK_SOURCE": true, - "IP_BOUND_IF": true, - "IP_DEFAULT_MULTICAST_LOOP": true, - "IP_DEFAULT_MULTICAST_TTL": true, - "IP_DF": true, - "IP_DIVERTFL": true, - "IP_DONTFRAG": true, - "IP_DROP_MEMBERSHIP": true, - "IP_DROP_SOURCE_MEMBERSHIP": true, - "IP_DUMMYNET3": true, - "IP_DUMMYNET_CONFIGURE": true, - "IP_DUMMYNET_DEL": true, - "IP_DUMMYNET_FLUSH": true, - "IP_DUMMYNET_GET": true, - "IP_EF": true, - "IP_ERRORMTU": true, - "IP_ESP_NETWORK_LEVEL": true, - "IP_ESP_TRANS_LEVEL": true, - "IP_FAITH": true, - "IP_FREEBIND": true, - "IP_FW3": true, - "IP_FW_ADD": true, - "IP_FW_DEL": true, - "IP_FW_FLUSH": true, - "IP_FW_GET": true, - "IP_FW_NAT_CFG": true, - "IP_FW_NAT_DEL": true, - "IP_FW_NAT_GET_CONFIG": true, - "IP_FW_NAT_GET_LOG": true, - "IP_FW_RESETLOG": true, - "IP_FW_TABLE_ADD": true, - "IP_FW_TABLE_DEL": true, - "IP_FW_TABLE_FLUSH": true, - "IP_FW_TABLE_GETSIZE": true, - "IP_FW_TABLE_LIST": true, - "IP_FW_ZERO": true, - "IP_HDRINCL": true, - "IP_IPCOMP_LEVEL": true, - "IP_IPSECFLOWINFO": true, - "IP_IPSEC_LOCAL_AUTH": true, - "IP_IPSEC_LOCAL_CRED": true, - "IP_IPSEC_LOCAL_ID": true, - "IP_IPSEC_POLICY": true, - "IP_IPSEC_REMOTE_AUTH": true, - "IP_IPSEC_REMOTE_CRED": true, - "IP_IPSEC_REMOTE_ID": true, - "IP_MAXPACKET": true, - "IP_MAX_GROUP_SRC_FILTER": true, - "IP_MAX_MEMBERSHIPS": true, - "IP_MAX_SOCK_MUTE_FILTER": true, - "IP_MAX_SOCK_SRC_FILTER": true, - "IP_MAX_SOURCE_FILTER": true, - "IP_MF": true, - "IP_MINFRAGSIZE": true, - "IP_MINTTL": true, - "IP_MIN_MEMBERSHIPS": true, - "IP_MSFILTER": true, - "IP_MSS": true, - "IP_MTU": true, - "IP_MTU_DISCOVER": true, - "IP_MULTICAST_IF": true, - "IP_MULTICAST_IFINDEX": true, - "IP_MULTICAST_LOOP": true, - "IP_MULTICAST_TTL": true, - "IP_MULTICAST_VIF": true, - "IP_NAT__XXX": true, - "IP_OFFMASK": true, - "IP_OLD_FW_ADD": true, - "IP_OLD_FW_DEL": true, - "IP_OLD_FW_FLUSH": true, - "IP_OLD_FW_GET": true, - "IP_OLD_FW_RESETLOG": true, - "IP_OLD_FW_ZERO": true, - "IP_ONESBCAST": true, - "IP_OPTIONS": true, - "IP_ORIGDSTADDR": true, - "IP_PASSSEC": true, - "IP_PIPEX": true, - "IP_PKTINFO": true, - "IP_PKTOPTIONS": true, - "IP_PMTUDISC": true, - "IP_PMTUDISC_DO": true, - "IP_PMTUDISC_DONT": true, - "IP_PMTUDISC_PROBE": true, - "IP_PMTUDISC_WANT": true, - "IP_PORTRANGE": true, - "IP_PORTRANGE_DEFAULT": true, - "IP_PORTRANGE_HIGH": true, - "IP_PORTRANGE_LOW": true, - "IP_RECVDSTADDR": true, - "IP_RECVDSTPORT": true, - "IP_RECVERR": true, - "IP_RECVIF": true, - "IP_RECVOPTS": true, - "IP_RECVORIGDSTADDR": true, - "IP_RECVPKTINFO": true, - "IP_RECVRETOPTS": true, - "IP_RECVRTABLE": true, - "IP_RECVTOS": true, - "IP_RECVTTL": true, - "IP_RETOPTS": true, - "IP_RF": true, - "IP_ROUTER_ALERT": true, - "IP_RSVP_OFF": true, - "IP_RSVP_ON": true, - "IP_RSVP_VIF_OFF": true, - "IP_RSVP_VIF_ON": true, - "IP_RTABLE": true, - "IP_SENDSRCADDR": true, - "IP_STRIPHDR": true, - "IP_TOS": true, - "IP_TRAFFIC_MGT_BACKGROUND": true, - "IP_TRANSPARENT": true, - "IP_TTL": true, - "IP_UNBLOCK_SOURCE": true, - "IP_XFRM_POLICY": true, - "IPv6MTUInfo": true, - "IPv6Mreq": true, - "ISIG": true, - "ISTRIP": true, - "IUCLC": true, - "IUTF8": true, - "IXANY": true, - "IXOFF": true, - "IXON": true, - "IfAddrmsg": true, - "IfAnnounceMsghdr": true, - "IfData": true, - "IfInfomsg": true, - "IfMsghdr": true, - "IfaMsghdr": true, - "IfmaMsghdr": true, - "IfmaMsghdr2": true, - "ImplementsGetwd": true, - "Inet4Pktinfo": true, - "Inet6Pktinfo": true, - "InotifyAddWatch": true, - "InotifyEvent": true, - "InotifyInit": true, - "InotifyInit1": true, - "InotifyRmWatch": true, - "InterfaceAddrMessage": true, - "InterfaceAnnounceMessage": true, - "InterfaceInfo": true, - "InterfaceMessage": true, - "InterfaceMulticastAddrMessage": true, - "InvalidHandle": true, - "Ioperm": true, - "Iopl": true, - "Iovec": true, - "IpAdapterInfo": true, - "IpAddrString": true, - "IpAddressString": true, - "IpMaskString": true, - "Issetugid": true, - "KEY_ALL_ACCESS": true, - "KEY_CREATE_LINK": true, - "KEY_CREATE_SUB_KEY": true, - "KEY_ENUMERATE_SUB_KEYS": true, - "KEY_EXECUTE": true, - "KEY_NOTIFY": true, - "KEY_QUERY_VALUE": true, - "KEY_READ": true, - "KEY_SET_VALUE": true, - "KEY_WOW64_32KEY": true, - "KEY_WOW64_64KEY": true, - "KEY_WRITE": true, - "Kevent": true, - "Kevent_t": true, - "Kill": true, - "Klogctl": true, - "Kqueue": true, - "LANG_ENGLISH": true, - "LAYERED_PROTOCOL": true, - "LCNT_OVERLOAD_FLUSH": true, - "LINUX_REBOOT_CMD_CAD_OFF": true, - "LINUX_REBOOT_CMD_CAD_ON": true, - "LINUX_REBOOT_CMD_HALT": true, - "LINUX_REBOOT_CMD_KEXEC": true, - "LINUX_REBOOT_CMD_POWER_OFF": true, - "LINUX_REBOOT_CMD_RESTART": true, - "LINUX_REBOOT_CMD_RESTART2": true, - "LINUX_REBOOT_CMD_SW_SUSPEND": true, - "LINUX_REBOOT_MAGIC1": true, - "LINUX_REBOOT_MAGIC2": true, - "LOCK_EX": true, - "LOCK_NB": true, - "LOCK_SH": true, - "LOCK_UN": true, - "LazyDLL": true, - "LazyProc": true, - "Lchown": true, - "Linger": true, - "Link": true, - "Listen": true, - "Listxattr": true, - "LoadCancelIoEx": true, - "LoadConnectEx": true, - "LoadCreateSymbolicLink": true, - "LoadDLL": true, - "LoadGetAddrInfo": true, - "LoadLibrary": true, - "LoadSetFileCompletionNotificationModes": true, - "LocalFree": true, - "Log2phys_t": true, - "LookupAccountName": true, - "LookupAccountSid": true, - "LookupSID": true, - "LsfJump": true, - "LsfSocket": true, - "LsfStmt": true, - "Lstat": true, - "MADV_AUTOSYNC": true, - "MADV_CAN_REUSE": true, - "MADV_CORE": true, - "MADV_DOFORK": true, - "MADV_DONTFORK": true, - "MADV_DONTNEED": true, - "MADV_FREE": true, - "MADV_FREE_REUSABLE": true, - "MADV_FREE_REUSE": true, - "MADV_HUGEPAGE": true, - "MADV_HWPOISON": true, - "MADV_MERGEABLE": true, - "MADV_NOCORE": true, - "MADV_NOHUGEPAGE": true, - "MADV_NORMAL": true, - "MADV_NOSYNC": true, - "MADV_PROTECT": true, - "MADV_RANDOM": true, - "MADV_REMOVE": true, - "MADV_SEQUENTIAL": true, - "MADV_SPACEAVAIL": true, - "MADV_UNMERGEABLE": true, - "MADV_WILLNEED": true, - "MADV_ZERO_WIRED_PAGES": true, - "MAP_32BIT": true, - "MAP_ALIGNED_SUPER": true, - "MAP_ALIGNMENT_16MB": true, - "MAP_ALIGNMENT_1TB": true, - "MAP_ALIGNMENT_256TB": true, - "MAP_ALIGNMENT_4GB": true, - "MAP_ALIGNMENT_64KB": true, - "MAP_ALIGNMENT_64PB": true, - "MAP_ALIGNMENT_MASK": true, - "MAP_ALIGNMENT_SHIFT": true, - "MAP_ANON": true, - "MAP_ANONYMOUS": true, - "MAP_COPY": true, - "MAP_DENYWRITE": true, - "MAP_EXECUTABLE": true, - "MAP_FILE": true, - "MAP_FIXED": true, - "MAP_FLAGMASK": true, - "MAP_GROWSDOWN": true, - "MAP_HASSEMAPHORE": true, - "MAP_HUGETLB": true, - "MAP_INHERIT": true, - "MAP_INHERIT_COPY": true, - "MAP_INHERIT_DEFAULT": true, - "MAP_INHERIT_DONATE_COPY": true, - "MAP_INHERIT_NONE": true, - "MAP_INHERIT_SHARE": true, - "MAP_JIT": true, - "MAP_LOCKED": true, - "MAP_NOCACHE": true, - "MAP_NOCORE": true, - "MAP_NOEXTEND": true, - "MAP_NONBLOCK": true, - "MAP_NORESERVE": true, - "MAP_NOSYNC": true, - "MAP_POPULATE": true, - "MAP_PREFAULT_READ": true, - "MAP_PRIVATE": true, - "MAP_RENAME": true, - "MAP_RESERVED0080": true, - "MAP_RESERVED0100": true, - "MAP_SHARED": true, - "MAP_STACK": true, - "MAP_TRYFIXED": true, - "MAP_TYPE": true, - "MAP_WIRED": true, - "MAXIMUM_REPARSE_DATA_BUFFER_SIZE": true, - "MAXLEN_IFDESCR": true, - "MAXLEN_PHYSADDR": true, - "MAX_ADAPTER_ADDRESS_LENGTH": true, - "MAX_ADAPTER_DESCRIPTION_LENGTH": true, - "MAX_ADAPTER_NAME_LENGTH": true, - "MAX_COMPUTERNAME_LENGTH": true, - "MAX_INTERFACE_NAME_LEN": true, - "MAX_LONG_PATH": true, - "MAX_PATH": true, - "MAX_PROTOCOL_CHAIN": true, - "MCL_CURRENT": true, - "MCL_FUTURE": true, - "MNT_DETACH": true, - "MNT_EXPIRE": true, - "MNT_FORCE": true, - "MSG_BCAST": true, - "MSG_CMSG_CLOEXEC": true, - "MSG_COMPAT": true, - "MSG_CONFIRM": true, - "MSG_CONTROLMBUF": true, - "MSG_CTRUNC": true, - "MSG_DONTROUTE": true, - "MSG_DONTWAIT": true, - "MSG_EOF": true, - "MSG_EOR": true, - "MSG_ERRQUEUE": true, - "MSG_FASTOPEN": true, - "MSG_FIN": true, - "MSG_FLUSH": true, - "MSG_HAVEMORE": true, - "MSG_HOLD": true, - "MSG_IOVUSRSPACE": true, - "MSG_LENUSRSPACE": true, - "MSG_MCAST": true, - "MSG_MORE": true, - "MSG_NAMEMBUF": true, - "MSG_NBIO": true, - "MSG_NEEDSA": true, - "MSG_NOSIGNAL": true, - "MSG_NOTIFICATION": true, - "MSG_OOB": true, - "MSG_PEEK": true, - "MSG_PROXY": true, - "MSG_RCVMORE": true, - "MSG_RST": true, - "MSG_SEND": true, - "MSG_SYN": true, - "MSG_TRUNC": true, - "MSG_TRYHARD": true, - "MSG_USERFLAGS": true, - "MSG_WAITALL": true, - "MSG_WAITFORONE": true, - "MSG_WAITSTREAM": true, - "MS_ACTIVE": true, - "MS_ASYNC": true, - "MS_BIND": true, - "MS_DEACTIVATE": true, - "MS_DIRSYNC": true, - "MS_INVALIDATE": true, - "MS_I_VERSION": true, - "MS_KERNMOUNT": true, - "MS_KILLPAGES": true, - "MS_MANDLOCK": true, - "MS_MGC_MSK": true, - "MS_MGC_VAL": true, - "MS_MOVE": true, - "MS_NOATIME": true, - "MS_NODEV": true, - "MS_NODIRATIME": true, - "MS_NOEXEC": true, - "MS_NOSUID": true, - "MS_NOUSER": true, - "MS_POSIXACL": true, - "MS_PRIVATE": true, - "MS_RDONLY": true, - "MS_REC": true, - "MS_RELATIME": true, - "MS_REMOUNT": true, - "MS_RMT_MASK": true, - "MS_SHARED": true, - "MS_SILENT": true, - "MS_SLAVE": true, - "MS_STRICTATIME": true, - "MS_SYNC": true, - "MS_SYNCHRONOUS": true, - "MS_UNBINDABLE": true, - "Madvise": true, - "MapViewOfFile": true, - "MaxTokenInfoClass": true, - "Mclpool": true, - "MibIfRow": true, - "Mkdir": true, - "Mkdirat": true, - "Mkfifo": true, - "Mknod": true, - "Mknodat": true, - "Mlock": true, - "Mlockall": true, - "Mmap": true, - "Mount": true, - "MoveFile": true, - "Mprotect": true, - "Msghdr": true, - "Munlock": true, - "Munlockall": true, - "Munmap": true, - "MustLoadDLL": true, - "NAME_MAX": true, - "NETLINK_ADD_MEMBERSHIP": true, - "NETLINK_AUDIT": true, - "NETLINK_BROADCAST_ERROR": true, - "NETLINK_CONNECTOR": true, - "NETLINK_DNRTMSG": true, - "NETLINK_DROP_MEMBERSHIP": true, - "NETLINK_ECRYPTFS": true, - "NETLINK_FIB_LOOKUP": true, - "NETLINK_FIREWALL": true, - "NETLINK_GENERIC": true, - "NETLINK_INET_DIAG": true, - "NETLINK_IP6_FW": true, - "NETLINK_ISCSI": true, - "NETLINK_KOBJECT_UEVENT": true, - "NETLINK_NETFILTER": true, - "NETLINK_NFLOG": true, - "NETLINK_NO_ENOBUFS": true, - "NETLINK_PKTINFO": true, - "NETLINK_RDMA": true, - "NETLINK_ROUTE": true, - "NETLINK_SCSITRANSPORT": true, - "NETLINK_SELINUX": true, - "NETLINK_UNUSED": true, - "NETLINK_USERSOCK": true, - "NETLINK_XFRM": true, - "NET_RT_DUMP": true, - "NET_RT_DUMP2": true, - "NET_RT_FLAGS": true, - "NET_RT_IFLIST": true, - "NET_RT_IFLIST2": true, - "NET_RT_IFLISTL": true, - "NET_RT_IFMALIST": true, - "NET_RT_MAXID": true, - "NET_RT_OIFLIST": true, - "NET_RT_OOIFLIST": true, - "NET_RT_STAT": true, - "NET_RT_STATS": true, - "NET_RT_TABLE": true, - "NET_RT_TRASH": true, - "NLA_ALIGNTO": true, - "NLA_F_NESTED": true, - "NLA_F_NET_BYTEORDER": true, - "NLA_HDRLEN": true, - "NLMSG_ALIGNTO": true, - "NLMSG_DONE": true, - "NLMSG_ERROR": true, - "NLMSG_HDRLEN": true, - "NLMSG_MIN_TYPE": true, - "NLMSG_NOOP": true, - "NLMSG_OVERRUN": true, - "NLM_F_ACK": true, - "NLM_F_APPEND": true, - "NLM_F_ATOMIC": true, - "NLM_F_CREATE": true, - "NLM_F_DUMP": true, - "NLM_F_ECHO": true, - "NLM_F_EXCL": true, - "NLM_F_MATCH": true, - "NLM_F_MULTI": true, - "NLM_F_REPLACE": true, - "NLM_F_REQUEST": true, - "NLM_F_ROOT": true, - "NOFLSH": true, - "NOTE_ABSOLUTE": true, - "NOTE_ATTRIB": true, - "NOTE_CHILD": true, - "NOTE_DELETE": true, - "NOTE_EOF": true, - "NOTE_EXEC": true, - "NOTE_EXIT": true, - "NOTE_EXITSTATUS": true, - "NOTE_EXTEND": true, - "NOTE_FFAND": true, - "NOTE_FFCOPY": true, - "NOTE_FFCTRLMASK": true, - "NOTE_FFLAGSMASK": true, - "NOTE_FFNOP": true, - "NOTE_FFOR": true, - "NOTE_FORK": true, - "NOTE_LINK": true, - "NOTE_LOWAT": true, - "NOTE_NONE": true, - "NOTE_NSECONDS": true, - "NOTE_PCTRLMASK": true, - "NOTE_PDATAMASK": true, - "NOTE_REAP": true, - "NOTE_RENAME": true, - "NOTE_RESOURCEEND": true, - "NOTE_REVOKE": true, - "NOTE_SECONDS": true, - "NOTE_SIGNAL": true, - "NOTE_TRACK": true, - "NOTE_TRACKERR": true, - "NOTE_TRIGGER": true, - "NOTE_TRUNCATE": true, - "NOTE_USECONDS": true, - "NOTE_VM_ERROR": true, - "NOTE_VM_PRESSURE": true, - "NOTE_VM_PRESSURE_SUDDEN_TERMINATE": true, - "NOTE_VM_PRESSURE_TERMINATE": true, - "NOTE_WRITE": true, - "NameCanonical": true, - "NameCanonicalEx": true, - "NameDisplay": true, - "NameDnsDomain": true, - "NameFullyQualifiedDN": true, - "NameSamCompatible": true, - "NameServicePrincipal": true, - "NameUniqueId": true, - "NameUnknown": true, - "NameUserPrincipal": true, - "Nanosleep": true, - "NetApiBufferFree": true, - "NetGetJoinInformation": true, - "NetSetupDomainName": true, - "NetSetupUnjoined": true, - "NetSetupUnknownStatus": true, - "NetSetupWorkgroupName": true, - "NetUserGetInfo": true, - "NetlinkMessage": true, - "NetlinkRIB": true, - "NetlinkRouteAttr": true, - "NetlinkRouteRequest": true, - "NewCallback": true, - "NewCallbackCDecl": true, - "NewLazyDLL": true, - "NlAttr": true, - "NlMsgerr": true, - "NlMsghdr": true, - "NsecToFiletime": true, - "NsecToTimespec": true, - "NsecToTimeval": true, - "Ntohs": true, - "OCRNL": true, - "OFDEL": true, - "OFILL": true, - "OFIOGETBMAP": true, - "OID_PKIX_KP_SERVER_AUTH": true, - "OID_SERVER_GATED_CRYPTO": true, - "OID_SGC_NETSCAPE": true, - "OLCUC": true, - "ONLCR": true, - "ONLRET": true, - "ONOCR": true, - "ONOEOT": true, - "OPEN_ALWAYS": true, - "OPEN_EXISTING": true, - "OPOST": true, - "O_ACCMODE": true, - "O_ALERT": true, - "O_ALT_IO": true, - "O_APPEND": true, - "O_ASYNC": true, - "O_CLOEXEC": true, - "O_CREAT": true, - "O_DIRECT": true, - "O_DIRECTORY": true, - "O_DSYNC": true, - "O_EVTONLY": true, - "O_EXCL": true, - "O_EXEC": true, - "O_EXLOCK": true, - "O_FSYNC": true, - "O_LARGEFILE": true, - "O_NDELAY": true, - "O_NOATIME": true, - "O_NOCTTY": true, - "O_NOFOLLOW": true, - "O_NONBLOCK": true, - "O_NOSIGPIPE": true, - "O_POPUP": true, - "O_RDONLY": true, - "O_RDWR": true, - "O_RSYNC": true, - "O_SHLOCK": true, - "O_SYMLINK": true, - "O_SYNC": true, - "O_TRUNC": true, - "O_TTY_INIT": true, - "O_WRONLY": true, - "Open": true, - "OpenCurrentProcessToken": true, - "OpenProcess": true, - "OpenProcessToken": true, - "Openat": true, - "Overlapped": true, - "PACKET_ADD_MEMBERSHIP": true, - "PACKET_BROADCAST": true, - "PACKET_DROP_MEMBERSHIP": true, - "PACKET_FASTROUTE": true, - "PACKET_HOST": true, - "PACKET_LOOPBACK": true, - "PACKET_MR_ALLMULTI": true, - "PACKET_MR_MULTICAST": true, - "PACKET_MR_PROMISC": true, - "PACKET_MULTICAST": true, - "PACKET_OTHERHOST": true, - "PACKET_OUTGOING": true, - "PACKET_RECV_OUTPUT": true, - "PACKET_RX_RING": true, - "PACKET_STATISTICS": true, - "PAGE_EXECUTE_READ": true, - "PAGE_EXECUTE_READWRITE": true, - "PAGE_EXECUTE_WRITECOPY": true, - "PAGE_READONLY": true, - "PAGE_READWRITE": true, - "PAGE_WRITECOPY": true, - "PARENB": true, - "PARMRK": true, - "PARODD": true, - "PENDIN": true, - "PFL_HIDDEN": true, - "PFL_MATCHES_PROTOCOL_ZERO": true, - "PFL_MULTIPLE_PROTO_ENTRIES": true, - "PFL_NETWORKDIRECT_PROVIDER": true, - "PFL_RECOMMENDED_PROTO_ENTRY": true, - "PF_FLUSH": true, - "PKCS_7_ASN_ENCODING": true, - "PMC5_PIPELINE_FLUSH": true, - "PRIO_PGRP": true, - "PRIO_PROCESS": true, - "PRIO_USER": true, - "PRI_IOFLUSH": true, - "PROCESS_QUERY_INFORMATION": true, - "PROCESS_TERMINATE": true, - "PROT_EXEC": true, - "PROT_GROWSDOWN": true, - "PROT_GROWSUP": true, - "PROT_NONE": true, - "PROT_READ": true, - "PROT_WRITE": true, - "PROV_DH_SCHANNEL": true, - "PROV_DSS": true, - "PROV_DSS_DH": true, - "PROV_EC_ECDSA_FULL": true, - "PROV_EC_ECDSA_SIG": true, - "PROV_EC_ECNRA_FULL": true, - "PROV_EC_ECNRA_SIG": true, - "PROV_FORTEZZA": true, - "PROV_INTEL_SEC": true, - "PROV_MS_EXCHANGE": true, - "PROV_REPLACE_OWF": true, - "PROV_RNG": true, - "PROV_RSA_AES": true, - "PROV_RSA_FULL": true, - "PROV_RSA_SCHANNEL": true, - "PROV_RSA_SIG": true, - "PROV_SPYRUS_LYNKS": true, - "PROV_SSL": true, - "PR_CAPBSET_DROP": true, - "PR_CAPBSET_READ": true, - "PR_CLEAR_SECCOMP_FILTER": true, - "PR_ENDIAN_BIG": true, - "PR_ENDIAN_LITTLE": true, - "PR_ENDIAN_PPC_LITTLE": true, - "PR_FPEMU_NOPRINT": true, - "PR_FPEMU_SIGFPE": true, - "PR_FP_EXC_ASYNC": true, - "PR_FP_EXC_DISABLED": true, - "PR_FP_EXC_DIV": true, - "PR_FP_EXC_INV": true, - "PR_FP_EXC_NONRECOV": true, - "PR_FP_EXC_OVF": true, - "PR_FP_EXC_PRECISE": true, - "PR_FP_EXC_RES": true, - "PR_FP_EXC_SW_ENABLE": true, - "PR_FP_EXC_UND": true, - "PR_GET_DUMPABLE": true, - "PR_GET_ENDIAN": true, - "PR_GET_FPEMU": true, - "PR_GET_FPEXC": true, - "PR_GET_KEEPCAPS": true, - "PR_GET_NAME": true, - "PR_GET_PDEATHSIG": true, - "PR_GET_SECCOMP": true, - "PR_GET_SECCOMP_FILTER": true, - "PR_GET_SECUREBITS": true, - "PR_GET_TIMERSLACK": true, - "PR_GET_TIMING": true, - "PR_GET_TSC": true, - "PR_GET_UNALIGN": true, - "PR_MCE_KILL": true, - "PR_MCE_KILL_CLEAR": true, - "PR_MCE_KILL_DEFAULT": true, - "PR_MCE_KILL_EARLY": true, - "PR_MCE_KILL_GET": true, - "PR_MCE_KILL_LATE": true, - "PR_MCE_KILL_SET": true, - "PR_SECCOMP_FILTER_EVENT": true, - "PR_SECCOMP_FILTER_SYSCALL": true, - "PR_SET_DUMPABLE": true, - "PR_SET_ENDIAN": true, - "PR_SET_FPEMU": true, - "PR_SET_FPEXC": true, - "PR_SET_KEEPCAPS": true, - "PR_SET_NAME": true, - "PR_SET_PDEATHSIG": true, - "PR_SET_PTRACER": true, - "PR_SET_SECCOMP": true, - "PR_SET_SECCOMP_FILTER": true, - "PR_SET_SECUREBITS": true, - "PR_SET_TIMERSLACK": true, - "PR_SET_TIMING": true, - "PR_SET_TSC": true, - "PR_SET_UNALIGN": true, - "PR_TASK_PERF_EVENTS_DISABLE": true, - "PR_TASK_PERF_EVENTS_ENABLE": true, - "PR_TIMING_STATISTICAL": true, - "PR_TIMING_TIMESTAMP": true, - "PR_TSC_ENABLE": true, - "PR_TSC_SIGSEGV": true, - "PR_UNALIGN_NOPRINT": true, - "PR_UNALIGN_SIGBUS": true, - "PTRACE_ARCH_PRCTL": true, - "PTRACE_ATTACH": true, - "PTRACE_CONT": true, - "PTRACE_DETACH": true, - "PTRACE_EVENT_CLONE": true, - "PTRACE_EVENT_EXEC": true, - "PTRACE_EVENT_EXIT": true, - "PTRACE_EVENT_FORK": true, - "PTRACE_EVENT_VFORK": true, - "PTRACE_EVENT_VFORK_DONE": true, - "PTRACE_GETCRUNCHREGS": true, - "PTRACE_GETEVENTMSG": true, - "PTRACE_GETFPREGS": true, - "PTRACE_GETFPXREGS": true, - "PTRACE_GETHBPREGS": true, - "PTRACE_GETREGS": true, - "PTRACE_GETREGSET": true, - "PTRACE_GETSIGINFO": true, - "PTRACE_GETVFPREGS": true, - "PTRACE_GETWMMXREGS": true, - "PTRACE_GET_THREAD_AREA": true, - "PTRACE_KILL": true, - "PTRACE_OLDSETOPTIONS": true, - "PTRACE_O_MASK": true, - "PTRACE_O_TRACECLONE": true, - "PTRACE_O_TRACEEXEC": true, - "PTRACE_O_TRACEEXIT": true, - "PTRACE_O_TRACEFORK": true, - "PTRACE_O_TRACESYSGOOD": true, - "PTRACE_O_TRACEVFORK": true, - "PTRACE_O_TRACEVFORKDONE": true, - "PTRACE_PEEKDATA": true, - "PTRACE_PEEKTEXT": true, - "PTRACE_PEEKUSR": true, - "PTRACE_POKEDATA": true, - "PTRACE_POKETEXT": true, - "PTRACE_POKEUSR": true, - "PTRACE_SETCRUNCHREGS": true, - "PTRACE_SETFPREGS": true, - "PTRACE_SETFPXREGS": true, - "PTRACE_SETHBPREGS": true, - "PTRACE_SETOPTIONS": true, - "PTRACE_SETREGS": true, - "PTRACE_SETREGSET": true, - "PTRACE_SETSIGINFO": true, - "PTRACE_SETVFPREGS": true, - "PTRACE_SETWMMXREGS": true, - "PTRACE_SET_SYSCALL": true, - "PTRACE_SET_THREAD_AREA": true, - "PTRACE_SINGLEBLOCK": true, - "PTRACE_SINGLESTEP": true, - "PTRACE_SYSCALL": true, - "PTRACE_SYSEMU": true, - "PTRACE_SYSEMU_SINGLESTEP": true, - "PTRACE_TRACEME": true, - "PT_ATTACH": true, - "PT_ATTACHEXC": true, - "PT_CONTINUE": true, - "PT_DATA_ADDR": true, - "PT_DENY_ATTACH": true, - "PT_DETACH": true, - "PT_FIRSTMACH": true, - "PT_FORCEQUOTA": true, - "PT_KILL": true, - "PT_MASK": true, - "PT_READ_D": true, - "PT_READ_I": true, - "PT_READ_U": true, - "PT_SIGEXC": true, - "PT_STEP": true, - "PT_TEXT_ADDR": true, - "PT_TEXT_END_ADDR": true, - "PT_THUPDATE": true, - "PT_TRACE_ME": true, - "PT_WRITE_D": true, - "PT_WRITE_I": true, - "PT_WRITE_U": true, - "ParseDirent": true, - "ParseNetlinkMessage": true, - "ParseNetlinkRouteAttr": true, - "ParseRoutingMessage": true, - "ParseRoutingSockaddr": true, - "ParseSocketControlMessage": true, - "ParseUnixCredentials": true, - "ParseUnixRights": true, - "PathMax": true, - "Pathconf": true, - "Pause": true, - "Pipe": true, - "Pipe2": true, - "PivotRoot": true, - "Pointer": true, - "PostQueuedCompletionStatus": true, - "Pread": true, - "Proc": true, - "ProcAttr": true, - "Process32First": true, - "Process32Next": true, - "ProcessEntry32": true, - "ProcessInformation": true, - "Protoent": true, - "PtraceAttach": true, - "PtraceCont": true, - "PtraceDetach": true, - "PtraceGetEventMsg": true, - "PtraceGetRegs": true, - "PtracePeekData": true, - "PtracePeekText": true, - "PtracePokeData": true, - "PtracePokeText": true, - "PtraceRegs": true, - "PtraceSetOptions": true, - "PtraceSetRegs": true, - "PtraceSingleStep": true, - "PtraceSyscall": true, - "Pwrite": true, - "REG_BINARY": true, - "REG_DWORD": true, - "REG_DWORD_BIG_ENDIAN": true, - "REG_DWORD_LITTLE_ENDIAN": true, - "REG_EXPAND_SZ": true, - "REG_FULL_RESOURCE_DESCRIPTOR": true, - "REG_LINK": true, - "REG_MULTI_SZ": true, - "REG_NONE": true, - "REG_QWORD": true, - "REG_QWORD_LITTLE_ENDIAN": true, - "REG_RESOURCE_LIST": true, - "REG_RESOURCE_REQUIREMENTS_LIST": true, - "REG_SZ": true, - "RLIMIT_AS": true, - "RLIMIT_CORE": true, - "RLIMIT_CPU": true, - "RLIMIT_DATA": true, - "RLIMIT_FSIZE": true, - "RLIMIT_NOFILE": true, - "RLIMIT_STACK": true, - "RLIM_INFINITY": true, - "RTAX_ADVMSS": true, - "RTAX_AUTHOR": true, - "RTAX_BRD": true, - "RTAX_CWND": true, - "RTAX_DST": true, - "RTAX_FEATURES": true, - "RTAX_FEATURE_ALLFRAG": true, - "RTAX_FEATURE_ECN": true, - "RTAX_FEATURE_SACK": true, - "RTAX_FEATURE_TIMESTAMP": true, - "RTAX_GATEWAY": true, - "RTAX_GENMASK": true, - "RTAX_HOPLIMIT": true, - "RTAX_IFA": true, - "RTAX_IFP": true, - "RTAX_INITCWND": true, - "RTAX_INITRWND": true, - "RTAX_LABEL": true, - "RTAX_LOCK": true, - "RTAX_MAX": true, - "RTAX_MTU": true, - "RTAX_NETMASK": true, - "RTAX_REORDERING": true, - "RTAX_RTO_MIN": true, - "RTAX_RTT": true, - "RTAX_RTTVAR": true, - "RTAX_SRC": true, - "RTAX_SRCMASK": true, - "RTAX_SSTHRESH": true, - "RTAX_TAG": true, - "RTAX_UNSPEC": true, - "RTAX_WINDOW": true, - "RTA_ALIGNTO": true, - "RTA_AUTHOR": true, - "RTA_BRD": true, - "RTA_CACHEINFO": true, - "RTA_DST": true, - "RTA_FLOW": true, - "RTA_GATEWAY": true, - "RTA_GENMASK": true, - "RTA_IFA": true, - "RTA_IFP": true, - "RTA_IIF": true, - "RTA_LABEL": true, - "RTA_MAX": true, - "RTA_METRICS": true, - "RTA_MULTIPATH": true, - "RTA_NETMASK": true, - "RTA_OIF": true, - "RTA_PREFSRC": true, - "RTA_PRIORITY": true, - "RTA_SRC": true, - "RTA_SRCMASK": true, - "RTA_TABLE": true, - "RTA_TAG": true, - "RTA_UNSPEC": true, - "RTCF_DIRECTSRC": true, - "RTCF_DOREDIRECT": true, - "RTCF_LOG": true, - "RTCF_MASQ": true, - "RTCF_NAT": true, - "RTCF_VALVE": true, - "RTF_ADDRCLASSMASK": true, - "RTF_ADDRCONF": true, - "RTF_ALLONLINK": true, - "RTF_ANNOUNCE": true, - "RTF_BLACKHOLE": true, - "RTF_BROADCAST": true, - "RTF_CACHE": true, - "RTF_CLONED": true, - "RTF_CLONING": true, - "RTF_CONDEMNED": true, - "RTF_DEFAULT": true, - "RTF_DELCLONE": true, - "RTF_DONE": true, - "RTF_DYNAMIC": true, - "RTF_FLOW": true, - "RTF_FMASK": true, - "RTF_GATEWAY": true, - "RTF_GWFLAG_COMPAT": true, - "RTF_HOST": true, - "RTF_IFREF": true, - "RTF_IFSCOPE": true, - "RTF_INTERFACE": true, - "RTF_IRTT": true, - "RTF_LINKRT": true, - "RTF_LLDATA": true, - "RTF_LLINFO": true, - "RTF_LOCAL": true, - "RTF_MASK": true, - "RTF_MODIFIED": true, - "RTF_MPATH": true, - "RTF_MPLS": true, - "RTF_MSS": true, - "RTF_MTU": true, - "RTF_MULTICAST": true, - "RTF_NAT": true, - "RTF_NOFORWARD": true, - "RTF_NONEXTHOP": true, - "RTF_NOPMTUDISC": true, - "RTF_PERMANENT_ARP": true, - "RTF_PINNED": true, - "RTF_POLICY": true, - "RTF_PRCLONING": true, - "RTF_PROTO1": true, - "RTF_PROTO2": true, - "RTF_PROTO3": true, - "RTF_REINSTATE": true, - "RTF_REJECT": true, - "RTF_RNH_LOCKED": true, - "RTF_SOURCE": true, - "RTF_SRC": true, - "RTF_STATIC": true, - "RTF_STICKY": true, - "RTF_THROW": true, - "RTF_TUNNEL": true, - "RTF_UP": true, - "RTF_USETRAILERS": true, - "RTF_WASCLONED": true, - "RTF_WINDOW": true, - "RTF_XRESOLVE": true, - "RTM_ADD": true, - "RTM_BASE": true, - "RTM_CHANGE": true, - "RTM_CHGADDR": true, - "RTM_DELACTION": true, - "RTM_DELADDR": true, - "RTM_DELADDRLABEL": true, - "RTM_DELETE": true, - "RTM_DELLINK": true, - "RTM_DELMADDR": true, - "RTM_DELNEIGH": true, - "RTM_DELQDISC": true, - "RTM_DELROUTE": true, - "RTM_DELRULE": true, - "RTM_DELTCLASS": true, - "RTM_DELTFILTER": true, - "RTM_DESYNC": true, - "RTM_F_CLONED": true, - "RTM_F_EQUALIZE": true, - "RTM_F_NOTIFY": true, - "RTM_F_PREFIX": true, - "RTM_GET": true, - "RTM_GET2": true, - "RTM_GETACTION": true, - "RTM_GETADDR": true, - "RTM_GETADDRLABEL": true, - "RTM_GETANYCAST": true, - "RTM_GETDCB": true, - "RTM_GETLINK": true, - "RTM_GETMULTICAST": true, - "RTM_GETNEIGH": true, - "RTM_GETNEIGHTBL": true, - "RTM_GETQDISC": true, - "RTM_GETROUTE": true, - "RTM_GETRULE": true, - "RTM_GETTCLASS": true, - "RTM_GETTFILTER": true, - "RTM_IEEE80211": true, - "RTM_IFANNOUNCE": true, - "RTM_IFINFO": true, - "RTM_IFINFO2": true, - "RTM_LLINFO_UPD": true, - "RTM_LOCK": true, - "RTM_LOSING": true, - "RTM_MAX": true, - "RTM_MAXSIZE": true, - "RTM_MISS": true, - "RTM_NEWACTION": true, - "RTM_NEWADDR": true, - "RTM_NEWADDRLABEL": true, - "RTM_NEWLINK": true, - "RTM_NEWMADDR": true, - "RTM_NEWMADDR2": true, - "RTM_NEWNDUSEROPT": true, - "RTM_NEWNEIGH": true, - "RTM_NEWNEIGHTBL": true, - "RTM_NEWPREFIX": true, - "RTM_NEWQDISC": true, - "RTM_NEWROUTE": true, - "RTM_NEWRULE": true, - "RTM_NEWTCLASS": true, - "RTM_NEWTFILTER": true, - "RTM_NR_FAMILIES": true, - "RTM_NR_MSGTYPES": true, - "RTM_OIFINFO": true, - "RTM_OLDADD": true, - "RTM_OLDDEL": true, - "RTM_OOIFINFO": true, - "RTM_REDIRECT": true, - "RTM_RESOLVE": true, - "RTM_RTTUNIT": true, - "RTM_SETDCB": true, - "RTM_SETGATE": true, - "RTM_SETLINK": true, - "RTM_SETNEIGHTBL": true, - "RTM_VERSION": true, - "RTNH_ALIGNTO": true, - "RTNH_F_DEAD": true, - "RTNH_F_ONLINK": true, - "RTNH_F_PERVASIVE": true, - "RTNLGRP_IPV4_IFADDR": true, - "RTNLGRP_IPV4_MROUTE": true, - "RTNLGRP_IPV4_ROUTE": true, - "RTNLGRP_IPV4_RULE": true, - "RTNLGRP_IPV6_IFADDR": true, - "RTNLGRP_IPV6_IFINFO": true, - "RTNLGRP_IPV6_MROUTE": true, - "RTNLGRP_IPV6_PREFIX": true, - "RTNLGRP_IPV6_ROUTE": true, - "RTNLGRP_IPV6_RULE": true, - "RTNLGRP_LINK": true, - "RTNLGRP_ND_USEROPT": true, - "RTNLGRP_NEIGH": true, - "RTNLGRP_NONE": true, - "RTNLGRP_NOTIFY": true, - "RTNLGRP_TC": true, - "RTN_ANYCAST": true, - "RTN_BLACKHOLE": true, - "RTN_BROADCAST": true, - "RTN_LOCAL": true, - "RTN_MAX": true, - "RTN_MULTICAST": true, - "RTN_NAT": true, - "RTN_PROHIBIT": true, - "RTN_THROW": true, - "RTN_UNICAST": true, - "RTN_UNREACHABLE": true, - "RTN_UNSPEC": true, - "RTN_XRESOLVE": true, - "RTPROT_BIRD": true, - "RTPROT_BOOT": true, - "RTPROT_DHCP": true, - "RTPROT_DNROUTED": true, - "RTPROT_GATED": true, - "RTPROT_KERNEL": true, - "RTPROT_MRT": true, - "RTPROT_NTK": true, - "RTPROT_RA": true, - "RTPROT_REDIRECT": true, - "RTPROT_STATIC": true, - "RTPROT_UNSPEC": true, - "RTPROT_XORP": true, - "RTPROT_ZEBRA": true, - "RTV_EXPIRE": true, - "RTV_HOPCOUNT": true, - "RTV_MTU": true, - "RTV_RPIPE": true, - "RTV_RTT": true, - "RTV_RTTVAR": true, - "RTV_SPIPE": true, - "RTV_SSTHRESH": true, - "RTV_WEIGHT": true, - "RT_CACHING_CONTEXT": true, - "RT_CLASS_DEFAULT": true, - "RT_CLASS_LOCAL": true, - "RT_CLASS_MAIN": true, - "RT_CLASS_MAX": true, - "RT_CLASS_UNSPEC": true, - "RT_DEFAULT_FIB": true, - "RT_NORTREF": true, - "RT_SCOPE_HOST": true, - "RT_SCOPE_LINK": true, - "RT_SCOPE_NOWHERE": true, - "RT_SCOPE_SITE": true, - "RT_SCOPE_UNIVERSE": true, - "RT_TABLEID_MAX": true, - "RT_TABLE_COMPAT": true, - "RT_TABLE_DEFAULT": true, - "RT_TABLE_LOCAL": true, - "RT_TABLE_MAIN": true, - "RT_TABLE_MAX": true, - "RT_TABLE_UNSPEC": true, - "RUSAGE_CHILDREN": true, - "RUSAGE_SELF": true, - "RUSAGE_THREAD": true, - "Radvisory_t": true, - "RawConn": true, - "RawSockaddr": true, - "RawSockaddrAny": true, - "RawSockaddrDatalink": true, - "RawSockaddrInet4": true, - "RawSockaddrInet6": true, - "RawSockaddrLinklayer": true, - "RawSockaddrNetlink": true, - "RawSockaddrUnix": true, - "RawSyscall": true, - "RawSyscall6": true, - "Read": true, - "ReadConsole": true, - "ReadDirectoryChanges": true, - "ReadDirent": true, - "ReadFile": true, - "Readlink": true, - "Reboot": true, - "Recvfrom": true, - "Recvmsg": true, - "RegCloseKey": true, - "RegEnumKeyEx": true, - "RegOpenKeyEx": true, - "RegQueryInfoKey": true, - "RegQueryValueEx": true, - "RemoveDirectory": true, - "Removexattr": true, - "Rename": true, - "Renameat": true, - "Revoke": true, - "Rlimit": true, - "Rmdir": true, - "RouteMessage": true, - "RouteRIB": true, - "RtAttr": true, - "RtGenmsg": true, - "RtMetrics": true, - "RtMsg": true, - "RtMsghdr": true, - "RtNexthop": true, - "Rusage": true, - "SCM_BINTIME": true, - "SCM_CREDENTIALS": true, - "SCM_CREDS": true, - "SCM_RIGHTS": true, - "SCM_TIMESTAMP": true, - "SCM_TIMESTAMPING": true, - "SCM_TIMESTAMPNS": true, - "SCM_TIMESTAMP_MONOTONIC": true, - "SHUT_RD": true, - "SHUT_RDWR": true, - "SHUT_WR": true, - "SID": true, - "SIDAndAttributes": true, - "SIGABRT": true, - "SIGALRM": true, - "SIGBUS": true, - "SIGCHLD": true, - "SIGCLD": true, - "SIGCONT": true, - "SIGEMT": true, - "SIGFPE": true, - "SIGHUP": true, - "SIGILL": true, - "SIGINFO": true, - "SIGINT": true, - "SIGIO": true, - "SIGIOT": true, - "SIGKILL": true, - "SIGLIBRT": true, - "SIGLWP": true, - "SIGPIPE": true, - "SIGPOLL": true, - "SIGPROF": true, - "SIGPWR": true, - "SIGQUIT": true, - "SIGSEGV": true, - "SIGSTKFLT": true, - "SIGSTOP": true, - "SIGSYS": true, - "SIGTERM": true, - "SIGTHR": true, - "SIGTRAP": true, - "SIGTSTP": true, - "SIGTTIN": true, - "SIGTTOU": true, - "SIGUNUSED": true, - "SIGURG": true, - "SIGUSR1": true, - "SIGUSR2": true, - "SIGVTALRM": true, - "SIGWINCH": true, - "SIGXCPU": true, - "SIGXFSZ": true, - "SIOCADDDLCI": true, - "SIOCADDMULTI": true, - "SIOCADDRT": true, - "SIOCAIFADDR": true, - "SIOCAIFGROUP": true, - "SIOCALIFADDR": true, - "SIOCARPIPLL": true, - "SIOCATMARK": true, - "SIOCAUTOADDR": true, - "SIOCAUTONETMASK": true, - "SIOCBRDGADD": true, - "SIOCBRDGADDS": true, - "SIOCBRDGARL": true, - "SIOCBRDGDADDR": true, - "SIOCBRDGDEL": true, - "SIOCBRDGDELS": true, - "SIOCBRDGFLUSH": true, - "SIOCBRDGFRL": true, - "SIOCBRDGGCACHE": true, - "SIOCBRDGGFD": true, - "SIOCBRDGGHT": true, - "SIOCBRDGGIFFLGS": true, - "SIOCBRDGGMA": true, - "SIOCBRDGGPARAM": true, - "SIOCBRDGGPRI": true, - "SIOCBRDGGRL": true, - "SIOCBRDGGSIFS": true, - "SIOCBRDGGTO": true, - "SIOCBRDGIFS": true, - "SIOCBRDGRTS": true, - "SIOCBRDGSADDR": true, - "SIOCBRDGSCACHE": true, - "SIOCBRDGSFD": true, - "SIOCBRDGSHT": true, - "SIOCBRDGSIFCOST": true, - "SIOCBRDGSIFFLGS": true, - "SIOCBRDGSIFPRIO": true, - "SIOCBRDGSMA": true, - "SIOCBRDGSPRI": true, - "SIOCBRDGSPROTO": true, - "SIOCBRDGSTO": true, - "SIOCBRDGSTXHC": true, - "SIOCDARP": true, - "SIOCDELDLCI": true, - "SIOCDELMULTI": true, - "SIOCDELRT": true, - "SIOCDEVPRIVATE": true, - "SIOCDIFADDR": true, - "SIOCDIFGROUP": true, - "SIOCDIFPHYADDR": true, - "SIOCDLIFADDR": true, - "SIOCDRARP": true, - "SIOCGARP": true, - "SIOCGDRVSPEC": true, - "SIOCGETKALIVE": true, - "SIOCGETLABEL": true, - "SIOCGETPFLOW": true, - "SIOCGETPFSYNC": true, - "SIOCGETSGCNT": true, - "SIOCGETVIFCNT": true, - "SIOCGETVLAN": true, - "SIOCGHIWAT": true, - "SIOCGIFADDR": true, - "SIOCGIFADDRPREF": true, - "SIOCGIFALIAS": true, - "SIOCGIFALTMTU": true, - "SIOCGIFASYNCMAP": true, - "SIOCGIFBOND": true, - "SIOCGIFBR": true, - "SIOCGIFBRDADDR": true, - "SIOCGIFCAP": true, - "SIOCGIFCONF": true, - "SIOCGIFCOUNT": true, - "SIOCGIFDATA": true, - "SIOCGIFDESCR": true, - "SIOCGIFDEVMTU": true, - "SIOCGIFDLT": true, - "SIOCGIFDSTADDR": true, - "SIOCGIFENCAP": true, - "SIOCGIFFIB": true, - "SIOCGIFFLAGS": true, - "SIOCGIFGATTR": true, - "SIOCGIFGENERIC": true, - "SIOCGIFGMEMB": true, - "SIOCGIFGROUP": true, - "SIOCGIFHARDMTU": true, - "SIOCGIFHWADDR": true, - "SIOCGIFINDEX": true, - "SIOCGIFKPI": true, - "SIOCGIFMAC": true, - "SIOCGIFMAP": true, - "SIOCGIFMEDIA": true, - "SIOCGIFMEM": true, - "SIOCGIFMETRIC": true, - "SIOCGIFMTU": true, - "SIOCGIFNAME": true, - "SIOCGIFNETMASK": true, - "SIOCGIFPDSTADDR": true, - "SIOCGIFPFLAGS": true, - "SIOCGIFPHYS": true, - "SIOCGIFPRIORITY": true, - "SIOCGIFPSRCADDR": true, - "SIOCGIFRDOMAIN": true, - "SIOCGIFRTLABEL": true, - "SIOCGIFSLAVE": true, - "SIOCGIFSTATUS": true, - "SIOCGIFTIMESLOT": true, - "SIOCGIFTXQLEN": true, - "SIOCGIFVLAN": true, - "SIOCGIFWAKEFLAGS": true, - "SIOCGIFXFLAGS": true, - "SIOCGLIFADDR": true, - "SIOCGLIFPHYADDR": true, - "SIOCGLIFPHYRTABLE": true, - "SIOCGLIFPHYTTL": true, - "SIOCGLINKSTR": true, - "SIOCGLOWAT": true, - "SIOCGPGRP": true, - "SIOCGPRIVATE_0": true, - "SIOCGPRIVATE_1": true, - "SIOCGRARP": true, - "SIOCGSPPPPARAMS": true, - "SIOCGSTAMP": true, - "SIOCGSTAMPNS": true, - "SIOCGVH": true, - "SIOCGVNETID": true, - "SIOCIFCREATE": true, - "SIOCIFCREATE2": true, - "SIOCIFDESTROY": true, - "SIOCIFGCLONERS": true, - "SIOCINITIFADDR": true, - "SIOCPROTOPRIVATE": true, - "SIOCRSLVMULTI": true, - "SIOCRTMSG": true, - "SIOCSARP": true, - "SIOCSDRVSPEC": true, - "SIOCSETKALIVE": true, - "SIOCSETLABEL": true, - "SIOCSETPFLOW": true, - "SIOCSETPFSYNC": true, - "SIOCSETVLAN": true, - "SIOCSHIWAT": true, - "SIOCSIFADDR": true, - "SIOCSIFADDRPREF": true, - "SIOCSIFALTMTU": true, - "SIOCSIFASYNCMAP": true, - "SIOCSIFBOND": true, - "SIOCSIFBR": true, - "SIOCSIFBRDADDR": true, - "SIOCSIFCAP": true, - "SIOCSIFDESCR": true, - "SIOCSIFDSTADDR": true, - "SIOCSIFENCAP": true, - "SIOCSIFFIB": true, - "SIOCSIFFLAGS": true, - "SIOCSIFGATTR": true, - "SIOCSIFGENERIC": true, - "SIOCSIFHWADDR": true, - "SIOCSIFHWBROADCAST": true, - "SIOCSIFKPI": true, - "SIOCSIFLINK": true, - "SIOCSIFLLADDR": true, - "SIOCSIFMAC": true, - "SIOCSIFMAP": true, - "SIOCSIFMEDIA": true, - "SIOCSIFMEM": true, - "SIOCSIFMETRIC": true, - "SIOCSIFMTU": true, - "SIOCSIFNAME": true, - "SIOCSIFNETMASK": true, - "SIOCSIFPFLAGS": true, - "SIOCSIFPHYADDR": true, - "SIOCSIFPHYS": true, - "SIOCSIFPRIORITY": true, - "SIOCSIFRDOMAIN": true, - "SIOCSIFRTLABEL": true, - "SIOCSIFRVNET": true, - "SIOCSIFSLAVE": true, - "SIOCSIFTIMESLOT": true, - "SIOCSIFTXQLEN": true, - "SIOCSIFVLAN": true, - "SIOCSIFVNET": true, - "SIOCSIFXFLAGS": true, - "SIOCSLIFPHYADDR": true, - "SIOCSLIFPHYRTABLE": true, - "SIOCSLIFPHYTTL": true, - "SIOCSLINKSTR": true, - "SIOCSLOWAT": true, - "SIOCSPGRP": true, - "SIOCSRARP": true, - "SIOCSSPPPPARAMS": true, - "SIOCSVH": true, - "SIOCSVNETID": true, - "SIOCZIFDATA": true, - "SIO_GET_EXTENSION_FUNCTION_POINTER": true, - "SIO_GET_INTERFACE_LIST": true, - "SIO_KEEPALIVE_VALS": true, - "SIO_UDP_CONNRESET": true, - "SOCK_CLOEXEC": true, - "SOCK_DCCP": true, - "SOCK_DGRAM": true, - "SOCK_FLAGS_MASK": true, - "SOCK_MAXADDRLEN": true, - "SOCK_NONBLOCK": true, - "SOCK_NOSIGPIPE": true, - "SOCK_PACKET": true, - "SOCK_RAW": true, - "SOCK_RDM": true, - "SOCK_SEQPACKET": true, - "SOCK_STREAM": true, - "SOL_AAL": true, - "SOL_ATM": true, - "SOL_DECNET": true, - "SOL_ICMPV6": true, - "SOL_IP": true, - "SOL_IPV6": true, - "SOL_IRDA": true, - "SOL_PACKET": true, - "SOL_RAW": true, - "SOL_SOCKET": true, - "SOL_TCP": true, - "SOL_X25": true, - "SOMAXCONN": true, - "SO_ACCEPTCONN": true, - "SO_ACCEPTFILTER": true, - "SO_ATTACH_FILTER": true, - "SO_BINDANY": true, - "SO_BINDTODEVICE": true, - "SO_BINTIME": true, - "SO_BROADCAST": true, - "SO_BSDCOMPAT": true, - "SO_DEBUG": true, - "SO_DETACH_FILTER": true, - "SO_DOMAIN": true, - "SO_DONTROUTE": true, - "SO_DONTTRUNC": true, - "SO_ERROR": true, - "SO_KEEPALIVE": true, - "SO_LABEL": true, - "SO_LINGER": true, - "SO_LINGER_SEC": true, - "SO_LISTENINCQLEN": true, - "SO_LISTENQLEN": true, - "SO_LISTENQLIMIT": true, - "SO_MARK": true, - "SO_NETPROC": true, - "SO_NKE": true, - "SO_NOADDRERR": true, - "SO_NOHEADER": true, - "SO_NOSIGPIPE": true, - "SO_NOTIFYCONFLICT": true, - "SO_NO_CHECK": true, - "SO_NO_DDP": true, - "SO_NO_OFFLOAD": true, - "SO_NP_EXTENSIONS": true, - "SO_NREAD": true, - "SO_NWRITE": true, - "SO_OOBINLINE": true, - "SO_OVERFLOWED": true, - "SO_PASSCRED": true, - "SO_PASSSEC": true, - "SO_PEERCRED": true, - "SO_PEERLABEL": true, - "SO_PEERNAME": true, - "SO_PEERSEC": true, - "SO_PRIORITY": true, - "SO_PROTOCOL": true, - "SO_PROTOTYPE": true, - "SO_RANDOMPORT": true, - "SO_RCVBUF": true, - "SO_RCVBUFFORCE": true, - "SO_RCVLOWAT": true, - "SO_RCVTIMEO": true, - "SO_RESTRICTIONS": true, - "SO_RESTRICT_DENYIN": true, - "SO_RESTRICT_DENYOUT": true, - "SO_RESTRICT_DENYSET": true, - "SO_REUSEADDR": true, - "SO_REUSEPORT": true, - "SO_REUSESHAREUID": true, - "SO_RTABLE": true, - "SO_RXQ_OVFL": true, - "SO_SECURITY_AUTHENTICATION": true, - "SO_SECURITY_ENCRYPTION_NETWORK": true, - "SO_SECURITY_ENCRYPTION_TRANSPORT": true, - "SO_SETFIB": true, - "SO_SNDBUF": true, - "SO_SNDBUFFORCE": true, - "SO_SNDLOWAT": true, - "SO_SNDTIMEO": true, - "SO_SPLICE": true, - "SO_TIMESTAMP": true, - "SO_TIMESTAMPING": true, - "SO_TIMESTAMPNS": true, - "SO_TIMESTAMP_MONOTONIC": true, - "SO_TYPE": true, - "SO_UPCALLCLOSEWAIT": true, - "SO_UPDATE_ACCEPT_CONTEXT": true, - "SO_UPDATE_CONNECT_CONTEXT": true, - "SO_USELOOPBACK": true, - "SO_USER_COOKIE": true, - "SO_VENDOR": true, - "SO_WANTMORE": true, - "SO_WANTOOBFLAG": true, - "SSLExtraCertChainPolicyPara": true, - "STANDARD_RIGHTS_ALL": true, - "STANDARD_RIGHTS_EXECUTE": true, - "STANDARD_RIGHTS_READ": true, - "STANDARD_RIGHTS_REQUIRED": true, - "STANDARD_RIGHTS_WRITE": true, - "STARTF_USESHOWWINDOW": true, - "STARTF_USESTDHANDLES": true, - "STD_ERROR_HANDLE": true, - "STD_INPUT_HANDLE": true, - "STD_OUTPUT_HANDLE": true, - "SUBLANG_ENGLISH_US": true, - "SW_FORCEMINIMIZE": true, - "SW_HIDE": true, - "SW_MAXIMIZE": true, - "SW_MINIMIZE": true, - "SW_NORMAL": true, - "SW_RESTORE": true, - "SW_SHOW": true, - "SW_SHOWDEFAULT": true, - "SW_SHOWMAXIMIZED": true, - "SW_SHOWMINIMIZED": true, - "SW_SHOWMINNOACTIVE": true, - "SW_SHOWNA": true, - "SW_SHOWNOACTIVATE": true, - "SW_SHOWNORMAL": true, - "SYMBOLIC_LINK_FLAG_DIRECTORY": true, - "SYNCHRONIZE": true, - "SYSCTL_VERSION": true, - "SYSCTL_VERS_0": true, - "SYSCTL_VERS_1": true, - "SYSCTL_VERS_MASK": true, - "SYS_ABORT2": true, - "SYS_ACCEPT": true, - "SYS_ACCEPT4": true, - "SYS_ACCEPT_NOCANCEL": true, - "SYS_ACCESS": true, - "SYS_ACCESS_EXTENDED": true, - "SYS_ACCT": true, - "SYS_ADD_KEY": true, - "SYS_ADD_PROFIL": true, - "SYS_ADJFREQ": true, - "SYS_ADJTIME": true, - "SYS_ADJTIMEX": true, - "SYS_AFS_SYSCALL": true, - "SYS_AIO_CANCEL": true, - "SYS_AIO_ERROR": true, - "SYS_AIO_FSYNC": true, - "SYS_AIO_READ": true, - "SYS_AIO_RETURN": true, - "SYS_AIO_SUSPEND": true, - "SYS_AIO_SUSPEND_NOCANCEL": true, - "SYS_AIO_WRITE": true, - "SYS_ALARM": true, - "SYS_ARCH_PRCTL": true, - "SYS_ARM_FADVISE64_64": true, - "SYS_ARM_SYNC_FILE_RANGE": true, - "SYS_ATGETMSG": true, - "SYS_ATPGETREQ": true, - "SYS_ATPGETRSP": true, - "SYS_ATPSNDREQ": true, - "SYS_ATPSNDRSP": true, - "SYS_ATPUTMSG": true, - "SYS_ATSOCKET": true, - "SYS_AUDIT": true, - "SYS_AUDITCTL": true, - "SYS_AUDITON": true, - "SYS_AUDIT_SESSION_JOIN": true, - "SYS_AUDIT_SESSION_PORT": true, - "SYS_AUDIT_SESSION_SELF": true, - "SYS_BDFLUSH": true, - "SYS_BIND": true, - "SYS_BINDAT": true, - "SYS_BREAK": true, - "SYS_BRK": true, - "SYS_BSDTHREAD_CREATE": true, - "SYS_BSDTHREAD_REGISTER": true, - "SYS_BSDTHREAD_TERMINATE": true, - "SYS_CAPGET": true, - "SYS_CAPSET": true, - "SYS_CAP_ENTER": true, - "SYS_CAP_FCNTLS_GET": true, - "SYS_CAP_FCNTLS_LIMIT": true, - "SYS_CAP_GETMODE": true, - "SYS_CAP_GETRIGHTS": true, - "SYS_CAP_IOCTLS_GET": true, - "SYS_CAP_IOCTLS_LIMIT": true, - "SYS_CAP_NEW": true, - "SYS_CAP_RIGHTS_GET": true, - "SYS_CAP_RIGHTS_LIMIT": true, - "SYS_CHDIR": true, - "SYS_CHFLAGS": true, - "SYS_CHFLAGSAT": true, - "SYS_CHMOD": true, - "SYS_CHMOD_EXTENDED": true, - "SYS_CHOWN": true, - "SYS_CHOWN32": true, - "SYS_CHROOT": true, - "SYS_CHUD": true, - "SYS_CLOCK_ADJTIME": true, - "SYS_CLOCK_GETCPUCLOCKID2": true, - "SYS_CLOCK_GETRES": true, - "SYS_CLOCK_GETTIME": true, - "SYS_CLOCK_NANOSLEEP": true, - "SYS_CLOCK_SETTIME": true, - "SYS_CLONE": true, - "SYS_CLOSE": true, - "SYS_CLOSEFROM": true, - "SYS_CLOSE_NOCANCEL": true, - "SYS_CONNECT": true, - "SYS_CONNECTAT": true, - "SYS_CONNECT_NOCANCEL": true, - "SYS_COPYFILE": true, - "SYS_CPUSET": true, - "SYS_CPUSET_GETAFFINITY": true, - "SYS_CPUSET_GETID": true, - "SYS_CPUSET_SETAFFINITY": true, - "SYS_CPUSET_SETID": true, - "SYS_CREAT": true, - "SYS_CREATE_MODULE": true, - "SYS_CSOPS": true, - "SYS_DELETE": true, - "SYS_DELETE_MODULE": true, - "SYS_DUP": true, - "SYS_DUP2": true, - "SYS_DUP3": true, - "SYS_EACCESS": true, - "SYS_EPOLL_CREATE": true, - "SYS_EPOLL_CREATE1": true, - "SYS_EPOLL_CTL": true, - "SYS_EPOLL_CTL_OLD": true, - "SYS_EPOLL_PWAIT": true, - "SYS_EPOLL_WAIT": true, - "SYS_EPOLL_WAIT_OLD": true, - "SYS_EVENTFD": true, - "SYS_EVENTFD2": true, - "SYS_EXCHANGEDATA": true, - "SYS_EXECVE": true, - "SYS_EXIT": true, - "SYS_EXIT_GROUP": true, - "SYS_EXTATTRCTL": true, - "SYS_EXTATTR_DELETE_FD": true, - "SYS_EXTATTR_DELETE_FILE": true, - "SYS_EXTATTR_DELETE_LINK": true, - "SYS_EXTATTR_GET_FD": true, - "SYS_EXTATTR_GET_FILE": true, - "SYS_EXTATTR_GET_LINK": true, - "SYS_EXTATTR_LIST_FD": true, - "SYS_EXTATTR_LIST_FILE": true, - "SYS_EXTATTR_LIST_LINK": true, - "SYS_EXTATTR_SET_FD": true, - "SYS_EXTATTR_SET_FILE": true, - "SYS_EXTATTR_SET_LINK": true, - "SYS_FACCESSAT": true, - "SYS_FADVISE64": true, - "SYS_FADVISE64_64": true, - "SYS_FALLOCATE": true, - "SYS_FANOTIFY_INIT": true, - "SYS_FANOTIFY_MARK": true, - "SYS_FCHDIR": true, - "SYS_FCHFLAGS": true, - "SYS_FCHMOD": true, - "SYS_FCHMODAT": true, - "SYS_FCHMOD_EXTENDED": true, - "SYS_FCHOWN": true, - "SYS_FCHOWN32": true, - "SYS_FCHOWNAT": true, - "SYS_FCHROOT": true, - "SYS_FCNTL": true, - "SYS_FCNTL64": true, - "SYS_FCNTL_NOCANCEL": true, - "SYS_FDATASYNC": true, - "SYS_FEXECVE": true, - "SYS_FFCLOCK_GETCOUNTER": true, - "SYS_FFCLOCK_GETESTIMATE": true, - "SYS_FFCLOCK_SETESTIMATE": true, - "SYS_FFSCTL": true, - "SYS_FGETATTRLIST": true, - "SYS_FGETXATTR": true, - "SYS_FHOPEN": true, - "SYS_FHSTAT": true, - "SYS_FHSTATFS": true, - "SYS_FILEPORT_MAKEFD": true, - "SYS_FILEPORT_MAKEPORT": true, - "SYS_FKTRACE": true, - "SYS_FLISTXATTR": true, - "SYS_FLOCK": true, - "SYS_FORK": true, - "SYS_FPATHCONF": true, - "SYS_FREEBSD6_FTRUNCATE": true, - "SYS_FREEBSD6_LSEEK": true, - "SYS_FREEBSD6_MMAP": true, - "SYS_FREEBSD6_PREAD": true, - "SYS_FREEBSD6_PWRITE": true, - "SYS_FREEBSD6_TRUNCATE": true, - "SYS_FREMOVEXATTR": true, - "SYS_FSCTL": true, - "SYS_FSETATTRLIST": true, - "SYS_FSETXATTR": true, - "SYS_FSGETPATH": true, - "SYS_FSTAT": true, - "SYS_FSTAT64": true, - "SYS_FSTAT64_EXTENDED": true, - "SYS_FSTATAT": true, - "SYS_FSTATAT64": true, - "SYS_FSTATFS": true, - "SYS_FSTATFS64": true, - "SYS_FSTATV": true, - "SYS_FSTATVFS1": true, - "SYS_FSTAT_EXTENDED": true, - "SYS_FSYNC": true, - "SYS_FSYNC_NOCANCEL": true, - "SYS_FSYNC_RANGE": true, - "SYS_FTIME": true, - "SYS_FTRUNCATE": true, - "SYS_FTRUNCATE64": true, - "SYS_FUTEX": true, - "SYS_FUTIMENS": true, - "SYS_FUTIMES": true, - "SYS_FUTIMESAT": true, - "SYS_GETATTRLIST": true, - "SYS_GETAUDIT": true, - "SYS_GETAUDIT_ADDR": true, - "SYS_GETAUID": true, - "SYS_GETCONTEXT": true, - "SYS_GETCPU": true, - "SYS_GETCWD": true, - "SYS_GETDENTS": true, - "SYS_GETDENTS64": true, - "SYS_GETDIRENTRIES": true, - "SYS_GETDIRENTRIES64": true, - "SYS_GETDIRENTRIESATTR": true, - "SYS_GETDTABLECOUNT": true, - "SYS_GETDTABLESIZE": true, - "SYS_GETEGID": true, - "SYS_GETEGID32": true, - "SYS_GETEUID": true, - "SYS_GETEUID32": true, - "SYS_GETFH": true, - "SYS_GETFSSTAT": true, - "SYS_GETFSSTAT64": true, - "SYS_GETGID": true, - "SYS_GETGID32": true, - "SYS_GETGROUPS": true, - "SYS_GETGROUPS32": true, - "SYS_GETHOSTUUID": true, - "SYS_GETITIMER": true, - "SYS_GETLCID": true, - "SYS_GETLOGIN": true, - "SYS_GETLOGINCLASS": true, - "SYS_GETPEERNAME": true, - "SYS_GETPGID": true, - "SYS_GETPGRP": true, - "SYS_GETPID": true, - "SYS_GETPMSG": true, - "SYS_GETPPID": true, - "SYS_GETPRIORITY": true, - "SYS_GETRESGID": true, - "SYS_GETRESGID32": true, - "SYS_GETRESUID": true, - "SYS_GETRESUID32": true, - "SYS_GETRLIMIT": true, - "SYS_GETRTABLE": true, - "SYS_GETRUSAGE": true, - "SYS_GETSGROUPS": true, - "SYS_GETSID": true, - "SYS_GETSOCKNAME": true, - "SYS_GETSOCKOPT": true, - "SYS_GETTHRID": true, - "SYS_GETTID": true, - "SYS_GETTIMEOFDAY": true, - "SYS_GETUID": true, - "SYS_GETUID32": true, - "SYS_GETVFSSTAT": true, - "SYS_GETWGROUPS": true, - "SYS_GETXATTR": true, - "SYS_GET_KERNEL_SYMS": true, - "SYS_GET_MEMPOLICY": true, - "SYS_GET_ROBUST_LIST": true, - "SYS_GET_THREAD_AREA": true, - "SYS_GTTY": true, - "SYS_IDENTITYSVC": true, - "SYS_IDLE": true, - "SYS_INITGROUPS": true, - "SYS_INIT_MODULE": true, - "SYS_INOTIFY_ADD_WATCH": true, - "SYS_INOTIFY_INIT": true, - "SYS_INOTIFY_INIT1": true, - "SYS_INOTIFY_RM_WATCH": true, - "SYS_IOCTL": true, - "SYS_IOPERM": true, - "SYS_IOPL": true, - "SYS_IOPOLICYSYS": true, - "SYS_IOPRIO_GET": true, - "SYS_IOPRIO_SET": true, - "SYS_IO_CANCEL": true, - "SYS_IO_DESTROY": true, - "SYS_IO_GETEVENTS": true, - "SYS_IO_SETUP": true, - "SYS_IO_SUBMIT": true, - "SYS_IPC": true, - "SYS_ISSETUGID": true, - "SYS_JAIL": true, - "SYS_JAIL_ATTACH": true, - "SYS_JAIL_GET": true, - "SYS_JAIL_REMOVE": true, - "SYS_JAIL_SET": true, - "SYS_KDEBUG_TRACE": true, - "SYS_KENV": true, - "SYS_KEVENT": true, - "SYS_KEVENT64": true, - "SYS_KEXEC_LOAD": true, - "SYS_KEYCTL": true, - "SYS_KILL": true, - "SYS_KLDFIND": true, - "SYS_KLDFIRSTMOD": true, - "SYS_KLDLOAD": true, - "SYS_KLDNEXT": true, - "SYS_KLDSTAT": true, - "SYS_KLDSYM": true, - "SYS_KLDUNLOAD": true, - "SYS_KLDUNLOADF": true, - "SYS_KQUEUE": true, - "SYS_KQUEUE1": true, - "SYS_KTIMER_CREATE": true, - "SYS_KTIMER_DELETE": true, - "SYS_KTIMER_GETOVERRUN": true, - "SYS_KTIMER_GETTIME": true, - "SYS_KTIMER_SETTIME": true, - "SYS_KTRACE": true, - "SYS_LCHFLAGS": true, - "SYS_LCHMOD": true, - "SYS_LCHOWN": true, - "SYS_LCHOWN32": true, - "SYS_LGETFH": true, - "SYS_LGETXATTR": true, - "SYS_LINK": true, - "SYS_LINKAT": true, - "SYS_LIO_LISTIO": true, - "SYS_LISTEN": true, - "SYS_LISTXATTR": true, - "SYS_LLISTXATTR": true, - "SYS_LOCK": true, - "SYS_LOOKUP_DCOOKIE": true, - "SYS_LPATHCONF": true, - "SYS_LREMOVEXATTR": true, - "SYS_LSEEK": true, - "SYS_LSETXATTR": true, - "SYS_LSTAT": true, - "SYS_LSTAT64": true, - "SYS_LSTAT64_EXTENDED": true, - "SYS_LSTATV": true, - "SYS_LSTAT_EXTENDED": true, - "SYS_LUTIMES": true, - "SYS_MAC_SYSCALL": true, - "SYS_MADVISE": true, - "SYS_MADVISE1": true, - "SYS_MAXSYSCALL": true, - "SYS_MBIND": true, - "SYS_MIGRATE_PAGES": true, - "SYS_MINCORE": true, - "SYS_MINHERIT": true, - "SYS_MKCOMPLEX": true, - "SYS_MKDIR": true, - "SYS_MKDIRAT": true, - "SYS_MKDIR_EXTENDED": true, - "SYS_MKFIFO": true, - "SYS_MKFIFOAT": true, - "SYS_MKFIFO_EXTENDED": true, - "SYS_MKNOD": true, - "SYS_MKNODAT": true, - "SYS_MLOCK": true, - "SYS_MLOCKALL": true, - "SYS_MMAP": true, - "SYS_MMAP2": true, - "SYS_MODCTL": true, - "SYS_MODFIND": true, - "SYS_MODFNEXT": true, - "SYS_MODIFY_LDT": true, - "SYS_MODNEXT": true, - "SYS_MODSTAT": true, - "SYS_MODWATCH": true, - "SYS_MOUNT": true, - "SYS_MOVE_PAGES": true, - "SYS_MPROTECT": true, - "SYS_MPX": true, - "SYS_MQUERY": true, - "SYS_MQ_GETSETATTR": true, - "SYS_MQ_NOTIFY": true, - "SYS_MQ_OPEN": true, - "SYS_MQ_TIMEDRECEIVE": true, - "SYS_MQ_TIMEDSEND": true, - "SYS_MQ_UNLINK": true, - "SYS_MREMAP": true, - "SYS_MSGCTL": true, - "SYS_MSGGET": true, - "SYS_MSGRCV": true, - "SYS_MSGRCV_NOCANCEL": true, - "SYS_MSGSND": true, - "SYS_MSGSND_NOCANCEL": true, - "SYS_MSGSYS": true, - "SYS_MSYNC": true, - "SYS_MSYNC_NOCANCEL": true, - "SYS_MUNLOCK": true, - "SYS_MUNLOCKALL": true, - "SYS_MUNMAP": true, - "SYS_NAME_TO_HANDLE_AT": true, - "SYS_NANOSLEEP": true, - "SYS_NEWFSTATAT": true, - "SYS_NFSCLNT": true, - "SYS_NFSSERVCTL": true, - "SYS_NFSSVC": true, - "SYS_NFSTAT": true, - "SYS_NICE": true, - "SYS_NLSTAT": true, - "SYS_NMOUNT": true, - "SYS_NSTAT": true, - "SYS_NTP_ADJTIME": true, - "SYS_NTP_GETTIME": true, - "SYS_OABI_SYSCALL_BASE": true, - "SYS_OBREAK": true, - "SYS_OLDFSTAT": true, - "SYS_OLDLSTAT": true, - "SYS_OLDOLDUNAME": true, - "SYS_OLDSTAT": true, - "SYS_OLDUNAME": true, - "SYS_OPEN": true, - "SYS_OPENAT": true, - "SYS_OPENBSD_POLL": true, - "SYS_OPEN_BY_HANDLE_AT": true, - "SYS_OPEN_EXTENDED": true, - "SYS_OPEN_NOCANCEL": true, - "SYS_OVADVISE": true, - "SYS_PACCEPT": true, - "SYS_PATHCONF": true, - "SYS_PAUSE": true, - "SYS_PCICONFIG_IOBASE": true, - "SYS_PCICONFIG_READ": true, - "SYS_PCICONFIG_WRITE": true, - "SYS_PDFORK": true, - "SYS_PDGETPID": true, - "SYS_PDKILL": true, - "SYS_PERF_EVENT_OPEN": true, - "SYS_PERSONALITY": true, - "SYS_PID_HIBERNATE": true, - "SYS_PID_RESUME": true, - "SYS_PID_SHUTDOWN_SOCKETS": true, - "SYS_PID_SUSPEND": true, - "SYS_PIPE": true, - "SYS_PIPE2": true, - "SYS_PIVOT_ROOT": true, - "SYS_PMC_CONTROL": true, - "SYS_PMC_GET_INFO": true, - "SYS_POLL": true, - "SYS_POLLTS": true, - "SYS_POLL_NOCANCEL": true, - "SYS_POSIX_FADVISE": true, - "SYS_POSIX_FALLOCATE": true, - "SYS_POSIX_OPENPT": true, - "SYS_POSIX_SPAWN": true, - "SYS_PPOLL": true, - "SYS_PRCTL": true, - "SYS_PREAD": true, - "SYS_PREAD64": true, - "SYS_PREADV": true, - "SYS_PREAD_NOCANCEL": true, - "SYS_PRLIMIT64": true, - "SYS_PROCCTL": true, - "SYS_PROCESS_POLICY": true, - "SYS_PROCESS_VM_READV": true, - "SYS_PROCESS_VM_WRITEV": true, - "SYS_PROC_INFO": true, - "SYS_PROF": true, - "SYS_PROFIL": true, - "SYS_PSELECT": true, - "SYS_PSELECT6": true, - "SYS_PSET_ASSIGN": true, - "SYS_PSET_CREATE": true, - "SYS_PSET_DESTROY": true, - "SYS_PSYNCH_CVBROAD": true, - "SYS_PSYNCH_CVCLRPREPOST": true, - "SYS_PSYNCH_CVSIGNAL": true, - "SYS_PSYNCH_CVWAIT": true, - "SYS_PSYNCH_MUTEXDROP": true, - "SYS_PSYNCH_MUTEXWAIT": true, - "SYS_PSYNCH_RW_DOWNGRADE": true, - "SYS_PSYNCH_RW_LONGRDLOCK": true, - "SYS_PSYNCH_RW_RDLOCK": true, - "SYS_PSYNCH_RW_UNLOCK": true, - "SYS_PSYNCH_RW_UNLOCK2": true, - "SYS_PSYNCH_RW_UPGRADE": true, - "SYS_PSYNCH_RW_WRLOCK": true, - "SYS_PSYNCH_RW_YIELDWRLOCK": true, - "SYS_PTRACE": true, - "SYS_PUTPMSG": true, - "SYS_PWRITE": true, - "SYS_PWRITE64": true, - "SYS_PWRITEV": true, - "SYS_PWRITE_NOCANCEL": true, - "SYS_QUERY_MODULE": true, - "SYS_QUOTACTL": true, - "SYS_RASCTL": true, - "SYS_RCTL_ADD_RULE": true, - "SYS_RCTL_GET_LIMITS": true, - "SYS_RCTL_GET_RACCT": true, - "SYS_RCTL_GET_RULES": true, - "SYS_RCTL_REMOVE_RULE": true, - "SYS_READ": true, - "SYS_READAHEAD": true, - "SYS_READDIR": true, - "SYS_READLINK": true, - "SYS_READLINKAT": true, - "SYS_READV": true, - "SYS_READV_NOCANCEL": true, - "SYS_READ_NOCANCEL": true, - "SYS_REBOOT": true, - "SYS_RECV": true, - "SYS_RECVFROM": true, - "SYS_RECVFROM_NOCANCEL": true, - "SYS_RECVMMSG": true, - "SYS_RECVMSG": true, - "SYS_RECVMSG_NOCANCEL": true, - "SYS_REMAP_FILE_PAGES": true, - "SYS_REMOVEXATTR": true, - "SYS_RENAME": true, - "SYS_RENAMEAT": true, - "SYS_REQUEST_KEY": true, - "SYS_RESTART_SYSCALL": true, - "SYS_REVOKE": true, - "SYS_RFORK": true, - "SYS_RMDIR": true, - "SYS_RTPRIO": true, - "SYS_RTPRIO_THREAD": true, - "SYS_RT_SIGACTION": true, - "SYS_RT_SIGPENDING": true, - "SYS_RT_SIGPROCMASK": true, - "SYS_RT_SIGQUEUEINFO": true, - "SYS_RT_SIGRETURN": true, - "SYS_RT_SIGSUSPEND": true, - "SYS_RT_SIGTIMEDWAIT": true, - "SYS_RT_TGSIGQUEUEINFO": true, - "SYS_SBRK": true, - "SYS_SCHED_GETAFFINITY": true, - "SYS_SCHED_GETPARAM": true, - "SYS_SCHED_GETSCHEDULER": true, - "SYS_SCHED_GET_PRIORITY_MAX": true, - "SYS_SCHED_GET_PRIORITY_MIN": true, - "SYS_SCHED_RR_GET_INTERVAL": true, - "SYS_SCHED_SETAFFINITY": true, - "SYS_SCHED_SETPARAM": true, - "SYS_SCHED_SETSCHEDULER": true, - "SYS_SCHED_YIELD": true, - "SYS_SCTP_GENERIC_RECVMSG": true, - "SYS_SCTP_GENERIC_SENDMSG": true, - "SYS_SCTP_GENERIC_SENDMSG_IOV": true, - "SYS_SCTP_PEELOFF": true, - "SYS_SEARCHFS": true, - "SYS_SECURITY": true, - "SYS_SELECT": true, - "SYS_SELECT_NOCANCEL": true, - "SYS_SEMCONFIG": true, - "SYS_SEMCTL": true, - "SYS_SEMGET": true, - "SYS_SEMOP": true, - "SYS_SEMSYS": true, - "SYS_SEMTIMEDOP": true, - "SYS_SEM_CLOSE": true, - "SYS_SEM_DESTROY": true, - "SYS_SEM_GETVALUE": true, - "SYS_SEM_INIT": true, - "SYS_SEM_OPEN": true, - "SYS_SEM_POST": true, - "SYS_SEM_TRYWAIT": true, - "SYS_SEM_UNLINK": true, - "SYS_SEM_WAIT": true, - "SYS_SEM_WAIT_NOCANCEL": true, - "SYS_SEND": true, - "SYS_SENDFILE": true, - "SYS_SENDFILE64": true, - "SYS_SENDMMSG": true, - "SYS_SENDMSG": true, - "SYS_SENDMSG_NOCANCEL": true, - "SYS_SENDTO": true, - "SYS_SENDTO_NOCANCEL": true, - "SYS_SETATTRLIST": true, - "SYS_SETAUDIT": true, - "SYS_SETAUDIT_ADDR": true, - "SYS_SETAUID": true, - "SYS_SETCONTEXT": true, - "SYS_SETDOMAINNAME": true, - "SYS_SETEGID": true, - "SYS_SETEUID": true, - "SYS_SETFIB": true, - "SYS_SETFSGID": true, - "SYS_SETFSGID32": true, - "SYS_SETFSUID": true, - "SYS_SETFSUID32": true, - "SYS_SETGID": true, - "SYS_SETGID32": true, - "SYS_SETGROUPS": true, - "SYS_SETGROUPS32": true, - "SYS_SETHOSTNAME": true, - "SYS_SETITIMER": true, - "SYS_SETLCID": true, - "SYS_SETLOGIN": true, - "SYS_SETLOGINCLASS": true, - "SYS_SETNS": true, - "SYS_SETPGID": true, - "SYS_SETPRIORITY": true, - "SYS_SETPRIVEXEC": true, - "SYS_SETREGID": true, - "SYS_SETREGID32": true, - "SYS_SETRESGID": true, - "SYS_SETRESGID32": true, - "SYS_SETRESUID": true, - "SYS_SETRESUID32": true, - "SYS_SETREUID": true, - "SYS_SETREUID32": true, - "SYS_SETRLIMIT": true, - "SYS_SETRTABLE": true, - "SYS_SETSGROUPS": true, - "SYS_SETSID": true, - "SYS_SETSOCKOPT": true, - "SYS_SETTID": true, - "SYS_SETTID_WITH_PID": true, - "SYS_SETTIMEOFDAY": true, - "SYS_SETUID": true, - "SYS_SETUID32": true, - "SYS_SETWGROUPS": true, - "SYS_SETXATTR": true, - "SYS_SET_MEMPOLICY": true, - "SYS_SET_ROBUST_LIST": true, - "SYS_SET_THREAD_AREA": true, - "SYS_SET_TID_ADDRESS": true, - "SYS_SGETMASK": true, - "SYS_SHARED_REGION_CHECK_NP": true, - "SYS_SHARED_REGION_MAP_AND_SLIDE_NP": true, - "SYS_SHMAT": true, - "SYS_SHMCTL": true, - "SYS_SHMDT": true, - "SYS_SHMGET": true, - "SYS_SHMSYS": true, - "SYS_SHM_OPEN": true, - "SYS_SHM_UNLINK": true, - "SYS_SHUTDOWN": true, - "SYS_SIGACTION": true, - "SYS_SIGALTSTACK": true, - "SYS_SIGNAL": true, - "SYS_SIGNALFD": true, - "SYS_SIGNALFD4": true, - "SYS_SIGPENDING": true, - "SYS_SIGPROCMASK": true, - "SYS_SIGQUEUE": true, - "SYS_SIGQUEUEINFO": true, - "SYS_SIGRETURN": true, - "SYS_SIGSUSPEND": true, - "SYS_SIGSUSPEND_NOCANCEL": true, - "SYS_SIGTIMEDWAIT": true, - "SYS_SIGWAIT": true, - "SYS_SIGWAITINFO": true, - "SYS_SOCKET": true, - "SYS_SOCKETCALL": true, - "SYS_SOCKETPAIR": true, - "SYS_SPLICE": true, - "SYS_SSETMASK": true, - "SYS_SSTK": true, - "SYS_STACK_SNAPSHOT": true, - "SYS_STAT": true, - "SYS_STAT64": true, - "SYS_STAT64_EXTENDED": true, - "SYS_STATFS": true, - "SYS_STATFS64": true, - "SYS_STATV": true, - "SYS_STATVFS1": true, - "SYS_STAT_EXTENDED": true, - "SYS_STIME": true, - "SYS_STTY": true, - "SYS_SWAPCONTEXT": true, - "SYS_SWAPCTL": true, - "SYS_SWAPOFF": true, - "SYS_SWAPON": true, - "SYS_SYMLINK": true, - "SYS_SYMLINKAT": true, - "SYS_SYNC": true, - "SYS_SYNCFS": true, - "SYS_SYNC_FILE_RANGE": true, - "SYS_SYSARCH": true, - "SYS_SYSCALL": true, - "SYS_SYSCALL_BASE": true, - "SYS_SYSFS": true, - "SYS_SYSINFO": true, - "SYS_SYSLOG": true, - "SYS_TEE": true, - "SYS_TGKILL": true, - "SYS_THREAD_SELFID": true, - "SYS_THR_CREATE": true, - "SYS_THR_EXIT": true, - "SYS_THR_KILL": true, - "SYS_THR_KILL2": true, - "SYS_THR_NEW": true, - "SYS_THR_SELF": true, - "SYS_THR_SET_NAME": true, - "SYS_THR_SUSPEND": true, - "SYS_THR_WAKE": true, - "SYS_TIME": true, - "SYS_TIMERFD_CREATE": true, - "SYS_TIMERFD_GETTIME": true, - "SYS_TIMERFD_SETTIME": true, - "SYS_TIMER_CREATE": true, - "SYS_TIMER_DELETE": true, - "SYS_TIMER_GETOVERRUN": true, - "SYS_TIMER_GETTIME": true, - "SYS_TIMER_SETTIME": true, - "SYS_TIMES": true, - "SYS_TKILL": true, - "SYS_TRUNCATE": true, - "SYS_TRUNCATE64": true, - "SYS_TUXCALL": true, - "SYS_UGETRLIMIT": true, - "SYS_ULIMIT": true, - "SYS_UMASK": true, - "SYS_UMASK_EXTENDED": true, - "SYS_UMOUNT": true, - "SYS_UMOUNT2": true, - "SYS_UNAME": true, - "SYS_UNDELETE": true, - "SYS_UNLINK": true, - "SYS_UNLINKAT": true, - "SYS_UNMOUNT": true, - "SYS_UNSHARE": true, - "SYS_USELIB": true, - "SYS_USTAT": true, - "SYS_UTIME": true, - "SYS_UTIMENSAT": true, - "SYS_UTIMES": true, - "SYS_UTRACE": true, - "SYS_UUIDGEN": true, - "SYS_VADVISE": true, - "SYS_VFORK": true, - "SYS_VHANGUP": true, - "SYS_VM86": true, - "SYS_VM86OLD": true, - "SYS_VMSPLICE": true, - "SYS_VM_PRESSURE_MONITOR": true, - "SYS_VSERVER": true, - "SYS_WAIT4": true, - "SYS_WAIT4_NOCANCEL": true, - "SYS_WAIT6": true, - "SYS_WAITEVENT": true, - "SYS_WAITID": true, - "SYS_WAITID_NOCANCEL": true, - "SYS_WAITPID": true, - "SYS_WATCHEVENT": true, - "SYS_WORKQ_KERNRETURN": true, - "SYS_WORKQ_OPEN": true, - "SYS_WRITE": true, - "SYS_WRITEV": true, - "SYS_WRITEV_NOCANCEL": true, - "SYS_WRITE_NOCANCEL": true, - "SYS_YIELD": true, - "SYS__LLSEEK": true, - "SYS__LWP_CONTINUE": true, - "SYS__LWP_CREATE": true, - "SYS__LWP_CTL": true, - "SYS__LWP_DETACH": true, - "SYS__LWP_EXIT": true, - "SYS__LWP_GETNAME": true, - "SYS__LWP_GETPRIVATE": true, - "SYS__LWP_KILL": true, - "SYS__LWP_PARK": true, - "SYS__LWP_SELF": true, - "SYS__LWP_SETNAME": true, - "SYS__LWP_SETPRIVATE": true, - "SYS__LWP_SUSPEND": true, - "SYS__LWP_UNPARK": true, - "SYS__LWP_UNPARK_ALL": true, - "SYS__LWP_WAIT": true, - "SYS__LWP_WAKEUP": true, - "SYS__NEWSELECT": true, - "SYS__PSET_BIND": true, - "SYS__SCHED_GETAFFINITY": true, - "SYS__SCHED_GETPARAM": true, - "SYS__SCHED_SETAFFINITY": true, - "SYS__SCHED_SETPARAM": true, - "SYS__SYSCTL": true, - "SYS__UMTX_LOCK": true, - "SYS__UMTX_OP": true, - "SYS__UMTX_UNLOCK": true, - "SYS___ACL_ACLCHECK_FD": true, - "SYS___ACL_ACLCHECK_FILE": true, - "SYS___ACL_ACLCHECK_LINK": true, - "SYS___ACL_DELETE_FD": true, - "SYS___ACL_DELETE_FILE": true, - "SYS___ACL_DELETE_LINK": true, - "SYS___ACL_GET_FD": true, - "SYS___ACL_GET_FILE": true, - "SYS___ACL_GET_LINK": true, - "SYS___ACL_SET_FD": true, - "SYS___ACL_SET_FILE": true, - "SYS___ACL_SET_LINK": true, - "SYS___CLONE": true, - "SYS___DISABLE_THREADSIGNAL": true, - "SYS___GETCWD": true, - "SYS___GETLOGIN": true, - "SYS___GET_TCB": true, - "SYS___MAC_EXECVE": true, - "SYS___MAC_GETFSSTAT": true, - "SYS___MAC_GET_FD": true, - "SYS___MAC_GET_FILE": true, - "SYS___MAC_GET_LCID": true, - "SYS___MAC_GET_LCTX": true, - "SYS___MAC_GET_LINK": true, - "SYS___MAC_GET_MOUNT": true, - "SYS___MAC_GET_PID": true, - "SYS___MAC_GET_PROC": true, - "SYS___MAC_MOUNT": true, - "SYS___MAC_SET_FD": true, - "SYS___MAC_SET_FILE": true, - "SYS___MAC_SET_LCTX": true, - "SYS___MAC_SET_LINK": true, - "SYS___MAC_SET_PROC": true, - "SYS___MAC_SYSCALL": true, - "SYS___OLD_SEMWAIT_SIGNAL": true, - "SYS___OLD_SEMWAIT_SIGNAL_NOCANCEL": true, - "SYS___POSIX_CHOWN": true, - "SYS___POSIX_FCHOWN": true, - "SYS___POSIX_LCHOWN": true, - "SYS___POSIX_RENAME": true, - "SYS___PTHREAD_CANCELED": true, - "SYS___PTHREAD_CHDIR": true, - "SYS___PTHREAD_FCHDIR": true, - "SYS___PTHREAD_KILL": true, - "SYS___PTHREAD_MARKCANCEL": true, - "SYS___PTHREAD_SIGMASK": true, - "SYS___QUOTACTL": true, - "SYS___SEMCTL": true, - "SYS___SEMWAIT_SIGNAL": true, - "SYS___SEMWAIT_SIGNAL_NOCANCEL": true, - "SYS___SETLOGIN": true, - "SYS___SETUGID": true, - "SYS___SET_TCB": true, - "SYS___SIGACTION_SIGTRAMP": true, - "SYS___SIGTIMEDWAIT": true, - "SYS___SIGWAIT": true, - "SYS___SIGWAIT_NOCANCEL": true, - "SYS___SYSCTL": true, - "SYS___TFORK": true, - "SYS___THREXIT": true, - "SYS___THRSIGDIVERT": true, - "SYS___THRSLEEP": true, - "SYS___THRWAKEUP": true, - "S_ARCH1": true, - "S_ARCH2": true, - "S_BLKSIZE": true, - "S_IEXEC": true, - "S_IFBLK": true, - "S_IFCHR": true, - "S_IFDIR": true, - "S_IFIFO": true, - "S_IFLNK": true, - "S_IFMT": true, - "S_IFREG": true, - "S_IFSOCK": true, - "S_IFWHT": true, - "S_IREAD": true, - "S_IRGRP": true, - "S_IROTH": true, - "S_IRUSR": true, - "S_IRWXG": true, - "S_IRWXO": true, - "S_IRWXU": true, - "S_ISGID": true, - "S_ISTXT": true, - "S_ISUID": true, - "S_ISVTX": true, - "S_IWGRP": true, - "S_IWOTH": true, - "S_IWRITE": true, - "S_IWUSR": true, - "S_IXGRP": true, - "S_IXOTH": true, - "S_IXUSR": true, - "S_LOGIN_SET": true, - "SecurityAttributes": true, - "Seek": true, - "Select": true, - "Sendfile": true, - "Sendmsg": true, - "SendmsgN": true, - "Sendto": true, - "Servent": true, - "SetBpf": true, - "SetBpfBuflen": true, - "SetBpfDatalink": true, - "SetBpfHeadercmpl": true, - "SetBpfImmediate": true, - "SetBpfInterface": true, - "SetBpfPromisc": true, - "SetBpfTimeout": true, - "SetCurrentDirectory": true, - "SetEndOfFile": true, - "SetEnvironmentVariable": true, - "SetFileAttributes": true, - "SetFileCompletionNotificationModes": true, - "SetFilePointer": true, - "SetFileTime": true, - "SetHandleInformation": true, - "SetKevent": true, - "SetLsfPromisc": true, - "SetNonblock": true, - "Setdomainname": true, - "Setegid": true, - "Setenv": true, - "Seteuid": true, - "Setfsgid": true, - "Setfsuid": true, - "Setgid": true, - "Setgroups": true, - "Sethostname": true, - "Setlogin": true, - "Setpgid": true, - "Setpriority": true, - "Setprivexec": true, - "Setregid": true, - "Setresgid": true, - "Setresuid": true, - "Setreuid": true, - "Setrlimit": true, - "Setsid": true, - "Setsockopt": true, - "SetsockoptByte": true, - "SetsockoptICMPv6Filter": true, - "SetsockoptIPMreq": true, - "SetsockoptIPMreqn": true, - "SetsockoptIPv6Mreq": true, - "SetsockoptInet4Addr": true, - "SetsockoptInt": true, - "SetsockoptLinger": true, - "SetsockoptString": true, - "SetsockoptTimeval": true, - "Settimeofday": true, - "Setuid": true, - "Setxattr": true, - "Shutdown": true, - "SidTypeAlias": true, - "SidTypeComputer": true, - "SidTypeDeletedAccount": true, - "SidTypeDomain": true, - "SidTypeGroup": true, - "SidTypeInvalid": true, - "SidTypeLabel": true, - "SidTypeUnknown": true, - "SidTypeUser": true, - "SidTypeWellKnownGroup": true, - "Signal": true, - "SizeofBpfHdr": true, - "SizeofBpfInsn": true, - "SizeofBpfProgram": true, - "SizeofBpfStat": true, - "SizeofBpfVersion": true, - "SizeofBpfZbuf": true, - "SizeofBpfZbufHeader": true, - "SizeofCmsghdr": true, - "SizeofICMPv6Filter": true, - "SizeofIPMreq": true, - "SizeofIPMreqn": true, - "SizeofIPv6MTUInfo": true, - "SizeofIPv6Mreq": true, - "SizeofIfAddrmsg": true, - "SizeofIfAnnounceMsghdr": true, - "SizeofIfData": true, - "SizeofIfInfomsg": true, - "SizeofIfMsghdr": true, - "SizeofIfaMsghdr": true, - "SizeofIfmaMsghdr": true, - "SizeofIfmaMsghdr2": true, - "SizeofInet4Pktinfo": true, - "SizeofInet6Pktinfo": true, - "SizeofInotifyEvent": true, - "SizeofLinger": true, - "SizeofMsghdr": true, - "SizeofNlAttr": true, - "SizeofNlMsgerr": true, - "SizeofNlMsghdr": true, - "SizeofRtAttr": true, - "SizeofRtGenmsg": true, - "SizeofRtMetrics": true, - "SizeofRtMsg": true, - "SizeofRtMsghdr": true, - "SizeofRtNexthop": true, - "SizeofSockFilter": true, - "SizeofSockFprog": true, - "SizeofSockaddrAny": true, - "SizeofSockaddrDatalink": true, - "SizeofSockaddrInet4": true, - "SizeofSockaddrInet6": true, - "SizeofSockaddrLinklayer": true, - "SizeofSockaddrNetlink": true, - "SizeofSockaddrUnix": true, - "SizeofTCPInfo": true, - "SizeofUcred": true, - "SlicePtrFromStrings": true, - "SockFilter": true, - "SockFprog": true, - "SockaddrDatalink": true, - "SockaddrGen": true, - "SockaddrInet4": true, - "SockaddrInet6": true, - "SockaddrLinklayer": true, - "SockaddrNetlink": true, - "SockaddrUnix": true, - "Socket": true, - "SocketControlMessage": true, - "SocketDisableIPv6": true, - "Socketpair": true, - "Splice": true, - "StartProcess": true, - "StartupInfo": true, - "Stat": true, - "Stat_t": true, - "Statfs": true, - "Statfs_t": true, - "Stderr": true, - "Stdin": true, - "Stdout": true, - "StringBytePtr": true, - "StringByteSlice": true, - "StringSlicePtr": true, - "StringToSid": true, - "StringToUTF16": true, - "StringToUTF16Ptr": true, - "Symlink": true, - "Sync": true, - "SyncFileRange": true, - "SysProcAttr": true, - "SysProcIDMap": true, - "Syscall": true, - "Syscall12": true, - "Syscall15": true, - "Syscall18": true, - "Syscall6": true, - "Syscall9": true, - "Sysctl": true, - "SysctlUint32": true, - "Sysctlnode": true, - "Sysinfo": true, - "Sysinfo_t": true, - "Systemtime": true, - "TCGETS": true, - "TCIFLUSH": true, - "TCIOFLUSH": true, - "TCOFLUSH": true, - "TCPInfo": true, - "TCPKeepalive": true, - "TCP_CA_NAME_MAX": true, - "TCP_CONGCTL": true, - "TCP_CONGESTION": true, - "TCP_CONNECTIONTIMEOUT": true, - "TCP_CORK": true, - "TCP_DEFER_ACCEPT": true, - "TCP_INFO": true, - "TCP_KEEPALIVE": true, - "TCP_KEEPCNT": true, - "TCP_KEEPIDLE": true, - "TCP_KEEPINIT": true, - "TCP_KEEPINTVL": true, - "TCP_LINGER2": true, - "TCP_MAXBURST": true, - "TCP_MAXHLEN": true, - "TCP_MAXOLEN": true, - "TCP_MAXSEG": true, - "TCP_MAXWIN": true, - "TCP_MAX_SACK": true, - "TCP_MAX_WINSHIFT": true, - "TCP_MD5SIG": true, - "TCP_MD5SIG_MAXKEYLEN": true, - "TCP_MINMSS": true, - "TCP_MINMSSOVERLOAD": true, - "TCP_MSS": true, - "TCP_NODELAY": true, - "TCP_NOOPT": true, - "TCP_NOPUSH": true, - "TCP_NSTATES": true, - "TCP_QUICKACK": true, - "TCP_RXT_CONNDROPTIME": true, - "TCP_RXT_FINDROP": true, - "TCP_SACK_ENABLE": true, - "TCP_SYNCNT": true, - "TCP_VENDOR": true, - "TCP_WINDOW_CLAMP": true, - "TCSAFLUSH": true, - "TCSETS": true, - "TF_DISCONNECT": true, - "TF_REUSE_SOCKET": true, - "TF_USE_DEFAULT_WORKER": true, - "TF_USE_KERNEL_APC": true, - "TF_USE_SYSTEM_THREAD": true, - "TF_WRITE_BEHIND": true, - "TH32CS_INHERIT": true, - "TH32CS_SNAPALL": true, - "TH32CS_SNAPHEAPLIST": true, - "TH32CS_SNAPMODULE": true, - "TH32CS_SNAPMODULE32": true, - "TH32CS_SNAPPROCESS": true, - "TH32CS_SNAPTHREAD": true, - "TIME_ZONE_ID_DAYLIGHT": true, - "TIME_ZONE_ID_STANDARD": true, - "TIME_ZONE_ID_UNKNOWN": true, - "TIOCCBRK": true, - "TIOCCDTR": true, - "TIOCCONS": true, - "TIOCDCDTIMESTAMP": true, - "TIOCDRAIN": true, - "TIOCDSIMICROCODE": true, - "TIOCEXCL": true, - "TIOCEXT": true, - "TIOCFLAG_CDTRCTS": true, - "TIOCFLAG_CLOCAL": true, - "TIOCFLAG_CRTSCTS": true, - "TIOCFLAG_MDMBUF": true, - "TIOCFLAG_PPS": true, - "TIOCFLAG_SOFTCAR": true, - "TIOCFLUSH": true, - "TIOCGDEV": true, - "TIOCGDRAINWAIT": true, - "TIOCGETA": true, - "TIOCGETD": true, - "TIOCGFLAGS": true, - "TIOCGICOUNT": true, - "TIOCGLCKTRMIOS": true, - "TIOCGLINED": true, - "TIOCGPGRP": true, - "TIOCGPTN": true, - "TIOCGQSIZE": true, - "TIOCGRANTPT": true, - "TIOCGRS485": true, - "TIOCGSERIAL": true, - "TIOCGSID": true, - "TIOCGSIZE": true, - "TIOCGSOFTCAR": true, - "TIOCGTSTAMP": true, - "TIOCGWINSZ": true, - "TIOCINQ": true, - "TIOCIXOFF": true, - "TIOCIXON": true, - "TIOCLINUX": true, - "TIOCMBIC": true, - "TIOCMBIS": true, - "TIOCMGDTRWAIT": true, - "TIOCMGET": true, - "TIOCMIWAIT": true, - "TIOCMODG": true, - "TIOCMODS": true, - "TIOCMSDTRWAIT": true, - "TIOCMSET": true, - "TIOCM_CAR": true, - "TIOCM_CD": true, - "TIOCM_CTS": true, - "TIOCM_DCD": true, - "TIOCM_DSR": true, - "TIOCM_DTR": true, - "TIOCM_LE": true, - "TIOCM_RI": true, - "TIOCM_RNG": true, - "TIOCM_RTS": true, - "TIOCM_SR": true, - "TIOCM_ST": true, - "TIOCNOTTY": true, - "TIOCNXCL": true, - "TIOCOUTQ": true, - "TIOCPKT": true, - "TIOCPKT_DATA": true, - "TIOCPKT_DOSTOP": true, - "TIOCPKT_FLUSHREAD": true, - "TIOCPKT_FLUSHWRITE": true, - "TIOCPKT_IOCTL": true, - "TIOCPKT_NOSTOP": true, - "TIOCPKT_START": true, - "TIOCPKT_STOP": true, - "TIOCPTMASTER": true, - "TIOCPTMGET": true, - "TIOCPTSNAME": true, - "TIOCPTYGNAME": true, - "TIOCPTYGRANT": true, - "TIOCPTYUNLK": true, - "TIOCRCVFRAME": true, - "TIOCREMOTE": true, - "TIOCSBRK": true, - "TIOCSCONS": true, - "TIOCSCTTY": true, - "TIOCSDRAINWAIT": true, - "TIOCSDTR": true, - "TIOCSERCONFIG": true, - "TIOCSERGETLSR": true, - "TIOCSERGETMULTI": true, - "TIOCSERGSTRUCT": true, - "TIOCSERGWILD": true, - "TIOCSERSETMULTI": true, - "TIOCSERSWILD": true, - "TIOCSER_TEMT": true, - "TIOCSETA": true, - "TIOCSETAF": true, - "TIOCSETAW": true, - "TIOCSETD": true, - "TIOCSFLAGS": true, - "TIOCSIG": true, - "TIOCSLCKTRMIOS": true, - "TIOCSLINED": true, - "TIOCSPGRP": true, - "TIOCSPTLCK": true, - "TIOCSQSIZE": true, - "TIOCSRS485": true, - "TIOCSSERIAL": true, - "TIOCSSIZE": true, - "TIOCSSOFTCAR": true, - "TIOCSTART": true, - "TIOCSTAT": true, - "TIOCSTI": true, - "TIOCSTOP": true, - "TIOCSTSTAMP": true, - "TIOCSWINSZ": true, - "TIOCTIMESTAMP": true, - "TIOCUCNTL": true, - "TIOCVHANGUP": true, - "TIOCXMTFRAME": true, - "TOKEN_ADJUST_DEFAULT": true, - "TOKEN_ADJUST_GROUPS": true, - "TOKEN_ADJUST_PRIVILEGES": true, - "TOKEN_ADJUST_SESSIONID": true, - "TOKEN_ALL_ACCESS": true, - "TOKEN_ASSIGN_PRIMARY": true, - "TOKEN_DUPLICATE": true, - "TOKEN_EXECUTE": true, - "TOKEN_IMPERSONATE": true, - "TOKEN_QUERY": true, - "TOKEN_QUERY_SOURCE": true, - "TOKEN_READ": true, - "TOKEN_WRITE": true, - "TOSTOP": true, - "TRUNCATE_EXISTING": true, - "TUNATTACHFILTER": true, - "TUNDETACHFILTER": true, - "TUNGETFEATURES": true, - "TUNGETIFF": true, - "TUNGETSNDBUF": true, - "TUNGETVNETHDRSZ": true, - "TUNSETDEBUG": true, - "TUNSETGROUP": true, - "TUNSETIFF": true, - "TUNSETLINK": true, - "TUNSETNOCSUM": true, - "TUNSETOFFLOAD": true, - "TUNSETOWNER": true, - "TUNSETPERSIST": true, - "TUNSETSNDBUF": true, - "TUNSETTXFILTER": true, - "TUNSETVNETHDRSZ": true, - "Tee": true, - "TerminateProcess": true, - "Termios": true, - "Tgkill": true, - "Time": true, - "Time_t": true, - "Times": true, - "Timespec": true, - "TimespecToNsec": true, - "Timeval": true, - "Timeval32": true, - "TimevalToNsec": true, - "Timex": true, - "Timezoneinformation": true, - "Tms": true, - "Token": true, - "TokenAccessInformation": true, - "TokenAuditPolicy": true, - "TokenDefaultDacl": true, - "TokenElevation": true, - "TokenElevationType": true, - "TokenGroups": true, - "TokenGroupsAndPrivileges": true, - "TokenHasRestrictions": true, - "TokenImpersonationLevel": true, - "TokenIntegrityLevel": true, - "TokenLinkedToken": true, - "TokenLogonSid": true, - "TokenMandatoryPolicy": true, - "TokenOrigin": true, - "TokenOwner": true, - "TokenPrimaryGroup": true, - "TokenPrivileges": true, - "TokenRestrictedSids": true, - "TokenSandBoxInert": true, - "TokenSessionId": true, - "TokenSessionReference": true, - "TokenSource": true, - "TokenStatistics": true, - "TokenType": true, - "TokenUIAccess": true, - "TokenUser": true, - "TokenVirtualizationAllowed": true, - "TokenVirtualizationEnabled": true, - "Tokenprimarygroup": true, - "Tokenuser": true, - "TranslateAccountName": true, - "TranslateName": true, - "TransmitFile": true, - "TransmitFileBuffers": true, - "Truncate": true, - "UNIX_PATH_MAX": true, - "USAGE_MATCH_TYPE_AND": true, - "USAGE_MATCH_TYPE_OR": true, - "UTF16FromString": true, - "UTF16PtrFromString": true, - "UTF16ToString": true, - "Ucred": true, - "Umask": true, - "Uname": true, - "Undelete": true, - "UnixCredentials": true, - "UnixRights": true, - "Unlink": true, - "Unlinkat": true, - "UnmapViewOfFile": true, - "Unmount": true, - "Unsetenv": true, - "Unshare": true, - "UserInfo10": true, - "Ustat": true, - "Ustat_t": true, - "Utimbuf": true, - "Utime": true, - "Utimes": true, - "UtimesNano": true, - "Utsname": true, - "VDISCARD": true, - "VDSUSP": true, - "VEOF": true, - "VEOL": true, - "VEOL2": true, - "VERASE": true, - "VERASE2": true, - "VINTR": true, - "VKILL": true, - "VLNEXT": true, - "VMIN": true, - "VQUIT": true, - "VREPRINT": true, - "VSTART": true, - "VSTATUS": true, - "VSTOP": true, - "VSUSP": true, - "VSWTC": true, - "VT0": true, - "VT1": true, - "VTDLY": true, - "VTIME": true, - "VWERASE": true, - "VirtualLock": true, - "VirtualUnlock": true, - "WAIT_ABANDONED": true, - "WAIT_FAILED": true, - "WAIT_OBJECT_0": true, - "WAIT_TIMEOUT": true, - "WALL": true, - "WALLSIG": true, - "WALTSIG": true, - "WCLONE": true, - "WCONTINUED": true, - "WCOREFLAG": true, - "WEXITED": true, - "WLINUXCLONE": true, - "WNOHANG": true, - "WNOTHREAD": true, - "WNOWAIT": true, - "WNOZOMBIE": true, - "WOPTSCHECKED": true, - "WORDSIZE": true, - "WSABuf": true, - "WSACleanup": true, - "WSADESCRIPTION_LEN": true, - "WSAData": true, - "WSAEACCES": true, - "WSAECONNABORTED": true, - "WSAECONNRESET": true, - "WSAEnumProtocols": true, - "WSAID_CONNECTEX": true, - "WSAIoctl": true, - "WSAPROTOCOL_LEN": true, - "WSAProtocolChain": true, - "WSAProtocolInfo": true, - "WSARecv": true, - "WSARecvFrom": true, - "WSASYS_STATUS_LEN": true, - "WSASend": true, - "WSASendTo": true, - "WSASendto": true, - "WSAStartup": true, - "WSTOPPED": true, - "WTRAPPED": true, - "WUNTRACED": true, - "Wait4": true, - "WaitForSingleObject": true, - "WaitStatus": true, - "Win32FileAttributeData": true, - "Win32finddata": true, - "Write": true, - "WriteConsole": true, - "WriteFile": true, - "X509_ASN_ENCODING": true, - "XCASE": true, - "XP1_CONNECTIONLESS": true, - "XP1_CONNECT_DATA": true, - "XP1_DISCONNECT_DATA": true, - "XP1_EXPEDITED_DATA": true, - "XP1_GRACEFUL_CLOSE": true, - "XP1_GUARANTEED_DELIVERY": true, - "XP1_GUARANTEED_ORDER": true, - "XP1_IFS_HANDLES": true, - "XP1_MESSAGE_ORIENTED": true, - "XP1_MULTIPOINT_CONTROL_PLANE": true, - "XP1_MULTIPOINT_DATA_PLANE": true, - "XP1_PARTIAL_MESSAGE": true, - "XP1_PSEUDO_STREAM": true, - "XP1_QOS_SUPPORTED": true, - "XP1_SAN_SUPPORT_SDP": true, - "XP1_SUPPORT_BROADCAST": true, - "XP1_SUPPORT_MULTIPOINT": true, - "XP1_UNI_RECV": true, - "XP1_UNI_SEND": true, - }, - "syscall/js": map[string]bool{ - "Error": true, - "Func": true, - "FuncOf": true, - "Global": true, - "Null": true, - "Type": true, - "TypeBoolean": true, - "TypeFunction": true, - "TypeNull": true, - "TypeNumber": true, - "TypeObject": true, - "TypeString": true, - "TypeSymbol": true, - "TypeUndefined": true, - "TypedArray": true, - "TypedArrayOf": true, - "Undefined": true, - "Value": true, - "ValueError": true, - "ValueOf": true, - "Wrapper": true, - }, - "testing": map[string]bool{ - "AllocsPerRun": true, - "B": true, - "Benchmark": true, - "BenchmarkResult": true, - "Cover": true, - "CoverBlock": true, - "CoverMode": true, - "Coverage": true, - "InternalBenchmark": true, - "InternalExample": true, - "InternalTest": true, - "M": true, - "Main": true, - "MainStart": true, - "PB": true, - "RegisterCover": true, - "RunBenchmarks": true, - "RunExamples": true, - "RunTests": true, - "Short": true, - "T": true, - "Verbose": true, - }, - "testing/iotest": map[string]bool{ - "DataErrReader": true, - "ErrTimeout": true, - "HalfReader": true, - "NewReadLogger": true, - "NewWriteLogger": true, - "OneByteReader": true, - "TimeoutReader": true, - "TruncateWriter": true, - }, - "testing/quick": map[string]bool{ - "Check": true, - "CheckEqual": true, - "CheckEqualError": true, - "CheckError": true, - "Config": true, - "Generator": true, - "SetupError": true, - "Value": true, - }, - "text/scanner": map[string]bool{ - "Char": true, - "Comment": true, - "EOF": true, - "Float": true, - "GoTokens": true, - "GoWhitespace": true, - "Ident": true, - "Int": true, - "Position": true, - "RawString": true, - "ScanChars": true, - "ScanComments": true, - "ScanFloats": true, - "ScanIdents": true, - "ScanInts": true, - "ScanRawStrings": true, - "ScanStrings": true, - "Scanner": true, - "SkipComments": true, - "String": true, - "TokenString": true, - }, - "text/tabwriter": map[string]bool{ - "AlignRight": true, - "Debug": true, - "DiscardEmptyColumns": true, - "Escape": true, - "FilterHTML": true, - "NewWriter": true, - "StripEscape": true, - "TabIndent": true, - "Writer": true, - }, - "text/template": map[string]bool{ - "ExecError": true, - "FuncMap": true, - "HTMLEscape": true, - "HTMLEscapeString": true, - "HTMLEscaper": true, - "IsTrue": true, - "JSEscape": true, - "JSEscapeString": true, - "JSEscaper": true, - "Must": true, - "New": true, - "ParseFiles": true, - "ParseGlob": true, - "Template": true, - "URLQueryEscaper": true, - }, - "text/template/parse": map[string]bool{ - "ActionNode": true, - "BoolNode": true, - "BranchNode": true, - "ChainNode": true, - "CommandNode": true, - "DotNode": true, - "FieldNode": true, - "IdentifierNode": true, - "IfNode": true, - "IsEmptyTree": true, - "ListNode": true, - "New": true, - "NewIdentifier": true, - "NilNode": true, - "Node": true, - "NodeAction": true, - "NodeBool": true, - "NodeChain": true, - "NodeCommand": true, - "NodeDot": true, - "NodeField": true, - "NodeIdentifier": true, - "NodeIf": true, - "NodeList": true, - "NodeNil": true, - "NodeNumber": true, - "NodePipe": true, - "NodeRange": true, - "NodeString": true, - "NodeTemplate": true, - "NodeText": true, - "NodeType": true, - "NodeVariable": true, - "NodeWith": true, - "NumberNode": true, - "Parse": true, - "PipeNode": true, - "Pos": true, - "RangeNode": true, - "StringNode": true, - "TemplateNode": true, - "TextNode": true, - "Tree": true, - "VariableNode": true, - "WithNode": true, - }, - "time": map[string]bool{ - "ANSIC": true, - "After": true, - "AfterFunc": true, - "April": true, - "August": true, - "Date": true, - "December": true, - "Duration": true, - "February": true, - "FixedZone": true, - "Friday": true, - "Hour": true, - "January": true, - "July": true, - "June": true, - "Kitchen": true, - "LoadLocation": true, - "LoadLocationFromTZData": true, - "Local": true, - "Location": true, - "March": true, - "May": true, - "Microsecond": true, - "Millisecond": true, - "Minute": true, - "Monday": true, - "Month": true, - "Nanosecond": true, - "NewTicker": true, - "NewTimer": true, - "November": true, - "Now": true, - "October": true, - "Parse": true, - "ParseDuration": true, - "ParseError": true, - "ParseInLocation": true, - "RFC1123": true, - "RFC1123Z": true, - "RFC3339": true, - "RFC3339Nano": true, - "RFC822": true, - "RFC822Z": true, - "RFC850": true, - "RubyDate": true, - "Saturday": true, - "Second": true, - "September": true, - "Since": true, - "Sleep": true, - "Stamp": true, - "StampMicro": true, - "StampMilli": true, - "StampNano": true, - "Sunday": true, - "Thursday": true, - "Tick": true, - "Ticker": true, - "Time": true, - "Timer": true, - "Tuesday": true, - "UTC": true, - "Unix": true, - "UnixDate": true, - "Until": true, - "Wednesday": true, - "Weekday": true, - }, - "unicode": map[string]bool{ - "ASCII_Hex_Digit": true, - "Adlam": true, - "Ahom": true, - "Anatolian_Hieroglyphs": true, - "Arabic": true, - "Armenian": true, - "Avestan": true, - "AzeriCase": true, - "Balinese": true, - "Bamum": true, - "Bassa_Vah": true, - "Batak": true, - "Bengali": true, - "Bhaiksuki": true, - "Bidi_Control": true, - "Bopomofo": true, - "Brahmi": true, - "Braille": true, - "Buginese": true, - "Buhid": true, - "C": true, - "Canadian_Aboriginal": true, - "Carian": true, - "CaseRange": true, - "CaseRanges": true, - "Categories": true, - "Caucasian_Albanian": true, - "Cc": true, - "Cf": true, - "Chakma": true, - "Cham": true, - "Cherokee": true, - "Co": true, - "Common": true, - "Coptic": true, - "Cs": true, - "Cuneiform": true, - "Cypriot": true, - "Cyrillic": true, - "Dash": true, - "Deprecated": true, - "Deseret": true, - "Devanagari": true, - "Diacritic": true, - "Digit": true, - "Duployan": true, - "Egyptian_Hieroglyphs": true, - "Elbasan": true, - "Ethiopic": true, - "Extender": true, - "FoldCategory": true, - "FoldScript": true, - "Georgian": true, - "Glagolitic": true, - "Gothic": true, - "Grantha": true, - "GraphicRanges": true, - "Greek": true, - "Gujarati": true, - "Gurmukhi": true, - "Han": true, - "Hangul": true, - "Hanunoo": true, - "Hatran": true, - "Hebrew": true, - "Hex_Digit": true, - "Hiragana": true, - "Hyphen": true, - "IDS_Binary_Operator": true, - "IDS_Trinary_Operator": true, - "Ideographic": true, - "Imperial_Aramaic": true, - "In": true, - "Inherited": true, - "Inscriptional_Pahlavi": true, - "Inscriptional_Parthian": true, - "Is": true, - "IsControl": true, - "IsDigit": true, - "IsGraphic": true, - "IsLetter": true, - "IsLower": true, - "IsMark": true, - "IsNumber": true, - "IsOneOf": true, - "IsPrint": true, - "IsPunct": true, - "IsSpace": true, - "IsSymbol": true, - "IsTitle": true, - "IsUpper": true, - "Javanese": true, - "Join_Control": true, - "Kaithi": true, - "Kannada": true, - "Katakana": true, - "Kayah_Li": true, - "Kharoshthi": true, - "Khmer": true, - "Khojki": true, - "Khudawadi": true, - "L": true, - "Lao": true, - "Latin": true, - "Lepcha": true, - "Letter": true, - "Limbu": true, - "Linear_A": true, - "Linear_B": true, - "Lisu": true, - "Ll": true, - "Lm": true, - "Lo": true, - "Logical_Order_Exception": true, - "Lower": true, - "LowerCase": true, - "Lt": true, - "Lu": true, - "Lycian": true, - "Lydian": true, - "M": true, - "Mahajani": true, - "Malayalam": true, - "Mandaic": true, - "Manichaean": true, - "Marchen": true, - "Mark": true, - "Masaram_Gondi": true, - "MaxASCII": true, - "MaxCase": true, - "MaxLatin1": true, - "MaxRune": true, - "Mc": true, - "Me": true, - "Meetei_Mayek": true, - "Mende_Kikakui": true, - "Meroitic_Cursive": true, - "Meroitic_Hieroglyphs": true, - "Miao": true, - "Mn": true, - "Modi": true, - "Mongolian": true, - "Mro": true, - "Multani": true, - "Myanmar": true, - "N": true, - "Nabataean": true, - "Nd": true, - "New_Tai_Lue": true, - "Newa": true, - "Nko": true, - "Nl": true, - "No": true, - "Noncharacter_Code_Point": true, - "Number": true, - "Nushu": true, - "Ogham": true, - "Ol_Chiki": true, - "Old_Hungarian": true, - "Old_Italic": true, - "Old_North_Arabian": true, - "Old_Permic": true, - "Old_Persian": true, - "Old_South_Arabian": true, - "Old_Turkic": true, - "Oriya": true, - "Osage": true, - "Osmanya": true, - "Other": true, - "Other_Alphabetic": true, - "Other_Default_Ignorable_Code_Point": true, - "Other_Grapheme_Extend": true, - "Other_ID_Continue": true, - "Other_ID_Start": true, - "Other_Lowercase": true, - "Other_Math": true, - "Other_Uppercase": true, - "P": true, - "Pahawh_Hmong": true, - "Palmyrene": true, - "Pattern_Syntax": true, - "Pattern_White_Space": true, - "Pau_Cin_Hau": true, - "Pc": true, - "Pd": true, - "Pe": true, - "Pf": true, - "Phags_Pa": true, - "Phoenician": true, - "Pi": true, - "Po": true, - "Prepended_Concatenation_Mark": true, - "PrintRanges": true, - "Properties": true, - "Ps": true, - "Psalter_Pahlavi": true, - "Punct": true, - "Quotation_Mark": true, - "Radical": true, - "Range16": true, - "Range32": true, - "RangeTable": true, - "Regional_Indicator": true, - "Rejang": true, - "ReplacementChar": true, - "Runic": true, - "S": true, - "STerm": true, - "Samaritan": true, - "Saurashtra": true, - "Sc": true, - "Scripts": true, - "Sentence_Terminal": true, - "Sharada": true, - "Shavian": true, - "Siddham": true, - "SignWriting": true, - "SimpleFold": true, - "Sinhala": true, - "Sk": true, - "Sm": true, - "So": true, - "Soft_Dotted": true, - "Sora_Sompeng": true, - "Soyombo": true, - "Space": true, - "SpecialCase": true, - "Sundanese": true, - "Syloti_Nagri": true, - "Symbol": true, - "Syriac": true, - "Tagalog": true, - "Tagbanwa": true, - "Tai_Le": true, - "Tai_Tham": true, - "Tai_Viet": true, - "Takri": true, - "Tamil": true, - "Tangut": true, - "Telugu": true, - "Terminal_Punctuation": true, - "Thaana": true, - "Thai": true, - "Tibetan": true, - "Tifinagh": true, - "Tirhuta": true, - "Title": true, - "TitleCase": true, - "To": true, - "ToLower": true, - "ToTitle": true, - "ToUpper": true, - "TurkishCase": true, - "Ugaritic": true, - "Unified_Ideograph": true, - "Upper": true, - "UpperCase": true, - "UpperLower": true, - "Vai": true, - "Variation_Selector": true, - "Version": true, - "Warang_Citi": true, - "White_Space": true, - "Yi": true, - "Z": true, - "Zanabazar_Square": true, - "Zl": true, - "Zp": true, - "Zs": true, - }, - "unicode/utf16": map[string]bool{ - "Decode": true, - "DecodeRune": true, - "Encode": true, - "EncodeRune": true, - "IsSurrogate": true, - }, - "unicode/utf8": map[string]bool{ - "DecodeLastRune": true, - "DecodeLastRuneInString": true, - "DecodeRune": true, - "DecodeRuneInString": true, - "EncodeRune": true, - "FullRune": true, - "FullRuneInString": true, - "MaxRune": true, - "RuneCount": true, - "RuneCountInString": true, - "RuneError": true, - "RuneLen": true, - "RuneSelf": true, - "RuneStart": true, - "UTFMax": true, - "Valid": true, - "ValidRune": true, - "ValidString": true, - }, - "unsafe": map[string]bool{ - "Alignof": true, - "ArbitraryType": true, - "Offsetof": true, - "Pointer": true, - "Sizeof": true, - }, -} diff --git a/vendor/golang.org/x/tools/internal/event/core/event.go b/vendor/golang.org/x/tools/internal/event/core/event.go new file mode 100644 index 000000000..e37b49491 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/event/core/event.go @@ -0,0 +1,85 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package core provides support for event based telemetry. +package core + +import ( + "fmt" + "time" + + "golang.org/x/tools/internal/event/label" +) + +// Event holds the information about an event of note that ocurred. +type Event struct { + at time.Time + + // As events are often on the stack, storing the first few labels directly + // in the event can avoid an allocation at all for the very common cases of + // simple events. + // The length needs to be large enough to cope with the majority of events + // but no so large as to cause undue stack pressure. + // A log message with two values will use 3 labels (one for each value and + // one for the message itself). + + static [3]label.Label // inline storage for the first few labels + dynamic []label.Label // dynamically sized storage for remaining labels +} + +// eventLabelMap implements label.Map for a the labels of an Event. +type eventLabelMap struct { + event Event +} + +func (ev Event) At() time.Time { return ev.at } + +func (ev Event) Format(f fmt.State, r rune) { + if !ev.at.IsZero() { + fmt.Fprint(f, ev.at.Format("2006/01/02 15:04:05 ")) + } + for index := 0; ev.Valid(index); index++ { + if l := ev.Label(index); l.Valid() { + fmt.Fprintf(f, "\n\t%v", l) + } + } +} + +func (ev Event) Valid(index int) bool { + return index >= 0 && index < len(ev.static)+len(ev.dynamic) +} + +func (ev Event) Label(index int) label.Label { + if index < len(ev.static) { + return ev.static[index] + } + return ev.dynamic[index-len(ev.static)] +} + +func (ev Event) Find(key label.Key) label.Label { + for _, l := range ev.static { + if l.Key() == key { + return l + } + } + for _, l := range ev.dynamic { + if l.Key() == key { + return l + } + } + return label.Label{} +} + +func MakeEvent(static [3]label.Label, labels []label.Label) Event { + return Event{ + static: static, + dynamic: labels, + } +} + +// CloneEvent event returns a copy of the event with the time adjusted to at. +func CloneEvent(ev Event, at time.Time) Event { + ev.at = at + return ev +} diff --git a/vendor/golang.org/x/tools/internal/event/core/export.go b/vendor/golang.org/x/tools/internal/event/core/export.go new file mode 100644 index 000000000..05f3a9a57 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/event/core/export.go @@ -0,0 +1,70 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package core + +import ( + "context" + "sync/atomic" + "time" + "unsafe" + + "golang.org/x/tools/internal/event/label" +) + +// Exporter is a function that handles events. +// It may return a modified context and event. +type Exporter func(context.Context, Event, label.Map) context.Context + +var ( + exporter unsafe.Pointer +) + +// SetExporter sets the global exporter function that handles all events. +// The exporter is called synchronously from the event call site, so it should +// return quickly so as not to hold up user code. +func SetExporter(e Exporter) { + p := unsafe.Pointer(&e) + if e == nil { + // &e is always valid, and so p is always valid, but for the early abort + // of ProcessEvent to be efficient it needs to make the nil check on the + // pointer without having to dereference it, so we make the nil function + // also a nil pointer + p = nil + } + atomic.StorePointer(&exporter, p) +} + +// deliver is called to deliver an event to the supplied exporter. +// it will fill in the time. +func deliver(ctx context.Context, exporter Exporter, ev Event) context.Context { + // add the current time to the event + ev.at = time.Now() + // hand the event off to the current exporter + return exporter(ctx, ev, ev) +} + +// Export is called to deliver an event to the global exporter if set. +func Export(ctx context.Context, ev Event) context.Context { + // get the global exporter and abort early if there is not one + exporterPtr := (*Exporter)(atomic.LoadPointer(&exporter)) + if exporterPtr == nil { + return ctx + } + return deliver(ctx, *exporterPtr, ev) +} + +// ExportPair is called to deliver a start event to the supplied exporter. +// It also returns a function that will deliver the end event to the same +// exporter. +// It will fill in the time. +func ExportPair(ctx context.Context, begin, end Event) (context.Context, func()) { + // get the global exporter and abort early if there is not one + exporterPtr := (*Exporter)(atomic.LoadPointer(&exporter)) + if exporterPtr == nil { + return ctx, func() {} + } + ctx = deliver(ctx, *exporterPtr, begin) + return ctx, func() { deliver(ctx, *exporterPtr, end) } +} diff --git a/vendor/golang.org/x/tools/internal/event/core/fast.go b/vendor/golang.org/x/tools/internal/event/core/fast.go new file mode 100644 index 000000000..06c1d4615 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/event/core/fast.go @@ -0,0 +1,77 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package core + +import ( + "context" + + "golang.org/x/tools/internal/event/keys" + "golang.org/x/tools/internal/event/label" +) + +// Log1 takes a message and one label delivers a log event to the exporter. +// It is a customized version of Print that is faster and does no allocation. +func Log1(ctx context.Context, message string, t1 label.Label) { + Export(ctx, MakeEvent([3]label.Label{ + keys.Msg.Of(message), + t1, + }, nil)) +} + +// Log2 takes a message and two labels and delivers a log event to the exporter. +// It is a customized version of Print that is faster and does no allocation. +func Log2(ctx context.Context, message string, t1 label.Label, t2 label.Label) { + Export(ctx, MakeEvent([3]label.Label{ + keys.Msg.Of(message), + t1, + t2, + }, nil)) +} + +// Metric1 sends a label event to the exporter with the supplied labels. +func Metric1(ctx context.Context, t1 label.Label) context.Context { + return Export(ctx, MakeEvent([3]label.Label{ + keys.Metric.New(), + t1, + }, nil)) +} + +// Metric2 sends a label event to the exporter with the supplied labels. +func Metric2(ctx context.Context, t1, t2 label.Label) context.Context { + return Export(ctx, MakeEvent([3]label.Label{ + keys.Metric.New(), + t1, + t2, + }, nil)) +} + +// Start1 sends a span start event with the supplied label list to the exporter. +// It also returns a function that will end the span, which should normally be +// deferred. +func Start1(ctx context.Context, name string, t1 label.Label) (context.Context, func()) { + return ExportPair(ctx, + MakeEvent([3]label.Label{ + keys.Start.Of(name), + t1, + }, nil), + MakeEvent([3]label.Label{ + keys.End.New(), + }, nil)) +} + +// Start2 sends a span start event with the supplied label list to the exporter. +// It also returns a function that will end the span, which should normally be +// deferred. +func Start2(ctx context.Context, name string, t1, t2 label.Label) (context.Context, func()) { + return ExportPair(ctx, + MakeEvent([3]label.Label{ + keys.Start.Of(name), + t1, + t2, + }, nil), + MakeEvent([3]label.Label{ + keys.End.New(), + }, nil)) +} diff --git a/vendor/golang.org/x/tools/internal/event/doc.go b/vendor/golang.org/x/tools/internal/event/doc.go new file mode 100644 index 000000000..5dc6e6bab --- /dev/null +++ b/vendor/golang.org/x/tools/internal/event/doc.go @@ -0,0 +1,7 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package event provides a set of packages that cover the main +// concepts of telemetry in an implementation agnostic way. +package event diff --git a/vendor/golang.org/x/tools/internal/event/event.go b/vendor/golang.org/x/tools/internal/event/event.go new file mode 100644 index 000000000..4d55e577d --- /dev/null +++ b/vendor/golang.org/x/tools/internal/event/event.go @@ -0,0 +1,127 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package event + +import ( + "context" + + "golang.org/x/tools/internal/event/core" + "golang.org/x/tools/internal/event/keys" + "golang.org/x/tools/internal/event/label" +) + +// Exporter is a function that handles events. +// It may return a modified context and event. +type Exporter func(context.Context, core.Event, label.Map) context.Context + +// SetExporter sets the global exporter function that handles all events. +// The exporter is called synchronously from the event call site, so it should +// return quickly so as not to hold up user code. +func SetExporter(e Exporter) { + core.SetExporter(core.Exporter(e)) +} + +// Log takes a message and a label list and combines them into a single event +// before delivering them to the exporter. +func Log(ctx context.Context, message string, labels ...label.Label) { + core.Export(ctx, core.MakeEvent([3]label.Label{ + keys.Msg.Of(message), + }, labels)) +} + +// IsLog returns true if the event was built by the Log function. +// It is intended to be used in exporters to identify the semantics of the +// event when deciding what to do with it. +func IsLog(ev core.Event) bool { + return ev.Label(0).Key() == keys.Msg +} + +// Error takes a message and a label list and combines them into a single event +// before delivering them to the exporter. It captures the error in the +// delivered event. +func Error(ctx context.Context, message string, err error, labels ...label.Label) { + core.Export(ctx, core.MakeEvent([3]label.Label{ + keys.Msg.Of(message), + keys.Err.Of(err), + }, labels)) +} + +// IsError returns true if the event was built by the Error function. +// It is intended to be used in exporters to identify the semantics of the +// event when deciding what to do with it. +func IsError(ev core.Event) bool { + return ev.Label(0).Key() == keys.Msg && + ev.Label(1).Key() == keys.Err +} + +// Metric sends a label event to the exporter with the supplied labels. +func Metric(ctx context.Context, labels ...label.Label) { + core.Export(ctx, core.MakeEvent([3]label.Label{ + keys.Metric.New(), + }, labels)) +} + +// IsMetric returns true if the event was built by the Metric function. +// It is intended to be used in exporters to identify the semantics of the +// event when deciding what to do with it. +func IsMetric(ev core.Event) bool { + return ev.Label(0).Key() == keys.Metric +} + +// Label sends a label event to the exporter with the supplied labels. +func Label(ctx context.Context, labels ...label.Label) context.Context { + return core.Export(ctx, core.MakeEvent([3]label.Label{ + keys.Label.New(), + }, labels)) +} + +// IsLabel returns true if the event was built by the Label function. +// It is intended to be used in exporters to identify the semantics of the +// event when deciding what to do with it. +func IsLabel(ev core.Event) bool { + return ev.Label(0).Key() == keys.Label +} + +// Start sends a span start event with the supplied label list to the exporter. +// It also returns a function that will end the span, which should normally be +// deferred. +func Start(ctx context.Context, name string, labels ...label.Label) (context.Context, func()) { + return core.ExportPair(ctx, + core.MakeEvent([3]label.Label{ + keys.Start.Of(name), + }, labels), + core.MakeEvent([3]label.Label{ + keys.End.New(), + }, nil)) +} + +// IsStart returns true if the event was built by the Start function. +// It is intended to be used in exporters to identify the semantics of the +// event when deciding what to do with it. +func IsStart(ev core.Event) bool { + return ev.Label(0).Key() == keys.Start +} + +// IsEnd returns true if the event was built by the End function. +// It is intended to be used in exporters to identify the semantics of the +// event when deciding what to do with it. +func IsEnd(ev core.Event) bool { + return ev.Label(0).Key() == keys.End +} + +// Detach returns a context without an associated span. +// This allows the creation of spans that are not children of the current span. +func Detach(ctx context.Context) context.Context { + return core.Export(ctx, core.MakeEvent([3]label.Label{ + keys.Detach.New(), + }, nil)) +} + +// IsDetach returns true if the event was built by the Detach function. +// It is intended to be used in exporters to identify the semantics of the +// event when deciding what to do with it. +func IsDetach(ev core.Event) bool { + return ev.Label(0).Key() == keys.Detach +} diff --git a/vendor/golang.org/x/tools/internal/event/keys/keys.go b/vendor/golang.org/x/tools/internal/event/keys/keys.go new file mode 100644 index 000000000..a02206e30 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/event/keys/keys.go @@ -0,0 +1,564 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package keys + +import ( + "fmt" + "io" + "math" + "strconv" + + "golang.org/x/tools/internal/event/label" +) + +// Value represents a key for untyped values. +type Value struct { + name string + description string +} + +// New creates a new Key for untyped values. +func New(name, description string) *Value { + return &Value{name: name, description: description} +} + +func (k *Value) Name() string { return k.name } +func (k *Value) Description() string { return k.description } + +func (k *Value) Format(w io.Writer, buf []byte, l label.Label) { + fmt.Fprint(w, k.From(l)) +} + +// Get can be used to get a label for the key from a label.Map. +func (k *Value) Get(lm label.Map) interface{} { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return nil +} + +// From can be used to get a value from a Label. +func (k *Value) From(t label.Label) interface{} { return t.UnpackValue() } + +// Of creates a new Label with this key and the supplied value. +func (k *Value) Of(value interface{}) label.Label { return label.OfValue(k, value) } + +// Tag represents a key for tagging labels that have no value. +// These are used when the existence of the label is the entire information it +// carries, such as marking events to be of a specific kind, or from a specific +// package. +type Tag struct { + name string + description string +} + +// NewTag creates a new Key for tagging labels. +func NewTag(name, description string) *Tag { + return &Tag{name: name, description: description} +} + +func (k *Tag) Name() string { return k.name } +func (k *Tag) Description() string { return k.description } + +func (k *Tag) Format(w io.Writer, buf []byte, l label.Label) {} + +// New creates a new Label with this key. +func (k *Tag) New() label.Label { return label.OfValue(k, nil) } + +// Int represents a key +type Int struct { + name string + description string +} + +// NewInt creates a new Key for int values. +func NewInt(name, description string) *Int { + return &Int{name: name, description: description} +} + +func (k *Int) Name() string { return k.name } +func (k *Int) Description() string { return k.description } + +func (k *Int) Format(w io.Writer, buf []byte, l label.Label) { + w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10)) +} + +// Of creates a new Label with this key and the supplied value. +func (k *Int) Of(v int) label.Label { return label.Of64(k, uint64(v)) } + +// Get can be used to get a label for the key from a label.Map. +func (k *Int) Get(lm label.Map) int { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return 0 +} + +// From can be used to get a value from a Label. +func (k *Int) From(t label.Label) int { return int(t.Unpack64()) } + +// Int8 represents a key +type Int8 struct { + name string + description string +} + +// NewInt8 creates a new Key for int8 values. +func NewInt8(name, description string) *Int8 { + return &Int8{name: name, description: description} +} + +func (k *Int8) Name() string { return k.name } +func (k *Int8) Description() string { return k.description } + +func (k *Int8) Format(w io.Writer, buf []byte, l label.Label) { + w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10)) +} + +// Of creates a new Label with this key and the supplied value. +func (k *Int8) Of(v int8) label.Label { return label.Of64(k, uint64(v)) } + +// Get can be used to get a label for the key from a label.Map. +func (k *Int8) Get(lm label.Map) int8 { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return 0 +} + +// From can be used to get a value from a Label. +func (k *Int8) From(t label.Label) int8 { return int8(t.Unpack64()) } + +// Int16 represents a key +type Int16 struct { + name string + description string +} + +// NewInt16 creates a new Key for int16 values. +func NewInt16(name, description string) *Int16 { + return &Int16{name: name, description: description} +} + +func (k *Int16) Name() string { return k.name } +func (k *Int16) Description() string { return k.description } + +func (k *Int16) Format(w io.Writer, buf []byte, l label.Label) { + w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10)) +} + +// Of creates a new Label with this key and the supplied value. +func (k *Int16) Of(v int16) label.Label { return label.Of64(k, uint64(v)) } + +// Get can be used to get a label for the key from a label.Map. +func (k *Int16) Get(lm label.Map) int16 { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return 0 +} + +// From can be used to get a value from a Label. +func (k *Int16) From(t label.Label) int16 { return int16(t.Unpack64()) } + +// Int32 represents a key +type Int32 struct { + name string + description string +} + +// NewInt32 creates a new Key for int32 values. +func NewInt32(name, description string) *Int32 { + return &Int32{name: name, description: description} +} + +func (k *Int32) Name() string { return k.name } +func (k *Int32) Description() string { return k.description } + +func (k *Int32) Format(w io.Writer, buf []byte, l label.Label) { + w.Write(strconv.AppendInt(buf, int64(k.From(l)), 10)) +} + +// Of creates a new Label with this key and the supplied value. +func (k *Int32) Of(v int32) label.Label { return label.Of64(k, uint64(v)) } + +// Get can be used to get a label for the key from a label.Map. +func (k *Int32) Get(lm label.Map) int32 { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return 0 +} + +// From can be used to get a value from a Label. +func (k *Int32) From(t label.Label) int32 { return int32(t.Unpack64()) } + +// Int64 represents a key +type Int64 struct { + name string + description string +} + +// NewInt64 creates a new Key for int64 values. +func NewInt64(name, description string) *Int64 { + return &Int64{name: name, description: description} +} + +func (k *Int64) Name() string { return k.name } +func (k *Int64) Description() string { return k.description } + +func (k *Int64) Format(w io.Writer, buf []byte, l label.Label) { + w.Write(strconv.AppendInt(buf, k.From(l), 10)) +} + +// Of creates a new Label with this key and the supplied value. +func (k *Int64) Of(v int64) label.Label { return label.Of64(k, uint64(v)) } + +// Get can be used to get a label for the key from a label.Map. +func (k *Int64) Get(lm label.Map) int64 { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return 0 +} + +// From can be used to get a value from a Label. +func (k *Int64) From(t label.Label) int64 { return int64(t.Unpack64()) } + +// UInt represents a key +type UInt struct { + name string + description string +} + +// NewUInt creates a new Key for uint values. +func NewUInt(name, description string) *UInt { + return &UInt{name: name, description: description} +} + +func (k *UInt) Name() string { return k.name } +func (k *UInt) Description() string { return k.description } + +func (k *UInt) Format(w io.Writer, buf []byte, l label.Label) { + w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10)) +} + +// Of creates a new Label with this key and the supplied value. +func (k *UInt) Of(v uint) label.Label { return label.Of64(k, uint64(v)) } + +// Get can be used to get a label for the key from a label.Map. +func (k *UInt) Get(lm label.Map) uint { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return 0 +} + +// From can be used to get a value from a Label. +func (k *UInt) From(t label.Label) uint { return uint(t.Unpack64()) } + +// UInt8 represents a key +type UInt8 struct { + name string + description string +} + +// NewUInt8 creates a new Key for uint8 values. +func NewUInt8(name, description string) *UInt8 { + return &UInt8{name: name, description: description} +} + +func (k *UInt8) Name() string { return k.name } +func (k *UInt8) Description() string { return k.description } + +func (k *UInt8) Format(w io.Writer, buf []byte, l label.Label) { + w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10)) +} + +// Of creates a new Label with this key and the supplied value. +func (k *UInt8) Of(v uint8) label.Label { return label.Of64(k, uint64(v)) } + +// Get can be used to get a label for the key from a label.Map. +func (k *UInt8) Get(lm label.Map) uint8 { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return 0 +} + +// From can be used to get a value from a Label. +func (k *UInt8) From(t label.Label) uint8 { return uint8(t.Unpack64()) } + +// UInt16 represents a key +type UInt16 struct { + name string + description string +} + +// NewUInt16 creates a new Key for uint16 values. +func NewUInt16(name, description string) *UInt16 { + return &UInt16{name: name, description: description} +} + +func (k *UInt16) Name() string { return k.name } +func (k *UInt16) Description() string { return k.description } + +func (k *UInt16) Format(w io.Writer, buf []byte, l label.Label) { + w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10)) +} + +// Of creates a new Label with this key and the supplied value. +func (k *UInt16) Of(v uint16) label.Label { return label.Of64(k, uint64(v)) } + +// Get can be used to get a label for the key from a label.Map. +func (k *UInt16) Get(lm label.Map) uint16 { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return 0 +} + +// From can be used to get a value from a Label. +func (k *UInt16) From(t label.Label) uint16 { return uint16(t.Unpack64()) } + +// UInt32 represents a key +type UInt32 struct { + name string + description string +} + +// NewUInt32 creates a new Key for uint32 values. +func NewUInt32(name, description string) *UInt32 { + return &UInt32{name: name, description: description} +} + +func (k *UInt32) Name() string { return k.name } +func (k *UInt32) Description() string { return k.description } + +func (k *UInt32) Format(w io.Writer, buf []byte, l label.Label) { + w.Write(strconv.AppendUint(buf, uint64(k.From(l)), 10)) +} + +// Of creates a new Label with this key and the supplied value. +func (k *UInt32) Of(v uint32) label.Label { return label.Of64(k, uint64(v)) } + +// Get can be used to get a label for the key from a label.Map. +func (k *UInt32) Get(lm label.Map) uint32 { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return 0 +} + +// From can be used to get a value from a Label. +func (k *UInt32) From(t label.Label) uint32 { return uint32(t.Unpack64()) } + +// UInt64 represents a key +type UInt64 struct { + name string + description string +} + +// NewUInt64 creates a new Key for uint64 values. +func NewUInt64(name, description string) *UInt64 { + return &UInt64{name: name, description: description} +} + +func (k *UInt64) Name() string { return k.name } +func (k *UInt64) Description() string { return k.description } + +func (k *UInt64) Format(w io.Writer, buf []byte, l label.Label) { + w.Write(strconv.AppendUint(buf, k.From(l), 10)) +} + +// Of creates a new Label with this key and the supplied value. +func (k *UInt64) Of(v uint64) label.Label { return label.Of64(k, v) } + +// Get can be used to get a label for the key from a label.Map. +func (k *UInt64) Get(lm label.Map) uint64 { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return 0 +} + +// From can be used to get a value from a Label. +func (k *UInt64) From(t label.Label) uint64 { return t.Unpack64() } + +// Float32 represents a key +type Float32 struct { + name string + description string +} + +// NewFloat32 creates a new Key for float32 values. +func NewFloat32(name, description string) *Float32 { + return &Float32{name: name, description: description} +} + +func (k *Float32) Name() string { return k.name } +func (k *Float32) Description() string { return k.description } + +func (k *Float32) Format(w io.Writer, buf []byte, l label.Label) { + w.Write(strconv.AppendFloat(buf, float64(k.From(l)), 'E', -1, 32)) +} + +// Of creates a new Label with this key and the supplied value. +func (k *Float32) Of(v float32) label.Label { + return label.Of64(k, uint64(math.Float32bits(v))) +} + +// Get can be used to get a label for the key from a label.Map. +func (k *Float32) Get(lm label.Map) float32 { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return 0 +} + +// From can be used to get a value from a Label. +func (k *Float32) From(t label.Label) float32 { + return math.Float32frombits(uint32(t.Unpack64())) +} + +// Float64 represents a key +type Float64 struct { + name string + description string +} + +// NewFloat64 creates a new Key for int64 values. +func NewFloat64(name, description string) *Float64 { + return &Float64{name: name, description: description} +} + +func (k *Float64) Name() string { return k.name } +func (k *Float64) Description() string { return k.description } + +func (k *Float64) Format(w io.Writer, buf []byte, l label.Label) { + w.Write(strconv.AppendFloat(buf, k.From(l), 'E', -1, 64)) +} + +// Of creates a new Label with this key and the supplied value. +func (k *Float64) Of(v float64) label.Label { + return label.Of64(k, math.Float64bits(v)) +} + +// Get can be used to get a label for the key from a label.Map. +func (k *Float64) Get(lm label.Map) float64 { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return 0 +} + +// From can be used to get a value from a Label. +func (k *Float64) From(t label.Label) float64 { + return math.Float64frombits(t.Unpack64()) +} + +// String represents a key +type String struct { + name string + description string +} + +// NewString creates a new Key for int64 values. +func NewString(name, description string) *String { + return &String{name: name, description: description} +} + +func (k *String) Name() string { return k.name } +func (k *String) Description() string { return k.description } + +func (k *String) Format(w io.Writer, buf []byte, l label.Label) { + w.Write(strconv.AppendQuote(buf, k.From(l))) +} + +// Of creates a new Label with this key and the supplied value. +func (k *String) Of(v string) label.Label { return label.OfString(k, v) } + +// Get can be used to get a label for the key from a label.Map. +func (k *String) Get(lm label.Map) string { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return "" +} + +// From can be used to get a value from a Label. +func (k *String) From(t label.Label) string { return t.UnpackString() } + +// Boolean represents a key +type Boolean struct { + name string + description string +} + +// NewBoolean creates a new Key for bool values. +func NewBoolean(name, description string) *Boolean { + return &Boolean{name: name, description: description} +} + +func (k *Boolean) Name() string { return k.name } +func (k *Boolean) Description() string { return k.description } + +func (k *Boolean) Format(w io.Writer, buf []byte, l label.Label) { + w.Write(strconv.AppendBool(buf, k.From(l))) +} + +// Of creates a new Label with this key and the supplied value. +func (k *Boolean) Of(v bool) label.Label { + if v { + return label.Of64(k, 1) + } + return label.Of64(k, 0) +} + +// Get can be used to get a label for the key from a label.Map. +func (k *Boolean) Get(lm label.Map) bool { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return false +} + +// From can be used to get a value from a Label. +func (k *Boolean) From(t label.Label) bool { return t.Unpack64() > 0 } + +// Error represents a key +type Error struct { + name string + description string +} + +// NewError creates a new Key for int64 values. +func NewError(name, description string) *Error { + return &Error{name: name, description: description} +} + +func (k *Error) Name() string { return k.name } +func (k *Error) Description() string { return k.description } + +func (k *Error) Format(w io.Writer, buf []byte, l label.Label) { + io.WriteString(w, k.From(l).Error()) +} + +// Of creates a new Label with this key and the supplied value. +func (k *Error) Of(v error) label.Label { return label.OfValue(k, v) } + +// Get can be used to get a label for the key from a label.Map. +func (k *Error) Get(lm label.Map) error { + if t := lm.Find(k); t.Valid() { + return k.From(t) + } + return nil +} + +// From can be used to get a value from a Label. +func (k *Error) From(t label.Label) error { + err, _ := t.UnpackValue().(error) + return err +} diff --git a/vendor/golang.org/x/tools/internal/event/keys/standard.go b/vendor/golang.org/x/tools/internal/event/keys/standard.go new file mode 100644 index 000000000..7e9586659 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/event/keys/standard.go @@ -0,0 +1,22 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package keys + +var ( + // Msg is a key used to add message strings to label lists. + Msg = NewString("message", "a readable message") + // Label is a key used to indicate an event adds labels to the context. + Label = NewTag("label", "a label context marker") + // Start is used for things like traces that have a name. + Start = NewString("start", "span start") + // Metric is a key used to indicate an event records metrics. + End = NewTag("end", "a span end marker") + // Metric is a key used to indicate an event records metrics. + Detach = NewTag("detach", "a span detach marker") + // Err is a key used to add error values to label lists. + Err = NewError("error", "an error that occurred") + // Metric is a key used to indicate an event records metrics. + Metric = NewTag("metric", "a metric event marker") +) diff --git a/vendor/golang.org/x/tools/internal/event/label/label.go b/vendor/golang.org/x/tools/internal/event/label/label.go new file mode 100644 index 000000000..b55c12eb2 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/event/label/label.go @@ -0,0 +1,213 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package label + +import ( + "fmt" + "io" + "reflect" + "unsafe" +) + +// Key is used as the identity of a Label. +// Keys are intended to be compared by pointer only, the name should be unique +// for communicating with external systems, but it is not required or enforced. +type Key interface { + // Name returns the key name. + Name() string + // Description returns a string that can be used to describe the value. + Description() string + + // Format is used in formatting to append the value of the label to the + // supplied buffer. + // The formatter may use the supplied buf as a scratch area to avoid + // allocations. + Format(w io.Writer, buf []byte, l Label) +} + +// Label holds a key and value pair. +// It is normally used when passing around lists of labels. +type Label struct { + key Key + packed uint64 + untyped interface{} +} + +// Map is the interface to a collection of Labels indexed by key. +type Map interface { + // Find returns the label that matches the supplied key. + Find(key Key) Label +} + +// List is the interface to something that provides an iterable +// list of labels. +// Iteration should start from 0 and continue until Valid returns false. +type List interface { + // Valid returns true if the index is within range for the list. + // It does not imply the label at that index will itself be valid. + Valid(index int) bool + // Label returns the label at the given index. + Label(index int) Label +} + +// list implements LabelList for a list of Labels. +type list struct { + labels []Label +} + +// filter wraps a LabelList filtering out specific labels. +type filter struct { + keys []Key + underlying List +} + +// listMap implements LabelMap for a simple list of labels. +type listMap struct { + labels []Label +} + +// mapChain implements LabelMap for a list of underlying LabelMap. +type mapChain struct { + maps []Map +} + +// OfValue creates a new label from the key and value. +// This method is for implementing new key types, label creation should +// normally be done with the Of method of the key. +func OfValue(k Key, value interface{}) Label { return Label{key: k, untyped: value} } + +// UnpackValue assumes the label was built using LabelOfValue and returns the value +// that was passed to that constructor. +// This method is for implementing new key types, for type safety normal +// access should be done with the From method of the key. +func (t Label) UnpackValue() interface{} { return t.untyped } + +// Of64 creates a new label from a key and a uint64. This is often +// used for non uint64 values that can be packed into a uint64. +// This method is for implementing new key types, label creation should +// normally be done with the Of method of the key. +func Of64(k Key, v uint64) Label { return Label{key: k, packed: v} } + +// Unpack64 assumes the label was built using LabelOf64 and returns the value that +// was passed to that constructor. +// This method is for implementing new key types, for type safety normal +// access should be done with the From method of the key. +func (t Label) Unpack64() uint64 { return t.packed } + +// OfString creates a new label from a key and a string. +// This method is for implementing new key types, label creation should +// normally be done with the Of method of the key. +func OfString(k Key, v string) Label { + hdr := (*reflect.StringHeader)(unsafe.Pointer(&v)) + return Label{ + key: k, + packed: uint64(hdr.Len), + untyped: unsafe.Pointer(hdr.Data), + } +} + +// UnpackString assumes the label was built using LabelOfString and returns the +// value that was passed to that constructor. +// This method is for implementing new key types, for type safety normal +// access should be done with the From method of the key. +func (t Label) UnpackString() string { + var v string + hdr := (*reflect.StringHeader)(unsafe.Pointer(&v)) + hdr.Data = uintptr(t.untyped.(unsafe.Pointer)) + hdr.Len = int(t.packed) + return *(*string)(unsafe.Pointer(hdr)) +} + +// Valid returns true if the Label is a valid one (it has a key). +func (t Label) Valid() bool { return t.key != nil } + +// Key returns the key of this Label. +func (t Label) Key() Key { return t.key } + +// Format is used for debug printing of labels. +func (t Label) Format(f fmt.State, r rune) { + if !t.Valid() { + io.WriteString(f, `nil`) + return + } + io.WriteString(f, t.Key().Name()) + io.WriteString(f, "=") + var buf [128]byte + t.Key().Format(f, buf[:0], t) +} + +func (l *list) Valid(index int) bool { + return index >= 0 && index < len(l.labels) +} + +func (l *list) Label(index int) Label { + return l.labels[index] +} + +func (f *filter) Valid(index int) bool { + return f.underlying.Valid(index) +} + +func (f *filter) Label(index int) Label { + l := f.underlying.Label(index) + for _, f := range f.keys { + if l.Key() == f { + return Label{} + } + } + return l +} + +func (lm listMap) Find(key Key) Label { + for _, l := range lm.labels { + if l.Key() == key { + return l + } + } + return Label{} +} + +func (c mapChain) Find(key Key) Label { + for _, src := range c.maps { + l := src.Find(key) + if l.Valid() { + return l + } + } + return Label{} +} + +var emptyList = &list{} + +func NewList(labels ...Label) List { + if len(labels) == 0 { + return emptyList + } + return &list{labels: labels} +} + +func Filter(l List, keys ...Key) List { + if len(keys) == 0 { + return l + } + return &filter{keys: keys, underlying: l} +} + +func NewMap(labels ...Label) Map { + return listMap{labels: labels} +} + +func MergeMaps(srcs ...Map) Map { + var nonNil []Map + for _, src := range srcs { + if src != nil { + nonNil = append(nonNil, src) + } + } + if len(nonNil) == 1 { + return nonNil[0] + } + return mapChain{maps: nonNil} +} diff --git a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk.go b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk.go index 7219c8e9f..9887f7e7a 100644 --- a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk.go +++ b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk.go @@ -14,14 +14,14 @@ import ( "sync" ) -// TraverseLink is used as a return value from WalkFuncs to indicate that the +// ErrTraverseLink is used as a return value from WalkFuncs to indicate that the // symlink named in the call may be traversed. -var TraverseLink = errors.New("fastwalk: traverse symlink, assuming target is a directory") +var ErrTraverseLink = errors.New("fastwalk: traverse symlink, assuming target is a directory") -// SkipFiles is a used as a return value from WalkFuncs to indicate that the +// ErrSkipFiles is a used as a return value from WalkFuncs to indicate that the // callback should not be called for any other files in the current directory. // Child directories will still be traversed. -var SkipFiles = errors.New("fastwalk: skip remaining files in directory") +var ErrSkipFiles = errors.New("fastwalk: skip remaining files in directory") // Walk is a faster implementation of filepath.Walk. // @@ -167,7 +167,7 @@ func (w *walker) onDirEnt(dirName, baseName string, typ os.FileMode) error { err := w.fn(joined, typ) if typ == os.ModeSymlink { - if err == TraverseLink { + if err == ErrTraverseLink { // Set callbackDone so we don't call it twice for both the // symlink-as-symlink and the symlink-as-directory later: w.enqueue(walkItem{dir: joined, callbackDone: true}) diff --git a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_portable.go b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_portable.go index a906b8759..b0d6327a9 100644 --- a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_portable.go +++ b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_portable.go @@ -26,7 +26,7 @@ func readDir(dirName string, fn func(dirName, entName string, typ os.FileMode) e continue } if err := fn(dirName, fi.Name(), fi.Mode()&os.ModeType); err != nil { - if err == SkipFiles { + if err == ErrSkipFiles { skipFiles = true continue } diff --git a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_unix.go b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_unix.go index 3369b1a0b..5901a8f61 100644 --- a/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_unix.go +++ b/vendor/golang.org/x/tools/internal/fastwalk/fastwalk_unix.go @@ -66,7 +66,7 @@ func readDir(dirName string, fn func(dirName, entName string, typ os.FileMode) e continue } if err := fn(dirName, name, typ); err != nil { - if err == SkipFiles { + if err == ErrSkipFiles { skipFiles = true continue } @@ -76,8 +76,9 @@ func readDir(dirName string, fn func(dirName, entName string, typ os.FileMode) e } func parseDirEnt(buf []byte) (consumed int, name string, typ os.FileMode) { - // golang.org/issue/15653 - dirent := (*syscall.Dirent)(unsafe.Pointer(&buf[0])) + // golang.org/issue/37269 + dirent := &syscall.Dirent{} + copy((*[unsafe.Sizeof(syscall.Dirent{})]byte)(unsafe.Pointer(dirent))[:], buf) if v := unsafe.Offsetof(dirent.Reclen) + unsafe.Sizeof(dirent.Reclen); uintptr(len(buf)) < v { panic(fmt.Sprintf("buf size of %d smaller than dirent header size %d", len(buf), v)) } diff --git a/vendor/golang.org/x/tools/internal/gocommand/invoke.go b/vendor/golang.org/x/tools/internal/gocommand/invoke.go new file mode 100644 index 000000000..f516e1762 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/gocommand/invoke.go @@ -0,0 +1,230 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package gocommand is a helper for calling the go command. +package gocommand + +import ( + "bytes" + "context" + "fmt" + "io" + "os" + "os/exec" + "regexp" + "strings" + "sync" + "time" + + "golang.org/x/tools/internal/event" +) + +// An Runner will run go command invocations and serialize +// them if it sees a concurrency error. +type Runner struct { + // once guards the runner initialization. + once sync.Once + + // inFlight tracks available workers. + inFlight chan struct{} + + // serialized guards the ability to run a go command serially, + // to avoid deadlocks when claiming workers. + serialized chan struct{} +} + +const maxInFlight = 10 + +func (runner *Runner) initialize() { + runner.once.Do(func() { + runner.inFlight = make(chan struct{}, maxInFlight) + runner.serialized = make(chan struct{}, 1) + }) +} + +// 1.13: go: updates to go.mod needed, but contents have changed +// 1.14: go: updating go.mod: existing contents have changed since last read +var modConcurrencyError = regexp.MustCompile(`go:.*go.mod.*contents have changed`) + +// Run is a convenience wrapper around RunRaw. +// It returns only stdout and a "friendly" error. +func (runner *Runner) Run(ctx context.Context, inv Invocation) (*bytes.Buffer, error) { + stdout, _, friendly, _ := runner.RunRaw(ctx, inv) + return stdout, friendly +} + +// RunPiped runs the invocation serially, always waiting for any concurrent +// invocations to complete first. +func (runner *Runner) RunPiped(ctx context.Context, inv Invocation, stdout, stderr io.Writer) error { + _, err := runner.runPiped(ctx, inv, stdout, stderr) + return err +} + +// RunRaw runs the invocation, serializing requests only if they fight over +// go.mod changes. +func (runner *Runner) RunRaw(ctx context.Context, inv Invocation) (*bytes.Buffer, *bytes.Buffer, error, error) { + // Make sure the runner is always initialized. + runner.initialize() + + // First, try to run the go command concurrently. + stdout, stderr, friendlyErr, err := runner.runConcurrent(ctx, inv) + + // If we encounter a load concurrency error, we need to retry serially. + if friendlyErr == nil || !modConcurrencyError.MatchString(friendlyErr.Error()) { + return stdout, stderr, friendlyErr, err + } + event.Error(ctx, "Load concurrency error, will retry serially", err) + + // Run serially by calling runPiped. + stdout.Reset() + stderr.Reset() + friendlyErr, err = runner.runPiped(ctx, inv, stdout, stderr) + return stdout, stderr, friendlyErr, err +} + +func (runner *Runner) runConcurrent(ctx context.Context, inv Invocation) (*bytes.Buffer, *bytes.Buffer, error, error) { + // Wait for 1 worker to become available. + select { + case <-ctx.Done(): + return nil, nil, nil, ctx.Err() + case runner.inFlight <- struct{}{}: + defer func() { <-runner.inFlight }() + } + + stdout, stderr := &bytes.Buffer{}, &bytes.Buffer{} + friendlyErr, err := inv.runWithFriendlyError(ctx, stdout, stderr) + return stdout, stderr, friendlyErr, err +} + +func (runner *Runner) runPiped(ctx context.Context, inv Invocation, stdout, stderr io.Writer) (error, error) { + // Make sure the runner is always initialized. + runner.initialize() + + // Acquire the serialization lock. This avoids deadlocks between two + // runPiped commands. + select { + case <-ctx.Done(): + return nil, ctx.Err() + case runner.serialized <- struct{}{}: + defer func() { <-runner.serialized }() + } + + // Wait for all in-progress go commands to return before proceeding, + // to avoid load concurrency errors. + for i := 0; i < maxInFlight; i++ { + select { + case <-ctx.Done(): + return nil, ctx.Err() + case runner.inFlight <- struct{}{}: + // Make sure we always "return" any workers we took. + defer func() { <-runner.inFlight }() + } + } + + return inv.runWithFriendlyError(ctx, stdout, stderr) +} + +// An Invocation represents a call to the go command. +type Invocation struct { + Verb string + Args []string + BuildFlags []string + Env []string + WorkingDir string + Logf func(format string, args ...interface{}) +} + +func (i *Invocation) runWithFriendlyError(ctx context.Context, stdout, stderr io.Writer) (friendlyError error, rawError error) { + rawError = i.run(ctx, stdout, stderr) + if rawError != nil { + friendlyError = rawError + // Check for 'go' executable not being found. + if ee, ok := rawError.(*exec.Error); ok && ee.Err == exec.ErrNotFound { + friendlyError = fmt.Errorf("go command required, not found: %v", ee) + } + if ctx.Err() != nil { + friendlyError = ctx.Err() + } + friendlyError = fmt.Errorf("err: %v: stderr: %s", friendlyError, stderr) + } + return +} + +func (i *Invocation) run(ctx context.Context, stdout, stderr io.Writer) error { + log := i.Logf + if log == nil { + log = func(string, ...interface{}) {} + } + + goArgs := []string{i.Verb} + switch i.Verb { + case "mod": + // mod needs the sub-verb before build flags. + goArgs = append(goArgs, i.Args[0]) + goArgs = append(goArgs, i.BuildFlags...) + goArgs = append(goArgs, i.Args[1:]...) + case "env": + // env doesn't take build flags. + goArgs = append(goArgs, i.Args...) + default: + goArgs = append(goArgs, i.BuildFlags...) + goArgs = append(goArgs, i.Args...) + } + cmd := exec.Command("go", goArgs...) + cmd.Stdout = stdout + cmd.Stderr = stderr + // On darwin the cwd gets resolved to the real path, which breaks anything that + // expects the working directory to keep the original path, including the + // go command when dealing with modules. + // The Go stdlib has a special feature where if the cwd and the PWD are the + // same node then it trusts the PWD, so by setting it in the env for the child + // process we fix up all the paths returned by the go command. + cmd.Env = append(os.Environ(), i.Env...) + if i.WorkingDir != "" { + cmd.Env = append(cmd.Env, "PWD="+i.WorkingDir) + cmd.Dir = i.WorkingDir + } + defer func(start time.Time) { log("%s for %v", time.Since(start), cmdDebugStr(cmd)) }(time.Now()) + + return runCmdContext(ctx, cmd) +} + +// runCmdContext is like exec.CommandContext except it sends os.Interrupt +// before os.Kill. +func runCmdContext(ctx context.Context, cmd *exec.Cmd) error { + if err := cmd.Start(); err != nil { + return err + } + resChan := make(chan error, 1) + go func() { + resChan <- cmd.Wait() + }() + + select { + case err := <-resChan: + return err + case <-ctx.Done(): + } + // Cancelled. Interrupt and see if it ends voluntarily. + cmd.Process.Signal(os.Interrupt) + select { + case err := <-resChan: + return err + case <-time.After(time.Second): + } + // Didn't shut down in response to interrupt. Kill it hard. + cmd.Process.Kill() + return <-resChan +} + +func cmdDebugStr(cmd *exec.Cmd) string { + env := make(map[string]string) + for _, kv := range cmd.Env { + split := strings.Split(kv, "=") + k, v := split[0], split[1] + env[k] = v + } + + return fmt.Sprintf("GOROOT=%v GOPATH=%v GO111MODULE=%v GOPROXY=%v PWD=%v go %v", env["GOROOT"], env["GOPATH"], env["GO111MODULE"], env["GOPROXY"], env["PWD"], cmd.Args) +} diff --git a/vendor/golang.org/x/tools/internal/gocommand/vendor.go b/vendor/golang.org/x/tools/internal/gocommand/vendor.go new file mode 100644 index 000000000..1cd8d8473 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/gocommand/vendor.go @@ -0,0 +1,102 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package gocommand + +import ( + "bytes" + "context" + "fmt" + "os" + "path/filepath" + "regexp" + "strings" + + "golang.org/x/mod/semver" +) + +// ModuleJSON holds information about a module. +type ModuleJSON struct { + Path string // module path + Replace *ModuleJSON // replaced by this module + Main bool // is this the main module? + Indirect bool // is this module only an indirect dependency of main module? + Dir string // directory holding files for this module, if any + GoMod string // path to go.mod file for this module, if any + GoVersion string // go version used in module +} + +var modFlagRegexp = regexp.MustCompile(`-mod[ =](\w+)`) + +// VendorEnabled reports whether vendoring is enabled. It takes a *Runner to execute Go commands +// with the supplied context.Context and Invocation. The Invocation can contain pre-defined fields, +// of which only Verb and Args are modified to run the appropriate Go command. +// Inspired by setDefaultBuildMod in modload/init.go +func VendorEnabled(ctx context.Context, inv Invocation, r *Runner) (*ModuleJSON, bool, error) { + mainMod, go114, err := getMainModuleAnd114(ctx, inv, r) + if err != nil { + return nil, false, err + } + + // We check the GOFLAGS to see if there is anything overridden or not. + inv.Verb = "env" + inv.Args = []string{"GOFLAGS"} + stdout, err := r.Run(ctx, inv) + if err != nil { + return nil, false, err + } + goflags := string(bytes.TrimSpace(stdout.Bytes())) + matches := modFlagRegexp.FindStringSubmatch(goflags) + var modFlag string + if len(matches) != 0 { + modFlag = matches[1] + } + if modFlag != "" { + // Don't override an explicit '-mod=' argument. + return mainMod, modFlag == "vendor", nil + } + if mainMod == nil || !go114 { + return mainMod, false, nil + } + // Check 1.14's automatic vendor mode. + if fi, err := os.Stat(filepath.Join(mainMod.Dir, "vendor")); err == nil && fi.IsDir() { + if mainMod.GoVersion != "" && semver.Compare("v"+mainMod.GoVersion, "v1.14") >= 0 { + // The Go version is at least 1.14, and a vendor directory exists. + // Set -mod=vendor by default. + return mainMod, true, nil + } + } + return mainMod, false, nil +} + +// getMainModuleAnd114 gets the main module's information and whether the +// go command in use is 1.14+. This is the information needed to figure out +// if vendoring should be enabled. +func getMainModuleAnd114(ctx context.Context, inv Invocation, r *Runner) (*ModuleJSON, bool, error) { + const format = `{{.Path}} +{{.Dir}} +{{.GoMod}} +{{.GoVersion}} +{{range context.ReleaseTags}}{{if eq . "go1.14"}}{{.}}{{end}}{{end}} +` + inv.Verb = "list" + inv.Args = []string{"-m", "-f", format} + stdout, err := r.Run(ctx, inv) + if err != nil { + return nil, false, err + } + + lines := strings.Split(stdout.String(), "\n") + if len(lines) < 5 { + return nil, false, fmt.Errorf("unexpected stdout: %q", stdout.String()) + } + mod := &ModuleJSON{ + Path: lines[0], + Dir: lines[1], + GoMod: lines[2], + GoVersion: lines[3], + Main: true, + } + return mod, lines[4] == "go1.14", nil +} diff --git a/vendor/golang.org/x/tools/internal/gopathwalk/walk.go b/vendor/golang.org/x/tools/internal/gopathwalk/walk.go index 04bb96a36..925ff5356 100644 --- a/vendor/golang.org/x/tools/internal/gopathwalk/walk.go +++ b/vendor/golang.org/x/tools/internal/gopathwalk/walk.go @@ -10,20 +10,22 @@ import ( "bufio" "bytes" "fmt" - "go/build" "io/ioutil" "log" "os" "path/filepath" "strings" + "time" "golang.org/x/tools/internal/fastwalk" ) // Options controls the behavior of a Walk call. type Options struct { - Debug bool // Enable debug logging - ModulesEnabled bool // Search module caches. Also disables legacy goimports ignore rules. + // If Logf is non-nil, debug logging is enabled through this function. + Logf func(format string, args ...interface{}) + // Search module caches. Also disables legacy goimports ignore rules. + ModulesEnabled bool } // RootType indicates the type of a Root. @@ -44,39 +46,44 @@ type Root struct { Type RootType } -// SrcDirsRoots returns the roots from build.Default.SrcDirs(). Not modules-compatible. -func SrcDirsRoots(ctx *build.Context) []Root { - var roots []Root - roots = append(roots, Root{filepath.Join(ctx.GOROOT, "src"), RootGOROOT}) - for _, p := range filepath.SplitList(ctx.GOPATH) { - roots = append(roots, Root{filepath.Join(p, "src"), RootGOPATH}) - } - return roots -} - // Walk walks Go source directories ($GOROOT, $GOPATH, etc) to find packages. // For each package found, add will be called (concurrently) with the absolute // paths of the containing source directory and the package directory. // add will be called concurrently. func Walk(roots []Root, add func(root Root, dir string), opts Options) { + WalkSkip(roots, add, func(Root, string) bool { return false }, opts) +} + +// WalkSkip walks Go source directories ($GOROOT, $GOPATH, etc) to find packages. +// For each package found, add will be called (concurrently) with the absolute +// paths of the containing source directory and the package directory. +// For each directory that will be scanned, skip will be called (concurrently) +// with the absolute paths of the containing source directory and the directory. +// If skip returns false on a directory it will be processed. +// add will be called concurrently. +// skip will be called concurrently. +func WalkSkip(roots []Root, add func(root Root, dir string), skip func(root Root, dir string) bool, opts Options) { for _, root := range roots { - walkDir(root, add, opts) + walkDir(root, add, skip, opts) } } -func walkDir(root Root, add func(Root, string), opts Options) { +// walkDir creates a walker and starts fastwalk with this walker. +func walkDir(root Root, add func(Root, string), skip func(root Root, dir string) bool, opts Options) { if _, err := os.Stat(root.Path); os.IsNotExist(err) { - if opts.Debug { - log.Printf("skipping nonexistant directory: %v", root.Path) + if opts.Logf != nil { + opts.Logf("skipping nonexistent directory: %v", root.Path) } return } - if opts.Debug { - log.Printf("scanning %s", root.Path) + start := time.Now() + if opts.Logf != nil { + opts.Logf("gopathwalk: scanning %s", root.Path) } w := &walker{ root: root, add: add, + skip: skip, opts: opts, } w.init() @@ -84,21 +91,22 @@ func walkDir(root Root, add func(Root, string), opts Options) { log.Printf("gopathwalk: scanning directory %v: %v", root.Path, err) } - if opts.Debug { - log.Printf("scanned %s", root.Path) + if opts.Logf != nil { + opts.Logf("gopathwalk: scanned %s in %v", root.Path, time.Since(start)) } } // walker is the callback for fastwalk.Walk. type walker struct { - root Root // The source directory to scan. - add func(Root, string) // The callback that will be invoked for every possible Go package dir. - opts Options // Options passed to Walk by the user. + root Root // The source directory to scan. + add func(Root, string) // The callback that will be invoked for every possible Go package dir. + skip func(Root, string) bool // The callback that will be invoked for every dir. dir is skipped if it returns true. + opts Options // Options passed to Walk by the user. ignoredDirs []os.FileInfo // The ignored directories, loaded from .goimportsignore files. } -// init initializes the walker based on its Options. +// init initializes the walker based on its Options func (w *walker) init() { var ignoredPaths []string if w.root.Type == RootModuleCache { @@ -113,11 +121,11 @@ func (w *walker) init() { full := filepath.Join(w.root.Path, p) if fi, err := os.Stat(full); err == nil { w.ignoredDirs = append(w.ignoredDirs, fi) - if w.opts.Debug { - log.Printf("Directory added to ignore list: %s", full) + if w.opts.Logf != nil { + w.opts.Logf("Directory added to ignore list: %s", full) } - } else if w.opts.Debug { - log.Printf("Error statting ignored directory: %v", err) + } else if w.opts.Logf != nil { + w.opts.Logf("Error statting ignored directory: %v", err) } } } @@ -128,11 +136,11 @@ func (w *walker) init() { func (w *walker) getIgnoredDirs(path string) []string { file := filepath.Join(path, ".goimportsignore") slurp, err := ioutil.ReadFile(file) - if w.opts.Debug { + if w.opts.Logf != nil { if err != nil { - log.Print(err) + w.opts.Logf("%v", err) } else { - log.Printf("Read %s", file) + w.opts.Logf("Read %s", file) } } if err != nil { @@ -151,29 +159,35 @@ func (w *walker) getIgnoredDirs(path string) []string { return ignoredDirs } -func (w *walker) shouldSkipDir(fi os.FileInfo) bool { +// shouldSkipDir reports whether the file should be skipped or not. +func (w *walker) shouldSkipDir(fi os.FileInfo, dir string) bool { for _, ignoredDir := range w.ignoredDirs { if os.SameFile(fi, ignoredDir) { return true } } + if w.skip != nil { + // Check with the user specified callback. + return w.skip(w.root, dir) + } return false } +// walk walks through the given path. func (w *walker) walk(path string, typ os.FileMode) error { dir := filepath.Dir(path) if typ.IsRegular() { if dir == w.root.Path && (w.root.Type == RootGOROOT || w.root.Type == RootGOPATH) { // Doesn't make sense to have regular files // directly in your $GOPATH/src or $GOROOT/src. - return fastwalk.SkipFiles + return fastwalk.ErrSkipFiles } if !strings.HasSuffix(path, ".go") { return nil } w.add(w.root, dir) - return fastwalk.SkipFiles + return fastwalk.ErrSkipFiles } if typ == os.ModeDir { base := filepath.Base(path) @@ -184,7 +198,7 @@ func (w *walker) walk(path string, typ os.FileMode) error { return filepath.SkipDir } fi, err := os.Lstat(path) - if err == nil && w.shouldSkipDir(fi) { + if err == nil && w.shouldSkipDir(fi, path) { return filepath.SkipDir } return nil @@ -201,7 +215,7 @@ func (w *walker) walk(path string, typ os.FileMode) error { return nil } if w.shouldTraverse(dir, fi) { - return fastwalk.TraverseLink + return fastwalk.ErrTraverseLink } } return nil @@ -224,7 +238,7 @@ func (w *walker) shouldTraverse(dir string, fi os.FileInfo) bool { if !ts.IsDir() { return false } - if w.shouldSkipDir(ts) { + if w.shouldSkipDir(ts, dir) { return false } // Check for symlink loops by statting each directory component diff --git a/vendor/golang.org/x/tools/internal/imports/fix.go b/vendor/golang.org/x/tools/internal/imports/fix.go new file mode 100644 index 000000000..613afc4d6 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/imports/fix.go @@ -0,0 +1,1716 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package imports + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "go/ast" + "go/build" + "go/parser" + "go/token" + "io/ioutil" + "os" + "path" + "path/filepath" + "reflect" + "sort" + "strconv" + "strings" + "sync" + "unicode" + "unicode/utf8" + + "golang.org/x/tools/go/ast/astutil" + "golang.org/x/tools/internal/gocommand" + "golang.org/x/tools/internal/gopathwalk" +) + +// importToGroup is a list of functions which map from an import path to +// a group number. +var importToGroup = []func(localPrefix, importPath string) (num int, ok bool){ + func(localPrefix, importPath string) (num int, ok bool) { + if localPrefix == "" { + return + } + for _, p := range strings.Split(localPrefix, ",") { + if strings.HasPrefix(importPath, p) || strings.TrimSuffix(p, "/") == importPath { + return 3, true + } + } + return + }, + func(_, importPath string) (num int, ok bool) { + if strings.HasPrefix(importPath, "appengine") { + return 2, true + } + return + }, + func(_, importPath string) (num int, ok bool) { + firstComponent := strings.Split(importPath, "/")[0] + if strings.Contains(firstComponent, ".") { + return 1, true + } + return + }, +} + +func importGroup(localPrefix, importPath string) int { + for _, fn := range importToGroup { + if n, ok := fn(localPrefix, importPath); ok { + return n + } + } + return 0 +} + +type ImportFixType int + +const ( + AddImport ImportFixType = iota + DeleteImport + SetImportName +) + +type ImportFix struct { + // StmtInfo represents the import statement this fix will add, remove, or change. + StmtInfo ImportInfo + // IdentName is the identifier that this fix will add or remove. + IdentName string + // FixType is the type of fix this is (AddImport, DeleteImport, SetImportName). + FixType ImportFixType + Relevance int // see pkg +} + +// An ImportInfo represents a single import statement. +type ImportInfo struct { + ImportPath string // import path, e.g. "crypto/rand". + Name string // import name, e.g. "crand", or "" if none. +} + +// A packageInfo represents what's known about a package. +type packageInfo struct { + name string // real package name, if known. + exports map[string]bool // known exports. +} + +// parseOtherFiles parses all the Go files in srcDir except filename, including +// test files if filename looks like a test. +func parseOtherFiles(fset *token.FileSet, srcDir, filename string) []*ast.File { + // This could use go/packages but it doesn't buy much, and it fails + // with https://golang.org/issue/26296 in LoadFiles mode in some cases. + considerTests := strings.HasSuffix(filename, "_test.go") + + fileBase := filepath.Base(filename) + packageFileInfos, err := ioutil.ReadDir(srcDir) + if err != nil { + return nil + } + + var files []*ast.File + for _, fi := range packageFileInfos { + if fi.Name() == fileBase || !strings.HasSuffix(fi.Name(), ".go") { + continue + } + if !considerTests && strings.HasSuffix(fi.Name(), "_test.go") { + continue + } + + f, err := parser.ParseFile(fset, filepath.Join(srcDir, fi.Name()), nil, 0) + if err != nil { + continue + } + + files = append(files, f) + } + + return files +} + +// addGlobals puts the names of package vars into the provided map. +func addGlobals(f *ast.File, globals map[string]bool) { + for _, decl := range f.Decls { + genDecl, ok := decl.(*ast.GenDecl) + if !ok { + continue + } + + for _, spec := range genDecl.Specs { + valueSpec, ok := spec.(*ast.ValueSpec) + if !ok { + continue + } + globals[valueSpec.Names[0].Name] = true + } + } +} + +// collectReferences builds a map of selector expressions, from +// left hand side (X) to a set of right hand sides (Sel). +func collectReferences(f *ast.File) references { + refs := references{} + + var visitor visitFn + visitor = func(node ast.Node) ast.Visitor { + if node == nil { + return visitor + } + switch v := node.(type) { + case *ast.SelectorExpr: + xident, ok := v.X.(*ast.Ident) + if !ok { + break + } + if xident.Obj != nil { + // If the parser can resolve it, it's not a package ref. + break + } + if !ast.IsExported(v.Sel.Name) { + // Whatever this is, it's not exported from a package. + break + } + pkgName := xident.Name + r := refs[pkgName] + if r == nil { + r = make(map[string]bool) + refs[pkgName] = r + } + r[v.Sel.Name] = true + } + return visitor + } + ast.Walk(visitor, f) + return refs +} + +// collectImports returns all the imports in f. +// Unnamed imports (., _) and "C" are ignored. +func collectImports(f *ast.File) []*ImportInfo { + var imports []*ImportInfo + for _, imp := range f.Imports { + var name string + if imp.Name != nil { + name = imp.Name.Name + } + if imp.Path.Value == `"C"` || name == "_" || name == "." { + continue + } + path := strings.Trim(imp.Path.Value, `"`) + imports = append(imports, &ImportInfo{ + Name: name, + ImportPath: path, + }) + } + return imports +} + +// findMissingImport searches pass's candidates for an import that provides +// pkg, containing all of syms. +func (p *pass) findMissingImport(pkg string, syms map[string]bool) *ImportInfo { + for _, candidate := range p.candidates { + pkgInfo, ok := p.knownPackages[candidate.ImportPath] + if !ok { + continue + } + if p.importIdentifier(candidate) != pkg { + continue + } + + allFound := true + for right := range syms { + if !pkgInfo.exports[right] { + allFound = false + break + } + } + + if allFound { + return candidate + } + } + return nil +} + +// references is set of references found in a Go file. The first map key is the +// left hand side of a selector expression, the second key is the right hand +// side, and the value should always be true. +type references map[string]map[string]bool + +// A pass contains all the inputs and state necessary to fix a file's imports. +// It can be modified in some ways during use; see comments below. +type pass struct { + // Inputs. These must be set before a call to load, and not modified after. + fset *token.FileSet // fset used to parse f and its siblings. + f *ast.File // the file being fixed. + srcDir string // the directory containing f. + env *ProcessEnv // the environment to use for go commands, etc. + loadRealPackageNames bool // if true, load package names from disk rather than guessing them. + otherFiles []*ast.File // sibling files. + + // Intermediate state, generated by load. + existingImports map[string]*ImportInfo + allRefs references + missingRefs references + + // Inputs to fix. These can be augmented between successive fix calls. + lastTry bool // indicates that this is the last call and fix should clean up as best it can. + candidates []*ImportInfo // candidate imports in priority order. + knownPackages map[string]*packageInfo // information about all known packages. +} + +// loadPackageNames saves the package names for everything referenced by imports. +func (p *pass) loadPackageNames(imports []*ImportInfo) error { + if p.env.Logf != nil { + p.env.Logf("loading package names for %v packages", len(imports)) + defer func() { + p.env.Logf("done loading package names for %v packages", len(imports)) + }() + } + var unknown []string + for _, imp := range imports { + if _, ok := p.knownPackages[imp.ImportPath]; ok { + continue + } + unknown = append(unknown, imp.ImportPath) + } + + resolver, err := p.env.GetResolver() + if err != nil { + return err + } + + names, err := resolver.loadPackageNames(unknown, p.srcDir) + if err != nil { + return err + } + + for path, name := range names { + p.knownPackages[path] = &packageInfo{ + name: name, + exports: map[string]bool{}, + } + } + return nil +} + +// importIdentifier returns the identifier that imp will introduce. It will +// guess if the package name has not been loaded, e.g. because the source +// is not available. +func (p *pass) importIdentifier(imp *ImportInfo) string { + if imp.Name != "" { + return imp.Name + } + known := p.knownPackages[imp.ImportPath] + if known != nil && known.name != "" { + return known.name + } + return ImportPathToAssumedName(imp.ImportPath) +} + +// load reads in everything necessary to run a pass, and reports whether the +// file already has all the imports it needs. It fills in p.missingRefs with the +// file's missing symbols, if any, or removes unused imports if not. +func (p *pass) load() ([]*ImportFix, bool) { + p.knownPackages = map[string]*packageInfo{} + p.missingRefs = references{} + p.existingImports = map[string]*ImportInfo{} + + // Load basic information about the file in question. + p.allRefs = collectReferences(p.f) + + // Load stuff from other files in the same package: + // global variables so we know they don't need resolving, and imports + // that we might want to mimic. + globals := map[string]bool{} + for _, otherFile := range p.otherFiles { + // Don't load globals from files that are in the same directory + // but a different package. Using them to suggest imports is OK. + if p.f.Name.Name == otherFile.Name.Name { + addGlobals(otherFile, globals) + } + p.candidates = append(p.candidates, collectImports(otherFile)...) + } + + // Resolve all the import paths we've seen to package names, and store + // f's imports by the identifier they introduce. + imports := collectImports(p.f) + if p.loadRealPackageNames { + err := p.loadPackageNames(append(imports, p.candidates...)) + if err != nil { + if p.env.Logf != nil { + p.env.Logf("loading package names: %v", err) + } + return nil, false + } + } + for _, imp := range imports { + p.existingImports[p.importIdentifier(imp)] = imp + } + + // Find missing references. + for left, rights := range p.allRefs { + if globals[left] { + continue + } + _, ok := p.existingImports[left] + if !ok { + p.missingRefs[left] = rights + continue + } + } + if len(p.missingRefs) != 0 { + return nil, false + } + + return p.fix() +} + +// fix attempts to satisfy missing imports using p.candidates. If it finds +// everything, or if p.lastTry is true, it updates fixes to add the imports it found, +// delete anything unused, and update import names, and returns true. +func (p *pass) fix() ([]*ImportFix, bool) { + // Find missing imports. + var selected []*ImportInfo + for left, rights := range p.missingRefs { + if imp := p.findMissingImport(left, rights); imp != nil { + selected = append(selected, imp) + } + } + + if !p.lastTry && len(selected) != len(p.missingRefs) { + return nil, false + } + + // Found everything, or giving up. Add the new imports and remove any unused. + var fixes []*ImportFix + for _, imp := range p.existingImports { + // We deliberately ignore globals here, because we can't be sure + // they're in the same package. People do things like put multiple + // main packages in the same directory, and we don't want to + // remove imports if they happen to have the same name as a var in + // a different package. + if _, ok := p.allRefs[p.importIdentifier(imp)]; !ok { + fixes = append(fixes, &ImportFix{ + StmtInfo: *imp, + IdentName: p.importIdentifier(imp), + FixType: DeleteImport, + }) + continue + } + + // An existing import may need to update its import name to be correct. + if name := p.importSpecName(imp); name != imp.Name { + fixes = append(fixes, &ImportFix{ + StmtInfo: ImportInfo{ + Name: name, + ImportPath: imp.ImportPath, + }, + IdentName: p.importIdentifier(imp), + FixType: SetImportName, + }) + } + } + + for _, imp := range selected { + fixes = append(fixes, &ImportFix{ + StmtInfo: ImportInfo{ + Name: p.importSpecName(imp), + ImportPath: imp.ImportPath, + }, + IdentName: p.importIdentifier(imp), + FixType: AddImport, + }) + } + + return fixes, true +} + +// importSpecName gets the import name of imp in the import spec. +// +// When the import identifier matches the assumed import name, the import name does +// not appear in the import spec. +func (p *pass) importSpecName(imp *ImportInfo) string { + // If we did not load the real package names, or the name is already set, + // we just return the existing name. + if !p.loadRealPackageNames || imp.Name != "" { + return imp.Name + } + + ident := p.importIdentifier(imp) + if ident == ImportPathToAssumedName(imp.ImportPath) { + return "" // ident not needed since the assumed and real names are the same. + } + return ident +} + +// apply will perform the fixes on f in order. +func apply(fset *token.FileSet, f *ast.File, fixes []*ImportFix) { + for _, fix := range fixes { + switch fix.FixType { + case DeleteImport: + astutil.DeleteNamedImport(fset, f, fix.StmtInfo.Name, fix.StmtInfo.ImportPath) + case AddImport: + astutil.AddNamedImport(fset, f, fix.StmtInfo.Name, fix.StmtInfo.ImportPath) + case SetImportName: + // Find the matching import path and change the name. + for _, spec := range f.Imports { + path := strings.Trim(spec.Path.Value, `"`) + if path == fix.StmtInfo.ImportPath { + spec.Name = &ast.Ident{ + Name: fix.StmtInfo.Name, + NamePos: spec.Pos(), + } + } + } + } + } +} + +// assumeSiblingImportsValid assumes that siblings' use of packages is valid, +// adding the exports they use. +func (p *pass) assumeSiblingImportsValid() { + for _, f := range p.otherFiles { + refs := collectReferences(f) + imports := collectImports(f) + importsByName := map[string]*ImportInfo{} + for _, imp := range imports { + importsByName[p.importIdentifier(imp)] = imp + } + for left, rights := range refs { + if imp, ok := importsByName[left]; ok { + if m, ok := stdlib[imp.ImportPath]; ok { + // We have the stdlib in memory; no need to guess. + rights = copyExports(m) + } + p.addCandidate(imp, &packageInfo{ + // no name; we already know it. + exports: rights, + }) + } + } + } +} + +// addCandidate adds a candidate import to p, and merges in the information +// in pkg. +func (p *pass) addCandidate(imp *ImportInfo, pkg *packageInfo) { + p.candidates = append(p.candidates, imp) + if existing, ok := p.knownPackages[imp.ImportPath]; ok { + if existing.name == "" { + existing.name = pkg.name + } + for export := range pkg.exports { + existing.exports[export] = true + } + } else { + p.knownPackages[imp.ImportPath] = pkg + } +} + +// fixImports adds and removes imports from f so that all its references are +// satisfied and there are no unused imports. +// +// This is declared as a variable rather than a function so goimports can +// easily be extended by adding a file with an init function. +var fixImports = fixImportsDefault + +func fixImportsDefault(fset *token.FileSet, f *ast.File, filename string, env *ProcessEnv) error { + fixes, err := getFixes(fset, f, filename, env) + if err != nil { + return err + } + apply(fset, f, fixes) + return err +} + +// getFixes gets the import fixes that need to be made to f in order to fix the imports. +// It does not modify the ast. +func getFixes(fset *token.FileSet, f *ast.File, filename string, env *ProcessEnv) ([]*ImportFix, error) { + abs, err := filepath.Abs(filename) + if err != nil { + return nil, err + } + srcDir := filepath.Dir(abs) + if env.Logf != nil { + env.Logf("fixImports(filename=%q), abs=%q, srcDir=%q ...", filename, abs, srcDir) + } + + // First pass: looking only at f, and using the naive algorithm to + // derive package names from import paths, see if the file is already + // complete. We can't add any imports yet, because we don't know + // if missing references are actually package vars. + p := &pass{fset: fset, f: f, srcDir: srcDir, env: env} + if fixes, done := p.load(); done { + return fixes, nil + } + + otherFiles := parseOtherFiles(fset, srcDir, filename) + + // Second pass: add information from other files in the same package, + // like their package vars and imports. + p.otherFiles = otherFiles + if fixes, done := p.load(); done { + return fixes, nil + } + + // Now we can try adding imports from the stdlib. + p.assumeSiblingImportsValid() + addStdlibCandidates(p, p.missingRefs) + if fixes, done := p.fix(); done { + return fixes, nil + } + + // Third pass: get real package names where we had previously used + // the naive algorithm. + p = &pass{fset: fset, f: f, srcDir: srcDir, env: env} + p.loadRealPackageNames = true + p.otherFiles = otherFiles + if fixes, done := p.load(); done { + return fixes, nil + } + + if err := addStdlibCandidates(p, p.missingRefs); err != nil { + return nil, err + } + p.assumeSiblingImportsValid() + if fixes, done := p.fix(); done { + return fixes, nil + } + + // Go look for candidates in $GOPATH, etc. We don't necessarily load + // the real exports of sibling imports, so keep assuming their contents. + if err := addExternalCandidates(p, p.missingRefs, filename); err != nil { + return nil, err + } + + p.lastTry = true + fixes, _ := p.fix() + return fixes, nil +} + +// Highest relevance, used for the standard library. Chosen arbitrarily to +// match pre-existing gopls code. +const MaxRelevance = 7 + +// getCandidatePkgs works with the passed callback to find all acceptable packages. +// It deduplicates by import path, and uses a cached stdlib rather than reading +// from disk. +func getCandidatePkgs(ctx context.Context, wrappedCallback *scanCallback, filename, filePkg string, env *ProcessEnv) error { + notSelf := func(p *pkg) bool { + return p.packageName != filePkg || p.dir != filepath.Dir(filename) + } + goenv, err := env.goEnv() + if err != nil { + return err + } + // Start off with the standard library. + for importPath, exports := range stdlib { + p := &pkg{ + dir: filepath.Join(goenv["GOROOT"], "src", importPath), + importPathShort: importPath, + packageName: path.Base(importPath), + relevance: MaxRelevance, + } + if notSelf(p) && wrappedCallback.dirFound(p) && wrappedCallback.packageNameLoaded(p) { + wrappedCallback.exportsLoaded(p, exports) + } + } + + var mu sync.Mutex + dupCheck := map[string]struct{}{} + + scanFilter := &scanCallback{ + rootFound: func(root gopathwalk.Root) bool { + // Exclude goroot results -- getting them is relatively expensive, not cached, + // and generally redundant with the in-memory version. + return root.Type != gopathwalk.RootGOROOT && wrappedCallback.rootFound(root) + }, + dirFound: wrappedCallback.dirFound, + packageNameLoaded: func(pkg *pkg) bool { + mu.Lock() + defer mu.Unlock() + if _, ok := dupCheck[pkg.importPathShort]; ok { + return false + } + dupCheck[pkg.importPathShort] = struct{}{} + return notSelf(pkg) && wrappedCallback.packageNameLoaded(pkg) + }, + exportsLoaded: func(pkg *pkg, exports []string) { + // If we're an x_test, load the package under test's test variant. + if strings.HasSuffix(filePkg, "_test") && pkg.dir == filepath.Dir(filename) { + var err error + _, exports, err = loadExportsFromFiles(ctx, env, pkg.dir, true) + if err != nil { + return + } + } + wrappedCallback.exportsLoaded(pkg, exports) + }, + } + resolver, err := env.GetResolver() + if err != nil { + return err + } + return resolver.scan(ctx, scanFilter) +} + +func ScoreImportPaths(ctx context.Context, env *ProcessEnv, paths []string) (map[string]int, error) { + result := make(map[string]int) + resolver, err := env.GetResolver() + if err != nil { + return nil, err + } + for _, path := range paths { + result[path] = resolver.scoreImportPath(ctx, path) + } + return result, nil +} + +func PrimeCache(ctx context.Context, env *ProcessEnv) error { + // Fully scan the disk for directories, but don't actually read any Go files. + callback := &scanCallback{ + rootFound: func(gopathwalk.Root) bool { + return true + }, + dirFound: func(pkg *pkg) bool { + return false + }, + packageNameLoaded: func(pkg *pkg) bool { + return false + }, + } + return getCandidatePkgs(ctx, callback, "", "", env) +} + +func candidateImportName(pkg *pkg) string { + if ImportPathToAssumedName(pkg.importPathShort) != pkg.packageName { + return pkg.packageName + } + return "" +} + +// GetAllCandidates calls wrapped for each package whose name starts with +// searchPrefix, and can be imported from filename with the package name filePkg. +func GetAllCandidates(ctx context.Context, wrapped func(ImportFix), searchPrefix, filename, filePkg string, env *ProcessEnv) error { + callback := &scanCallback{ + rootFound: func(gopathwalk.Root) bool { + return true + }, + dirFound: func(pkg *pkg) bool { + if !canUse(filename, pkg.dir) { + return false + } + // Try the assumed package name first, then a simpler path match + // in case of packages named vN, which are not uncommon. + return strings.HasPrefix(ImportPathToAssumedName(pkg.importPathShort), searchPrefix) || + strings.HasPrefix(path.Base(pkg.importPathShort), searchPrefix) + }, + packageNameLoaded: func(pkg *pkg) bool { + if !strings.HasPrefix(pkg.packageName, searchPrefix) { + return false + } + wrapped(ImportFix{ + StmtInfo: ImportInfo{ + ImportPath: pkg.importPathShort, + Name: candidateImportName(pkg), + }, + IdentName: pkg.packageName, + FixType: AddImport, + Relevance: pkg.relevance, + }) + return false + }, + } + return getCandidatePkgs(ctx, callback, filename, filePkg, env) +} + +// GetImportPaths calls wrapped for each package whose import path starts with +// searchPrefix, and can be imported from filename with the package name filePkg. +func GetImportPaths(ctx context.Context, wrapped func(ImportFix), searchPrefix, filename, filePkg string, env *ProcessEnv) error { + callback := &scanCallback{ + rootFound: func(gopathwalk.Root) bool { + return true + }, + dirFound: func(pkg *pkg) bool { + if !canUse(filename, pkg.dir) { + return false + } + return strings.HasPrefix(pkg.importPathShort, searchPrefix) + }, + packageNameLoaded: func(pkg *pkg) bool { + wrapped(ImportFix{ + StmtInfo: ImportInfo{ + ImportPath: pkg.importPathShort, + Name: candidateImportName(pkg), + }, + IdentName: pkg.packageName, + FixType: AddImport, + Relevance: pkg.relevance, + }) + return false + }, + } + return getCandidatePkgs(ctx, callback, filename, filePkg, env) +} + +// A PackageExport is a package and its exports. +type PackageExport struct { + Fix *ImportFix + Exports []string +} + +// GetPackageExports returns all known packages with name pkg and their exports. +func GetPackageExports(ctx context.Context, wrapped func(PackageExport), searchPkg, filename, filePkg string, env *ProcessEnv) error { + callback := &scanCallback{ + rootFound: func(gopathwalk.Root) bool { + return true + }, + dirFound: func(pkg *pkg) bool { + return pkgIsCandidate(filename, references{searchPkg: nil}, pkg) + }, + packageNameLoaded: func(pkg *pkg) bool { + return pkg.packageName == searchPkg + }, + exportsLoaded: func(pkg *pkg, exports []string) { + sort.Strings(exports) + wrapped(PackageExport{ + Fix: &ImportFix{ + StmtInfo: ImportInfo{ + ImportPath: pkg.importPathShort, + Name: candidateImportName(pkg), + }, + IdentName: pkg.packageName, + FixType: AddImport, + Relevance: pkg.relevance, + }, + Exports: exports, + }) + }, + } + return getCandidatePkgs(ctx, callback, filename, filePkg, env) +} + +var RequiredGoEnvVars = []string{"GO111MODULE", "GOFLAGS", "GOINSECURE", "GOMOD", "GOMODCACHE", "GONOPROXY", "GONOSUMDB", "GOPATH", "GOPROXY", "GOROOT", "GOSUMDB"} + +// ProcessEnv contains environment variables and settings that affect the use of +// the go command, the go/build package, etc. +type ProcessEnv struct { + GocmdRunner *gocommand.Runner + + BuildFlags []string + + // Env overrides the OS environment, and can be used to specify + // GOPROXY, GO111MODULE, etc. PATH cannot be set here, because + // exec.Command will not honor it. + // Specifying all of RequiredGoEnvVars avoids a call to `go env`. + Env map[string]string + + WorkingDir string + + // If Logf is non-nil, debug logging is enabled through this function. + Logf func(format string, args ...interface{}) + + initialized bool + + resolver Resolver +} + +func (e *ProcessEnv) goEnv() (map[string]string, error) { + if err := e.init(); err != nil { + return nil, err + } + return e.Env, nil +} + +func (e *ProcessEnv) matchFile(dir, name string) (bool, error) { + return build.Default.MatchFile(dir, name) +} + +// CopyConfig copies the env's configuration into a new env. +func (e *ProcessEnv) CopyConfig() *ProcessEnv { + copy := &ProcessEnv{ + GocmdRunner: e.GocmdRunner, + initialized: e.initialized, + BuildFlags: e.BuildFlags, + Logf: e.Logf, + WorkingDir: e.WorkingDir, + resolver: nil, + Env: map[string]string{}, + } + for k, v := range e.Env { + copy.Env[k] = v + } + return copy +} + +func (e *ProcessEnv) init() error { + if e.initialized { + return nil + } + + foundAllRequired := true + for _, k := range RequiredGoEnvVars { + if _, ok := e.Env[k]; !ok { + foundAllRequired = false + break + } + } + if foundAllRequired { + e.initialized = true + return nil + } + + if e.Env == nil { + e.Env = map[string]string{} + } + + goEnv := map[string]string{} + stdout, err := e.invokeGo(context.TODO(), "env", append([]string{"-json"}, RequiredGoEnvVars...)...) + if err != nil { + return err + } + if err := json.Unmarshal(stdout.Bytes(), &goEnv); err != nil { + return err + } + for k, v := range goEnv { + e.Env[k] = v + } + e.initialized = true + return nil +} + +func (e *ProcessEnv) env() []string { + var env []string // the gocommand package will prepend os.Environ. + for k, v := range e.Env { + env = append(env, k+"="+v) + } + return env +} + +func (e *ProcessEnv) GetResolver() (Resolver, error) { + if e.resolver != nil { + return e.resolver, nil + } + if err := e.init(); err != nil { + return nil, err + } + if len(e.Env["GOMOD"]) == 0 { + e.resolver = newGopathResolver(e) + return e.resolver, nil + } + e.resolver = newModuleResolver(e) + return e.resolver, nil +} + +func (e *ProcessEnv) buildContext() (*build.Context, error) { + ctx := build.Default + goenv, err := e.goEnv() + if err != nil { + return nil, err + } + ctx.GOROOT = goenv["GOROOT"] + ctx.GOPATH = goenv["GOPATH"] + + // As of Go 1.14, build.Context has a Dir field + // (see golang.org/issue/34860). + // Populate it only if present. + rc := reflect.ValueOf(&ctx).Elem() + dir := rc.FieldByName("Dir") + if !dir.IsValid() { + // Working drafts of Go 1.14 named the field "WorkingDir" instead. + // TODO(bcmills): Remove this case after the Go 1.14 beta has been released. + dir = rc.FieldByName("WorkingDir") + } + if dir.IsValid() && dir.Kind() == reflect.String { + dir.SetString(e.WorkingDir) + } + + return &ctx, nil +} + +func (e *ProcessEnv) invokeGo(ctx context.Context, verb string, args ...string) (*bytes.Buffer, error) { + inv := gocommand.Invocation{ + Verb: verb, + Args: args, + BuildFlags: e.BuildFlags, + Env: e.env(), + Logf: e.Logf, + WorkingDir: e.WorkingDir, + } + return e.GocmdRunner.Run(ctx, inv) +} + +func addStdlibCandidates(pass *pass, refs references) error { + goenv, err := pass.env.goEnv() + if err != nil { + return err + } + add := func(pkg string) { + // Prevent self-imports. + if path.Base(pkg) == pass.f.Name.Name && filepath.Join(goenv["GOROOT"], "src", pkg) == pass.srcDir { + return + } + exports := copyExports(stdlib[pkg]) + pass.addCandidate( + &ImportInfo{ImportPath: pkg}, + &packageInfo{name: path.Base(pkg), exports: exports}) + } + for left := range refs { + if left == "rand" { + // Make sure we try crypto/rand before math/rand. + add("crypto/rand") + add("math/rand") + continue + } + for importPath := range stdlib { + if path.Base(importPath) == left { + add(importPath) + } + } + } + return nil +} + +// A Resolver does the build-system-specific parts of goimports. +type Resolver interface { + // loadPackageNames loads the package names in importPaths. + loadPackageNames(importPaths []string, srcDir string) (map[string]string, error) + // scan works with callback to search for packages. See scanCallback for details. + scan(ctx context.Context, callback *scanCallback) error + // loadExports returns the set of exported symbols in the package at dir. + // loadExports may be called concurrently. + loadExports(ctx context.Context, pkg *pkg, includeTest bool) (string, []string, error) + // scoreImportPath returns the relevance for an import path. + scoreImportPath(ctx context.Context, path string) int + + ClearForNewScan() +} + +// A scanCallback controls a call to scan and receives its results. +// In general, minor errors will be silently discarded; a user should not +// expect to receive a full series of calls for everything. +type scanCallback struct { + // rootFound is called before scanning a new root dir. If it returns true, + // the root will be scanned. Returning false will not necessarily prevent + // directories from that root making it to dirFound. + rootFound func(gopathwalk.Root) bool + // dirFound is called when a directory is found that is possibly a Go package. + // pkg will be populated with everything except packageName. + // If it returns true, the package's name will be loaded. + dirFound func(pkg *pkg) bool + // packageNameLoaded is called when a package is found and its name is loaded. + // If it returns true, the package's exports will be loaded. + packageNameLoaded func(pkg *pkg) bool + // exportsLoaded is called when a package's exports have been loaded. + exportsLoaded func(pkg *pkg, exports []string) +} + +func addExternalCandidates(pass *pass, refs references, filename string) error { + var mu sync.Mutex + found := make(map[string][]pkgDistance) + callback := &scanCallback{ + rootFound: func(gopathwalk.Root) bool { + return true // We want everything. + }, + dirFound: func(pkg *pkg) bool { + return pkgIsCandidate(filename, refs, pkg) + }, + packageNameLoaded: func(pkg *pkg) bool { + if _, want := refs[pkg.packageName]; !want { + return false + } + if pkg.dir == pass.srcDir && pass.f.Name.Name == pkg.packageName { + // The candidate is in the same directory and has the + // same package name. Don't try to import ourselves. + return false + } + if !canUse(filename, pkg.dir) { + return false + } + mu.Lock() + defer mu.Unlock() + found[pkg.packageName] = append(found[pkg.packageName], pkgDistance{pkg, distance(pass.srcDir, pkg.dir)}) + return false // We'll do our own loading after we sort. + }, + } + resolver, err := pass.env.GetResolver() + if err != nil { + return err + } + if err = resolver.scan(context.Background(), callback); err != nil { + return err + } + + // Search for imports matching potential package references. + type result struct { + imp *ImportInfo + pkg *packageInfo + } + results := make(chan result, len(refs)) + + ctx, cancel := context.WithCancel(context.TODO()) + var wg sync.WaitGroup + defer func() { + cancel() + wg.Wait() + }() + var ( + firstErr error + firstErrOnce sync.Once + ) + for pkgName, symbols := range refs { + wg.Add(1) + go func(pkgName string, symbols map[string]bool) { + defer wg.Done() + + found, err := findImport(ctx, pass, found[pkgName], pkgName, symbols, filename) + + if err != nil { + firstErrOnce.Do(func() { + firstErr = err + cancel() + }) + return + } + + if found == nil { + return // No matching package. + } + + imp := &ImportInfo{ + ImportPath: found.importPathShort, + } + + pkg := &packageInfo{ + name: pkgName, + exports: symbols, + } + results <- result{imp, pkg} + }(pkgName, symbols) + } + go func() { + wg.Wait() + close(results) + }() + + for result := range results { + pass.addCandidate(result.imp, result.pkg) + } + return firstErr +} + +// notIdentifier reports whether ch is an invalid identifier character. +func notIdentifier(ch rune) bool { + return !('a' <= ch && ch <= 'z' || 'A' <= ch && ch <= 'Z' || + '0' <= ch && ch <= '9' || + ch == '_' || + ch >= utf8.RuneSelf && (unicode.IsLetter(ch) || unicode.IsDigit(ch))) +} + +// ImportPathToAssumedName returns the assumed package name of an import path. +// It does this using only string parsing of the import path. +// It picks the last element of the path that does not look like a major +// version, and then picks the valid identifier off the start of that element. +// It is used to determine if a local rename should be added to an import for +// clarity. +// This function could be moved to a standard package and exported if we want +// for use in other tools. +func ImportPathToAssumedName(importPath string) string { + base := path.Base(importPath) + if strings.HasPrefix(base, "v") { + if _, err := strconv.Atoi(base[1:]); err == nil { + dir := path.Dir(importPath) + if dir != "." { + base = path.Base(dir) + } + } + } + base = strings.TrimPrefix(base, "go-") + if i := strings.IndexFunc(base, notIdentifier); i >= 0 { + base = base[:i] + } + return base +} + +// gopathResolver implements resolver for GOPATH workspaces. +type gopathResolver struct { + env *ProcessEnv + walked bool + cache *dirInfoCache + scanSema chan struct{} // scanSema prevents concurrent scans. +} + +func newGopathResolver(env *ProcessEnv) *gopathResolver { + r := &gopathResolver{ + env: env, + cache: &dirInfoCache{ + dirs: map[string]*directoryPackageInfo{}, + listeners: map[*int]cacheListener{}, + }, + scanSema: make(chan struct{}, 1), + } + r.scanSema <- struct{}{} + return r +} + +func (r *gopathResolver) ClearForNewScan() { + <-r.scanSema + r.cache = &dirInfoCache{ + dirs: map[string]*directoryPackageInfo{}, + listeners: map[*int]cacheListener{}, + } + r.walked = false + r.scanSema <- struct{}{} +} + +func (r *gopathResolver) loadPackageNames(importPaths []string, srcDir string) (map[string]string, error) { + names := map[string]string{} + bctx, err := r.env.buildContext() + if err != nil { + return nil, err + } + for _, path := range importPaths { + names[path] = importPathToName(bctx, path, srcDir) + } + return names, nil +} + +// importPathToName finds out the actual package name, as declared in its .go files. +func importPathToName(bctx *build.Context, importPath, srcDir string) string { + // Fast path for standard library without going to disk. + if _, ok := stdlib[importPath]; ok { + return path.Base(importPath) // stdlib packages always match their paths. + } + + buildPkg, err := bctx.Import(importPath, srcDir, build.FindOnly) + if err != nil { + return "" + } + pkgName, err := packageDirToName(buildPkg.Dir) + if err != nil { + return "" + } + return pkgName +} + +// packageDirToName is a faster version of build.Import if +// the only thing desired is the package name. Given a directory, +// packageDirToName then only parses one file in the package, +// trusting that the files in the directory are consistent. +func packageDirToName(dir string) (packageName string, err error) { + d, err := os.Open(dir) + if err != nil { + return "", err + } + names, err := d.Readdirnames(-1) + d.Close() + if err != nil { + return "", err + } + sort.Strings(names) // to have predictable behavior + var lastErr error + var nfile int + for _, name := range names { + if !strings.HasSuffix(name, ".go") { + continue + } + if strings.HasSuffix(name, "_test.go") { + continue + } + nfile++ + fullFile := filepath.Join(dir, name) + + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, fullFile, nil, parser.PackageClauseOnly) + if err != nil { + lastErr = err + continue + } + pkgName := f.Name.Name + if pkgName == "documentation" { + // Special case from go/build.ImportDir, not + // handled by ctx.MatchFile. + continue + } + if pkgName == "main" { + // Also skip package main, assuming it's a +build ignore generator or example. + // Since you can't import a package main anyway, there's no harm here. + continue + } + return pkgName, nil + } + if lastErr != nil { + return "", lastErr + } + return "", fmt.Errorf("no importable package found in %d Go files", nfile) +} + +type pkg struct { + dir string // absolute file path to pkg directory ("/usr/lib/go/src/net/http") + importPathShort string // vendorless import path ("net/http", "a/b") + packageName string // package name loaded from source if requested + relevance int // a weakly-defined score of how relevant a package is. 0 is most relevant. +} + +type pkgDistance struct { + pkg *pkg + distance int // relative distance to target +} + +// byDistanceOrImportPathShortLength sorts by relative distance breaking ties +// on the short import path length and then the import string itself. +type byDistanceOrImportPathShortLength []pkgDistance + +func (s byDistanceOrImportPathShortLength) Len() int { return len(s) } +func (s byDistanceOrImportPathShortLength) Less(i, j int) bool { + di, dj := s[i].distance, s[j].distance + if di == -1 { + return false + } + if dj == -1 { + return true + } + if di != dj { + return di < dj + } + + vi, vj := s[i].pkg.importPathShort, s[j].pkg.importPathShort + if len(vi) != len(vj) { + return len(vi) < len(vj) + } + return vi < vj +} +func (s byDistanceOrImportPathShortLength) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +func distance(basepath, targetpath string) int { + p, err := filepath.Rel(basepath, targetpath) + if err != nil { + return -1 + } + if p == "." { + return 0 + } + return strings.Count(p, string(filepath.Separator)) + 1 +} + +func (r *gopathResolver) scan(ctx context.Context, callback *scanCallback) error { + add := func(root gopathwalk.Root, dir string) { + // We assume cached directories have not changed. We can skip them and their + // children. + if _, ok := r.cache.Load(dir); ok { + return + } + + importpath := filepath.ToSlash(dir[len(root.Path)+len("/"):]) + info := directoryPackageInfo{ + status: directoryScanned, + dir: dir, + rootType: root.Type, + nonCanonicalImportPath: VendorlessPath(importpath), + } + r.cache.Store(dir, info) + } + processDir := func(info directoryPackageInfo) { + // Skip this directory if we were not able to get the package information successfully. + if scanned, err := info.reachedStatus(directoryScanned); !scanned || err != nil { + return + } + + p := &pkg{ + importPathShort: info.nonCanonicalImportPath, + dir: info.dir, + relevance: MaxRelevance - 1, + } + if info.rootType == gopathwalk.RootGOROOT { + p.relevance = MaxRelevance + } + + if !callback.dirFound(p) { + return + } + var err error + p.packageName, err = r.cache.CachePackageName(info) + if err != nil { + return + } + + if !callback.packageNameLoaded(p) { + return + } + if _, exports, err := r.loadExports(ctx, p, false); err == nil { + callback.exportsLoaded(p, exports) + } + } + stop := r.cache.ScanAndListen(ctx, processDir) + defer stop() + + goenv, err := r.env.goEnv() + if err != nil { + return err + } + var roots []gopathwalk.Root + roots = append(roots, gopathwalk.Root{filepath.Join(goenv["GOROOT"], "src"), gopathwalk.RootGOROOT}) + for _, p := range filepath.SplitList(goenv["GOPATH"]) { + roots = append(roots, gopathwalk.Root{filepath.Join(p, "src"), gopathwalk.RootGOPATH}) + } + // The callback is not necessarily safe to use in the goroutine below. Process roots eagerly. + roots = filterRoots(roots, callback.rootFound) + // We can't cancel walks, because we need them to finish to have a usable + // cache. Instead, run them in a separate goroutine and detach. + scanDone := make(chan struct{}) + go func() { + select { + case <-ctx.Done(): + return + case <-r.scanSema: + } + defer func() { r.scanSema <- struct{}{} }() + gopathwalk.Walk(roots, add, gopathwalk.Options{Logf: r.env.Logf, ModulesEnabled: false}) + close(scanDone) + }() + select { + case <-ctx.Done(): + case <-scanDone: + } + return nil +} + +func (r *gopathResolver) scoreImportPath(ctx context.Context, path string) int { + if _, ok := stdlib[path]; ok { + return MaxRelevance + } + return MaxRelevance - 1 +} + +func filterRoots(roots []gopathwalk.Root, include func(gopathwalk.Root) bool) []gopathwalk.Root { + var result []gopathwalk.Root + for _, root := range roots { + if !include(root) { + continue + } + result = append(result, root) + } + return result +} + +func (r *gopathResolver) loadExports(ctx context.Context, pkg *pkg, includeTest bool) (string, []string, error) { + if info, ok := r.cache.Load(pkg.dir); ok && !includeTest { + return r.cache.CacheExports(ctx, r.env, info) + } + return loadExportsFromFiles(ctx, r.env, pkg.dir, includeTest) +} + +// VendorlessPath returns the devendorized version of the import path ipath. +// For example, VendorlessPath("foo/bar/vendor/a/b") returns "a/b". +func VendorlessPath(ipath string) string { + // Devendorize for use in import statement. + if i := strings.LastIndex(ipath, "/vendor/"); i >= 0 { + return ipath[i+len("/vendor/"):] + } + if strings.HasPrefix(ipath, "vendor/") { + return ipath[len("vendor/"):] + } + return ipath +} + +func loadExportsFromFiles(ctx context.Context, env *ProcessEnv, dir string, includeTest bool) (string, []string, error) { + // Look for non-test, buildable .go files which could provide exports. + all, err := ioutil.ReadDir(dir) + if err != nil { + return "", nil, err + } + var files []os.FileInfo + for _, fi := range all { + name := fi.Name() + if !strings.HasSuffix(name, ".go") || (!includeTest && strings.HasSuffix(name, "_test.go")) { + continue + } + match, err := env.matchFile(dir, fi.Name()) + if err != nil || !match { + continue + } + files = append(files, fi) + } + + if len(files) == 0 { + return "", nil, fmt.Errorf("dir %v contains no buildable, non-test .go files", dir) + } + + var pkgName string + var exports []string + fset := token.NewFileSet() + for _, fi := range files { + select { + case <-ctx.Done(): + return "", nil, ctx.Err() + default: + } + + fullFile := filepath.Join(dir, fi.Name()) + f, err := parser.ParseFile(fset, fullFile, nil, 0) + if err != nil { + if env.Logf != nil { + env.Logf("error parsing %v: %v", fullFile, err) + } + continue + } + if f.Name.Name == "documentation" { + // Special case from go/build.ImportDir, not + // handled by MatchFile above. + continue + } + if includeTest && strings.HasSuffix(f.Name.Name, "_test") { + // x_test package. We want internal test files only. + continue + } + pkgName = f.Name.Name + for name := range f.Scope.Objects { + if ast.IsExported(name) { + exports = append(exports, name) + } + } + } + + if env.Logf != nil { + sortedExports := append([]string(nil), exports...) + sort.Strings(sortedExports) + env.Logf("loaded exports in dir %v (package %v): %v", dir, pkgName, strings.Join(sortedExports, ", ")) + } + return pkgName, exports, nil +} + +// findImport searches for a package with the given symbols. +// If no package is found, findImport returns ("", false, nil) +func findImport(ctx context.Context, pass *pass, candidates []pkgDistance, pkgName string, symbols map[string]bool, filename string) (*pkg, error) { + // Sort the candidates by their import package length, + // assuming that shorter package names are better than long + // ones. Note that this sorts by the de-vendored name, so + // there's no "penalty" for vendoring. + sort.Sort(byDistanceOrImportPathShortLength(candidates)) + if pass.env.Logf != nil { + for i, c := range candidates { + pass.env.Logf("%s candidate %d/%d: %v in %v", pkgName, i+1, len(candidates), c.pkg.importPathShort, c.pkg.dir) + } + } + resolver, err := pass.env.GetResolver() + if err != nil { + return nil, err + } + + // Collect exports for packages with matching names. + rescv := make([]chan *pkg, len(candidates)) + for i := range candidates { + rescv[i] = make(chan *pkg, 1) + } + const maxConcurrentPackageImport = 4 + loadExportsSem := make(chan struct{}, maxConcurrentPackageImport) + + ctx, cancel := context.WithCancel(ctx) + var wg sync.WaitGroup + defer func() { + cancel() + wg.Wait() + }() + + wg.Add(1) + go func() { + defer wg.Done() + for i, c := range candidates { + select { + case loadExportsSem <- struct{}{}: + case <-ctx.Done(): + return + } + + wg.Add(1) + go func(c pkgDistance, resc chan<- *pkg) { + defer func() { + <-loadExportsSem + wg.Done() + }() + + if pass.env.Logf != nil { + pass.env.Logf("loading exports in dir %s (seeking package %s)", c.pkg.dir, pkgName) + } + // If we're an x_test, load the package under test's test variant. + includeTest := strings.HasSuffix(pass.f.Name.Name, "_test") && c.pkg.dir == pass.srcDir + _, exports, err := resolver.loadExports(ctx, c.pkg, includeTest) + if err != nil { + if pass.env.Logf != nil { + pass.env.Logf("loading exports in dir %s (seeking package %s): %v", c.pkg.dir, pkgName, err) + } + resc <- nil + return + } + + exportsMap := make(map[string]bool, len(exports)) + for _, sym := range exports { + exportsMap[sym] = true + } + + // If it doesn't have the right + // symbols, send nil to mean no match. + for symbol := range symbols { + if !exportsMap[symbol] { + resc <- nil + return + } + } + resc <- c.pkg + }(c, rescv[i]) + } + }() + + for _, resc := range rescv { + pkg := <-resc + if pkg == nil { + continue + } + return pkg, nil + } + return nil, nil +} + +// pkgIsCandidate reports whether pkg is a candidate for satisfying the +// finding which package pkgIdent in the file named by filename is trying +// to refer to. +// +// This check is purely lexical and is meant to be as fast as possible +// because it's run over all $GOPATH directories to filter out poor +// candidates in order to limit the CPU and I/O later parsing the +// exports in candidate packages. +// +// filename is the file being formatted. +// pkgIdent is the package being searched for, like "client" (if +// searching for "client.New") +func pkgIsCandidate(filename string, refs references, pkg *pkg) bool { + // Check "internal" and "vendor" visibility: + if !canUse(filename, pkg.dir) { + return false + } + + // Speed optimization to minimize disk I/O: + // the last two components on disk must contain the + // package name somewhere. + // + // This permits mismatch naming like directory + // "go-foo" being package "foo", or "pkg.v3" being "pkg", + // or directory "google.golang.org/api/cloudbilling/v1" + // being package "cloudbilling", but doesn't + // permit a directory "foo" to be package + // "bar", which is strongly discouraged + // anyway. There's no reason goimports needs + // to be slow just to accommodate that. + for pkgIdent := range refs { + lastTwo := lastTwoComponents(pkg.importPathShort) + if strings.Contains(lastTwo, pkgIdent) { + return true + } + if hasHyphenOrUpperASCII(lastTwo) && !hasHyphenOrUpperASCII(pkgIdent) { + lastTwo = lowerASCIIAndRemoveHyphen(lastTwo) + if strings.Contains(lastTwo, pkgIdent) { + return true + } + } + } + return false +} + +func hasHyphenOrUpperASCII(s string) bool { + for i := 0; i < len(s); i++ { + b := s[i] + if b == '-' || ('A' <= b && b <= 'Z') { + return true + } + } + return false +} + +func lowerASCIIAndRemoveHyphen(s string) (ret string) { + buf := make([]byte, 0, len(s)) + for i := 0; i < len(s); i++ { + b := s[i] + switch { + case b == '-': + continue + case 'A' <= b && b <= 'Z': + buf = append(buf, b+('a'-'A')) + default: + buf = append(buf, b) + } + } + return string(buf) +} + +// canUse reports whether the package in dir is usable from filename, +// respecting the Go "internal" and "vendor" visibility rules. +func canUse(filename, dir string) bool { + // Fast path check, before any allocations. If it doesn't contain vendor + // or internal, it's not tricky: + // Note that this can false-negative on directories like "notinternal", + // but we check it correctly below. This is just a fast path. + if !strings.Contains(dir, "vendor") && !strings.Contains(dir, "internal") { + return true + } + + dirSlash := filepath.ToSlash(dir) + if !strings.Contains(dirSlash, "/vendor/") && !strings.Contains(dirSlash, "/internal/") && !strings.HasSuffix(dirSlash, "/internal") { + return true + } + // Vendor or internal directory only visible from children of parent. + // That means the path from the current directory to the target directory + // can contain ../vendor or ../internal but not ../foo/vendor or ../foo/internal + // or bar/vendor or bar/internal. + // After stripping all the leading ../, the only okay place to see vendor or internal + // is at the very beginning of the path. + absfile, err := filepath.Abs(filename) + if err != nil { + return false + } + absdir, err := filepath.Abs(dir) + if err != nil { + return false + } + rel, err := filepath.Rel(absfile, absdir) + if err != nil { + return false + } + relSlash := filepath.ToSlash(rel) + if i := strings.LastIndex(relSlash, "../"); i >= 0 { + relSlash = relSlash[i+len("../"):] + } + return !strings.Contains(relSlash, "/vendor/") && !strings.Contains(relSlash, "/internal/") && !strings.HasSuffix(relSlash, "/internal") +} + +// lastTwoComponents returns at most the last two path components +// of v, using either / or \ as the path separator. +func lastTwoComponents(v string) string { + nslash := 0 + for i := len(v) - 1; i >= 0; i-- { + if v[i] == '/' || v[i] == '\\' { + nslash++ + if nslash == 2 { + return v[i:] + } + } + } + return v +} + +type visitFn func(node ast.Node) ast.Visitor + +func (fn visitFn) Visit(node ast.Node) ast.Visitor { + return fn(node) +} + +func copyExports(pkg []string) map[string]bool { + m := make(map[string]bool, len(pkg)) + for _, v := range pkg { + m[v] = true + } + return m +} diff --git a/vendor/golang.org/x/tools/imports/imports.go b/vendor/golang.org/x/tools/internal/imports/imports.go similarity index 76% rename from vendor/golang.org/x/tools/imports/imports.go rename to vendor/golang.org/x/tools/internal/imports/imports.go index 07101cb80..2815edc33 100644 --- a/vendor/golang.org/x/tools/imports/imports.go +++ b/vendor/golang.org/x/tools/internal/imports/imports.go @@ -6,20 +6,18 @@ // Package imports implements a Go pretty-printer (like package "go/format") // that also adds or removes import statements as necessary. -package imports // import "golang.org/x/tools/imports" +package imports import ( "bufio" "bytes" "fmt" "go/ast" - "go/build" "go/format" "go/parser" "go/printer" "go/token" "io" - "io/ioutil" "regexp" "strconv" "strings" @@ -27,8 +25,15 @@ import ( "golang.org/x/tools/go/ast/astutil" ) -// Options specifies options for processing files. +// Options is golang.org/x/tools/imports.Options with extra internal-only options. type Options struct { + Env *ProcessEnv // The environment to use. Note: this contains the cached module and filesystem state. + + // LocalPrefix is a comma-separated string of import path prefixes, which, if + // set, instructs Process to sort the import paths with the given prefixes + // into another group after 3rd-party packages. + LocalPrefix string + Fragment bool // Accept fragment of a source file (no package statement) AllErrors bool // Report all errors (not just the first 10 on different lines) @@ -39,29 +44,8 @@ type Options struct { FormatOnly bool // Disable the insertion and deletion of imports } -// Process formats and adjusts imports for the provided file. -// If opt is nil the defaults are used. -// -// Note that filename's directory influences which imports can be chosen, -// so it is important that filename be accurate. -// To process data ``as if'' it were in filename, pass the data as a non-nil src. -func Process(filename string, src []byte, opt *Options) ([]byte, error) { - env := &fixEnv{GOPATH: build.Default.GOPATH, GOROOT: build.Default.GOROOT} - return process(filename, src, opt, env) -} - -func process(filename string, src []byte, opt *Options, env *fixEnv) ([]byte, error) { - if opt == nil { - opt = &Options{Comments: true, TabIndent: true, TabWidth: 8} - } - if src == nil { - b, err := ioutil.ReadFile(filename) - if err != nil { - return nil, err - } - src = b - } - +// Process implements golang.org/x/tools/imports.Process with explicit context in opt.Env. +func Process(filename string, src []byte, opt *Options) (formatted []byte, err error) { fileSet := token.NewFileSet() file, adjust, err := parse(fileSet, filename, src, opt) if err != nil { @@ -69,12 +53,59 @@ func process(filename string, src []byte, opt *Options, env *fixEnv) ([]byte, er } if !opt.FormatOnly { - if err := fixImports(fileSet, file, filename, env); err != nil { + if err := fixImports(fileSet, file, filename, opt.Env); err != nil { return nil, err } } + return formatFile(fileSet, file, src, adjust, opt) +} - sortImports(fileSet, file) +// FixImports returns a list of fixes to the imports that, when applied, +// will leave the imports in the same state as Process. src and opt must +// be specified. +// +// Note that filename's directory influences which imports can be chosen, +// so it is important that filename be accurate. +func FixImports(filename string, src []byte, opt *Options) (fixes []*ImportFix, err error) { + fileSet := token.NewFileSet() + file, _, err := parse(fileSet, filename, src, opt) + if err != nil { + return nil, err + } + + return getFixes(fileSet, file, filename, opt.Env) +} + +// ApplyFixes applies all of the fixes to the file and formats it. extraMode +// is added in when parsing the file. src and opts must be specified, but no +// env is needed. +func ApplyFixes(fixes []*ImportFix, filename string, src []byte, opt *Options, extraMode parser.Mode) (formatted []byte, err error) { + // Don't use parse() -- we don't care about fragments or statement lists + // here, and we need to work with unparseable files. + fileSet := token.NewFileSet() + parserMode := parser.Mode(0) + if opt.Comments { + parserMode |= parser.ParseComments + } + if opt.AllErrors { + parserMode |= parser.AllErrors + } + parserMode |= extraMode + + file, err := parser.ParseFile(fileSet, filename, src, parserMode) + if file == nil { + return nil, err + } + + // Apply the fixes to the file. + apply(fileSet, file, fixes) + + return formatFile(fileSet, file, src, nil, opt) +} + +func formatFile(fileSet *token.FileSet, file *ast.File, src []byte, adjust func(orig []byte, src []byte) []byte, opt *Options) ([]byte, error) { + mergeImports(fileSet, file) + sortImports(opt.LocalPrefix, fileSet, file) imps := astutil.Imports(fileSet, file) var spacesBefore []string // import paths we need spaces before for _, impSection := range imps { @@ -85,7 +116,7 @@ func process(filename string, src []byte, opt *Options, env *fixEnv) ([]byte, er lastGroup := -1 for _, importSpec := range impSection { importPath, _ := strconv.Unquote(importSpec.Path.Value) - groupNum := importGroup(importPath) + groupNum := importGroup(opt.LocalPrefix, importPath) if groupNum != lastGroup && lastGroup != -1 { spacesBefore = append(spacesBefore, importPath) } @@ -101,7 +132,7 @@ func process(filename string, src []byte, opt *Options, env *fixEnv) ([]byte, er printConfig := &printer.Config{Mode: printerMode, Tabwidth: opt.TabWidth} var buf bytes.Buffer - err = printConfig.Fprint(&buf, fileSet, file) + err := printConfig.Fprint(&buf, fileSet, file) if err != nil { return nil, err } diff --git a/vendor/golang.org/x/tools/internal/imports/mod.go b/vendor/golang.org/x/tools/internal/imports/mod.go new file mode 100644 index 000000000..94880d616 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/imports/mod.go @@ -0,0 +1,663 @@ +package imports + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io/ioutil" + "os" + "path" + "path/filepath" + "regexp" + "sort" + "strconv" + "strings" + + "golang.org/x/mod/module" + "golang.org/x/tools/internal/gocommand" + "golang.org/x/tools/internal/gopathwalk" +) + +// ModuleResolver implements resolver for modules using the go command as little +// as feasible. +type ModuleResolver struct { + env *ProcessEnv + moduleCacheDir string + dummyVendorMod *gocommand.ModuleJSON // If vendoring is enabled, the pseudo-module that represents the /vendor directory. + roots []gopathwalk.Root + scanSema chan struct{} // scanSema prevents concurrent scans and guards scannedRoots. + scannedRoots map[gopathwalk.Root]bool + + initialized bool + main *gocommand.ModuleJSON + modsByModPath []*gocommand.ModuleJSON // All modules, ordered by # of path components in module Path... + modsByDir []*gocommand.ModuleJSON // ...or Dir. + + // moduleCacheCache stores information about the module cache. + moduleCacheCache *dirInfoCache + otherCache *dirInfoCache +} + +func newModuleResolver(e *ProcessEnv) *ModuleResolver { + r := &ModuleResolver{ + env: e, + scanSema: make(chan struct{}, 1), + } + r.scanSema <- struct{}{} + return r +} + +func (r *ModuleResolver) init() error { + if r.initialized { + return nil + } + + goenv, err := r.env.goEnv() + if err != nil { + return err + } + inv := gocommand.Invocation{ + BuildFlags: r.env.BuildFlags, + Env: r.env.env(), + Logf: r.env.Logf, + WorkingDir: r.env.WorkingDir, + } + mainMod, vendorEnabled, err := gocommand.VendorEnabled(context.TODO(), inv, r.env.GocmdRunner) + if err != nil { + return err + } + + if mainMod != nil && vendorEnabled { + // Vendor mode is on, so all the non-Main modules are irrelevant, + // and we need to search /vendor for everything. + r.main = mainMod + r.dummyVendorMod = &gocommand.ModuleJSON{ + Path: "", + Dir: filepath.Join(mainMod.Dir, "vendor"), + } + r.modsByModPath = []*gocommand.ModuleJSON{mainMod, r.dummyVendorMod} + r.modsByDir = []*gocommand.ModuleJSON{mainMod, r.dummyVendorMod} + } else { + // Vendor mode is off, so run go list -m ... to find everything. + r.initAllMods() + } + + if gmc := r.env.Env["GOMODCACHE"]; gmc != "" { + r.moduleCacheDir = gmc + } else { + r.moduleCacheDir = filepath.Join(filepath.SplitList(goenv["GOPATH"])[0], "/pkg/mod") + } + + sort.Slice(r.modsByModPath, func(i, j int) bool { + count := func(x int) int { + return strings.Count(r.modsByModPath[x].Path, "/") + } + return count(j) < count(i) // descending order + }) + sort.Slice(r.modsByDir, func(i, j int) bool { + count := func(x int) int { + return strings.Count(r.modsByDir[x].Dir, "/") + } + return count(j) < count(i) // descending order + }) + + r.roots = []gopathwalk.Root{ + {filepath.Join(goenv["GOROOT"], "/src"), gopathwalk.RootGOROOT}, + } + if r.main != nil { + r.roots = append(r.roots, gopathwalk.Root{r.main.Dir, gopathwalk.RootCurrentModule}) + } + if vendorEnabled { + r.roots = append(r.roots, gopathwalk.Root{r.dummyVendorMod.Dir, gopathwalk.RootOther}) + } else { + addDep := func(mod *gocommand.ModuleJSON) { + if mod.Replace == nil { + // This is redundant with the cache, but we'll skip it cheaply enough. + r.roots = append(r.roots, gopathwalk.Root{mod.Dir, gopathwalk.RootModuleCache}) + } else { + r.roots = append(r.roots, gopathwalk.Root{mod.Dir, gopathwalk.RootOther}) + } + } + // Walk dependent modules before scanning the full mod cache, direct deps first. + for _, mod := range r.modsByModPath { + if !mod.Indirect && !mod.Main { + addDep(mod) + } + } + for _, mod := range r.modsByModPath { + if mod.Indirect && !mod.Main { + addDep(mod) + } + } + r.roots = append(r.roots, gopathwalk.Root{r.moduleCacheDir, gopathwalk.RootModuleCache}) + } + + r.scannedRoots = map[gopathwalk.Root]bool{} + if r.moduleCacheCache == nil { + r.moduleCacheCache = &dirInfoCache{ + dirs: map[string]*directoryPackageInfo{}, + listeners: map[*int]cacheListener{}, + } + } + if r.otherCache == nil { + r.otherCache = &dirInfoCache{ + dirs: map[string]*directoryPackageInfo{}, + listeners: map[*int]cacheListener{}, + } + } + r.initialized = true + return nil +} + +func (r *ModuleResolver) initAllMods() error { + stdout, err := r.env.invokeGo(context.TODO(), "list", "-m", "-json", "...") + if err != nil { + return err + } + for dec := json.NewDecoder(stdout); dec.More(); { + mod := &gocommand.ModuleJSON{} + if err := dec.Decode(mod); err != nil { + return err + } + if mod.Dir == "" { + if r.env.Logf != nil { + r.env.Logf("module %v has not been downloaded and will be ignored", mod.Path) + } + // Can't do anything with a module that's not downloaded. + continue + } + // golang/go#36193: the go command doesn't always clean paths. + mod.Dir = filepath.Clean(mod.Dir) + r.modsByModPath = append(r.modsByModPath, mod) + r.modsByDir = append(r.modsByDir, mod) + if mod.Main { + r.main = mod + } + } + return nil +} + +func (r *ModuleResolver) ClearForNewScan() { + <-r.scanSema + r.scannedRoots = map[gopathwalk.Root]bool{} + r.otherCache = &dirInfoCache{ + dirs: map[string]*directoryPackageInfo{}, + listeners: map[*int]cacheListener{}, + } + r.scanSema <- struct{}{} +} + +func (r *ModuleResolver) ClearForNewMod() { + <-r.scanSema + *r = ModuleResolver{ + env: r.env, + moduleCacheCache: r.moduleCacheCache, + otherCache: r.otherCache, + scanSema: r.scanSema, + } + r.init() + r.scanSema <- struct{}{} +} + +// findPackage returns the module and directory that contains the package at +// the given import path, or returns nil, "" if no module is in scope. +func (r *ModuleResolver) findPackage(importPath string) (*gocommand.ModuleJSON, string) { + // This can't find packages in the stdlib, but that's harmless for all + // the existing code paths. + for _, m := range r.modsByModPath { + if !strings.HasPrefix(importPath, m.Path) { + continue + } + pathInModule := importPath[len(m.Path):] + pkgDir := filepath.Join(m.Dir, pathInModule) + if r.dirIsNestedModule(pkgDir, m) { + continue + } + + if info, ok := r.cacheLoad(pkgDir); ok { + if loaded, err := info.reachedStatus(nameLoaded); loaded { + if err != nil { + continue // No package in this dir. + } + return m, pkgDir + } + if scanned, err := info.reachedStatus(directoryScanned); scanned && err != nil { + continue // Dir is unreadable, etc. + } + // This is slightly wrong: a directory doesn't have to have an + // importable package to count as a package for package-to-module + // resolution. package main or _test files should count but + // don't. + // TODO(heschi): fix this. + if _, err := r.cachePackageName(info); err == nil { + return m, pkgDir + } + } + + // Not cached. Read the filesystem. + pkgFiles, err := ioutil.ReadDir(pkgDir) + if err != nil { + continue + } + // A module only contains a package if it has buildable go + // files in that directory. If not, it could be provided by an + // outer module. See #29736. + for _, fi := range pkgFiles { + if ok, _ := r.env.matchFile(pkgDir, fi.Name()); ok { + return m, pkgDir + } + } + } + return nil, "" +} + +func (r *ModuleResolver) cacheLoad(dir string) (directoryPackageInfo, bool) { + if info, ok := r.moduleCacheCache.Load(dir); ok { + return info, ok + } + return r.otherCache.Load(dir) +} + +func (r *ModuleResolver) cacheStore(info directoryPackageInfo) { + if info.rootType == gopathwalk.RootModuleCache { + r.moduleCacheCache.Store(info.dir, info) + } else { + r.otherCache.Store(info.dir, info) + } +} + +func (r *ModuleResolver) cacheKeys() []string { + return append(r.moduleCacheCache.Keys(), r.otherCache.Keys()...) +} + +// cachePackageName caches the package name for a dir already in the cache. +func (r *ModuleResolver) cachePackageName(info directoryPackageInfo) (string, error) { + if info.rootType == gopathwalk.RootModuleCache { + return r.moduleCacheCache.CachePackageName(info) + } + return r.otherCache.CachePackageName(info) +} + +func (r *ModuleResolver) cacheExports(ctx context.Context, env *ProcessEnv, info directoryPackageInfo) (string, []string, error) { + if info.rootType == gopathwalk.RootModuleCache { + return r.moduleCacheCache.CacheExports(ctx, env, info) + } + return r.otherCache.CacheExports(ctx, env, info) +} + +// findModuleByDir returns the module that contains dir, or nil if no such +// module is in scope. +func (r *ModuleResolver) findModuleByDir(dir string) *gocommand.ModuleJSON { + // This is quite tricky and may not be correct. dir could be: + // - a package in the main module. + // - a replace target underneath the main module's directory. + // - a nested module in the above. + // - a replace target somewhere totally random. + // - a nested module in the above. + // - in the mod cache. + // - in /vendor/ in -mod=vendor mode. + // - nested module? Dunno. + // Rumor has it that replace targets cannot contain other replace targets. + for _, m := range r.modsByDir { + if !strings.HasPrefix(dir, m.Dir) { + continue + } + + if r.dirIsNestedModule(dir, m) { + continue + } + + return m + } + return nil +} + +// dirIsNestedModule reports if dir is contained in a nested module underneath +// mod, not actually in mod. +func (r *ModuleResolver) dirIsNestedModule(dir string, mod *gocommand.ModuleJSON) bool { + if !strings.HasPrefix(dir, mod.Dir) { + return false + } + if r.dirInModuleCache(dir) { + // Nested modules in the module cache are pruned, + // so it cannot be a nested module. + return false + } + if mod != nil && mod == r.dummyVendorMod { + // The /vendor pseudomodule is flattened and doesn't actually count. + return false + } + modDir, _ := r.modInfo(dir) + if modDir == "" { + return false + } + return modDir != mod.Dir +} + +func (r *ModuleResolver) modInfo(dir string) (modDir string, modName string) { + readModName := func(modFile string) string { + modBytes, err := ioutil.ReadFile(modFile) + if err != nil { + return "" + } + return modulePath(modBytes) + } + + if r.dirInModuleCache(dir) { + matches := modCacheRegexp.FindStringSubmatch(dir) + index := strings.Index(dir, matches[1]+"@"+matches[2]) + modDir := filepath.Join(dir[:index], matches[1]+"@"+matches[2]) + return modDir, readModName(filepath.Join(modDir, "go.mod")) + } + for { + if info, ok := r.cacheLoad(dir); ok { + return info.moduleDir, info.moduleName + } + f := filepath.Join(dir, "go.mod") + info, err := os.Stat(f) + if err == nil && !info.IsDir() { + return dir, readModName(f) + } + + d := filepath.Dir(dir) + if len(d) >= len(dir) { + return "", "" // reached top of file system, no go.mod + } + dir = d + } +} + +func (r *ModuleResolver) dirInModuleCache(dir string) bool { + if r.moduleCacheDir == "" { + return false + } + return strings.HasPrefix(dir, r.moduleCacheDir) +} + +func (r *ModuleResolver) loadPackageNames(importPaths []string, srcDir string) (map[string]string, error) { + if err := r.init(); err != nil { + return nil, err + } + names := map[string]string{} + for _, path := range importPaths { + _, packageDir := r.findPackage(path) + if packageDir == "" { + continue + } + name, err := packageDirToName(packageDir) + if err != nil { + continue + } + names[path] = name + } + return names, nil +} + +func (r *ModuleResolver) scan(ctx context.Context, callback *scanCallback) error { + if err := r.init(); err != nil { + return err + } + + processDir := func(info directoryPackageInfo) { + // Skip this directory if we were not able to get the package information successfully. + if scanned, err := info.reachedStatus(directoryScanned); !scanned || err != nil { + return + } + pkg, err := r.canonicalize(info) + if err != nil { + return + } + + if !callback.dirFound(pkg) { + return + } + pkg.packageName, err = r.cachePackageName(info) + if err != nil { + return + } + + if !callback.packageNameLoaded(pkg) { + return + } + _, exports, err := r.loadExports(ctx, pkg, false) + if err != nil { + return + } + callback.exportsLoaded(pkg, exports) + } + + // Start processing everything in the cache, and listen for the new stuff + // we discover in the walk below. + stop1 := r.moduleCacheCache.ScanAndListen(ctx, processDir) + defer stop1() + stop2 := r.otherCache.ScanAndListen(ctx, processDir) + defer stop2() + + // We assume cached directories are fully cached, including all their + // children, and have not changed. We can skip them. + skip := func(root gopathwalk.Root, dir string) bool { + info, ok := r.cacheLoad(dir) + if !ok { + return false + } + // This directory can be skipped as long as we have already scanned it. + // Packages with errors will continue to have errors, so there is no need + // to rescan them. + packageScanned, _ := info.reachedStatus(directoryScanned) + return packageScanned + } + + // Add anything new to the cache, and process it if we're still listening. + add := func(root gopathwalk.Root, dir string) { + r.cacheStore(r.scanDirForPackage(root, dir)) + } + + // r.roots and the callback are not necessarily safe to use in the + // goroutine below. Process them eagerly. + roots := filterRoots(r.roots, callback.rootFound) + // We can't cancel walks, because we need them to finish to have a usable + // cache. Instead, run them in a separate goroutine and detach. + scanDone := make(chan struct{}) + go func() { + select { + case <-ctx.Done(): + return + case <-r.scanSema: + } + defer func() { r.scanSema <- struct{}{} }() + // We have the lock on r.scannedRoots, and no other scans can run. + for _, root := range roots { + if ctx.Err() != nil { + return + } + + if r.scannedRoots[root] { + continue + } + gopathwalk.WalkSkip([]gopathwalk.Root{root}, add, skip, gopathwalk.Options{Logf: r.env.Logf, ModulesEnabled: true}) + r.scannedRoots[root] = true + } + close(scanDone) + }() + select { + case <-ctx.Done(): + case <-scanDone: + } + return nil +} + +func (r *ModuleResolver) scoreImportPath(ctx context.Context, path string) int { + if _, ok := stdlib[path]; ok { + return MaxRelevance + } + mod, _ := r.findPackage(path) + return modRelevance(mod) +} + +func modRelevance(mod *gocommand.ModuleJSON) int { + switch { + case mod == nil: // out of scope + return MaxRelevance - 4 + case mod.Indirect: + return MaxRelevance - 3 + case !mod.Main: + return MaxRelevance - 2 + default: + return MaxRelevance - 1 // main module ties with stdlib + } +} + +// canonicalize gets the result of canonicalizing the packages using the results +// of initializing the resolver from 'go list -m'. +func (r *ModuleResolver) canonicalize(info directoryPackageInfo) (*pkg, error) { + // Packages in GOROOT are already canonical, regardless of the std/cmd modules. + if info.rootType == gopathwalk.RootGOROOT { + return &pkg{ + importPathShort: info.nonCanonicalImportPath, + dir: info.dir, + packageName: path.Base(info.nonCanonicalImportPath), + relevance: MaxRelevance, + }, nil + } + + importPath := info.nonCanonicalImportPath + mod := r.findModuleByDir(info.dir) + // Check if the directory is underneath a module that's in scope. + if mod != nil { + // It is. If dir is the target of a replace directive, + // our guessed import path is wrong. Use the real one. + if mod.Dir == info.dir { + importPath = mod.Path + } else { + dirInMod := info.dir[len(mod.Dir)+len("/"):] + importPath = path.Join(mod.Path, filepath.ToSlash(dirInMod)) + } + } else if !strings.HasPrefix(importPath, info.moduleName) { + // The module's name doesn't match the package's import path. It + // probably needs a replace directive we don't have. + return nil, fmt.Errorf("package in %q is not valid without a replace statement", info.dir) + } + + res := &pkg{ + importPathShort: importPath, + dir: info.dir, + relevance: modRelevance(mod), + } + // We may have discovered a package that has a different version + // in scope already. Canonicalize to that one if possible. + if _, canonicalDir := r.findPackage(importPath); canonicalDir != "" { + res.dir = canonicalDir + } + return res, nil +} + +func (r *ModuleResolver) loadExports(ctx context.Context, pkg *pkg, includeTest bool) (string, []string, error) { + if err := r.init(); err != nil { + return "", nil, err + } + if info, ok := r.cacheLoad(pkg.dir); ok && !includeTest { + return r.cacheExports(ctx, r.env, info) + } + return loadExportsFromFiles(ctx, r.env, pkg.dir, includeTest) +} + +func (r *ModuleResolver) scanDirForPackage(root gopathwalk.Root, dir string) directoryPackageInfo { + subdir := "" + if dir != root.Path { + subdir = dir[len(root.Path)+len("/"):] + } + importPath := filepath.ToSlash(subdir) + if strings.HasPrefix(importPath, "vendor/") { + // Only enter vendor directories if they're explicitly requested as a root. + return directoryPackageInfo{ + status: directoryScanned, + err: fmt.Errorf("unwanted vendor directory"), + } + } + switch root.Type { + case gopathwalk.RootCurrentModule: + importPath = path.Join(r.main.Path, filepath.ToSlash(subdir)) + case gopathwalk.RootModuleCache: + matches := modCacheRegexp.FindStringSubmatch(subdir) + if len(matches) == 0 { + return directoryPackageInfo{ + status: directoryScanned, + err: fmt.Errorf("invalid module cache path: %v", subdir), + } + } + modPath, err := module.UnescapePath(filepath.ToSlash(matches[1])) + if err != nil { + if r.env.Logf != nil { + r.env.Logf("decoding module cache path %q: %v", subdir, err) + } + return directoryPackageInfo{ + status: directoryScanned, + err: fmt.Errorf("decoding module cache path %q: %v", subdir, err), + } + } + importPath = path.Join(modPath, filepath.ToSlash(matches[3])) + } + + modDir, modName := r.modInfo(dir) + result := directoryPackageInfo{ + status: directoryScanned, + dir: dir, + rootType: root.Type, + nonCanonicalImportPath: importPath, + moduleDir: modDir, + moduleName: modName, + } + if root.Type == gopathwalk.RootGOROOT { + // stdlib packages are always in scope, despite the confusing go.mod + return result + } + return result +} + +// modCacheRegexp splits a path in a module cache into module, module version, and package. +var modCacheRegexp = regexp.MustCompile(`(.*)@([^/\\]*)(.*)`) + +var ( + slashSlash = []byte("//") + moduleStr = []byte("module") +) + +// modulePath returns the module path from the gomod file text. +// If it cannot find a module path, it returns an empty string. +// It is tolerant of unrelated problems in the go.mod file. +// +// Copied from cmd/go/internal/modfile. +func modulePath(mod []byte) string { + for len(mod) > 0 { + line := mod + mod = nil + if i := bytes.IndexByte(line, '\n'); i >= 0 { + line, mod = line[:i], line[i+1:] + } + if i := bytes.Index(line, slashSlash); i >= 0 { + line = line[:i] + } + line = bytes.TrimSpace(line) + if !bytes.HasPrefix(line, moduleStr) { + continue + } + line = line[len(moduleStr):] + n := len(line) + line = bytes.TrimSpace(line) + if len(line) == n || len(line) == 0 { + continue + } + + if line[0] == '"' || line[0] == '`' { + p, err := strconv.Unquote(string(line)) + if err != nil { + return "" // malformed quoted string or multiline module path + } + return p + } + + return string(line) + } + return "" // missing module path +} diff --git a/vendor/golang.org/x/tools/internal/imports/mod_cache.go b/vendor/golang.org/x/tools/internal/imports/mod_cache.go new file mode 100644 index 000000000..5b4f03acc --- /dev/null +++ b/vendor/golang.org/x/tools/internal/imports/mod_cache.go @@ -0,0 +1,232 @@ +package imports + +import ( + "context" + "fmt" + "sync" + + "golang.org/x/tools/internal/gopathwalk" +) + +// To find packages to import, the resolver needs to know about all of the +// the packages that could be imported. This includes packages that are +// already in modules that are in (1) the current module, (2) replace targets, +// and (3) packages in the module cache. Packages in (1) and (2) may change over +// time, as the client may edit the current module and locally replaced modules. +// The module cache (which includes all of the packages in (3)) can only +// ever be added to. +// +// The resolver can thus save state about packages in the module cache +// and guarantee that this will not change over time. To obtain information +// about new modules added to the module cache, the module cache should be +// rescanned. +// +// It is OK to serve information about modules that have been deleted, +// as they do still exist. +// TODO(suzmue): can we share information with the caller about +// what module needs to be downloaded to import this package? + +type directoryPackageStatus int + +const ( + _ directoryPackageStatus = iota + directoryScanned + nameLoaded + exportsLoaded +) + +type directoryPackageInfo struct { + // status indicates the extent to which this struct has been filled in. + status directoryPackageStatus + // err is non-nil when there was an error trying to reach status. + err error + + // Set when status >= directoryScanned. + + // dir is the absolute directory of this package. + dir string + rootType gopathwalk.RootType + // nonCanonicalImportPath is the package's expected import path. It may + // not actually be importable at that path. + nonCanonicalImportPath string + + // Module-related information. + moduleDir string // The directory that is the module root of this dir. + moduleName string // The module name that contains this dir. + + // Set when status >= nameLoaded. + + packageName string // the package name, as declared in the source. + + // Set when status >= exportsLoaded. + + exports []string +} + +// reachedStatus returns true when info has a status at least target and any error associated with +// an attempt to reach target. +func (info *directoryPackageInfo) reachedStatus(target directoryPackageStatus) (bool, error) { + if info.err == nil { + return info.status >= target, nil + } + if info.status == target { + return true, info.err + } + return true, nil +} + +// dirInfoCache is a concurrency safe map for storing information about +// directories that may contain packages. +// +// The information in this cache is built incrementally. Entries are initialized in scan. +// No new keys should be added in any other functions, as all directories containing +// packages are identified in scan. +// +// Other functions, including loadExports and findPackage, may update entries in this cache +// as they discover new things about the directory. +// +// The information in the cache is not expected to change for the cache's +// lifetime, so there is no protection against competing writes. Users should +// take care not to hold the cache across changes to the underlying files. +// +// TODO(suzmue): consider other concurrency strategies and data structures (RWLocks, sync.Map, etc) +type dirInfoCache struct { + mu sync.Mutex + // dirs stores information about packages in directories, keyed by absolute path. + dirs map[string]*directoryPackageInfo + listeners map[*int]cacheListener +} + +type cacheListener func(directoryPackageInfo) + +// ScanAndListen calls listener on all the items in the cache, and on anything +// newly added. The returned stop function waits for all in-flight callbacks to +// finish and blocks new ones. +func (d *dirInfoCache) ScanAndListen(ctx context.Context, listener cacheListener) func() { + ctx, cancel := context.WithCancel(ctx) + + // Flushing out all the callbacks is tricky without knowing how many there + // are going to be. Setting an arbitrary limit makes it much easier. + const maxInFlight = 10 + sema := make(chan struct{}, maxInFlight) + for i := 0; i < maxInFlight; i++ { + sema <- struct{}{} + } + + cookie := new(int) // A unique ID we can use for the listener. + + // We can't hold mu while calling the listener. + d.mu.Lock() + var keys []string + for key := range d.dirs { + keys = append(keys, key) + } + d.listeners[cookie] = func(info directoryPackageInfo) { + select { + case <-ctx.Done(): + return + case <-sema: + } + listener(info) + sema <- struct{}{} + } + d.mu.Unlock() + + stop := func() { + cancel() + d.mu.Lock() + delete(d.listeners, cookie) + d.mu.Unlock() + for i := 0; i < maxInFlight; i++ { + <-sema + } + } + + // Process the pre-existing keys. + for _, k := range keys { + select { + case <-ctx.Done(): + return stop + default: + } + if v, ok := d.Load(k); ok { + listener(v) + } + } + + return stop +} + +// Store stores the package info for dir. +func (d *dirInfoCache) Store(dir string, info directoryPackageInfo) { + d.mu.Lock() + _, old := d.dirs[dir] + d.dirs[dir] = &info + var listeners []cacheListener + for _, l := range d.listeners { + listeners = append(listeners, l) + } + d.mu.Unlock() + + if !old { + for _, l := range listeners { + l(info) + } + } +} + +// Load returns a copy of the directoryPackageInfo for absolute directory dir. +func (d *dirInfoCache) Load(dir string) (directoryPackageInfo, bool) { + d.mu.Lock() + defer d.mu.Unlock() + info, ok := d.dirs[dir] + if !ok { + return directoryPackageInfo{}, false + } + return *info, true +} + +// Keys returns the keys currently present in d. +func (d *dirInfoCache) Keys() (keys []string) { + d.mu.Lock() + defer d.mu.Unlock() + for key := range d.dirs { + keys = append(keys, key) + } + return keys +} + +func (d *dirInfoCache) CachePackageName(info directoryPackageInfo) (string, error) { + if loaded, err := info.reachedStatus(nameLoaded); loaded { + return info.packageName, err + } + if scanned, err := info.reachedStatus(directoryScanned); !scanned || err != nil { + return "", fmt.Errorf("cannot read package name, scan error: %v", err) + } + info.packageName, info.err = packageDirToName(info.dir) + info.status = nameLoaded + d.Store(info.dir, info) + return info.packageName, info.err +} + +func (d *dirInfoCache) CacheExports(ctx context.Context, env *ProcessEnv, info directoryPackageInfo) (string, []string, error) { + if reached, _ := info.reachedStatus(exportsLoaded); reached { + return info.packageName, info.exports, info.err + } + if reached, err := info.reachedStatus(nameLoaded); reached && err != nil { + return "", nil, err + } + info.packageName, info.exports, info.err = loadExportsFromFiles(ctx, env, info.dir, false) + if info.err == context.Canceled || info.err == context.DeadlineExceeded { + return info.packageName, info.exports, info.err + } + // The cache structure wants things to proceed linearly. We can skip a + // step here, but only if we succeed. + if info.status == nameLoaded || info.err == nil { + info.status = exportsLoaded + } else { + info.status = nameLoaded + } + d.Store(info.dir, info) + return info.packageName, info.exports, info.err +} diff --git a/vendor/golang.org/x/tools/imports/sortimports.go b/vendor/golang.org/x/tools/internal/imports/sortimports.go similarity index 70% rename from vendor/golang.org/x/tools/imports/sortimports.go rename to vendor/golang.org/x/tools/internal/imports/sortimports.go index f3dd56c7a..be8ffa25f 100644 --- a/vendor/golang.org/x/tools/imports/sortimports.go +++ b/vendor/golang.org/x/tools/internal/imports/sortimports.go @@ -15,7 +15,7 @@ import ( // sortImports sorts runs of consecutive import lines in import blocks in f. // It also removes duplicate imports when it is possible to do so without data loss. -func sortImports(fset *token.FileSet, f *ast.File) { +func sortImports(localPrefix string, fset *token.FileSet, f *ast.File) { for i, d := range f.Decls { d, ok := d.(*ast.GenDecl) if !ok || d.Tok != token.IMPORT { @@ -40,11 +40,11 @@ func sortImports(fset *token.FileSet, f *ast.File) { for j, s := range d.Specs { if j > i && fset.Position(s.Pos()).Line > 1+fset.Position(d.Specs[j-1].End()).Line { // j begins a new run. End this one. - specs = append(specs, sortSpecs(fset, f, d.Specs[i:j])...) + specs = append(specs, sortSpecs(localPrefix, fset, f, d.Specs[i:j])...) i = j } } - specs = append(specs, sortSpecs(fset, f, d.Specs[i:])...) + specs = append(specs, sortSpecs(localPrefix, fset, f, d.Specs[i:])...) d.Specs = specs // Deduping can leave a blank line before the rparen; clean that up. @@ -58,6 +58,53 @@ func sortImports(fset *token.FileSet, f *ast.File) { } } +// mergeImports merges all the import declarations into the first one. +// Taken from golang.org/x/tools/ast/astutil. +func mergeImports(fset *token.FileSet, f *ast.File) { + if len(f.Decls) <= 1 { + return + } + + // Merge all the import declarations into the first one. + var first *ast.GenDecl + for i := 0; i < len(f.Decls); i++ { + decl := f.Decls[i] + gen, ok := decl.(*ast.GenDecl) + if !ok || gen.Tok != token.IMPORT || declImports(gen, "C") { + continue + } + if first == nil { + first = gen + continue // Don't touch the first one. + } + // We now know there is more than one package in this import + // declaration. Ensure that it ends up parenthesized. + first.Lparen = first.Pos() + // Move the imports of the other import declaration to the first one. + for _, spec := range gen.Specs { + spec.(*ast.ImportSpec).Path.ValuePos = first.Pos() + first.Specs = append(first.Specs, spec) + } + f.Decls = append(f.Decls[:i], f.Decls[i+1:]...) + i-- + } +} + +// declImports reports whether gen contains an import of path. +// Taken from golang.org/x/tools/ast/astutil. +func declImports(gen *ast.GenDecl, path string) bool { + if gen.Tok != token.IMPORT { + return false + } + for _, spec := range gen.Specs { + impspec := spec.(*ast.ImportSpec) + if importPath(impspec) == path { + return true + } + } + return false +} + func importPath(s ast.Spec) string { t, err := strconv.Unquote(s.(*ast.ImportSpec).Path.Value) if err == nil { @@ -95,7 +142,7 @@ type posSpan struct { End token.Pos } -func sortSpecs(fset *token.FileSet, f *ast.File, specs []ast.Spec) []ast.Spec { +func sortSpecs(localPrefix string, fset *token.FileSet, f *ast.File, specs []ast.Spec) []ast.Spec { // Can't short-circuit here even if specs are already sorted, // since they might yet need deduplication. // A lone import, however, may be safely ignored. @@ -144,7 +191,7 @@ func sortSpecs(fset *token.FileSet, f *ast.File, specs []ast.Spec) []ast.Spec { // Reassign the import paths to have the same position sequence. // Reassign each comment to abut the end of its spec. // Sort the comments by new position. - sort.Sort(byImportSpec(specs)) + sort.Sort(byImportSpec{localPrefix, specs}) // Dedup. Thanks to our sorting, we can just consider // adjacent pairs of imports. @@ -197,16 +244,19 @@ func sortSpecs(fset *token.FileSet, f *ast.File, specs []ast.Spec) []ast.Spec { return specs } -type byImportSpec []ast.Spec // slice of *ast.ImportSpec +type byImportSpec struct { + localPrefix string + specs []ast.Spec // slice of *ast.ImportSpec +} -func (x byImportSpec) Len() int { return len(x) } -func (x byImportSpec) Swap(i, j int) { x[i], x[j] = x[j], x[i] } +func (x byImportSpec) Len() int { return len(x.specs) } +func (x byImportSpec) Swap(i, j int) { x.specs[i], x.specs[j] = x.specs[j], x.specs[i] } func (x byImportSpec) Less(i, j int) bool { - ipath := importPath(x[i]) - jpath := importPath(x[j]) + ipath := importPath(x.specs[i]) + jpath := importPath(x.specs[j]) - igroup := importGroup(ipath) - jgroup := importGroup(jpath) + igroup := importGroup(x.localPrefix, ipath) + jgroup := importGroup(x.localPrefix, jpath) if igroup != jgroup { return igroup < jgroup } @@ -214,13 +264,13 @@ func (x byImportSpec) Less(i, j int) bool { if ipath != jpath { return ipath < jpath } - iname := importName(x[i]) - jname := importName(x[j]) + iname := importName(x.specs[i]) + jname := importName(x.specs[j]) if iname != jname { return iname < jname } - return importComment(x[i]) < importComment(x[j]) + return importComment(x.specs[i]) < importComment(x.specs[j]) } type byCommentPos []*ast.CommentGroup diff --git a/vendor/golang.org/x/tools/internal/imports/zstdlib.go b/vendor/golang.org/x/tools/internal/imports/zstdlib.go new file mode 100644 index 000000000..7b573b983 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/imports/zstdlib.go @@ -0,0 +1,10516 @@ +// Code generated by mkstdlib.go. DO NOT EDIT. + +package imports + +var stdlib = map[string][]string{ + "archive/tar": []string{ + "ErrFieldTooLong", + "ErrHeader", + "ErrWriteAfterClose", + "ErrWriteTooLong", + "FileInfoHeader", + "Format", + "FormatGNU", + "FormatPAX", + "FormatUSTAR", + "FormatUnknown", + "Header", + "NewReader", + "NewWriter", + "Reader", + "TypeBlock", + "TypeChar", + "TypeCont", + "TypeDir", + "TypeFifo", + "TypeGNULongLink", + "TypeGNULongName", + "TypeGNUSparse", + "TypeLink", + "TypeReg", + "TypeRegA", + "TypeSymlink", + "TypeXGlobalHeader", + "TypeXHeader", + "Writer", + }, + "archive/zip": []string{ + "Compressor", + "Decompressor", + "Deflate", + "ErrAlgorithm", + "ErrChecksum", + "ErrFormat", + "File", + "FileHeader", + "FileInfoHeader", + "NewReader", + "NewWriter", + "OpenReader", + "ReadCloser", + "Reader", + "RegisterCompressor", + "RegisterDecompressor", + "Store", + "Writer", + }, + "bufio": []string{ + "ErrAdvanceTooFar", + "ErrBadReadCount", + "ErrBufferFull", + "ErrFinalToken", + "ErrInvalidUnreadByte", + "ErrInvalidUnreadRune", + "ErrNegativeAdvance", + "ErrNegativeCount", + "ErrTooLong", + "MaxScanTokenSize", + "NewReadWriter", + "NewReader", + "NewReaderSize", + "NewScanner", + "NewWriter", + "NewWriterSize", + "ReadWriter", + "Reader", + "ScanBytes", + "ScanLines", + "ScanRunes", + "ScanWords", + "Scanner", + "SplitFunc", + "Writer", + }, + "bytes": []string{ + "Buffer", + "Compare", + "Contains", + "ContainsAny", + "ContainsRune", + "Count", + "Equal", + "EqualFold", + "ErrTooLarge", + "Fields", + "FieldsFunc", + "HasPrefix", + "HasSuffix", + "Index", + "IndexAny", + "IndexByte", + "IndexFunc", + "IndexRune", + "Join", + "LastIndex", + "LastIndexAny", + "LastIndexByte", + "LastIndexFunc", + "Map", + "MinRead", + "NewBuffer", + "NewBufferString", + "NewReader", + "Reader", + "Repeat", + "Replace", + "ReplaceAll", + "Runes", + "Split", + "SplitAfter", + "SplitAfterN", + "SplitN", + "Title", + "ToLower", + "ToLowerSpecial", + "ToTitle", + "ToTitleSpecial", + "ToUpper", + "ToUpperSpecial", + "ToValidUTF8", + "Trim", + "TrimFunc", + "TrimLeft", + "TrimLeftFunc", + "TrimPrefix", + "TrimRight", + "TrimRightFunc", + "TrimSpace", + "TrimSuffix", + }, + "compress/bzip2": []string{ + "NewReader", + "StructuralError", + }, + "compress/flate": []string{ + "BestCompression", + "BestSpeed", + "CorruptInputError", + "DefaultCompression", + "HuffmanOnly", + "InternalError", + "NewReader", + "NewReaderDict", + "NewWriter", + "NewWriterDict", + "NoCompression", + "ReadError", + "Reader", + "Resetter", + "WriteError", + "Writer", + }, + "compress/gzip": []string{ + "BestCompression", + "BestSpeed", + "DefaultCompression", + "ErrChecksum", + "ErrHeader", + "Header", + "HuffmanOnly", + "NewReader", + "NewWriter", + "NewWriterLevel", + "NoCompression", + "Reader", + "Writer", + }, + "compress/lzw": []string{ + "LSB", + "MSB", + "NewReader", + "NewWriter", + "Order", + }, + "compress/zlib": []string{ + "BestCompression", + "BestSpeed", + "DefaultCompression", + "ErrChecksum", + "ErrDictionary", + "ErrHeader", + "HuffmanOnly", + "NewReader", + "NewReaderDict", + "NewWriter", + "NewWriterLevel", + "NewWriterLevelDict", + "NoCompression", + "Resetter", + "Writer", + }, + "container/heap": []string{ + "Fix", + "Init", + "Interface", + "Pop", + "Push", + "Remove", + }, + "container/list": []string{ + "Element", + "List", + "New", + }, + "container/ring": []string{ + "New", + "Ring", + }, + "context": []string{ + "Background", + "CancelFunc", + "Canceled", + "Context", + "DeadlineExceeded", + "TODO", + "WithCancel", + "WithDeadline", + "WithTimeout", + "WithValue", + }, + "crypto": []string{ + "BLAKE2b_256", + "BLAKE2b_384", + "BLAKE2b_512", + "BLAKE2s_256", + "Decrypter", + "DecrypterOpts", + "Hash", + "MD4", + "MD5", + "MD5SHA1", + "PrivateKey", + "PublicKey", + "RIPEMD160", + "RegisterHash", + "SHA1", + "SHA224", + "SHA256", + "SHA384", + "SHA3_224", + "SHA3_256", + "SHA3_384", + "SHA3_512", + "SHA512", + "SHA512_224", + "SHA512_256", + "Signer", + "SignerOpts", + }, + "crypto/aes": []string{ + "BlockSize", + "KeySizeError", + "NewCipher", + }, + "crypto/cipher": []string{ + "AEAD", + "Block", + "BlockMode", + "NewCBCDecrypter", + "NewCBCEncrypter", + "NewCFBDecrypter", + "NewCFBEncrypter", + "NewCTR", + "NewGCM", + "NewGCMWithNonceSize", + "NewGCMWithTagSize", + "NewOFB", + "Stream", + "StreamReader", + "StreamWriter", + }, + "crypto/des": []string{ + "BlockSize", + "KeySizeError", + "NewCipher", + "NewTripleDESCipher", + }, + "crypto/dsa": []string{ + "ErrInvalidPublicKey", + "GenerateKey", + "GenerateParameters", + "L1024N160", + "L2048N224", + "L2048N256", + "L3072N256", + "ParameterSizes", + "Parameters", + "PrivateKey", + "PublicKey", + "Sign", + "Verify", + }, + "crypto/ecdsa": []string{ + "GenerateKey", + "PrivateKey", + "PublicKey", + "Sign", + "SignASN1", + "Verify", + "VerifyASN1", + }, + "crypto/ed25519": []string{ + "GenerateKey", + "NewKeyFromSeed", + "PrivateKey", + "PrivateKeySize", + "PublicKey", + "PublicKeySize", + "SeedSize", + "Sign", + "SignatureSize", + "Verify", + }, + "crypto/elliptic": []string{ + "Curve", + "CurveParams", + "GenerateKey", + "Marshal", + "MarshalCompressed", + "P224", + "P256", + "P384", + "P521", + "Unmarshal", + "UnmarshalCompressed", + }, + "crypto/hmac": []string{ + "Equal", + "New", + }, + "crypto/md5": []string{ + "BlockSize", + "New", + "Size", + "Sum", + }, + "crypto/rand": []string{ + "Int", + "Prime", + "Read", + "Reader", + }, + "crypto/rc4": []string{ + "Cipher", + "KeySizeError", + "NewCipher", + }, + "crypto/rsa": []string{ + "CRTValue", + "DecryptOAEP", + "DecryptPKCS1v15", + "DecryptPKCS1v15SessionKey", + "EncryptOAEP", + "EncryptPKCS1v15", + "ErrDecryption", + "ErrMessageTooLong", + "ErrVerification", + "GenerateKey", + "GenerateMultiPrimeKey", + "OAEPOptions", + "PKCS1v15DecryptOptions", + "PSSOptions", + "PSSSaltLengthAuto", + "PSSSaltLengthEqualsHash", + "PrecomputedValues", + "PrivateKey", + "PublicKey", + "SignPKCS1v15", + "SignPSS", + "VerifyPKCS1v15", + "VerifyPSS", + }, + "crypto/sha1": []string{ + "BlockSize", + "New", + "Size", + "Sum", + }, + "crypto/sha256": []string{ + "BlockSize", + "New", + "New224", + "Size", + "Size224", + "Sum224", + "Sum256", + }, + "crypto/sha512": []string{ + "BlockSize", + "New", + "New384", + "New512_224", + "New512_256", + "Size", + "Size224", + "Size256", + "Size384", + "Sum384", + "Sum512", + "Sum512_224", + "Sum512_256", + }, + "crypto/subtle": []string{ + "ConstantTimeByteEq", + "ConstantTimeCompare", + "ConstantTimeCopy", + "ConstantTimeEq", + "ConstantTimeLessOrEq", + "ConstantTimeSelect", + }, + "crypto/tls": []string{ + "Certificate", + "CertificateRequestInfo", + "CipherSuite", + "CipherSuiteName", + "CipherSuites", + "Client", + "ClientAuthType", + "ClientHelloInfo", + "ClientSessionCache", + "ClientSessionState", + "Config", + "Conn", + "ConnectionState", + "CurveID", + "CurveP256", + "CurveP384", + "CurveP521", + "Dial", + "DialWithDialer", + "Dialer", + "ECDSAWithP256AndSHA256", + "ECDSAWithP384AndSHA384", + "ECDSAWithP521AndSHA512", + "ECDSAWithSHA1", + "Ed25519", + "InsecureCipherSuites", + "Listen", + "LoadX509KeyPair", + "NewLRUClientSessionCache", + "NewListener", + "NoClientCert", + "PKCS1WithSHA1", + "PKCS1WithSHA256", + "PKCS1WithSHA384", + "PKCS1WithSHA512", + "PSSWithSHA256", + "PSSWithSHA384", + "PSSWithSHA512", + "RecordHeaderError", + "RenegotiateFreelyAsClient", + "RenegotiateNever", + "RenegotiateOnceAsClient", + "RenegotiationSupport", + "RequestClientCert", + "RequireAndVerifyClientCert", + "RequireAnyClientCert", + "Server", + "SignatureScheme", + "TLS_AES_128_GCM_SHA256", + "TLS_AES_256_GCM_SHA384", + "TLS_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA", + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305", + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA", + "TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305", + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_RSA_WITH_RC4_128_SHA", + "TLS_FALLBACK_SCSV", + "TLS_RSA_WITH_3DES_EDE_CBC_SHA", + "TLS_RSA_WITH_AES_128_CBC_SHA", + "TLS_RSA_WITH_AES_128_CBC_SHA256", + "TLS_RSA_WITH_AES_128_GCM_SHA256", + "TLS_RSA_WITH_AES_256_CBC_SHA", + "TLS_RSA_WITH_AES_256_GCM_SHA384", + "TLS_RSA_WITH_RC4_128_SHA", + "VerifyClientCertIfGiven", + "VersionSSL30", + "VersionTLS10", + "VersionTLS11", + "VersionTLS12", + "VersionTLS13", + "X25519", + "X509KeyPair", + }, + "crypto/x509": []string{ + "CANotAuthorizedForExtKeyUsage", + "CANotAuthorizedForThisName", + "CertPool", + "Certificate", + "CertificateInvalidError", + "CertificateRequest", + "ConstraintViolationError", + "CreateCertificate", + "CreateCertificateRequest", + "CreateRevocationList", + "DSA", + "DSAWithSHA1", + "DSAWithSHA256", + "DecryptPEMBlock", + "ECDSA", + "ECDSAWithSHA1", + "ECDSAWithSHA256", + "ECDSAWithSHA384", + "ECDSAWithSHA512", + "Ed25519", + "EncryptPEMBlock", + "ErrUnsupportedAlgorithm", + "Expired", + "ExtKeyUsage", + "ExtKeyUsageAny", + "ExtKeyUsageClientAuth", + "ExtKeyUsageCodeSigning", + "ExtKeyUsageEmailProtection", + "ExtKeyUsageIPSECEndSystem", + "ExtKeyUsageIPSECTunnel", + "ExtKeyUsageIPSECUser", + "ExtKeyUsageMicrosoftCommercialCodeSigning", + "ExtKeyUsageMicrosoftKernelCodeSigning", + "ExtKeyUsageMicrosoftServerGatedCrypto", + "ExtKeyUsageNetscapeServerGatedCrypto", + "ExtKeyUsageOCSPSigning", + "ExtKeyUsageServerAuth", + "ExtKeyUsageTimeStamping", + "HostnameError", + "IncompatibleUsage", + "IncorrectPasswordError", + "InsecureAlgorithmError", + "InvalidReason", + "IsEncryptedPEMBlock", + "KeyUsage", + "KeyUsageCRLSign", + "KeyUsageCertSign", + "KeyUsageContentCommitment", + "KeyUsageDataEncipherment", + "KeyUsageDecipherOnly", + "KeyUsageDigitalSignature", + "KeyUsageEncipherOnly", + "KeyUsageKeyAgreement", + "KeyUsageKeyEncipherment", + "MD2WithRSA", + "MD5WithRSA", + "MarshalECPrivateKey", + "MarshalPKCS1PrivateKey", + "MarshalPKCS1PublicKey", + "MarshalPKCS8PrivateKey", + "MarshalPKIXPublicKey", + "NameConstraintsWithoutSANs", + "NameMismatch", + "NewCertPool", + "NotAuthorizedToSign", + "PEMCipher", + "PEMCipher3DES", + "PEMCipherAES128", + "PEMCipherAES192", + "PEMCipherAES256", + "PEMCipherDES", + "ParseCRL", + "ParseCertificate", + "ParseCertificateRequest", + "ParseCertificates", + "ParseDERCRL", + "ParseECPrivateKey", + "ParsePKCS1PrivateKey", + "ParsePKCS1PublicKey", + "ParsePKCS8PrivateKey", + "ParsePKIXPublicKey", + "PublicKeyAlgorithm", + "PureEd25519", + "RSA", + "RevocationList", + "SHA1WithRSA", + "SHA256WithRSA", + "SHA256WithRSAPSS", + "SHA384WithRSA", + "SHA384WithRSAPSS", + "SHA512WithRSA", + "SHA512WithRSAPSS", + "SignatureAlgorithm", + "SystemCertPool", + "SystemRootsError", + "TooManyConstraints", + "TooManyIntermediates", + "UnconstrainedName", + "UnhandledCriticalExtension", + "UnknownAuthorityError", + "UnknownPublicKeyAlgorithm", + "UnknownSignatureAlgorithm", + "VerifyOptions", + }, + "crypto/x509/pkix": []string{ + "AlgorithmIdentifier", + "AttributeTypeAndValue", + "AttributeTypeAndValueSET", + "CertificateList", + "Extension", + "Name", + "RDNSequence", + "RelativeDistinguishedNameSET", + "RevokedCertificate", + "TBSCertificateList", + }, + "database/sql": []string{ + "ColumnType", + "Conn", + "DB", + "DBStats", + "Drivers", + "ErrConnDone", + "ErrNoRows", + "ErrTxDone", + "IsolationLevel", + "LevelDefault", + "LevelLinearizable", + "LevelReadCommitted", + "LevelReadUncommitted", + "LevelRepeatableRead", + "LevelSerializable", + "LevelSnapshot", + "LevelWriteCommitted", + "Named", + "NamedArg", + "NullBool", + "NullFloat64", + "NullInt32", + "NullInt64", + "NullString", + "NullTime", + "Open", + "OpenDB", + "Out", + "RawBytes", + "Register", + "Result", + "Row", + "Rows", + "Scanner", + "Stmt", + "Tx", + "TxOptions", + }, + "database/sql/driver": []string{ + "Bool", + "ColumnConverter", + "Conn", + "ConnBeginTx", + "ConnPrepareContext", + "Connector", + "DefaultParameterConverter", + "Driver", + "DriverContext", + "ErrBadConn", + "ErrRemoveArgument", + "ErrSkip", + "Execer", + "ExecerContext", + "Int32", + "IsScanValue", + "IsValue", + "IsolationLevel", + "NamedValue", + "NamedValueChecker", + "NotNull", + "Null", + "Pinger", + "Queryer", + "QueryerContext", + "Result", + "ResultNoRows", + "Rows", + "RowsAffected", + "RowsColumnTypeDatabaseTypeName", + "RowsColumnTypeLength", + "RowsColumnTypeNullable", + "RowsColumnTypePrecisionScale", + "RowsColumnTypeScanType", + "RowsNextResultSet", + "SessionResetter", + "Stmt", + "StmtExecContext", + "StmtQueryContext", + "String", + "Tx", + "TxOptions", + "Validator", + "Value", + "ValueConverter", + "Valuer", + }, + "debug/dwarf": []string{ + "AddrType", + "ArrayType", + "Attr", + "AttrAbstractOrigin", + "AttrAccessibility", + "AttrAddrBase", + "AttrAddrClass", + "AttrAlignment", + "AttrAllocated", + "AttrArtificial", + "AttrAssociated", + "AttrBaseTypes", + "AttrBinaryScale", + "AttrBitOffset", + "AttrBitSize", + "AttrByteSize", + "AttrCallAllCalls", + "AttrCallAllSourceCalls", + "AttrCallAllTailCalls", + "AttrCallColumn", + "AttrCallDataLocation", + "AttrCallDataValue", + "AttrCallFile", + "AttrCallLine", + "AttrCallOrigin", + "AttrCallPC", + "AttrCallParameter", + "AttrCallReturnPC", + "AttrCallTailCall", + "AttrCallTarget", + "AttrCallTargetClobbered", + "AttrCallValue", + "AttrCalling", + "AttrCommonRef", + "AttrCompDir", + "AttrConstExpr", + "AttrConstValue", + "AttrContainingType", + "AttrCount", + "AttrDataBitOffset", + "AttrDataLocation", + "AttrDataMemberLoc", + "AttrDecimalScale", + "AttrDecimalSign", + "AttrDeclColumn", + "AttrDeclFile", + "AttrDeclLine", + "AttrDeclaration", + "AttrDefaultValue", + "AttrDefaulted", + "AttrDeleted", + "AttrDescription", + "AttrDigitCount", + "AttrDiscr", + "AttrDiscrList", + "AttrDiscrValue", + "AttrDwoName", + "AttrElemental", + "AttrEncoding", + "AttrEndianity", + "AttrEntrypc", + "AttrEnumClass", + "AttrExplicit", + "AttrExportSymbols", + "AttrExtension", + "AttrExternal", + "AttrFrameBase", + "AttrFriend", + "AttrHighpc", + "AttrIdentifierCase", + "AttrImport", + "AttrInline", + "AttrIsOptional", + "AttrLanguage", + "AttrLinkageName", + "AttrLocation", + "AttrLoclistsBase", + "AttrLowerBound", + "AttrLowpc", + "AttrMacroInfo", + "AttrMacros", + "AttrMainSubprogram", + "AttrMutable", + "AttrName", + "AttrNamelistItem", + "AttrNoreturn", + "AttrObjectPointer", + "AttrOrdering", + "AttrPictureString", + "AttrPriority", + "AttrProducer", + "AttrPrototyped", + "AttrPure", + "AttrRanges", + "AttrRank", + "AttrRecursive", + "AttrReference", + "AttrReturnAddr", + "AttrRnglistsBase", + "AttrRvalueReference", + "AttrSegment", + "AttrSibling", + "AttrSignature", + "AttrSmall", + "AttrSpecification", + "AttrStartScope", + "AttrStaticLink", + "AttrStmtList", + "AttrStrOffsetsBase", + "AttrStride", + "AttrStrideSize", + "AttrStringLength", + "AttrStringLengthBitSize", + "AttrStringLengthByteSize", + "AttrThreadsScaled", + "AttrTrampoline", + "AttrType", + "AttrUpperBound", + "AttrUseLocation", + "AttrUseUTF8", + "AttrVarParam", + "AttrVirtuality", + "AttrVisibility", + "AttrVtableElemLoc", + "BasicType", + "BoolType", + "CharType", + "Class", + "ClassAddrPtr", + "ClassAddress", + "ClassBlock", + "ClassConstant", + "ClassExprLoc", + "ClassFlag", + "ClassLinePtr", + "ClassLocList", + "ClassLocListPtr", + "ClassMacPtr", + "ClassRangeListPtr", + "ClassReference", + "ClassReferenceAlt", + "ClassReferenceSig", + "ClassRngList", + "ClassRngListsPtr", + "ClassStrOffsetsPtr", + "ClassString", + "ClassStringAlt", + "ClassUnknown", + "CommonType", + "ComplexType", + "Data", + "DecodeError", + "DotDotDotType", + "Entry", + "EnumType", + "EnumValue", + "ErrUnknownPC", + "Field", + "FloatType", + "FuncType", + "IntType", + "LineEntry", + "LineFile", + "LineReader", + "LineReaderPos", + "New", + "Offset", + "PtrType", + "QualType", + "Reader", + "StructField", + "StructType", + "Tag", + "TagAccessDeclaration", + "TagArrayType", + "TagAtomicType", + "TagBaseType", + "TagCallSite", + "TagCallSiteParameter", + "TagCatchDwarfBlock", + "TagClassType", + "TagCoarrayType", + "TagCommonDwarfBlock", + "TagCommonInclusion", + "TagCompileUnit", + "TagCondition", + "TagConstType", + "TagConstant", + "TagDwarfProcedure", + "TagDynamicType", + "TagEntryPoint", + "TagEnumerationType", + "TagEnumerator", + "TagFileType", + "TagFormalParameter", + "TagFriend", + "TagGenericSubrange", + "TagImmutableType", + "TagImportedDeclaration", + "TagImportedModule", + "TagImportedUnit", + "TagInheritance", + "TagInlinedSubroutine", + "TagInterfaceType", + "TagLabel", + "TagLexDwarfBlock", + "TagMember", + "TagModule", + "TagMutableType", + "TagNamelist", + "TagNamelistItem", + "TagNamespace", + "TagPackedType", + "TagPartialUnit", + "TagPointerType", + "TagPtrToMemberType", + "TagReferenceType", + "TagRestrictType", + "TagRvalueReferenceType", + "TagSetType", + "TagSharedType", + "TagSkeletonUnit", + "TagStringType", + "TagStructType", + "TagSubprogram", + "TagSubrangeType", + "TagSubroutineType", + "TagTemplateAlias", + "TagTemplateTypeParameter", + "TagTemplateValueParameter", + "TagThrownType", + "TagTryDwarfBlock", + "TagTypeUnit", + "TagTypedef", + "TagUnionType", + "TagUnspecifiedParameters", + "TagUnspecifiedType", + "TagVariable", + "TagVariant", + "TagVariantPart", + "TagVolatileType", + "TagWithStmt", + "Type", + "TypedefType", + "UcharType", + "UintType", + "UnspecifiedType", + "UnsupportedType", + "VoidType", + }, + "debug/elf": []string{ + "ARM_MAGIC_TRAMP_NUMBER", + "COMPRESS_HIOS", + "COMPRESS_HIPROC", + "COMPRESS_LOOS", + "COMPRESS_LOPROC", + "COMPRESS_ZLIB", + "Chdr32", + "Chdr64", + "Class", + "CompressionType", + "DF_BIND_NOW", + "DF_ORIGIN", + "DF_STATIC_TLS", + "DF_SYMBOLIC", + "DF_TEXTREL", + "DT_BIND_NOW", + "DT_DEBUG", + "DT_ENCODING", + "DT_FINI", + "DT_FINI_ARRAY", + "DT_FINI_ARRAYSZ", + "DT_FLAGS", + "DT_HASH", + "DT_HIOS", + "DT_HIPROC", + "DT_INIT", + "DT_INIT_ARRAY", + "DT_INIT_ARRAYSZ", + "DT_JMPREL", + "DT_LOOS", + "DT_LOPROC", + "DT_NEEDED", + "DT_NULL", + "DT_PLTGOT", + "DT_PLTREL", + "DT_PLTRELSZ", + "DT_PREINIT_ARRAY", + "DT_PREINIT_ARRAYSZ", + "DT_REL", + "DT_RELA", + "DT_RELAENT", + "DT_RELASZ", + "DT_RELENT", + "DT_RELSZ", + "DT_RPATH", + "DT_RUNPATH", + "DT_SONAME", + "DT_STRSZ", + "DT_STRTAB", + "DT_SYMBOLIC", + "DT_SYMENT", + "DT_SYMTAB", + "DT_TEXTREL", + "DT_VERNEED", + "DT_VERNEEDNUM", + "DT_VERSYM", + "Data", + "Dyn32", + "Dyn64", + "DynFlag", + "DynTag", + "EI_ABIVERSION", + "EI_CLASS", + "EI_DATA", + "EI_NIDENT", + "EI_OSABI", + "EI_PAD", + "EI_VERSION", + "ELFCLASS32", + "ELFCLASS64", + "ELFCLASSNONE", + "ELFDATA2LSB", + "ELFDATA2MSB", + "ELFDATANONE", + "ELFMAG", + "ELFOSABI_86OPEN", + "ELFOSABI_AIX", + "ELFOSABI_ARM", + "ELFOSABI_AROS", + "ELFOSABI_CLOUDABI", + "ELFOSABI_FENIXOS", + "ELFOSABI_FREEBSD", + "ELFOSABI_HPUX", + "ELFOSABI_HURD", + "ELFOSABI_IRIX", + "ELFOSABI_LINUX", + "ELFOSABI_MODESTO", + "ELFOSABI_NETBSD", + "ELFOSABI_NONE", + "ELFOSABI_NSK", + "ELFOSABI_OPENBSD", + "ELFOSABI_OPENVMS", + "ELFOSABI_SOLARIS", + "ELFOSABI_STANDALONE", + "ELFOSABI_TRU64", + "EM_386", + "EM_486", + "EM_56800EX", + "EM_68HC05", + "EM_68HC08", + "EM_68HC11", + "EM_68HC12", + "EM_68HC16", + "EM_68K", + "EM_78KOR", + "EM_8051", + "EM_860", + "EM_88K", + "EM_960", + "EM_AARCH64", + "EM_ALPHA", + "EM_ALPHA_STD", + "EM_ALTERA_NIOS2", + "EM_AMDGPU", + "EM_ARC", + "EM_ARCA", + "EM_ARC_COMPACT", + "EM_ARC_COMPACT2", + "EM_ARM", + "EM_AVR", + "EM_AVR32", + "EM_BA1", + "EM_BA2", + "EM_BLACKFIN", + "EM_BPF", + "EM_C166", + "EM_CDP", + "EM_CE", + "EM_CLOUDSHIELD", + "EM_COGE", + "EM_COLDFIRE", + "EM_COOL", + "EM_COREA_1ST", + "EM_COREA_2ND", + "EM_CR", + "EM_CR16", + "EM_CRAYNV2", + "EM_CRIS", + "EM_CRX", + "EM_CSR_KALIMBA", + "EM_CUDA", + "EM_CYPRESS_M8C", + "EM_D10V", + "EM_D30V", + "EM_DSP24", + "EM_DSPIC30F", + "EM_DXP", + "EM_ECOG1", + "EM_ECOG16", + "EM_ECOG1X", + "EM_ECOG2", + "EM_ETPU", + "EM_EXCESS", + "EM_F2MC16", + "EM_FIREPATH", + "EM_FR20", + "EM_FR30", + "EM_FT32", + "EM_FX66", + "EM_H8S", + "EM_H8_300", + "EM_H8_300H", + "EM_H8_500", + "EM_HUANY", + "EM_IA_64", + "EM_INTEL205", + "EM_INTEL206", + "EM_INTEL207", + "EM_INTEL208", + "EM_INTEL209", + "EM_IP2K", + "EM_JAVELIN", + "EM_K10M", + "EM_KM32", + "EM_KMX16", + "EM_KMX32", + "EM_KMX8", + "EM_KVARC", + "EM_L10M", + "EM_LANAI", + "EM_LATTICEMICO32", + "EM_M16C", + "EM_M32", + "EM_M32C", + "EM_M32R", + "EM_MANIK", + "EM_MAX", + "EM_MAXQ30", + "EM_MCHP_PIC", + "EM_MCST_ELBRUS", + "EM_ME16", + "EM_METAG", + "EM_MICROBLAZE", + "EM_MIPS", + "EM_MIPS_RS3_LE", + "EM_MIPS_RS4_BE", + "EM_MIPS_X", + "EM_MMA", + "EM_MMDSP_PLUS", + "EM_MMIX", + "EM_MN10200", + "EM_MN10300", + "EM_MOXIE", + "EM_MSP430", + "EM_NCPU", + "EM_NDR1", + "EM_NDS32", + "EM_NONE", + "EM_NORC", + "EM_NS32K", + "EM_OPEN8", + "EM_OPENRISC", + "EM_PARISC", + "EM_PCP", + "EM_PDP10", + "EM_PDP11", + "EM_PDSP", + "EM_PJ", + "EM_PPC", + "EM_PPC64", + "EM_PRISM", + "EM_QDSP6", + "EM_R32C", + "EM_RCE", + "EM_RH32", + "EM_RISCV", + "EM_RL78", + "EM_RS08", + "EM_RX", + "EM_S370", + "EM_S390", + "EM_SCORE7", + "EM_SEP", + "EM_SE_C17", + "EM_SE_C33", + "EM_SH", + "EM_SHARC", + "EM_SLE9X", + "EM_SNP1K", + "EM_SPARC", + "EM_SPARC32PLUS", + "EM_SPARCV9", + "EM_ST100", + "EM_ST19", + "EM_ST200", + "EM_ST7", + "EM_ST9PLUS", + "EM_STARCORE", + "EM_STM8", + "EM_STXP7X", + "EM_SVX", + "EM_TILE64", + "EM_TILEGX", + "EM_TILEPRO", + "EM_TINYJ", + "EM_TI_ARP32", + "EM_TI_C2000", + "EM_TI_C5500", + "EM_TI_C6000", + "EM_TI_PRU", + "EM_TMM_GPP", + "EM_TPC", + "EM_TRICORE", + "EM_TRIMEDIA", + "EM_TSK3000", + "EM_UNICORE", + "EM_V800", + "EM_V850", + "EM_VAX", + "EM_VIDEOCORE", + "EM_VIDEOCORE3", + "EM_VIDEOCORE5", + "EM_VISIUM", + "EM_VPP500", + "EM_X86_64", + "EM_XCORE", + "EM_XGATE", + "EM_XIMO16", + "EM_XTENSA", + "EM_Z80", + "EM_ZSP", + "ET_CORE", + "ET_DYN", + "ET_EXEC", + "ET_HIOS", + "ET_HIPROC", + "ET_LOOS", + "ET_LOPROC", + "ET_NONE", + "ET_REL", + "EV_CURRENT", + "EV_NONE", + "ErrNoSymbols", + "File", + "FileHeader", + "FormatError", + "Header32", + "Header64", + "ImportedSymbol", + "Machine", + "NT_FPREGSET", + "NT_PRPSINFO", + "NT_PRSTATUS", + "NType", + "NewFile", + "OSABI", + "Open", + "PF_MASKOS", + "PF_MASKPROC", + "PF_R", + "PF_W", + "PF_X", + "PT_DYNAMIC", + "PT_HIOS", + "PT_HIPROC", + "PT_INTERP", + "PT_LOAD", + "PT_LOOS", + "PT_LOPROC", + "PT_NOTE", + "PT_NULL", + "PT_PHDR", + "PT_SHLIB", + "PT_TLS", + "Prog", + "Prog32", + "Prog64", + "ProgFlag", + "ProgHeader", + "ProgType", + "R_386", + "R_386_16", + "R_386_32", + "R_386_32PLT", + "R_386_8", + "R_386_COPY", + "R_386_GLOB_DAT", + "R_386_GOT32", + "R_386_GOT32X", + "R_386_GOTOFF", + "R_386_GOTPC", + "R_386_IRELATIVE", + "R_386_JMP_SLOT", + "R_386_NONE", + "R_386_PC16", + "R_386_PC32", + "R_386_PC8", + "R_386_PLT32", + "R_386_RELATIVE", + "R_386_SIZE32", + "R_386_TLS_DESC", + "R_386_TLS_DESC_CALL", + "R_386_TLS_DTPMOD32", + "R_386_TLS_DTPOFF32", + "R_386_TLS_GD", + "R_386_TLS_GD_32", + "R_386_TLS_GD_CALL", + "R_386_TLS_GD_POP", + "R_386_TLS_GD_PUSH", + "R_386_TLS_GOTDESC", + "R_386_TLS_GOTIE", + "R_386_TLS_IE", + "R_386_TLS_IE_32", + "R_386_TLS_LDM", + "R_386_TLS_LDM_32", + "R_386_TLS_LDM_CALL", + "R_386_TLS_LDM_POP", + "R_386_TLS_LDM_PUSH", + "R_386_TLS_LDO_32", + "R_386_TLS_LE", + "R_386_TLS_LE_32", + "R_386_TLS_TPOFF", + "R_386_TLS_TPOFF32", + "R_390", + "R_390_12", + "R_390_16", + "R_390_20", + "R_390_32", + "R_390_64", + "R_390_8", + "R_390_COPY", + "R_390_GLOB_DAT", + "R_390_GOT12", + "R_390_GOT16", + "R_390_GOT20", + "R_390_GOT32", + "R_390_GOT64", + "R_390_GOTENT", + "R_390_GOTOFF", + "R_390_GOTOFF16", + "R_390_GOTOFF64", + "R_390_GOTPC", + "R_390_GOTPCDBL", + "R_390_GOTPLT12", + "R_390_GOTPLT16", + "R_390_GOTPLT20", + "R_390_GOTPLT32", + "R_390_GOTPLT64", + "R_390_GOTPLTENT", + "R_390_GOTPLTOFF16", + "R_390_GOTPLTOFF32", + "R_390_GOTPLTOFF64", + "R_390_JMP_SLOT", + "R_390_NONE", + "R_390_PC16", + "R_390_PC16DBL", + "R_390_PC32", + "R_390_PC32DBL", + "R_390_PC64", + "R_390_PLT16DBL", + "R_390_PLT32", + "R_390_PLT32DBL", + "R_390_PLT64", + "R_390_RELATIVE", + "R_390_TLS_DTPMOD", + "R_390_TLS_DTPOFF", + "R_390_TLS_GD32", + "R_390_TLS_GD64", + "R_390_TLS_GDCALL", + "R_390_TLS_GOTIE12", + "R_390_TLS_GOTIE20", + "R_390_TLS_GOTIE32", + "R_390_TLS_GOTIE64", + "R_390_TLS_IE32", + "R_390_TLS_IE64", + "R_390_TLS_IEENT", + "R_390_TLS_LDCALL", + "R_390_TLS_LDM32", + "R_390_TLS_LDM64", + "R_390_TLS_LDO32", + "R_390_TLS_LDO64", + "R_390_TLS_LE32", + "R_390_TLS_LE64", + "R_390_TLS_LOAD", + "R_390_TLS_TPOFF", + "R_AARCH64", + "R_AARCH64_ABS16", + "R_AARCH64_ABS32", + "R_AARCH64_ABS64", + "R_AARCH64_ADD_ABS_LO12_NC", + "R_AARCH64_ADR_GOT_PAGE", + "R_AARCH64_ADR_PREL_LO21", + "R_AARCH64_ADR_PREL_PG_HI21", + "R_AARCH64_ADR_PREL_PG_HI21_NC", + "R_AARCH64_CALL26", + "R_AARCH64_CONDBR19", + "R_AARCH64_COPY", + "R_AARCH64_GLOB_DAT", + "R_AARCH64_GOT_LD_PREL19", + "R_AARCH64_IRELATIVE", + "R_AARCH64_JUMP26", + "R_AARCH64_JUMP_SLOT", + "R_AARCH64_LD64_GOTOFF_LO15", + "R_AARCH64_LD64_GOTPAGE_LO15", + "R_AARCH64_LD64_GOT_LO12_NC", + "R_AARCH64_LDST128_ABS_LO12_NC", + "R_AARCH64_LDST16_ABS_LO12_NC", + "R_AARCH64_LDST32_ABS_LO12_NC", + "R_AARCH64_LDST64_ABS_LO12_NC", + "R_AARCH64_LDST8_ABS_LO12_NC", + "R_AARCH64_LD_PREL_LO19", + "R_AARCH64_MOVW_SABS_G0", + "R_AARCH64_MOVW_SABS_G1", + "R_AARCH64_MOVW_SABS_G2", + "R_AARCH64_MOVW_UABS_G0", + "R_AARCH64_MOVW_UABS_G0_NC", + "R_AARCH64_MOVW_UABS_G1", + "R_AARCH64_MOVW_UABS_G1_NC", + "R_AARCH64_MOVW_UABS_G2", + "R_AARCH64_MOVW_UABS_G2_NC", + "R_AARCH64_MOVW_UABS_G3", + "R_AARCH64_NONE", + "R_AARCH64_NULL", + "R_AARCH64_P32_ABS16", + "R_AARCH64_P32_ABS32", + "R_AARCH64_P32_ADD_ABS_LO12_NC", + "R_AARCH64_P32_ADR_GOT_PAGE", + "R_AARCH64_P32_ADR_PREL_LO21", + "R_AARCH64_P32_ADR_PREL_PG_HI21", + "R_AARCH64_P32_CALL26", + "R_AARCH64_P32_CONDBR19", + "R_AARCH64_P32_COPY", + "R_AARCH64_P32_GLOB_DAT", + "R_AARCH64_P32_GOT_LD_PREL19", + "R_AARCH64_P32_IRELATIVE", + "R_AARCH64_P32_JUMP26", + "R_AARCH64_P32_JUMP_SLOT", + "R_AARCH64_P32_LD32_GOT_LO12_NC", + "R_AARCH64_P32_LDST128_ABS_LO12_NC", + "R_AARCH64_P32_LDST16_ABS_LO12_NC", + "R_AARCH64_P32_LDST32_ABS_LO12_NC", + "R_AARCH64_P32_LDST64_ABS_LO12_NC", + "R_AARCH64_P32_LDST8_ABS_LO12_NC", + "R_AARCH64_P32_LD_PREL_LO19", + "R_AARCH64_P32_MOVW_SABS_G0", + "R_AARCH64_P32_MOVW_UABS_G0", + "R_AARCH64_P32_MOVW_UABS_G0_NC", + "R_AARCH64_P32_MOVW_UABS_G1", + "R_AARCH64_P32_PREL16", + "R_AARCH64_P32_PREL32", + "R_AARCH64_P32_RELATIVE", + "R_AARCH64_P32_TLSDESC", + "R_AARCH64_P32_TLSDESC_ADD_LO12_NC", + "R_AARCH64_P32_TLSDESC_ADR_PAGE21", + "R_AARCH64_P32_TLSDESC_ADR_PREL21", + "R_AARCH64_P32_TLSDESC_CALL", + "R_AARCH64_P32_TLSDESC_LD32_LO12_NC", + "R_AARCH64_P32_TLSDESC_LD_PREL19", + "R_AARCH64_P32_TLSGD_ADD_LO12_NC", + "R_AARCH64_P32_TLSGD_ADR_PAGE21", + "R_AARCH64_P32_TLSIE_ADR_GOTTPREL_PAGE21", + "R_AARCH64_P32_TLSIE_LD32_GOTTPREL_LO12_NC", + "R_AARCH64_P32_TLSIE_LD_GOTTPREL_PREL19", + "R_AARCH64_P32_TLSLE_ADD_TPREL_HI12", + "R_AARCH64_P32_TLSLE_ADD_TPREL_LO12", + "R_AARCH64_P32_TLSLE_ADD_TPREL_LO12_NC", + "R_AARCH64_P32_TLSLE_MOVW_TPREL_G0", + "R_AARCH64_P32_TLSLE_MOVW_TPREL_G0_NC", + "R_AARCH64_P32_TLSLE_MOVW_TPREL_G1", + "R_AARCH64_P32_TLS_DTPMOD", + "R_AARCH64_P32_TLS_DTPREL", + "R_AARCH64_P32_TLS_TPREL", + "R_AARCH64_P32_TSTBR14", + "R_AARCH64_PREL16", + "R_AARCH64_PREL32", + "R_AARCH64_PREL64", + "R_AARCH64_RELATIVE", + "R_AARCH64_TLSDESC", + "R_AARCH64_TLSDESC_ADD", + "R_AARCH64_TLSDESC_ADD_LO12_NC", + "R_AARCH64_TLSDESC_ADR_PAGE21", + "R_AARCH64_TLSDESC_ADR_PREL21", + "R_AARCH64_TLSDESC_CALL", + "R_AARCH64_TLSDESC_LD64_LO12_NC", + "R_AARCH64_TLSDESC_LDR", + "R_AARCH64_TLSDESC_LD_PREL19", + "R_AARCH64_TLSDESC_OFF_G0_NC", + "R_AARCH64_TLSDESC_OFF_G1", + "R_AARCH64_TLSGD_ADD_LO12_NC", + "R_AARCH64_TLSGD_ADR_PAGE21", + "R_AARCH64_TLSGD_ADR_PREL21", + "R_AARCH64_TLSGD_MOVW_G0_NC", + "R_AARCH64_TLSGD_MOVW_G1", + "R_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21", + "R_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC", + "R_AARCH64_TLSIE_LD_GOTTPREL_PREL19", + "R_AARCH64_TLSIE_MOVW_GOTTPREL_G0_NC", + "R_AARCH64_TLSIE_MOVW_GOTTPREL_G1", + "R_AARCH64_TLSLD_ADR_PAGE21", + "R_AARCH64_TLSLD_ADR_PREL21", + "R_AARCH64_TLSLD_LDST128_DTPREL_LO12", + "R_AARCH64_TLSLD_LDST128_DTPREL_LO12_NC", + "R_AARCH64_TLSLE_ADD_TPREL_HI12", + "R_AARCH64_TLSLE_ADD_TPREL_LO12", + "R_AARCH64_TLSLE_ADD_TPREL_LO12_NC", + "R_AARCH64_TLSLE_LDST128_TPREL_LO12", + "R_AARCH64_TLSLE_LDST128_TPREL_LO12_NC", + "R_AARCH64_TLSLE_MOVW_TPREL_G0", + "R_AARCH64_TLSLE_MOVW_TPREL_G0_NC", + "R_AARCH64_TLSLE_MOVW_TPREL_G1", + "R_AARCH64_TLSLE_MOVW_TPREL_G1_NC", + "R_AARCH64_TLSLE_MOVW_TPREL_G2", + "R_AARCH64_TLS_DTPMOD64", + "R_AARCH64_TLS_DTPREL64", + "R_AARCH64_TLS_TPREL64", + "R_AARCH64_TSTBR14", + "R_ALPHA", + "R_ALPHA_BRADDR", + "R_ALPHA_COPY", + "R_ALPHA_GLOB_DAT", + "R_ALPHA_GPDISP", + "R_ALPHA_GPREL32", + "R_ALPHA_GPRELHIGH", + "R_ALPHA_GPRELLOW", + "R_ALPHA_GPVALUE", + "R_ALPHA_HINT", + "R_ALPHA_IMMED_BR_HI32", + "R_ALPHA_IMMED_GP_16", + "R_ALPHA_IMMED_GP_HI32", + "R_ALPHA_IMMED_LO32", + "R_ALPHA_IMMED_SCN_HI32", + "R_ALPHA_JMP_SLOT", + "R_ALPHA_LITERAL", + "R_ALPHA_LITUSE", + "R_ALPHA_NONE", + "R_ALPHA_OP_PRSHIFT", + "R_ALPHA_OP_PSUB", + "R_ALPHA_OP_PUSH", + "R_ALPHA_OP_STORE", + "R_ALPHA_REFLONG", + "R_ALPHA_REFQUAD", + "R_ALPHA_RELATIVE", + "R_ALPHA_SREL16", + "R_ALPHA_SREL32", + "R_ALPHA_SREL64", + "R_ARM", + "R_ARM_ABS12", + "R_ARM_ABS16", + "R_ARM_ABS32", + "R_ARM_ABS32_NOI", + "R_ARM_ABS8", + "R_ARM_ALU_PCREL_15_8", + "R_ARM_ALU_PCREL_23_15", + "R_ARM_ALU_PCREL_7_0", + "R_ARM_ALU_PC_G0", + "R_ARM_ALU_PC_G0_NC", + "R_ARM_ALU_PC_G1", + "R_ARM_ALU_PC_G1_NC", + "R_ARM_ALU_PC_G2", + "R_ARM_ALU_SBREL_19_12_NC", + "R_ARM_ALU_SBREL_27_20_CK", + "R_ARM_ALU_SB_G0", + "R_ARM_ALU_SB_G0_NC", + "R_ARM_ALU_SB_G1", + "R_ARM_ALU_SB_G1_NC", + "R_ARM_ALU_SB_G2", + "R_ARM_AMP_VCALL9", + "R_ARM_BASE_ABS", + "R_ARM_CALL", + "R_ARM_COPY", + "R_ARM_GLOB_DAT", + "R_ARM_GNU_VTENTRY", + "R_ARM_GNU_VTINHERIT", + "R_ARM_GOT32", + "R_ARM_GOTOFF", + "R_ARM_GOTOFF12", + "R_ARM_GOTPC", + "R_ARM_GOTRELAX", + "R_ARM_GOT_ABS", + "R_ARM_GOT_BREL12", + "R_ARM_GOT_PREL", + "R_ARM_IRELATIVE", + "R_ARM_JUMP24", + "R_ARM_JUMP_SLOT", + "R_ARM_LDC_PC_G0", + "R_ARM_LDC_PC_G1", + "R_ARM_LDC_PC_G2", + "R_ARM_LDC_SB_G0", + "R_ARM_LDC_SB_G1", + "R_ARM_LDC_SB_G2", + "R_ARM_LDRS_PC_G0", + "R_ARM_LDRS_PC_G1", + "R_ARM_LDRS_PC_G2", + "R_ARM_LDRS_SB_G0", + "R_ARM_LDRS_SB_G1", + "R_ARM_LDRS_SB_G2", + "R_ARM_LDR_PC_G1", + "R_ARM_LDR_PC_G2", + "R_ARM_LDR_SBREL_11_10_NC", + "R_ARM_LDR_SB_G0", + "R_ARM_LDR_SB_G1", + "R_ARM_LDR_SB_G2", + "R_ARM_ME_TOO", + "R_ARM_MOVT_ABS", + "R_ARM_MOVT_BREL", + "R_ARM_MOVT_PREL", + "R_ARM_MOVW_ABS_NC", + "R_ARM_MOVW_BREL", + "R_ARM_MOVW_BREL_NC", + "R_ARM_MOVW_PREL_NC", + "R_ARM_NONE", + "R_ARM_PC13", + "R_ARM_PC24", + "R_ARM_PLT32", + "R_ARM_PLT32_ABS", + "R_ARM_PREL31", + "R_ARM_PRIVATE_0", + "R_ARM_PRIVATE_1", + "R_ARM_PRIVATE_10", + "R_ARM_PRIVATE_11", + "R_ARM_PRIVATE_12", + "R_ARM_PRIVATE_13", + "R_ARM_PRIVATE_14", + "R_ARM_PRIVATE_15", + "R_ARM_PRIVATE_2", + "R_ARM_PRIVATE_3", + "R_ARM_PRIVATE_4", + "R_ARM_PRIVATE_5", + "R_ARM_PRIVATE_6", + "R_ARM_PRIVATE_7", + "R_ARM_PRIVATE_8", + "R_ARM_PRIVATE_9", + "R_ARM_RABS32", + "R_ARM_RBASE", + "R_ARM_REL32", + "R_ARM_REL32_NOI", + "R_ARM_RELATIVE", + "R_ARM_RPC24", + "R_ARM_RREL32", + "R_ARM_RSBREL32", + "R_ARM_RXPC25", + "R_ARM_SBREL31", + "R_ARM_SBREL32", + "R_ARM_SWI24", + "R_ARM_TARGET1", + "R_ARM_TARGET2", + "R_ARM_THM_ABS5", + "R_ARM_THM_ALU_ABS_G0_NC", + "R_ARM_THM_ALU_ABS_G1_NC", + "R_ARM_THM_ALU_ABS_G2_NC", + "R_ARM_THM_ALU_ABS_G3", + "R_ARM_THM_ALU_PREL_11_0", + "R_ARM_THM_GOT_BREL12", + "R_ARM_THM_JUMP11", + "R_ARM_THM_JUMP19", + "R_ARM_THM_JUMP24", + "R_ARM_THM_JUMP6", + "R_ARM_THM_JUMP8", + "R_ARM_THM_MOVT_ABS", + "R_ARM_THM_MOVT_BREL", + "R_ARM_THM_MOVT_PREL", + "R_ARM_THM_MOVW_ABS_NC", + "R_ARM_THM_MOVW_BREL", + "R_ARM_THM_MOVW_BREL_NC", + "R_ARM_THM_MOVW_PREL_NC", + "R_ARM_THM_PC12", + "R_ARM_THM_PC22", + "R_ARM_THM_PC8", + "R_ARM_THM_RPC22", + "R_ARM_THM_SWI8", + "R_ARM_THM_TLS_CALL", + "R_ARM_THM_TLS_DESCSEQ16", + "R_ARM_THM_TLS_DESCSEQ32", + "R_ARM_THM_XPC22", + "R_ARM_TLS_CALL", + "R_ARM_TLS_DESCSEQ", + "R_ARM_TLS_DTPMOD32", + "R_ARM_TLS_DTPOFF32", + "R_ARM_TLS_GD32", + "R_ARM_TLS_GOTDESC", + "R_ARM_TLS_IE12GP", + "R_ARM_TLS_IE32", + "R_ARM_TLS_LDM32", + "R_ARM_TLS_LDO12", + "R_ARM_TLS_LDO32", + "R_ARM_TLS_LE12", + "R_ARM_TLS_LE32", + "R_ARM_TLS_TPOFF32", + "R_ARM_V4BX", + "R_ARM_XPC25", + "R_INFO", + "R_INFO32", + "R_MIPS", + "R_MIPS_16", + "R_MIPS_26", + "R_MIPS_32", + "R_MIPS_64", + "R_MIPS_ADD_IMMEDIATE", + "R_MIPS_CALL16", + "R_MIPS_CALL_HI16", + "R_MIPS_CALL_LO16", + "R_MIPS_DELETE", + "R_MIPS_GOT16", + "R_MIPS_GOT_DISP", + "R_MIPS_GOT_HI16", + "R_MIPS_GOT_LO16", + "R_MIPS_GOT_OFST", + "R_MIPS_GOT_PAGE", + "R_MIPS_GPREL16", + "R_MIPS_GPREL32", + "R_MIPS_HI16", + "R_MIPS_HIGHER", + "R_MIPS_HIGHEST", + "R_MIPS_INSERT_A", + "R_MIPS_INSERT_B", + "R_MIPS_JALR", + "R_MIPS_LITERAL", + "R_MIPS_LO16", + "R_MIPS_NONE", + "R_MIPS_PC16", + "R_MIPS_PJUMP", + "R_MIPS_REL16", + "R_MIPS_REL32", + "R_MIPS_RELGOT", + "R_MIPS_SCN_DISP", + "R_MIPS_SHIFT5", + "R_MIPS_SHIFT6", + "R_MIPS_SUB", + "R_MIPS_TLS_DTPMOD32", + "R_MIPS_TLS_DTPMOD64", + "R_MIPS_TLS_DTPREL32", + "R_MIPS_TLS_DTPREL64", + "R_MIPS_TLS_DTPREL_HI16", + "R_MIPS_TLS_DTPREL_LO16", + "R_MIPS_TLS_GD", + "R_MIPS_TLS_GOTTPREL", + "R_MIPS_TLS_LDM", + "R_MIPS_TLS_TPREL32", + "R_MIPS_TLS_TPREL64", + "R_MIPS_TLS_TPREL_HI16", + "R_MIPS_TLS_TPREL_LO16", + "R_PPC", + "R_PPC64", + "R_PPC64_ADDR14", + "R_PPC64_ADDR14_BRNTAKEN", + "R_PPC64_ADDR14_BRTAKEN", + "R_PPC64_ADDR16", + "R_PPC64_ADDR16_DS", + "R_PPC64_ADDR16_HA", + "R_PPC64_ADDR16_HI", + "R_PPC64_ADDR16_HIGH", + "R_PPC64_ADDR16_HIGHA", + "R_PPC64_ADDR16_HIGHER", + "R_PPC64_ADDR16_HIGHERA", + "R_PPC64_ADDR16_HIGHEST", + "R_PPC64_ADDR16_HIGHESTA", + "R_PPC64_ADDR16_LO", + "R_PPC64_ADDR16_LO_DS", + "R_PPC64_ADDR24", + "R_PPC64_ADDR32", + "R_PPC64_ADDR64", + "R_PPC64_ADDR64_LOCAL", + "R_PPC64_DTPMOD64", + "R_PPC64_DTPREL16", + "R_PPC64_DTPREL16_DS", + "R_PPC64_DTPREL16_HA", + "R_PPC64_DTPREL16_HI", + "R_PPC64_DTPREL16_HIGH", + "R_PPC64_DTPREL16_HIGHA", + "R_PPC64_DTPREL16_HIGHER", + "R_PPC64_DTPREL16_HIGHERA", + "R_PPC64_DTPREL16_HIGHEST", + "R_PPC64_DTPREL16_HIGHESTA", + "R_PPC64_DTPREL16_LO", + "R_PPC64_DTPREL16_LO_DS", + "R_PPC64_DTPREL64", + "R_PPC64_ENTRY", + "R_PPC64_GOT16", + "R_PPC64_GOT16_DS", + "R_PPC64_GOT16_HA", + "R_PPC64_GOT16_HI", + "R_PPC64_GOT16_LO", + "R_PPC64_GOT16_LO_DS", + "R_PPC64_GOT_DTPREL16_DS", + "R_PPC64_GOT_DTPREL16_HA", + "R_PPC64_GOT_DTPREL16_HI", + "R_PPC64_GOT_DTPREL16_LO_DS", + "R_PPC64_GOT_TLSGD16", + "R_PPC64_GOT_TLSGD16_HA", + "R_PPC64_GOT_TLSGD16_HI", + "R_PPC64_GOT_TLSGD16_LO", + "R_PPC64_GOT_TLSLD16", + "R_PPC64_GOT_TLSLD16_HA", + "R_PPC64_GOT_TLSLD16_HI", + "R_PPC64_GOT_TLSLD16_LO", + "R_PPC64_GOT_TPREL16_DS", + "R_PPC64_GOT_TPREL16_HA", + "R_PPC64_GOT_TPREL16_HI", + "R_PPC64_GOT_TPREL16_LO_DS", + "R_PPC64_IRELATIVE", + "R_PPC64_JMP_IREL", + "R_PPC64_JMP_SLOT", + "R_PPC64_NONE", + "R_PPC64_PLT16_LO_DS", + "R_PPC64_PLTGOT16", + "R_PPC64_PLTGOT16_DS", + "R_PPC64_PLTGOT16_HA", + "R_PPC64_PLTGOT16_HI", + "R_PPC64_PLTGOT16_LO", + "R_PPC64_PLTGOT_LO_DS", + "R_PPC64_REL14", + "R_PPC64_REL14_BRNTAKEN", + "R_PPC64_REL14_BRTAKEN", + "R_PPC64_REL16", + "R_PPC64_REL16DX_HA", + "R_PPC64_REL16_HA", + "R_PPC64_REL16_HI", + "R_PPC64_REL16_LO", + "R_PPC64_REL24", + "R_PPC64_REL24_NOTOC", + "R_PPC64_REL32", + "R_PPC64_REL64", + "R_PPC64_SECTOFF_DS", + "R_PPC64_SECTOFF_LO_DS", + "R_PPC64_TLS", + "R_PPC64_TLSGD", + "R_PPC64_TLSLD", + "R_PPC64_TOC", + "R_PPC64_TOC16", + "R_PPC64_TOC16_DS", + "R_PPC64_TOC16_HA", + "R_PPC64_TOC16_HI", + "R_PPC64_TOC16_LO", + "R_PPC64_TOC16_LO_DS", + "R_PPC64_TOCSAVE", + "R_PPC64_TPREL16", + "R_PPC64_TPREL16_DS", + "R_PPC64_TPREL16_HA", + "R_PPC64_TPREL16_HI", + "R_PPC64_TPREL16_HIGH", + "R_PPC64_TPREL16_HIGHA", + "R_PPC64_TPREL16_HIGHER", + "R_PPC64_TPREL16_HIGHERA", + "R_PPC64_TPREL16_HIGHEST", + "R_PPC64_TPREL16_HIGHESTA", + "R_PPC64_TPREL16_LO", + "R_PPC64_TPREL16_LO_DS", + "R_PPC64_TPREL64", + "R_PPC_ADDR14", + "R_PPC_ADDR14_BRNTAKEN", + "R_PPC_ADDR14_BRTAKEN", + "R_PPC_ADDR16", + "R_PPC_ADDR16_HA", + "R_PPC_ADDR16_HI", + "R_PPC_ADDR16_LO", + "R_PPC_ADDR24", + "R_PPC_ADDR32", + "R_PPC_COPY", + "R_PPC_DTPMOD32", + "R_PPC_DTPREL16", + "R_PPC_DTPREL16_HA", + "R_PPC_DTPREL16_HI", + "R_PPC_DTPREL16_LO", + "R_PPC_DTPREL32", + "R_PPC_EMB_BIT_FLD", + "R_PPC_EMB_MRKREF", + "R_PPC_EMB_NADDR16", + "R_PPC_EMB_NADDR16_HA", + "R_PPC_EMB_NADDR16_HI", + "R_PPC_EMB_NADDR16_LO", + "R_PPC_EMB_NADDR32", + "R_PPC_EMB_RELSDA", + "R_PPC_EMB_RELSEC16", + "R_PPC_EMB_RELST_HA", + "R_PPC_EMB_RELST_HI", + "R_PPC_EMB_RELST_LO", + "R_PPC_EMB_SDA21", + "R_PPC_EMB_SDA2I16", + "R_PPC_EMB_SDA2REL", + "R_PPC_EMB_SDAI16", + "R_PPC_GLOB_DAT", + "R_PPC_GOT16", + "R_PPC_GOT16_HA", + "R_PPC_GOT16_HI", + "R_PPC_GOT16_LO", + "R_PPC_GOT_TLSGD16", + "R_PPC_GOT_TLSGD16_HA", + "R_PPC_GOT_TLSGD16_HI", + "R_PPC_GOT_TLSGD16_LO", + "R_PPC_GOT_TLSLD16", + "R_PPC_GOT_TLSLD16_HA", + "R_PPC_GOT_TLSLD16_HI", + "R_PPC_GOT_TLSLD16_LO", + "R_PPC_GOT_TPREL16", + "R_PPC_GOT_TPREL16_HA", + "R_PPC_GOT_TPREL16_HI", + "R_PPC_GOT_TPREL16_LO", + "R_PPC_JMP_SLOT", + "R_PPC_LOCAL24PC", + "R_PPC_NONE", + "R_PPC_PLT16_HA", + "R_PPC_PLT16_HI", + "R_PPC_PLT16_LO", + "R_PPC_PLT32", + "R_PPC_PLTREL24", + "R_PPC_PLTREL32", + "R_PPC_REL14", + "R_PPC_REL14_BRNTAKEN", + "R_PPC_REL14_BRTAKEN", + "R_PPC_REL24", + "R_PPC_REL32", + "R_PPC_RELATIVE", + "R_PPC_SDAREL16", + "R_PPC_SECTOFF", + "R_PPC_SECTOFF_HA", + "R_PPC_SECTOFF_HI", + "R_PPC_SECTOFF_LO", + "R_PPC_TLS", + "R_PPC_TPREL16", + "R_PPC_TPREL16_HA", + "R_PPC_TPREL16_HI", + "R_PPC_TPREL16_LO", + "R_PPC_TPREL32", + "R_PPC_UADDR16", + "R_PPC_UADDR32", + "R_RISCV", + "R_RISCV_32", + "R_RISCV_32_PCREL", + "R_RISCV_64", + "R_RISCV_ADD16", + "R_RISCV_ADD32", + "R_RISCV_ADD64", + "R_RISCV_ADD8", + "R_RISCV_ALIGN", + "R_RISCV_BRANCH", + "R_RISCV_CALL", + "R_RISCV_CALL_PLT", + "R_RISCV_COPY", + "R_RISCV_GNU_VTENTRY", + "R_RISCV_GNU_VTINHERIT", + "R_RISCV_GOT_HI20", + "R_RISCV_GPREL_I", + "R_RISCV_GPREL_S", + "R_RISCV_HI20", + "R_RISCV_JAL", + "R_RISCV_JUMP_SLOT", + "R_RISCV_LO12_I", + "R_RISCV_LO12_S", + "R_RISCV_NONE", + "R_RISCV_PCREL_HI20", + "R_RISCV_PCREL_LO12_I", + "R_RISCV_PCREL_LO12_S", + "R_RISCV_RELATIVE", + "R_RISCV_RELAX", + "R_RISCV_RVC_BRANCH", + "R_RISCV_RVC_JUMP", + "R_RISCV_RVC_LUI", + "R_RISCV_SET16", + "R_RISCV_SET32", + "R_RISCV_SET6", + "R_RISCV_SET8", + "R_RISCV_SUB16", + "R_RISCV_SUB32", + "R_RISCV_SUB6", + "R_RISCV_SUB64", + "R_RISCV_SUB8", + "R_RISCV_TLS_DTPMOD32", + "R_RISCV_TLS_DTPMOD64", + "R_RISCV_TLS_DTPREL32", + "R_RISCV_TLS_DTPREL64", + "R_RISCV_TLS_GD_HI20", + "R_RISCV_TLS_GOT_HI20", + "R_RISCV_TLS_TPREL32", + "R_RISCV_TLS_TPREL64", + "R_RISCV_TPREL_ADD", + "R_RISCV_TPREL_HI20", + "R_RISCV_TPREL_I", + "R_RISCV_TPREL_LO12_I", + "R_RISCV_TPREL_LO12_S", + "R_RISCV_TPREL_S", + "R_SPARC", + "R_SPARC_10", + "R_SPARC_11", + "R_SPARC_13", + "R_SPARC_16", + "R_SPARC_22", + "R_SPARC_32", + "R_SPARC_5", + "R_SPARC_6", + "R_SPARC_64", + "R_SPARC_7", + "R_SPARC_8", + "R_SPARC_COPY", + "R_SPARC_DISP16", + "R_SPARC_DISP32", + "R_SPARC_DISP64", + "R_SPARC_DISP8", + "R_SPARC_GLOB_DAT", + "R_SPARC_GLOB_JMP", + "R_SPARC_GOT10", + "R_SPARC_GOT13", + "R_SPARC_GOT22", + "R_SPARC_H44", + "R_SPARC_HH22", + "R_SPARC_HI22", + "R_SPARC_HIPLT22", + "R_SPARC_HIX22", + "R_SPARC_HM10", + "R_SPARC_JMP_SLOT", + "R_SPARC_L44", + "R_SPARC_LM22", + "R_SPARC_LO10", + "R_SPARC_LOPLT10", + "R_SPARC_LOX10", + "R_SPARC_M44", + "R_SPARC_NONE", + "R_SPARC_OLO10", + "R_SPARC_PC10", + "R_SPARC_PC22", + "R_SPARC_PCPLT10", + "R_SPARC_PCPLT22", + "R_SPARC_PCPLT32", + "R_SPARC_PC_HH22", + "R_SPARC_PC_HM10", + "R_SPARC_PC_LM22", + "R_SPARC_PLT32", + "R_SPARC_PLT64", + "R_SPARC_REGISTER", + "R_SPARC_RELATIVE", + "R_SPARC_UA16", + "R_SPARC_UA32", + "R_SPARC_UA64", + "R_SPARC_WDISP16", + "R_SPARC_WDISP19", + "R_SPARC_WDISP22", + "R_SPARC_WDISP30", + "R_SPARC_WPLT30", + "R_SYM32", + "R_SYM64", + "R_TYPE32", + "R_TYPE64", + "R_X86_64", + "R_X86_64_16", + "R_X86_64_32", + "R_X86_64_32S", + "R_X86_64_64", + "R_X86_64_8", + "R_X86_64_COPY", + "R_X86_64_DTPMOD64", + "R_X86_64_DTPOFF32", + "R_X86_64_DTPOFF64", + "R_X86_64_GLOB_DAT", + "R_X86_64_GOT32", + "R_X86_64_GOT64", + "R_X86_64_GOTOFF64", + "R_X86_64_GOTPC32", + "R_X86_64_GOTPC32_TLSDESC", + "R_X86_64_GOTPC64", + "R_X86_64_GOTPCREL", + "R_X86_64_GOTPCREL64", + "R_X86_64_GOTPCRELX", + "R_X86_64_GOTPLT64", + "R_X86_64_GOTTPOFF", + "R_X86_64_IRELATIVE", + "R_X86_64_JMP_SLOT", + "R_X86_64_NONE", + "R_X86_64_PC16", + "R_X86_64_PC32", + "R_X86_64_PC32_BND", + "R_X86_64_PC64", + "R_X86_64_PC8", + "R_X86_64_PLT32", + "R_X86_64_PLT32_BND", + "R_X86_64_PLTOFF64", + "R_X86_64_RELATIVE", + "R_X86_64_RELATIVE64", + "R_X86_64_REX_GOTPCRELX", + "R_X86_64_SIZE32", + "R_X86_64_SIZE64", + "R_X86_64_TLSDESC", + "R_X86_64_TLSDESC_CALL", + "R_X86_64_TLSGD", + "R_X86_64_TLSLD", + "R_X86_64_TPOFF32", + "R_X86_64_TPOFF64", + "Rel32", + "Rel64", + "Rela32", + "Rela64", + "SHF_ALLOC", + "SHF_COMPRESSED", + "SHF_EXECINSTR", + "SHF_GROUP", + "SHF_INFO_LINK", + "SHF_LINK_ORDER", + "SHF_MASKOS", + "SHF_MASKPROC", + "SHF_MERGE", + "SHF_OS_NONCONFORMING", + "SHF_STRINGS", + "SHF_TLS", + "SHF_WRITE", + "SHN_ABS", + "SHN_COMMON", + "SHN_HIOS", + "SHN_HIPROC", + "SHN_HIRESERVE", + "SHN_LOOS", + "SHN_LOPROC", + "SHN_LORESERVE", + "SHN_UNDEF", + "SHN_XINDEX", + "SHT_DYNAMIC", + "SHT_DYNSYM", + "SHT_FINI_ARRAY", + "SHT_GNU_ATTRIBUTES", + "SHT_GNU_HASH", + "SHT_GNU_LIBLIST", + "SHT_GNU_VERDEF", + "SHT_GNU_VERNEED", + "SHT_GNU_VERSYM", + "SHT_GROUP", + "SHT_HASH", + "SHT_HIOS", + "SHT_HIPROC", + "SHT_HIUSER", + "SHT_INIT_ARRAY", + "SHT_LOOS", + "SHT_LOPROC", + "SHT_LOUSER", + "SHT_NOBITS", + "SHT_NOTE", + "SHT_NULL", + "SHT_PREINIT_ARRAY", + "SHT_PROGBITS", + "SHT_REL", + "SHT_RELA", + "SHT_SHLIB", + "SHT_STRTAB", + "SHT_SYMTAB", + "SHT_SYMTAB_SHNDX", + "STB_GLOBAL", + "STB_HIOS", + "STB_HIPROC", + "STB_LOCAL", + "STB_LOOS", + "STB_LOPROC", + "STB_WEAK", + "STT_COMMON", + "STT_FILE", + "STT_FUNC", + "STT_HIOS", + "STT_HIPROC", + "STT_LOOS", + "STT_LOPROC", + "STT_NOTYPE", + "STT_OBJECT", + "STT_SECTION", + "STT_TLS", + "STV_DEFAULT", + "STV_HIDDEN", + "STV_INTERNAL", + "STV_PROTECTED", + "ST_BIND", + "ST_INFO", + "ST_TYPE", + "ST_VISIBILITY", + "Section", + "Section32", + "Section64", + "SectionFlag", + "SectionHeader", + "SectionIndex", + "SectionType", + "Sym32", + "Sym32Size", + "Sym64", + "Sym64Size", + "SymBind", + "SymType", + "SymVis", + "Symbol", + "Type", + "Version", + }, + "debug/gosym": []string{ + "DecodingError", + "Func", + "LineTable", + "NewLineTable", + "NewTable", + "Obj", + "Sym", + "Table", + "UnknownFileError", + "UnknownLineError", + }, + "debug/macho": []string{ + "ARM64_RELOC_ADDEND", + "ARM64_RELOC_BRANCH26", + "ARM64_RELOC_GOT_LOAD_PAGE21", + "ARM64_RELOC_GOT_LOAD_PAGEOFF12", + "ARM64_RELOC_PAGE21", + "ARM64_RELOC_PAGEOFF12", + "ARM64_RELOC_POINTER_TO_GOT", + "ARM64_RELOC_SUBTRACTOR", + "ARM64_RELOC_TLVP_LOAD_PAGE21", + "ARM64_RELOC_TLVP_LOAD_PAGEOFF12", + "ARM64_RELOC_UNSIGNED", + "ARM_RELOC_BR24", + "ARM_RELOC_HALF", + "ARM_RELOC_HALF_SECTDIFF", + "ARM_RELOC_LOCAL_SECTDIFF", + "ARM_RELOC_PAIR", + "ARM_RELOC_PB_LA_PTR", + "ARM_RELOC_SECTDIFF", + "ARM_RELOC_VANILLA", + "ARM_THUMB_32BIT_BRANCH", + "ARM_THUMB_RELOC_BR22", + "Cpu", + "Cpu386", + "CpuAmd64", + "CpuArm", + "CpuArm64", + "CpuPpc", + "CpuPpc64", + "Dylib", + "DylibCmd", + "Dysymtab", + "DysymtabCmd", + "ErrNotFat", + "FatArch", + "FatArchHeader", + "FatFile", + "File", + "FileHeader", + "FlagAllModsBound", + "FlagAllowStackExecution", + "FlagAppExtensionSafe", + "FlagBindAtLoad", + "FlagBindsToWeak", + "FlagCanonical", + "FlagDeadStrippableDylib", + "FlagDyldLink", + "FlagForceFlat", + "FlagHasTLVDescriptors", + "FlagIncrLink", + "FlagLazyInit", + "FlagNoFixPrebinding", + "FlagNoHeapExecution", + "FlagNoMultiDefs", + "FlagNoReexportedDylibs", + "FlagNoUndefs", + "FlagPIE", + "FlagPrebindable", + "FlagPrebound", + "FlagRootSafe", + "FlagSetuidSafe", + "FlagSplitSegs", + "FlagSubsectionsViaSymbols", + "FlagTwoLevel", + "FlagWeakDefines", + "FormatError", + "GENERIC_RELOC_LOCAL_SECTDIFF", + "GENERIC_RELOC_PAIR", + "GENERIC_RELOC_PB_LA_PTR", + "GENERIC_RELOC_SECTDIFF", + "GENERIC_RELOC_TLV", + "GENERIC_RELOC_VANILLA", + "Load", + "LoadBytes", + "LoadCmd", + "LoadCmdDylib", + "LoadCmdDylinker", + "LoadCmdDysymtab", + "LoadCmdRpath", + "LoadCmdSegment", + "LoadCmdSegment64", + "LoadCmdSymtab", + "LoadCmdThread", + "LoadCmdUnixThread", + "Magic32", + "Magic64", + "MagicFat", + "NewFatFile", + "NewFile", + "Nlist32", + "Nlist64", + "Open", + "OpenFat", + "Regs386", + "RegsAMD64", + "Reloc", + "RelocTypeARM", + "RelocTypeARM64", + "RelocTypeGeneric", + "RelocTypeX86_64", + "Rpath", + "RpathCmd", + "Section", + "Section32", + "Section64", + "SectionHeader", + "Segment", + "Segment32", + "Segment64", + "SegmentHeader", + "Symbol", + "Symtab", + "SymtabCmd", + "Thread", + "Type", + "TypeBundle", + "TypeDylib", + "TypeExec", + "TypeObj", + "X86_64_RELOC_BRANCH", + "X86_64_RELOC_GOT", + "X86_64_RELOC_GOT_LOAD", + "X86_64_RELOC_SIGNED", + "X86_64_RELOC_SIGNED_1", + "X86_64_RELOC_SIGNED_2", + "X86_64_RELOC_SIGNED_4", + "X86_64_RELOC_SUBTRACTOR", + "X86_64_RELOC_TLV", + "X86_64_RELOC_UNSIGNED", + }, + "debug/pe": []string{ + "COFFSymbol", + "COFFSymbolSize", + "DataDirectory", + "File", + "FileHeader", + "FormatError", + "IMAGE_DIRECTORY_ENTRY_ARCHITECTURE", + "IMAGE_DIRECTORY_ENTRY_BASERELOC", + "IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT", + "IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR", + "IMAGE_DIRECTORY_ENTRY_DEBUG", + "IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT", + "IMAGE_DIRECTORY_ENTRY_EXCEPTION", + "IMAGE_DIRECTORY_ENTRY_EXPORT", + "IMAGE_DIRECTORY_ENTRY_GLOBALPTR", + "IMAGE_DIRECTORY_ENTRY_IAT", + "IMAGE_DIRECTORY_ENTRY_IMPORT", + "IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG", + "IMAGE_DIRECTORY_ENTRY_RESOURCE", + "IMAGE_DIRECTORY_ENTRY_SECURITY", + "IMAGE_DIRECTORY_ENTRY_TLS", + "IMAGE_DLLCHARACTERISTICS_APPCONTAINER", + "IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE", + "IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY", + "IMAGE_DLLCHARACTERISTICS_GUARD_CF", + "IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA", + "IMAGE_DLLCHARACTERISTICS_NO_BIND", + "IMAGE_DLLCHARACTERISTICS_NO_ISOLATION", + "IMAGE_DLLCHARACTERISTICS_NO_SEH", + "IMAGE_DLLCHARACTERISTICS_NX_COMPAT", + "IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE", + "IMAGE_DLLCHARACTERISTICS_WDM_DRIVER", + "IMAGE_FILE_32BIT_MACHINE", + "IMAGE_FILE_AGGRESIVE_WS_TRIM", + "IMAGE_FILE_BYTES_REVERSED_HI", + "IMAGE_FILE_BYTES_REVERSED_LO", + "IMAGE_FILE_DEBUG_STRIPPED", + "IMAGE_FILE_DLL", + "IMAGE_FILE_EXECUTABLE_IMAGE", + "IMAGE_FILE_LARGE_ADDRESS_AWARE", + "IMAGE_FILE_LINE_NUMS_STRIPPED", + "IMAGE_FILE_LOCAL_SYMS_STRIPPED", + "IMAGE_FILE_MACHINE_AM33", + "IMAGE_FILE_MACHINE_AMD64", + "IMAGE_FILE_MACHINE_ARM", + "IMAGE_FILE_MACHINE_ARM64", + "IMAGE_FILE_MACHINE_ARMNT", + "IMAGE_FILE_MACHINE_EBC", + "IMAGE_FILE_MACHINE_I386", + "IMAGE_FILE_MACHINE_IA64", + "IMAGE_FILE_MACHINE_M32R", + "IMAGE_FILE_MACHINE_MIPS16", + "IMAGE_FILE_MACHINE_MIPSFPU", + "IMAGE_FILE_MACHINE_MIPSFPU16", + "IMAGE_FILE_MACHINE_POWERPC", + "IMAGE_FILE_MACHINE_POWERPCFP", + "IMAGE_FILE_MACHINE_R4000", + "IMAGE_FILE_MACHINE_SH3", + "IMAGE_FILE_MACHINE_SH3DSP", + "IMAGE_FILE_MACHINE_SH4", + "IMAGE_FILE_MACHINE_SH5", + "IMAGE_FILE_MACHINE_THUMB", + "IMAGE_FILE_MACHINE_UNKNOWN", + "IMAGE_FILE_MACHINE_WCEMIPSV2", + "IMAGE_FILE_NET_RUN_FROM_SWAP", + "IMAGE_FILE_RELOCS_STRIPPED", + "IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP", + "IMAGE_FILE_SYSTEM", + "IMAGE_FILE_UP_SYSTEM_ONLY", + "IMAGE_SUBSYSTEM_EFI_APPLICATION", + "IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER", + "IMAGE_SUBSYSTEM_EFI_ROM", + "IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER", + "IMAGE_SUBSYSTEM_NATIVE", + "IMAGE_SUBSYSTEM_NATIVE_WINDOWS", + "IMAGE_SUBSYSTEM_OS2_CUI", + "IMAGE_SUBSYSTEM_POSIX_CUI", + "IMAGE_SUBSYSTEM_UNKNOWN", + "IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION", + "IMAGE_SUBSYSTEM_WINDOWS_CE_GUI", + "IMAGE_SUBSYSTEM_WINDOWS_CUI", + "IMAGE_SUBSYSTEM_WINDOWS_GUI", + "IMAGE_SUBSYSTEM_XBOX", + "ImportDirectory", + "NewFile", + "Open", + "OptionalHeader32", + "OptionalHeader64", + "Reloc", + "Section", + "SectionHeader", + "SectionHeader32", + "StringTable", + "Symbol", + }, + "debug/plan9obj": []string{ + "File", + "FileHeader", + "Magic386", + "Magic64", + "MagicAMD64", + "MagicARM", + "NewFile", + "Open", + "Section", + "SectionHeader", + "Sym", + }, + "encoding": []string{ + "BinaryMarshaler", + "BinaryUnmarshaler", + "TextMarshaler", + "TextUnmarshaler", + }, + "encoding/ascii85": []string{ + "CorruptInputError", + "Decode", + "Encode", + "MaxEncodedLen", + "NewDecoder", + "NewEncoder", + }, + "encoding/asn1": []string{ + "BitString", + "ClassApplication", + "ClassContextSpecific", + "ClassPrivate", + "ClassUniversal", + "Enumerated", + "Flag", + "Marshal", + "MarshalWithParams", + "NullBytes", + "NullRawValue", + "ObjectIdentifier", + "RawContent", + "RawValue", + "StructuralError", + "SyntaxError", + "TagBMPString", + "TagBitString", + "TagBoolean", + "TagEnum", + "TagGeneralString", + "TagGeneralizedTime", + "TagIA5String", + "TagInteger", + "TagNull", + "TagNumericString", + "TagOID", + "TagOctetString", + "TagPrintableString", + "TagSequence", + "TagSet", + "TagT61String", + "TagUTCTime", + "TagUTF8String", + "Unmarshal", + "UnmarshalWithParams", + }, + "encoding/base32": []string{ + "CorruptInputError", + "Encoding", + "HexEncoding", + "NewDecoder", + "NewEncoder", + "NewEncoding", + "NoPadding", + "StdEncoding", + "StdPadding", + }, + "encoding/base64": []string{ + "CorruptInputError", + "Encoding", + "NewDecoder", + "NewEncoder", + "NewEncoding", + "NoPadding", + "RawStdEncoding", + "RawURLEncoding", + "StdEncoding", + "StdPadding", + "URLEncoding", + }, + "encoding/binary": []string{ + "BigEndian", + "ByteOrder", + "LittleEndian", + "MaxVarintLen16", + "MaxVarintLen32", + "MaxVarintLen64", + "PutUvarint", + "PutVarint", + "Read", + "ReadUvarint", + "ReadVarint", + "Size", + "Uvarint", + "Varint", + "Write", + }, + "encoding/csv": []string{ + "ErrBareQuote", + "ErrFieldCount", + "ErrQuote", + "ErrTrailingComma", + "NewReader", + "NewWriter", + "ParseError", + "Reader", + "Writer", + }, + "encoding/gob": []string{ + "CommonType", + "Decoder", + "Encoder", + "GobDecoder", + "GobEncoder", + "NewDecoder", + "NewEncoder", + "Register", + "RegisterName", + }, + "encoding/hex": []string{ + "Decode", + "DecodeString", + "DecodedLen", + "Dump", + "Dumper", + "Encode", + "EncodeToString", + "EncodedLen", + "ErrLength", + "InvalidByteError", + "NewDecoder", + "NewEncoder", + }, + "encoding/json": []string{ + "Compact", + "Decoder", + "Delim", + "Encoder", + "HTMLEscape", + "Indent", + "InvalidUTF8Error", + "InvalidUnmarshalError", + "Marshal", + "MarshalIndent", + "Marshaler", + "MarshalerError", + "NewDecoder", + "NewEncoder", + "Number", + "RawMessage", + "SyntaxError", + "Token", + "Unmarshal", + "UnmarshalFieldError", + "UnmarshalTypeError", + "Unmarshaler", + "UnsupportedTypeError", + "UnsupportedValueError", + "Valid", + }, + "encoding/pem": []string{ + "Block", + "Decode", + "Encode", + "EncodeToMemory", + }, + "encoding/xml": []string{ + "Attr", + "CharData", + "Comment", + "CopyToken", + "Decoder", + "Directive", + "Encoder", + "EndElement", + "Escape", + "EscapeText", + "HTMLAutoClose", + "HTMLEntity", + "Header", + "Marshal", + "MarshalIndent", + "Marshaler", + "MarshalerAttr", + "Name", + "NewDecoder", + "NewEncoder", + "NewTokenDecoder", + "ProcInst", + "StartElement", + "SyntaxError", + "TagPathError", + "Token", + "TokenReader", + "Unmarshal", + "UnmarshalError", + "Unmarshaler", + "UnmarshalerAttr", + "UnsupportedTypeError", + }, + "errors": []string{ + "As", + "Is", + "New", + "Unwrap", + }, + "expvar": []string{ + "Do", + "Float", + "Func", + "Get", + "Handler", + "Int", + "KeyValue", + "Map", + "NewFloat", + "NewInt", + "NewMap", + "NewString", + "Publish", + "String", + "Var", + }, + "flag": []string{ + "Arg", + "Args", + "Bool", + "BoolVar", + "CommandLine", + "ContinueOnError", + "Duration", + "DurationVar", + "ErrHelp", + "ErrorHandling", + "ExitOnError", + "Flag", + "FlagSet", + "Float64", + "Float64Var", + "Getter", + "Int", + "Int64", + "Int64Var", + "IntVar", + "Lookup", + "NArg", + "NFlag", + "NewFlagSet", + "PanicOnError", + "Parse", + "Parsed", + "PrintDefaults", + "Set", + "String", + "StringVar", + "Uint", + "Uint64", + "Uint64Var", + "UintVar", + "UnquoteUsage", + "Usage", + "Value", + "Var", + "Visit", + "VisitAll", + }, + "fmt": []string{ + "Errorf", + "Formatter", + "Fprint", + "Fprintf", + "Fprintln", + "Fscan", + "Fscanf", + "Fscanln", + "GoStringer", + "Print", + "Printf", + "Println", + "Scan", + "ScanState", + "Scanf", + "Scanln", + "Scanner", + "Sprint", + "Sprintf", + "Sprintln", + "Sscan", + "Sscanf", + "Sscanln", + "State", + "Stringer", + }, + "go/ast": []string{ + "ArrayType", + "AssignStmt", + "Bad", + "BadDecl", + "BadExpr", + "BadStmt", + "BasicLit", + "BinaryExpr", + "BlockStmt", + "BranchStmt", + "CallExpr", + "CaseClause", + "ChanDir", + "ChanType", + "CommClause", + "Comment", + "CommentGroup", + "CommentMap", + "CompositeLit", + "Con", + "Decl", + "DeclStmt", + "DeferStmt", + "Ellipsis", + "EmptyStmt", + "Expr", + "ExprStmt", + "Field", + "FieldFilter", + "FieldList", + "File", + "FileExports", + "Filter", + "FilterDecl", + "FilterFile", + "FilterFuncDuplicates", + "FilterImportDuplicates", + "FilterPackage", + "FilterUnassociatedComments", + "ForStmt", + "Fprint", + "Fun", + "FuncDecl", + "FuncLit", + "FuncType", + "GenDecl", + "GoStmt", + "Ident", + "IfStmt", + "ImportSpec", + "Importer", + "IncDecStmt", + "IndexExpr", + "Inspect", + "InterfaceType", + "IsExported", + "KeyValueExpr", + "LabeledStmt", + "Lbl", + "MapType", + "MergeMode", + "MergePackageFiles", + "NewCommentMap", + "NewIdent", + "NewObj", + "NewPackage", + "NewScope", + "Node", + "NotNilFilter", + "ObjKind", + "Object", + "Package", + "PackageExports", + "ParenExpr", + "Pkg", + "Print", + "RECV", + "RangeStmt", + "ReturnStmt", + "SEND", + "Scope", + "SelectStmt", + "SelectorExpr", + "SendStmt", + "SliceExpr", + "SortImports", + "Spec", + "StarExpr", + "Stmt", + "StructType", + "SwitchStmt", + "Typ", + "TypeAssertExpr", + "TypeSpec", + "TypeSwitchStmt", + "UnaryExpr", + "ValueSpec", + "Var", + "Visitor", + "Walk", + }, + "go/build": []string{ + "AllowBinary", + "ArchChar", + "Context", + "Default", + "FindOnly", + "IgnoreVendor", + "Import", + "ImportComment", + "ImportDir", + "ImportMode", + "IsLocalImport", + "MultiplePackageError", + "NoGoError", + "Package", + "ToolDir", + }, + "go/constant": []string{ + "BinaryOp", + "BitLen", + "Bool", + "BoolVal", + "Bytes", + "Compare", + "Complex", + "Denom", + "Float", + "Float32Val", + "Float64Val", + "Imag", + "Int", + "Int64Val", + "Kind", + "Make", + "MakeBool", + "MakeFloat64", + "MakeFromBytes", + "MakeFromLiteral", + "MakeImag", + "MakeInt64", + "MakeString", + "MakeUint64", + "MakeUnknown", + "Num", + "Real", + "Shift", + "Sign", + "String", + "StringVal", + "ToComplex", + "ToFloat", + "ToInt", + "Uint64Val", + "UnaryOp", + "Unknown", + "Val", + "Value", + }, + "go/doc": []string{ + "AllDecls", + "AllMethods", + "Example", + "Examples", + "Filter", + "Func", + "IllegalPrefixes", + "IsPredeclared", + "Mode", + "New", + "NewFromFiles", + "Note", + "Package", + "PreserveAST", + "Synopsis", + "ToHTML", + "ToText", + "Type", + "Value", + }, + "go/format": []string{ + "Node", + "Source", + }, + "go/importer": []string{ + "Default", + "For", + "ForCompiler", + "Lookup", + }, + "go/parser": []string{ + "AllErrors", + "DeclarationErrors", + "ImportsOnly", + "Mode", + "PackageClauseOnly", + "ParseComments", + "ParseDir", + "ParseExpr", + "ParseExprFrom", + "ParseFile", + "SpuriousErrors", + "Trace", + }, + "go/printer": []string{ + "CommentedNode", + "Config", + "Fprint", + "Mode", + "RawFormat", + "SourcePos", + "TabIndent", + "UseSpaces", + }, + "go/scanner": []string{ + "Error", + "ErrorHandler", + "ErrorList", + "Mode", + "PrintError", + "ScanComments", + "Scanner", + }, + "go/token": []string{ + "ADD", + "ADD_ASSIGN", + "AND", + "AND_ASSIGN", + "AND_NOT", + "AND_NOT_ASSIGN", + "ARROW", + "ASSIGN", + "BREAK", + "CASE", + "CHAN", + "CHAR", + "COLON", + "COMMA", + "COMMENT", + "CONST", + "CONTINUE", + "DEC", + "DEFAULT", + "DEFER", + "DEFINE", + "ELLIPSIS", + "ELSE", + "EOF", + "EQL", + "FALLTHROUGH", + "FLOAT", + "FOR", + "FUNC", + "File", + "FileSet", + "GEQ", + "GO", + "GOTO", + "GTR", + "HighestPrec", + "IDENT", + "IF", + "ILLEGAL", + "IMAG", + "IMPORT", + "INC", + "INT", + "INTERFACE", + "IsExported", + "IsIdentifier", + "IsKeyword", + "LAND", + "LBRACE", + "LBRACK", + "LEQ", + "LOR", + "LPAREN", + "LSS", + "Lookup", + "LowestPrec", + "MAP", + "MUL", + "MUL_ASSIGN", + "NEQ", + "NOT", + "NewFileSet", + "NoPos", + "OR", + "OR_ASSIGN", + "PACKAGE", + "PERIOD", + "Pos", + "Position", + "QUO", + "QUO_ASSIGN", + "RANGE", + "RBRACE", + "RBRACK", + "REM", + "REM_ASSIGN", + "RETURN", + "RPAREN", + "SELECT", + "SEMICOLON", + "SHL", + "SHL_ASSIGN", + "SHR", + "SHR_ASSIGN", + "STRING", + "STRUCT", + "SUB", + "SUB_ASSIGN", + "SWITCH", + "TYPE", + "Token", + "UnaryPrec", + "VAR", + "XOR", + "XOR_ASSIGN", + }, + "go/types": []string{ + "Array", + "AssertableTo", + "AssignableTo", + "Basic", + "BasicInfo", + "BasicKind", + "Bool", + "Builtin", + "Byte", + "Chan", + "ChanDir", + "CheckExpr", + "Checker", + "Comparable", + "Complex128", + "Complex64", + "Config", + "Const", + "ConvertibleTo", + "DefPredeclaredTestFuncs", + "Default", + "Error", + "Eval", + "ExprString", + "FieldVal", + "Float32", + "Float64", + "Func", + "Id", + "Identical", + "IdenticalIgnoreTags", + "Implements", + "ImportMode", + "Importer", + "ImporterFrom", + "Info", + "Initializer", + "Int", + "Int16", + "Int32", + "Int64", + "Int8", + "Interface", + "Invalid", + "IsBoolean", + "IsComplex", + "IsConstType", + "IsFloat", + "IsInteger", + "IsInterface", + "IsNumeric", + "IsOrdered", + "IsString", + "IsUnsigned", + "IsUntyped", + "Label", + "LookupFieldOrMethod", + "Map", + "MethodExpr", + "MethodSet", + "MethodVal", + "MissingMethod", + "Named", + "NewArray", + "NewChan", + "NewChecker", + "NewConst", + "NewField", + "NewFunc", + "NewInterface", + "NewInterfaceType", + "NewLabel", + "NewMap", + "NewMethodSet", + "NewNamed", + "NewPackage", + "NewParam", + "NewPkgName", + "NewPointer", + "NewScope", + "NewSignature", + "NewSlice", + "NewStruct", + "NewTuple", + "NewTypeName", + "NewVar", + "Nil", + "Object", + "ObjectString", + "Package", + "PkgName", + "Pointer", + "Qualifier", + "RecvOnly", + "RelativeTo", + "Rune", + "Scope", + "Selection", + "SelectionKind", + "SelectionString", + "SendOnly", + "SendRecv", + "Signature", + "Sizes", + "SizesFor", + "Slice", + "StdSizes", + "String", + "Struct", + "Tuple", + "Typ", + "Type", + "TypeAndValue", + "TypeName", + "TypeString", + "Uint", + "Uint16", + "Uint32", + "Uint64", + "Uint8", + "Uintptr", + "Universe", + "Unsafe", + "UnsafePointer", + "UntypedBool", + "UntypedComplex", + "UntypedFloat", + "UntypedInt", + "UntypedNil", + "UntypedRune", + "UntypedString", + "Var", + "WriteExpr", + "WriteSignature", + "WriteType", + }, + "hash": []string{ + "Hash", + "Hash32", + "Hash64", + }, + "hash/adler32": []string{ + "Checksum", + "New", + "Size", + }, + "hash/crc32": []string{ + "Castagnoli", + "Checksum", + "ChecksumIEEE", + "IEEE", + "IEEETable", + "Koopman", + "MakeTable", + "New", + "NewIEEE", + "Size", + "Table", + "Update", + }, + "hash/crc64": []string{ + "Checksum", + "ECMA", + "ISO", + "MakeTable", + "New", + "Size", + "Table", + "Update", + }, + "hash/fnv": []string{ + "New128", + "New128a", + "New32", + "New32a", + "New64", + "New64a", + }, + "hash/maphash": []string{ + "Hash", + "MakeSeed", + "Seed", + }, + "html": []string{ + "EscapeString", + "UnescapeString", + }, + "html/template": []string{ + "CSS", + "ErrAmbigContext", + "ErrBadHTML", + "ErrBranchEnd", + "ErrEndContext", + "ErrNoSuchTemplate", + "ErrOutputContext", + "ErrPartialCharset", + "ErrPartialEscape", + "ErrPredefinedEscaper", + "ErrRangeLoopReentry", + "ErrSlashAmbig", + "Error", + "ErrorCode", + "FuncMap", + "HTML", + "HTMLAttr", + "HTMLEscape", + "HTMLEscapeString", + "HTMLEscaper", + "IsTrue", + "JS", + "JSEscape", + "JSEscapeString", + "JSEscaper", + "JSStr", + "Must", + "New", + "OK", + "ParseFiles", + "ParseGlob", + "Srcset", + "Template", + "URL", + "URLQueryEscaper", + }, + "image": []string{ + "Alpha", + "Alpha16", + "Black", + "CMYK", + "Config", + "Decode", + "DecodeConfig", + "ErrFormat", + "Gray", + "Gray16", + "Image", + "NRGBA", + "NRGBA64", + "NYCbCrA", + "NewAlpha", + "NewAlpha16", + "NewCMYK", + "NewGray", + "NewGray16", + "NewNRGBA", + "NewNRGBA64", + "NewNYCbCrA", + "NewPaletted", + "NewRGBA", + "NewRGBA64", + "NewUniform", + "NewYCbCr", + "Opaque", + "Paletted", + "PalettedImage", + "Point", + "Pt", + "RGBA", + "RGBA64", + "Rect", + "Rectangle", + "RegisterFormat", + "Transparent", + "Uniform", + "White", + "YCbCr", + "YCbCrSubsampleRatio", + "YCbCrSubsampleRatio410", + "YCbCrSubsampleRatio411", + "YCbCrSubsampleRatio420", + "YCbCrSubsampleRatio422", + "YCbCrSubsampleRatio440", + "YCbCrSubsampleRatio444", + "ZP", + "ZR", + }, + "image/color": []string{ + "Alpha", + "Alpha16", + "Alpha16Model", + "AlphaModel", + "Black", + "CMYK", + "CMYKModel", + "CMYKToRGB", + "Color", + "Gray", + "Gray16", + "Gray16Model", + "GrayModel", + "Model", + "ModelFunc", + "NRGBA", + "NRGBA64", + "NRGBA64Model", + "NRGBAModel", + "NYCbCrA", + "NYCbCrAModel", + "Opaque", + "Palette", + "RGBA", + "RGBA64", + "RGBA64Model", + "RGBAModel", + "RGBToCMYK", + "RGBToYCbCr", + "Transparent", + "White", + "YCbCr", + "YCbCrModel", + "YCbCrToRGB", + }, + "image/color/palette": []string{ + "Plan9", + "WebSafe", + }, + "image/draw": []string{ + "Draw", + "DrawMask", + "Drawer", + "FloydSteinberg", + "Image", + "Op", + "Over", + "Quantizer", + "Src", + }, + "image/gif": []string{ + "Decode", + "DecodeAll", + "DecodeConfig", + "DisposalBackground", + "DisposalNone", + "DisposalPrevious", + "Encode", + "EncodeAll", + "GIF", + "Options", + }, + "image/jpeg": []string{ + "Decode", + "DecodeConfig", + "DefaultQuality", + "Encode", + "FormatError", + "Options", + "Reader", + "UnsupportedError", + }, + "image/png": []string{ + "BestCompression", + "BestSpeed", + "CompressionLevel", + "Decode", + "DecodeConfig", + "DefaultCompression", + "Encode", + "Encoder", + "EncoderBuffer", + "EncoderBufferPool", + "FormatError", + "NoCompression", + "UnsupportedError", + }, + "index/suffixarray": []string{ + "Index", + "New", + }, + "io": []string{ + "ByteReader", + "ByteScanner", + "ByteWriter", + "Closer", + "Copy", + "CopyBuffer", + "CopyN", + "EOF", + "ErrClosedPipe", + "ErrNoProgress", + "ErrShortBuffer", + "ErrShortWrite", + "ErrUnexpectedEOF", + "LimitReader", + "LimitedReader", + "MultiReader", + "MultiWriter", + "NewSectionReader", + "Pipe", + "PipeReader", + "PipeWriter", + "ReadAtLeast", + "ReadCloser", + "ReadFull", + "ReadSeeker", + "ReadWriteCloser", + "ReadWriteSeeker", + "ReadWriter", + "Reader", + "ReaderAt", + "ReaderFrom", + "RuneReader", + "RuneScanner", + "SectionReader", + "SeekCurrent", + "SeekEnd", + "SeekStart", + "Seeker", + "StringWriter", + "TeeReader", + "WriteCloser", + "WriteSeeker", + "WriteString", + "Writer", + "WriterAt", + "WriterTo", + }, + "io/ioutil": []string{ + "Discard", + "NopCloser", + "ReadAll", + "ReadDir", + "ReadFile", + "TempDir", + "TempFile", + "WriteFile", + }, + "log": []string{ + "Fatal", + "Fatalf", + "Fatalln", + "Flags", + "LUTC", + "Ldate", + "Llongfile", + "Lmicroseconds", + "Lmsgprefix", + "Logger", + "Lshortfile", + "LstdFlags", + "Ltime", + "New", + "Output", + "Panic", + "Panicf", + "Panicln", + "Prefix", + "Print", + "Printf", + "Println", + "SetFlags", + "SetOutput", + "SetPrefix", + "Writer", + }, + "log/syslog": []string{ + "Dial", + "LOG_ALERT", + "LOG_AUTH", + "LOG_AUTHPRIV", + "LOG_CRIT", + "LOG_CRON", + "LOG_DAEMON", + "LOG_DEBUG", + "LOG_EMERG", + "LOG_ERR", + "LOG_FTP", + "LOG_INFO", + "LOG_KERN", + "LOG_LOCAL0", + "LOG_LOCAL1", + "LOG_LOCAL2", + "LOG_LOCAL3", + "LOG_LOCAL4", + "LOG_LOCAL5", + "LOG_LOCAL6", + "LOG_LOCAL7", + "LOG_LPR", + "LOG_MAIL", + "LOG_NEWS", + "LOG_NOTICE", + "LOG_SYSLOG", + "LOG_USER", + "LOG_UUCP", + "LOG_WARNING", + "New", + "NewLogger", + "Priority", + "Writer", + }, + "math": []string{ + "Abs", + "Acos", + "Acosh", + "Asin", + "Asinh", + "Atan", + "Atan2", + "Atanh", + "Cbrt", + "Ceil", + "Copysign", + "Cos", + "Cosh", + "Dim", + "E", + "Erf", + "Erfc", + "Erfcinv", + "Erfinv", + "Exp", + "Exp2", + "Expm1", + "FMA", + "Float32bits", + "Float32frombits", + "Float64bits", + "Float64frombits", + "Floor", + "Frexp", + "Gamma", + "Hypot", + "Ilogb", + "Inf", + "IsInf", + "IsNaN", + "J0", + "J1", + "Jn", + "Ldexp", + "Lgamma", + "Ln10", + "Ln2", + "Log", + "Log10", + "Log10E", + "Log1p", + "Log2", + "Log2E", + "Logb", + "Max", + "MaxFloat32", + "MaxFloat64", + "MaxInt16", + "MaxInt32", + "MaxInt64", + "MaxInt8", + "MaxUint16", + "MaxUint32", + "MaxUint64", + "MaxUint8", + "Min", + "MinInt16", + "MinInt32", + "MinInt64", + "MinInt8", + "Mod", + "Modf", + "NaN", + "Nextafter", + "Nextafter32", + "Phi", + "Pi", + "Pow", + "Pow10", + "Remainder", + "Round", + "RoundToEven", + "Signbit", + "Sin", + "Sincos", + "Sinh", + "SmallestNonzeroFloat32", + "SmallestNonzeroFloat64", + "Sqrt", + "Sqrt2", + "SqrtE", + "SqrtPhi", + "SqrtPi", + "Tan", + "Tanh", + "Trunc", + "Y0", + "Y1", + "Yn", + }, + "math/big": []string{ + "Above", + "Accuracy", + "AwayFromZero", + "Below", + "ErrNaN", + "Exact", + "Float", + "Int", + "Jacobi", + "MaxBase", + "MaxExp", + "MaxPrec", + "MinExp", + "NewFloat", + "NewInt", + "NewRat", + "ParseFloat", + "Rat", + "RoundingMode", + "ToNearestAway", + "ToNearestEven", + "ToNegativeInf", + "ToPositiveInf", + "ToZero", + "Word", + }, + "math/bits": []string{ + "Add", + "Add32", + "Add64", + "Div", + "Div32", + "Div64", + "LeadingZeros", + "LeadingZeros16", + "LeadingZeros32", + "LeadingZeros64", + "LeadingZeros8", + "Len", + "Len16", + "Len32", + "Len64", + "Len8", + "Mul", + "Mul32", + "Mul64", + "OnesCount", + "OnesCount16", + "OnesCount32", + "OnesCount64", + "OnesCount8", + "Rem", + "Rem32", + "Rem64", + "Reverse", + "Reverse16", + "Reverse32", + "Reverse64", + "Reverse8", + "ReverseBytes", + "ReverseBytes16", + "ReverseBytes32", + "ReverseBytes64", + "RotateLeft", + "RotateLeft16", + "RotateLeft32", + "RotateLeft64", + "RotateLeft8", + "Sub", + "Sub32", + "Sub64", + "TrailingZeros", + "TrailingZeros16", + "TrailingZeros32", + "TrailingZeros64", + "TrailingZeros8", + "UintSize", + }, + "math/cmplx": []string{ + "Abs", + "Acos", + "Acosh", + "Asin", + "Asinh", + "Atan", + "Atanh", + "Conj", + "Cos", + "Cosh", + "Cot", + "Exp", + "Inf", + "IsInf", + "IsNaN", + "Log", + "Log10", + "NaN", + "Phase", + "Polar", + "Pow", + "Rect", + "Sin", + "Sinh", + "Sqrt", + "Tan", + "Tanh", + }, + "math/rand": []string{ + "ExpFloat64", + "Float32", + "Float64", + "Int", + "Int31", + "Int31n", + "Int63", + "Int63n", + "Intn", + "New", + "NewSource", + "NewZipf", + "NormFloat64", + "Perm", + "Rand", + "Read", + "Seed", + "Shuffle", + "Source", + "Source64", + "Uint32", + "Uint64", + "Zipf", + }, + "mime": []string{ + "AddExtensionType", + "BEncoding", + "ErrInvalidMediaParameter", + "ExtensionsByType", + "FormatMediaType", + "ParseMediaType", + "QEncoding", + "TypeByExtension", + "WordDecoder", + "WordEncoder", + }, + "mime/multipart": []string{ + "ErrMessageTooLarge", + "File", + "FileHeader", + "Form", + "NewReader", + "NewWriter", + "Part", + "Reader", + "Writer", + }, + "mime/quotedprintable": []string{ + "NewReader", + "NewWriter", + "Reader", + "Writer", + }, + "net": []string{ + "Addr", + "AddrError", + "Buffers", + "CIDRMask", + "Conn", + "DNSConfigError", + "DNSError", + "DefaultResolver", + "Dial", + "DialIP", + "DialTCP", + "DialTimeout", + "DialUDP", + "DialUnix", + "Dialer", + "ErrWriteToConnected", + "Error", + "FileConn", + "FileListener", + "FilePacketConn", + "FlagBroadcast", + "FlagLoopback", + "FlagMulticast", + "FlagPointToPoint", + "FlagUp", + "Flags", + "HardwareAddr", + "IP", + "IPAddr", + "IPConn", + "IPMask", + "IPNet", + "IPv4", + "IPv4Mask", + "IPv4allrouter", + "IPv4allsys", + "IPv4bcast", + "IPv4len", + "IPv4zero", + "IPv6interfacelocalallnodes", + "IPv6len", + "IPv6linklocalallnodes", + "IPv6linklocalallrouters", + "IPv6loopback", + "IPv6unspecified", + "IPv6zero", + "Interface", + "InterfaceAddrs", + "InterfaceByIndex", + "InterfaceByName", + "Interfaces", + "InvalidAddrError", + "JoinHostPort", + "Listen", + "ListenConfig", + "ListenIP", + "ListenMulticastUDP", + "ListenPacket", + "ListenTCP", + "ListenUDP", + "ListenUnix", + "ListenUnixgram", + "Listener", + "LookupAddr", + "LookupCNAME", + "LookupHost", + "LookupIP", + "LookupMX", + "LookupNS", + "LookupPort", + "LookupSRV", + "LookupTXT", + "MX", + "NS", + "OpError", + "PacketConn", + "ParseCIDR", + "ParseError", + "ParseIP", + "ParseMAC", + "Pipe", + "ResolveIPAddr", + "ResolveTCPAddr", + "ResolveUDPAddr", + "ResolveUnixAddr", + "Resolver", + "SRV", + "SplitHostPort", + "TCPAddr", + "TCPConn", + "TCPListener", + "UDPAddr", + "UDPConn", + "UnixAddr", + "UnixConn", + "UnixListener", + "UnknownNetworkError", + }, + "net/http": []string{ + "CanonicalHeaderKey", + "Client", + "CloseNotifier", + "ConnState", + "Cookie", + "CookieJar", + "DefaultClient", + "DefaultMaxHeaderBytes", + "DefaultMaxIdleConnsPerHost", + "DefaultServeMux", + "DefaultTransport", + "DetectContentType", + "Dir", + "ErrAbortHandler", + "ErrBodyNotAllowed", + "ErrBodyReadAfterClose", + "ErrContentLength", + "ErrHandlerTimeout", + "ErrHeaderTooLong", + "ErrHijacked", + "ErrLineTooLong", + "ErrMissingBoundary", + "ErrMissingContentLength", + "ErrMissingFile", + "ErrNoCookie", + "ErrNoLocation", + "ErrNotMultipart", + "ErrNotSupported", + "ErrServerClosed", + "ErrShortBody", + "ErrSkipAltProtocol", + "ErrUnexpectedTrailer", + "ErrUseLastResponse", + "ErrWriteAfterFlush", + "Error", + "File", + "FileServer", + "FileSystem", + "Flusher", + "Get", + "Handle", + "HandleFunc", + "Handler", + "HandlerFunc", + "Head", + "Header", + "Hijacker", + "ListenAndServe", + "ListenAndServeTLS", + "LocalAddrContextKey", + "MaxBytesReader", + "MethodConnect", + "MethodDelete", + "MethodGet", + "MethodHead", + "MethodOptions", + "MethodPatch", + "MethodPost", + "MethodPut", + "MethodTrace", + "NewFileTransport", + "NewRequest", + "NewRequestWithContext", + "NewServeMux", + "NoBody", + "NotFound", + "NotFoundHandler", + "ParseHTTPVersion", + "ParseTime", + "Post", + "PostForm", + "ProtocolError", + "ProxyFromEnvironment", + "ProxyURL", + "PushOptions", + "Pusher", + "ReadRequest", + "ReadResponse", + "Redirect", + "RedirectHandler", + "Request", + "Response", + "ResponseWriter", + "RoundTripper", + "SameSite", + "SameSiteDefaultMode", + "SameSiteLaxMode", + "SameSiteNoneMode", + "SameSiteStrictMode", + "Serve", + "ServeContent", + "ServeFile", + "ServeMux", + "ServeTLS", + "Server", + "ServerContextKey", + "SetCookie", + "StateActive", + "StateClosed", + "StateHijacked", + "StateIdle", + "StateNew", + "StatusAccepted", + "StatusAlreadyReported", + "StatusBadGateway", + "StatusBadRequest", + "StatusConflict", + "StatusContinue", + "StatusCreated", + "StatusEarlyHints", + "StatusExpectationFailed", + "StatusFailedDependency", + "StatusForbidden", + "StatusFound", + "StatusGatewayTimeout", + "StatusGone", + "StatusHTTPVersionNotSupported", + "StatusIMUsed", + "StatusInsufficientStorage", + "StatusInternalServerError", + "StatusLengthRequired", + "StatusLocked", + "StatusLoopDetected", + "StatusMethodNotAllowed", + "StatusMisdirectedRequest", + "StatusMovedPermanently", + "StatusMultiStatus", + "StatusMultipleChoices", + "StatusNetworkAuthenticationRequired", + "StatusNoContent", + "StatusNonAuthoritativeInfo", + "StatusNotAcceptable", + "StatusNotExtended", + "StatusNotFound", + "StatusNotImplemented", + "StatusNotModified", + "StatusOK", + "StatusPartialContent", + "StatusPaymentRequired", + "StatusPermanentRedirect", + "StatusPreconditionFailed", + "StatusPreconditionRequired", + "StatusProcessing", + "StatusProxyAuthRequired", + "StatusRequestEntityTooLarge", + "StatusRequestHeaderFieldsTooLarge", + "StatusRequestTimeout", + "StatusRequestURITooLong", + "StatusRequestedRangeNotSatisfiable", + "StatusResetContent", + "StatusSeeOther", + "StatusServiceUnavailable", + "StatusSwitchingProtocols", + "StatusTeapot", + "StatusTemporaryRedirect", + "StatusText", + "StatusTooEarly", + "StatusTooManyRequests", + "StatusUnauthorized", + "StatusUnavailableForLegalReasons", + "StatusUnprocessableEntity", + "StatusUnsupportedMediaType", + "StatusUpgradeRequired", + "StatusUseProxy", + "StatusVariantAlsoNegotiates", + "StripPrefix", + "TimeFormat", + "TimeoutHandler", + "TrailerPrefix", + "Transport", + }, + "net/http/cgi": []string{ + "Handler", + "Request", + "RequestFromMap", + "Serve", + }, + "net/http/cookiejar": []string{ + "Jar", + "New", + "Options", + "PublicSuffixList", + }, + "net/http/fcgi": []string{ + "ErrConnClosed", + "ErrRequestAborted", + "ProcessEnv", + "Serve", + }, + "net/http/httptest": []string{ + "DefaultRemoteAddr", + "NewRecorder", + "NewRequest", + "NewServer", + "NewTLSServer", + "NewUnstartedServer", + "ResponseRecorder", + "Server", + }, + "net/http/httptrace": []string{ + "ClientTrace", + "ContextClientTrace", + "DNSDoneInfo", + "DNSStartInfo", + "GotConnInfo", + "WithClientTrace", + "WroteRequestInfo", + }, + "net/http/httputil": []string{ + "BufferPool", + "ClientConn", + "DumpRequest", + "DumpRequestOut", + "DumpResponse", + "ErrClosed", + "ErrLineTooLong", + "ErrPersistEOF", + "ErrPipeline", + "NewChunkedReader", + "NewChunkedWriter", + "NewClientConn", + "NewProxyClientConn", + "NewServerConn", + "NewSingleHostReverseProxy", + "ReverseProxy", + "ServerConn", + }, + "net/http/pprof": []string{ + "Cmdline", + "Handler", + "Index", + "Profile", + "Symbol", + "Trace", + }, + "net/mail": []string{ + "Address", + "AddressParser", + "ErrHeaderNotPresent", + "Header", + "Message", + "ParseAddress", + "ParseAddressList", + "ParseDate", + "ReadMessage", + }, + "net/rpc": []string{ + "Accept", + "Call", + "Client", + "ClientCodec", + "DefaultDebugPath", + "DefaultRPCPath", + "DefaultServer", + "Dial", + "DialHTTP", + "DialHTTPPath", + "ErrShutdown", + "HandleHTTP", + "NewClient", + "NewClientWithCodec", + "NewServer", + "Register", + "RegisterName", + "Request", + "Response", + "ServeCodec", + "ServeConn", + "ServeRequest", + "Server", + "ServerCodec", + "ServerError", + }, + "net/rpc/jsonrpc": []string{ + "Dial", + "NewClient", + "NewClientCodec", + "NewServerCodec", + "ServeConn", + }, + "net/smtp": []string{ + "Auth", + "CRAMMD5Auth", + "Client", + "Dial", + "NewClient", + "PlainAuth", + "SendMail", + "ServerInfo", + }, + "net/textproto": []string{ + "CanonicalMIMEHeaderKey", + "Conn", + "Dial", + "Error", + "MIMEHeader", + "NewConn", + "NewReader", + "NewWriter", + "Pipeline", + "ProtocolError", + "Reader", + "TrimBytes", + "TrimString", + "Writer", + }, + "net/url": []string{ + "Error", + "EscapeError", + "InvalidHostError", + "Parse", + "ParseQuery", + "ParseRequestURI", + "PathEscape", + "PathUnescape", + "QueryEscape", + "QueryUnescape", + "URL", + "User", + "UserPassword", + "Userinfo", + "Values", + }, + "os": []string{ + "Args", + "Chdir", + "Chmod", + "Chown", + "Chtimes", + "Clearenv", + "Create", + "DevNull", + "Environ", + "ErrClosed", + "ErrDeadlineExceeded", + "ErrExist", + "ErrInvalid", + "ErrNoDeadline", + "ErrNotExist", + "ErrPermission", + "Executable", + "Exit", + "Expand", + "ExpandEnv", + "File", + "FileInfo", + "FileMode", + "FindProcess", + "Getegid", + "Getenv", + "Geteuid", + "Getgid", + "Getgroups", + "Getpagesize", + "Getpid", + "Getppid", + "Getuid", + "Getwd", + "Hostname", + "Interrupt", + "IsExist", + "IsNotExist", + "IsPathSeparator", + "IsPermission", + "IsTimeout", + "Kill", + "Lchown", + "Link", + "LinkError", + "LookupEnv", + "Lstat", + "Mkdir", + "MkdirAll", + "ModeAppend", + "ModeCharDevice", + "ModeDevice", + "ModeDir", + "ModeExclusive", + "ModeIrregular", + "ModeNamedPipe", + "ModePerm", + "ModeSetgid", + "ModeSetuid", + "ModeSocket", + "ModeSticky", + "ModeSymlink", + "ModeTemporary", + "ModeType", + "NewFile", + "NewSyscallError", + "O_APPEND", + "O_CREATE", + "O_EXCL", + "O_RDONLY", + "O_RDWR", + "O_SYNC", + "O_TRUNC", + "O_WRONLY", + "Open", + "OpenFile", + "PathError", + "PathListSeparator", + "PathSeparator", + "Pipe", + "ProcAttr", + "Process", + "ProcessState", + "Readlink", + "Remove", + "RemoveAll", + "Rename", + "SEEK_CUR", + "SEEK_END", + "SEEK_SET", + "SameFile", + "Setenv", + "Signal", + "StartProcess", + "Stat", + "Stderr", + "Stdin", + "Stdout", + "Symlink", + "SyscallError", + "TempDir", + "Truncate", + "Unsetenv", + "UserCacheDir", + "UserConfigDir", + "UserHomeDir", + }, + "os/exec": []string{ + "Cmd", + "Command", + "CommandContext", + "ErrNotFound", + "Error", + "ExitError", + "LookPath", + }, + "os/signal": []string{ + "Ignore", + "Ignored", + "Notify", + "Reset", + "Stop", + }, + "os/user": []string{ + "Current", + "Group", + "Lookup", + "LookupGroup", + "LookupGroupId", + "LookupId", + "UnknownGroupError", + "UnknownGroupIdError", + "UnknownUserError", + "UnknownUserIdError", + "User", + }, + "path": []string{ + "Base", + "Clean", + "Dir", + "ErrBadPattern", + "Ext", + "IsAbs", + "Join", + "Match", + "Split", + }, + "path/filepath": []string{ + "Abs", + "Base", + "Clean", + "Dir", + "ErrBadPattern", + "EvalSymlinks", + "Ext", + "FromSlash", + "Glob", + "HasPrefix", + "IsAbs", + "Join", + "ListSeparator", + "Match", + "Rel", + "Separator", + "SkipDir", + "Split", + "SplitList", + "ToSlash", + "VolumeName", + "Walk", + "WalkFunc", + }, + "plugin": []string{ + "Open", + "Plugin", + "Symbol", + }, + "reflect": []string{ + "Append", + "AppendSlice", + "Array", + "ArrayOf", + "Bool", + "BothDir", + "Chan", + "ChanDir", + "ChanOf", + "Complex128", + "Complex64", + "Copy", + "DeepEqual", + "Float32", + "Float64", + "Func", + "FuncOf", + "Indirect", + "Int", + "Int16", + "Int32", + "Int64", + "Int8", + "Interface", + "Invalid", + "Kind", + "MakeChan", + "MakeFunc", + "MakeMap", + "MakeMapWithSize", + "MakeSlice", + "Map", + "MapIter", + "MapOf", + "Method", + "New", + "NewAt", + "Ptr", + "PtrTo", + "RecvDir", + "Select", + "SelectCase", + "SelectDefault", + "SelectDir", + "SelectRecv", + "SelectSend", + "SendDir", + "Slice", + "SliceHeader", + "SliceOf", + "String", + "StringHeader", + "Struct", + "StructField", + "StructOf", + "StructTag", + "Swapper", + "Type", + "TypeOf", + "Uint", + "Uint16", + "Uint32", + "Uint64", + "Uint8", + "Uintptr", + "UnsafePointer", + "Value", + "ValueError", + "ValueOf", + "Zero", + }, + "regexp": []string{ + "Compile", + "CompilePOSIX", + "Match", + "MatchReader", + "MatchString", + "MustCompile", + "MustCompilePOSIX", + "QuoteMeta", + "Regexp", + }, + "regexp/syntax": []string{ + "ClassNL", + "Compile", + "DotNL", + "EmptyBeginLine", + "EmptyBeginText", + "EmptyEndLine", + "EmptyEndText", + "EmptyNoWordBoundary", + "EmptyOp", + "EmptyOpContext", + "EmptyWordBoundary", + "ErrInternalError", + "ErrInvalidCharClass", + "ErrInvalidCharRange", + "ErrInvalidEscape", + "ErrInvalidNamedCapture", + "ErrInvalidPerlOp", + "ErrInvalidRepeatOp", + "ErrInvalidRepeatSize", + "ErrInvalidUTF8", + "ErrMissingBracket", + "ErrMissingParen", + "ErrMissingRepeatArgument", + "ErrTrailingBackslash", + "ErrUnexpectedParen", + "Error", + "ErrorCode", + "Flags", + "FoldCase", + "Inst", + "InstAlt", + "InstAltMatch", + "InstCapture", + "InstEmptyWidth", + "InstFail", + "InstMatch", + "InstNop", + "InstOp", + "InstRune", + "InstRune1", + "InstRuneAny", + "InstRuneAnyNotNL", + "IsWordChar", + "Literal", + "MatchNL", + "NonGreedy", + "OneLine", + "Op", + "OpAlternate", + "OpAnyChar", + "OpAnyCharNotNL", + "OpBeginLine", + "OpBeginText", + "OpCapture", + "OpCharClass", + "OpConcat", + "OpEmptyMatch", + "OpEndLine", + "OpEndText", + "OpLiteral", + "OpNoMatch", + "OpNoWordBoundary", + "OpPlus", + "OpQuest", + "OpRepeat", + "OpStar", + "OpWordBoundary", + "POSIX", + "Parse", + "Perl", + "PerlX", + "Prog", + "Regexp", + "Simple", + "UnicodeGroups", + "WasDollar", + }, + "runtime": []string{ + "BlockProfile", + "BlockProfileRecord", + "Breakpoint", + "CPUProfile", + "Caller", + "Callers", + "CallersFrames", + "Compiler", + "Error", + "Frame", + "Frames", + "Func", + "FuncForPC", + "GC", + "GOARCH", + "GOMAXPROCS", + "GOOS", + "GOROOT", + "Goexit", + "GoroutineProfile", + "Gosched", + "KeepAlive", + "LockOSThread", + "MemProfile", + "MemProfileRate", + "MemProfileRecord", + "MemStats", + "MutexProfile", + "NumCPU", + "NumCgoCall", + "NumGoroutine", + "ReadMemStats", + "ReadTrace", + "SetBlockProfileRate", + "SetCPUProfileRate", + "SetCgoTraceback", + "SetFinalizer", + "SetMutexProfileFraction", + "Stack", + "StackRecord", + "StartTrace", + "StopTrace", + "ThreadCreateProfile", + "TypeAssertionError", + "UnlockOSThread", + "Version", + }, + "runtime/debug": []string{ + "BuildInfo", + "FreeOSMemory", + "GCStats", + "Module", + "PrintStack", + "ReadBuildInfo", + "ReadGCStats", + "SetGCPercent", + "SetMaxStack", + "SetMaxThreads", + "SetPanicOnFault", + "SetTraceback", + "Stack", + "WriteHeapDump", + }, + "runtime/pprof": []string{ + "Do", + "ForLabels", + "Label", + "LabelSet", + "Labels", + "Lookup", + "NewProfile", + "Profile", + "Profiles", + "SetGoroutineLabels", + "StartCPUProfile", + "StopCPUProfile", + "WithLabels", + "WriteHeapProfile", + }, + "runtime/trace": []string{ + "IsEnabled", + "Log", + "Logf", + "NewTask", + "Region", + "Start", + "StartRegion", + "Stop", + "Task", + "WithRegion", + }, + "sort": []string{ + "Float64Slice", + "Float64s", + "Float64sAreSorted", + "IntSlice", + "Interface", + "Ints", + "IntsAreSorted", + "IsSorted", + "Reverse", + "Search", + "SearchFloat64s", + "SearchInts", + "SearchStrings", + "Slice", + "SliceIsSorted", + "SliceStable", + "Sort", + "Stable", + "StringSlice", + "Strings", + "StringsAreSorted", + }, + "strconv": []string{ + "AppendBool", + "AppendFloat", + "AppendInt", + "AppendQuote", + "AppendQuoteRune", + "AppendQuoteRuneToASCII", + "AppendQuoteRuneToGraphic", + "AppendQuoteToASCII", + "AppendQuoteToGraphic", + "AppendUint", + "Atoi", + "CanBackquote", + "ErrRange", + "ErrSyntax", + "FormatBool", + "FormatComplex", + "FormatFloat", + "FormatInt", + "FormatUint", + "IntSize", + "IsGraphic", + "IsPrint", + "Itoa", + "NumError", + "ParseBool", + "ParseComplex", + "ParseFloat", + "ParseInt", + "ParseUint", + "Quote", + "QuoteRune", + "QuoteRuneToASCII", + "QuoteRuneToGraphic", + "QuoteToASCII", + "QuoteToGraphic", + "Unquote", + "UnquoteChar", + }, + "strings": []string{ + "Builder", + "Compare", + "Contains", + "ContainsAny", + "ContainsRune", + "Count", + "EqualFold", + "Fields", + "FieldsFunc", + "HasPrefix", + "HasSuffix", + "Index", + "IndexAny", + "IndexByte", + "IndexFunc", + "IndexRune", + "Join", + "LastIndex", + "LastIndexAny", + "LastIndexByte", + "LastIndexFunc", + "Map", + "NewReader", + "NewReplacer", + "Reader", + "Repeat", + "Replace", + "ReplaceAll", + "Replacer", + "Split", + "SplitAfter", + "SplitAfterN", + "SplitN", + "Title", + "ToLower", + "ToLowerSpecial", + "ToTitle", + "ToTitleSpecial", + "ToUpper", + "ToUpperSpecial", + "ToValidUTF8", + "Trim", + "TrimFunc", + "TrimLeft", + "TrimLeftFunc", + "TrimPrefix", + "TrimRight", + "TrimRightFunc", + "TrimSpace", + "TrimSuffix", + }, + "sync": []string{ + "Cond", + "Locker", + "Map", + "Mutex", + "NewCond", + "Once", + "Pool", + "RWMutex", + "WaitGroup", + }, + "sync/atomic": []string{ + "AddInt32", + "AddInt64", + "AddUint32", + "AddUint64", + "AddUintptr", + "CompareAndSwapInt32", + "CompareAndSwapInt64", + "CompareAndSwapPointer", + "CompareAndSwapUint32", + "CompareAndSwapUint64", + "CompareAndSwapUintptr", + "LoadInt32", + "LoadInt64", + "LoadPointer", + "LoadUint32", + "LoadUint64", + "LoadUintptr", + "StoreInt32", + "StoreInt64", + "StorePointer", + "StoreUint32", + "StoreUint64", + "StoreUintptr", + "SwapInt32", + "SwapInt64", + "SwapPointer", + "SwapUint32", + "SwapUint64", + "SwapUintptr", + "Value", + }, + "syscall": []string{ + "AF_ALG", + "AF_APPLETALK", + "AF_ARP", + "AF_ASH", + "AF_ATM", + "AF_ATMPVC", + "AF_ATMSVC", + "AF_AX25", + "AF_BLUETOOTH", + "AF_BRIDGE", + "AF_CAIF", + "AF_CAN", + "AF_CCITT", + "AF_CHAOS", + "AF_CNT", + "AF_COIP", + "AF_DATAKIT", + "AF_DECnet", + "AF_DLI", + "AF_E164", + "AF_ECMA", + "AF_ECONET", + "AF_ENCAP", + "AF_FILE", + "AF_HYLINK", + "AF_IEEE80211", + "AF_IEEE802154", + "AF_IMPLINK", + "AF_INET", + "AF_INET6", + "AF_INET6_SDP", + "AF_INET_SDP", + "AF_IPX", + "AF_IRDA", + "AF_ISDN", + "AF_ISO", + "AF_IUCV", + "AF_KEY", + "AF_LAT", + "AF_LINK", + "AF_LLC", + "AF_LOCAL", + "AF_MAX", + "AF_MPLS", + "AF_NATM", + "AF_NDRV", + "AF_NETBEUI", + "AF_NETBIOS", + "AF_NETGRAPH", + "AF_NETLINK", + "AF_NETROM", + "AF_NS", + "AF_OROUTE", + "AF_OSI", + "AF_PACKET", + "AF_PHONET", + "AF_PPP", + "AF_PPPOX", + "AF_PUP", + "AF_RDS", + "AF_RESERVED_36", + "AF_ROSE", + "AF_ROUTE", + "AF_RXRPC", + "AF_SCLUSTER", + "AF_SECURITY", + "AF_SIP", + "AF_SLOW", + "AF_SNA", + "AF_SYSTEM", + "AF_TIPC", + "AF_UNIX", + "AF_UNSPEC", + "AF_VENDOR00", + "AF_VENDOR01", + "AF_VENDOR02", + "AF_VENDOR03", + "AF_VENDOR04", + "AF_VENDOR05", + "AF_VENDOR06", + "AF_VENDOR07", + "AF_VENDOR08", + "AF_VENDOR09", + "AF_VENDOR10", + "AF_VENDOR11", + "AF_VENDOR12", + "AF_VENDOR13", + "AF_VENDOR14", + "AF_VENDOR15", + "AF_VENDOR16", + "AF_VENDOR17", + "AF_VENDOR18", + "AF_VENDOR19", + "AF_VENDOR20", + "AF_VENDOR21", + "AF_VENDOR22", + "AF_VENDOR23", + "AF_VENDOR24", + "AF_VENDOR25", + "AF_VENDOR26", + "AF_VENDOR27", + "AF_VENDOR28", + "AF_VENDOR29", + "AF_VENDOR30", + "AF_VENDOR31", + "AF_VENDOR32", + "AF_VENDOR33", + "AF_VENDOR34", + "AF_VENDOR35", + "AF_VENDOR36", + "AF_VENDOR37", + "AF_VENDOR38", + "AF_VENDOR39", + "AF_VENDOR40", + "AF_VENDOR41", + "AF_VENDOR42", + "AF_VENDOR43", + "AF_VENDOR44", + "AF_VENDOR45", + "AF_VENDOR46", + "AF_VENDOR47", + "AF_WANPIPE", + "AF_X25", + "AI_CANONNAME", + "AI_NUMERICHOST", + "AI_PASSIVE", + "APPLICATION_ERROR", + "ARPHRD_ADAPT", + "ARPHRD_APPLETLK", + "ARPHRD_ARCNET", + "ARPHRD_ASH", + "ARPHRD_ATM", + "ARPHRD_AX25", + "ARPHRD_BIF", + "ARPHRD_CHAOS", + "ARPHRD_CISCO", + "ARPHRD_CSLIP", + "ARPHRD_CSLIP6", + "ARPHRD_DDCMP", + "ARPHRD_DLCI", + "ARPHRD_ECONET", + "ARPHRD_EETHER", + "ARPHRD_ETHER", + "ARPHRD_EUI64", + "ARPHRD_FCAL", + "ARPHRD_FCFABRIC", + "ARPHRD_FCPL", + "ARPHRD_FCPP", + "ARPHRD_FDDI", + "ARPHRD_FRAD", + "ARPHRD_FRELAY", + "ARPHRD_HDLC", + "ARPHRD_HIPPI", + "ARPHRD_HWX25", + "ARPHRD_IEEE1394", + "ARPHRD_IEEE802", + "ARPHRD_IEEE80211", + "ARPHRD_IEEE80211_PRISM", + "ARPHRD_IEEE80211_RADIOTAP", + "ARPHRD_IEEE802154", + "ARPHRD_IEEE802154_PHY", + "ARPHRD_IEEE802_TR", + "ARPHRD_INFINIBAND", + "ARPHRD_IPDDP", + "ARPHRD_IPGRE", + "ARPHRD_IRDA", + "ARPHRD_LAPB", + "ARPHRD_LOCALTLK", + "ARPHRD_LOOPBACK", + "ARPHRD_METRICOM", + "ARPHRD_NETROM", + "ARPHRD_NONE", + "ARPHRD_PIMREG", + "ARPHRD_PPP", + "ARPHRD_PRONET", + "ARPHRD_RAWHDLC", + "ARPHRD_ROSE", + "ARPHRD_RSRVD", + "ARPHRD_SIT", + "ARPHRD_SKIP", + "ARPHRD_SLIP", + "ARPHRD_SLIP6", + "ARPHRD_STRIP", + "ARPHRD_TUNNEL", + "ARPHRD_TUNNEL6", + "ARPHRD_VOID", + "ARPHRD_X25", + "AUTHTYPE_CLIENT", + "AUTHTYPE_SERVER", + "Accept", + "Accept4", + "AcceptEx", + "Access", + "Acct", + "AddrinfoW", + "Adjtime", + "Adjtimex", + "AttachLsf", + "B0", + "B1000000", + "B110", + "B115200", + "B1152000", + "B1200", + "B134", + "B14400", + "B150", + "B1500000", + "B1800", + "B19200", + "B200", + "B2000000", + "B230400", + "B2400", + "B2500000", + "B28800", + "B300", + "B3000000", + "B3500000", + "B38400", + "B4000000", + "B460800", + "B4800", + "B50", + "B500000", + "B57600", + "B576000", + "B600", + "B7200", + "B75", + "B76800", + "B921600", + "B9600", + "BASE_PROTOCOL", + "BIOCFEEDBACK", + "BIOCFLUSH", + "BIOCGBLEN", + "BIOCGDIRECTION", + "BIOCGDIRFILT", + "BIOCGDLT", + "BIOCGDLTLIST", + "BIOCGETBUFMODE", + "BIOCGETIF", + "BIOCGETZMAX", + "BIOCGFEEDBACK", + "BIOCGFILDROP", + "BIOCGHDRCMPLT", + "BIOCGRSIG", + "BIOCGRTIMEOUT", + "BIOCGSEESENT", + "BIOCGSTATS", + "BIOCGSTATSOLD", + "BIOCGTSTAMP", + "BIOCIMMEDIATE", + "BIOCLOCK", + "BIOCPROMISC", + "BIOCROTZBUF", + "BIOCSBLEN", + "BIOCSDIRECTION", + "BIOCSDIRFILT", + "BIOCSDLT", + "BIOCSETBUFMODE", + "BIOCSETF", + "BIOCSETFNR", + "BIOCSETIF", + "BIOCSETWF", + "BIOCSETZBUF", + "BIOCSFEEDBACK", + "BIOCSFILDROP", + "BIOCSHDRCMPLT", + "BIOCSRSIG", + "BIOCSRTIMEOUT", + "BIOCSSEESENT", + "BIOCSTCPF", + "BIOCSTSTAMP", + "BIOCSUDPF", + "BIOCVERSION", + "BPF_A", + "BPF_ABS", + "BPF_ADD", + "BPF_ALIGNMENT", + "BPF_ALIGNMENT32", + "BPF_ALU", + "BPF_AND", + "BPF_B", + "BPF_BUFMODE_BUFFER", + "BPF_BUFMODE_ZBUF", + "BPF_DFLTBUFSIZE", + "BPF_DIRECTION_IN", + "BPF_DIRECTION_OUT", + "BPF_DIV", + "BPF_H", + "BPF_IMM", + "BPF_IND", + "BPF_JA", + "BPF_JEQ", + "BPF_JGE", + "BPF_JGT", + "BPF_JMP", + "BPF_JSET", + "BPF_K", + "BPF_LD", + "BPF_LDX", + "BPF_LEN", + "BPF_LSH", + "BPF_MAJOR_VERSION", + "BPF_MAXBUFSIZE", + "BPF_MAXINSNS", + "BPF_MEM", + "BPF_MEMWORDS", + "BPF_MINBUFSIZE", + "BPF_MINOR_VERSION", + "BPF_MISC", + "BPF_MSH", + "BPF_MUL", + "BPF_NEG", + "BPF_OR", + "BPF_RELEASE", + "BPF_RET", + "BPF_RSH", + "BPF_ST", + "BPF_STX", + "BPF_SUB", + "BPF_TAX", + "BPF_TXA", + "BPF_T_BINTIME", + "BPF_T_BINTIME_FAST", + "BPF_T_BINTIME_MONOTONIC", + "BPF_T_BINTIME_MONOTONIC_FAST", + "BPF_T_FAST", + "BPF_T_FLAG_MASK", + "BPF_T_FORMAT_MASK", + "BPF_T_MICROTIME", + "BPF_T_MICROTIME_FAST", + "BPF_T_MICROTIME_MONOTONIC", + "BPF_T_MICROTIME_MONOTONIC_FAST", + "BPF_T_MONOTONIC", + "BPF_T_MONOTONIC_FAST", + "BPF_T_NANOTIME", + "BPF_T_NANOTIME_FAST", + "BPF_T_NANOTIME_MONOTONIC", + "BPF_T_NANOTIME_MONOTONIC_FAST", + "BPF_T_NONE", + "BPF_T_NORMAL", + "BPF_W", + "BPF_X", + "BRKINT", + "Bind", + "BindToDevice", + "BpfBuflen", + "BpfDatalink", + "BpfHdr", + "BpfHeadercmpl", + "BpfInsn", + "BpfInterface", + "BpfJump", + "BpfProgram", + "BpfStat", + "BpfStats", + "BpfStmt", + "BpfTimeout", + "BpfTimeval", + "BpfVersion", + "BpfZbuf", + "BpfZbufHeader", + "ByHandleFileInformation", + "BytePtrFromString", + "ByteSliceFromString", + "CCR0_FLUSH", + "CERT_CHAIN_POLICY_AUTHENTICODE", + "CERT_CHAIN_POLICY_AUTHENTICODE_TS", + "CERT_CHAIN_POLICY_BASE", + "CERT_CHAIN_POLICY_BASIC_CONSTRAINTS", + "CERT_CHAIN_POLICY_EV", + "CERT_CHAIN_POLICY_MICROSOFT_ROOT", + "CERT_CHAIN_POLICY_NT_AUTH", + "CERT_CHAIN_POLICY_SSL", + "CERT_E_CN_NO_MATCH", + "CERT_E_EXPIRED", + "CERT_E_PURPOSE", + "CERT_E_ROLE", + "CERT_E_UNTRUSTEDROOT", + "CERT_STORE_ADD_ALWAYS", + "CERT_STORE_DEFER_CLOSE_UNTIL_LAST_FREE_FLAG", + "CERT_STORE_PROV_MEMORY", + "CERT_TRUST_HAS_EXCLUDED_NAME_CONSTRAINT", + "CERT_TRUST_HAS_NOT_DEFINED_NAME_CONSTRAINT", + "CERT_TRUST_HAS_NOT_PERMITTED_NAME_CONSTRAINT", + "CERT_TRUST_HAS_NOT_SUPPORTED_CRITICAL_EXT", + "CERT_TRUST_HAS_NOT_SUPPORTED_NAME_CONSTRAINT", + "CERT_TRUST_INVALID_BASIC_CONSTRAINTS", + "CERT_TRUST_INVALID_EXTENSION", + "CERT_TRUST_INVALID_NAME_CONSTRAINTS", + "CERT_TRUST_INVALID_POLICY_CONSTRAINTS", + "CERT_TRUST_IS_CYCLIC", + "CERT_TRUST_IS_EXPLICIT_DISTRUST", + "CERT_TRUST_IS_NOT_SIGNATURE_VALID", + "CERT_TRUST_IS_NOT_TIME_VALID", + "CERT_TRUST_IS_NOT_VALID_FOR_USAGE", + "CERT_TRUST_IS_OFFLINE_REVOCATION", + "CERT_TRUST_IS_REVOKED", + "CERT_TRUST_IS_UNTRUSTED_ROOT", + "CERT_TRUST_NO_ERROR", + "CERT_TRUST_NO_ISSUANCE_CHAIN_POLICY", + "CERT_TRUST_REVOCATION_STATUS_UNKNOWN", + "CFLUSH", + "CLOCAL", + "CLONE_CHILD_CLEARTID", + "CLONE_CHILD_SETTID", + "CLONE_CSIGNAL", + "CLONE_DETACHED", + "CLONE_FILES", + "CLONE_FS", + "CLONE_IO", + "CLONE_NEWIPC", + "CLONE_NEWNET", + "CLONE_NEWNS", + "CLONE_NEWPID", + "CLONE_NEWUSER", + "CLONE_NEWUTS", + "CLONE_PARENT", + "CLONE_PARENT_SETTID", + "CLONE_PID", + "CLONE_PTRACE", + "CLONE_SETTLS", + "CLONE_SIGHAND", + "CLONE_SYSVSEM", + "CLONE_THREAD", + "CLONE_UNTRACED", + "CLONE_VFORK", + "CLONE_VM", + "CPUID_CFLUSH", + "CREAD", + "CREATE_ALWAYS", + "CREATE_NEW", + "CREATE_NEW_PROCESS_GROUP", + "CREATE_UNICODE_ENVIRONMENT", + "CRYPT_DEFAULT_CONTAINER_OPTIONAL", + "CRYPT_DELETEKEYSET", + "CRYPT_MACHINE_KEYSET", + "CRYPT_NEWKEYSET", + "CRYPT_SILENT", + "CRYPT_VERIFYCONTEXT", + "CS5", + "CS6", + "CS7", + "CS8", + "CSIZE", + "CSTART", + "CSTATUS", + "CSTOP", + "CSTOPB", + "CSUSP", + "CTL_MAXNAME", + "CTL_NET", + "CTL_QUERY", + "CTRL_BREAK_EVENT", + "CTRL_CLOSE_EVENT", + "CTRL_C_EVENT", + "CTRL_LOGOFF_EVENT", + "CTRL_SHUTDOWN_EVENT", + "CancelIo", + "CancelIoEx", + "CertAddCertificateContextToStore", + "CertChainContext", + "CertChainElement", + "CertChainPara", + "CertChainPolicyPara", + "CertChainPolicyStatus", + "CertCloseStore", + "CertContext", + "CertCreateCertificateContext", + "CertEnhKeyUsage", + "CertEnumCertificatesInStore", + "CertFreeCertificateChain", + "CertFreeCertificateContext", + "CertGetCertificateChain", + "CertInfo", + "CertOpenStore", + "CertOpenSystemStore", + "CertRevocationCrlInfo", + "CertRevocationInfo", + "CertSimpleChain", + "CertTrustListInfo", + "CertTrustStatus", + "CertUsageMatch", + "CertVerifyCertificateChainPolicy", + "Chdir", + "CheckBpfVersion", + "Chflags", + "Chmod", + "Chown", + "Chroot", + "Clearenv", + "Close", + "CloseHandle", + "CloseOnExec", + "Closesocket", + "CmsgLen", + "CmsgSpace", + "Cmsghdr", + "CommandLineToArgv", + "ComputerName", + "Conn", + "Connect", + "ConnectEx", + "ConvertSidToStringSid", + "ConvertStringSidToSid", + "CopySid", + "Creat", + "CreateDirectory", + "CreateFile", + "CreateFileMapping", + "CreateHardLink", + "CreateIoCompletionPort", + "CreatePipe", + "CreateProcess", + "CreateProcessAsUser", + "CreateSymbolicLink", + "CreateToolhelp32Snapshot", + "Credential", + "CryptAcquireContext", + "CryptGenRandom", + "CryptReleaseContext", + "DIOCBSFLUSH", + "DIOCOSFPFLUSH", + "DLL", + "DLLError", + "DLT_A429", + "DLT_A653_ICM", + "DLT_AIRONET_HEADER", + "DLT_AOS", + "DLT_APPLE_IP_OVER_IEEE1394", + "DLT_ARCNET", + "DLT_ARCNET_LINUX", + "DLT_ATM_CLIP", + "DLT_ATM_RFC1483", + "DLT_AURORA", + "DLT_AX25", + "DLT_AX25_KISS", + "DLT_BACNET_MS_TP", + "DLT_BLUETOOTH_HCI_H4", + "DLT_BLUETOOTH_HCI_H4_WITH_PHDR", + "DLT_CAN20B", + "DLT_CAN_SOCKETCAN", + "DLT_CHAOS", + "DLT_CHDLC", + "DLT_CISCO_IOS", + "DLT_C_HDLC", + "DLT_C_HDLC_WITH_DIR", + "DLT_DBUS", + "DLT_DECT", + "DLT_DOCSIS", + "DLT_DVB_CI", + "DLT_ECONET", + "DLT_EN10MB", + "DLT_EN3MB", + "DLT_ENC", + "DLT_ERF", + "DLT_ERF_ETH", + "DLT_ERF_POS", + "DLT_FC_2", + "DLT_FC_2_WITH_FRAME_DELIMS", + "DLT_FDDI", + "DLT_FLEXRAY", + "DLT_FRELAY", + "DLT_FRELAY_WITH_DIR", + "DLT_GCOM_SERIAL", + "DLT_GCOM_T1E1", + "DLT_GPF_F", + "DLT_GPF_T", + "DLT_GPRS_LLC", + "DLT_GSMTAP_ABIS", + "DLT_GSMTAP_UM", + "DLT_HDLC", + "DLT_HHDLC", + "DLT_HIPPI", + "DLT_IBM_SN", + "DLT_IBM_SP", + "DLT_IEEE802", + "DLT_IEEE802_11", + "DLT_IEEE802_11_RADIO", + "DLT_IEEE802_11_RADIO_AVS", + "DLT_IEEE802_15_4", + "DLT_IEEE802_15_4_LINUX", + "DLT_IEEE802_15_4_NOFCS", + "DLT_IEEE802_15_4_NONASK_PHY", + "DLT_IEEE802_16_MAC_CPS", + "DLT_IEEE802_16_MAC_CPS_RADIO", + "DLT_IPFILTER", + "DLT_IPMB", + "DLT_IPMB_LINUX", + "DLT_IPNET", + "DLT_IPOIB", + "DLT_IPV4", + "DLT_IPV6", + "DLT_IP_OVER_FC", + "DLT_JUNIPER_ATM1", + "DLT_JUNIPER_ATM2", + "DLT_JUNIPER_ATM_CEMIC", + "DLT_JUNIPER_CHDLC", + "DLT_JUNIPER_ES", + "DLT_JUNIPER_ETHER", + "DLT_JUNIPER_FIBRECHANNEL", + "DLT_JUNIPER_FRELAY", + "DLT_JUNIPER_GGSN", + "DLT_JUNIPER_ISM", + "DLT_JUNIPER_MFR", + "DLT_JUNIPER_MLFR", + "DLT_JUNIPER_MLPPP", + "DLT_JUNIPER_MONITOR", + "DLT_JUNIPER_PIC_PEER", + "DLT_JUNIPER_PPP", + "DLT_JUNIPER_PPPOE", + "DLT_JUNIPER_PPPOE_ATM", + "DLT_JUNIPER_SERVICES", + "DLT_JUNIPER_SRX_E2E", + "DLT_JUNIPER_ST", + "DLT_JUNIPER_VP", + "DLT_JUNIPER_VS", + "DLT_LAPB_WITH_DIR", + "DLT_LAPD", + "DLT_LIN", + "DLT_LINUX_EVDEV", + "DLT_LINUX_IRDA", + "DLT_LINUX_LAPD", + "DLT_LINUX_PPP_WITHDIRECTION", + "DLT_LINUX_SLL", + "DLT_LOOP", + "DLT_LTALK", + "DLT_MATCHING_MAX", + "DLT_MATCHING_MIN", + "DLT_MFR", + "DLT_MOST", + "DLT_MPEG_2_TS", + "DLT_MPLS", + "DLT_MTP2", + "DLT_MTP2_WITH_PHDR", + "DLT_MTP3", + "DLT_MUX27010", + "DLT_NETANALYZER", + "DLT_NETANALYZER_TRANSPARENT", + "DLT_NFC_LLCP", + "DLT_NFLOG", + "DLT_NG40", + "DLT_NULL", + "DLT_PCI_EXP", + "DLT_PFLOG", + "DLT_PFSYNC", + "DLT_PPI", + "DLT_PPP", + "DLT_PPP_BSDOS", + "DLT_PPP_ETHER", + "DLT_PPP_PPPD", + "DLT_PPP_SERIAL", + "DLT_PPP_WITH_DIR", + "DLT_PPP_WITH_DIRECTION", + "DLT_PRISM_HEADER", + "DLT_PRONET", + "DLT_RAIF1", + "DLT_RAW", + "DLT_RAWAF_MASK", + "DLT_RIO", + "DLT_SCCP", + "DLT_SITA", + "DLT_SLIP", + "DLT_SLIP_BSDOS", + "DLT_STANAG_5066_D_PDU", + "DLT_SUNATM", + "DLT_SYMANTEC_FIREWALL", + "DLT_TZSP", + "DLT_USB", + "DLT_USB_LINUX", + "DLT_USB_LINUX_MMAPPED", + "DLT_USER0", + "DLT_USER1", + "DLT_USER10", + "DLT_USER11", + "DLT_USER12", + "DLT_USER13", + "DLT_USER14", + "DLT_USER15", + "DLT_USER2", + "DLT_USER3", + "DLT_USER4", + "DLT_USER5", + "DLT_USER6", + "DLT_USER7", + "DLT_USER8", + "DLT_USER9", + "DLT_WIHART", + "DLT_X2E_SERIAL", + "DLT_X2E_XORAYA", + "DNSMXData", + "DNSPTRData", + "DNSRecord", + "DNSSRVData", + "DNSTXTData", + "DNS_INFO_NO_RECORDS", + "DNS_TYPE_A", + "DNS_TYPE_A6", + "DNS_TYPE_AAAA", + "DNS_TYPE_ADDRS", + "DNS_TYPE_AFSDB", + "DNS_TYPE_ALL", + "DNS_TYPE_ANY", + "DNS_TYPE_ATMA", + "DNS_TYPE_AXFR", + "DNS_TYPE_CERT", + "DNS_TYPE_CNAME", + "DNS_TYPE_DHCID", + "DNS_TYPE_DNAME", + "DNS_TYPE_DNSKEY", + "DNS_TYPE_DS", + "DNS_TYPE_EID", + "DNS_TYPE_GID", + "DNS_TYPE_GPOS", + "DNS_TYPE_HINFO", + "DNS_TYPE_ISDN", + "DNS_TYPE_IXFR", + "DNS_TYPE_KEY", + "DNS_TYPE_KX", + "DNS_TYPE_LOC", + "DNS_TYPE_MAILA", + "DNS_TYPE_MAILB", + "DNS_TYPE_MB", + "DNS_TYPE_MD", + "DNS_TYPE_MF", + "DNS_TYPE_MG", + "DNS_TYPE_MINFO", + "DNS_TYPE_MR", + "DNS_TYPE_MX", + "DNS_TYPE_NAPTR", + "DNS_TYPE_NBSTAT", + "DNS_TYPE_NIMLOC", + "DNS_TYPE_NS", + "DNS_TYPE_NSAP", + "DNS_TYPE_NSAPPTR", + "DNS_TYPE_NSEC", + "DNS_TYPE_NULL", + "DNS_TYPE_NXT", + "DNS_TYPE_OPT", + "DNS_TYPE_PTR", + "DNS_TYPE_PX", + "DNS_TYPE_RP", + "DNS_TYPE_RRSIG", + "DNS_TYPE_RT", + "DNS_TYPE_SIG", + "DNS_TYPE_SINK", + "DNS_TYPE_SOA", + "DNS_TYPE_SRV", + "DNS_TYPE_TEXT", + "DNS_TYPE_TKEY", + "DNS_TYPE_TSIG", + "DNS_TYPE_UID", + "DNS_TYPE_UINFO", + "DNS_TYPE_UNSPEC", + "DNS_TYPE_WINS", + "DNS_TYPE_WINSR", + "DNS_TYPE_WKS", + "DNS_TYPE_X25", + "DT_BLK", + "DT_CHR", + "DT_DIR", + "DT_FIFO", + "DT_LNK", + "DT_REG", + "DT_SOCK", + "DT_UNKNOWN", + "DT_WHT", + "DUPLICATE_CLOSE_SOURCE", + "DUPLICATE_SAME_ACCESS", + "DeleteFile", + "DetachLsf", + "DeviceIoControl", + "Dirent", + "DnsNameCompare", + "DnsQuery", + "DnsRecordListFree", + "DnsSectionAdditional", + "DnsSectionAnswer", + "DnsSectionAuthority", + "DnsSectionQuestion", + "Dup", + "Dup2", + "Dup3", + "DuplicateHandle", + "E2BIG", + "EACCES", + "EADDRINUSE", + "EADDRNOTAVAIL", + "EADV", + "EAFNOSUPPORT", + "EAGAIN", + "EALREADY", + "EAUTH", + "EBADARCH", + "EBADE", + "EBADEXEC", + "EBADF", + "EBADFD", + "EBADMACHO", + "EBADMSG", + "EBADR", + "EBADRPC", + "EBADRQC", + "EBADSLT", + "EBFONT", + "EBUSY", + "ECANCELED", + "ECAPMODE", + "ECHILD", + "ECHO", + "ECHOCTL", + "ECHOE", + "ECHOK", + "ECHOKE", + "ECHONL", + "ECHOPRT", + "ECHRNG", + "ECOMM", + "ECONNABORTED", + "ECONNREFUSED", + "ECONNRESET", + "EDEADLK", + "EDEADLOCK", + "EDESTADDRREQ", + "EDEVERR", + "EDOM", + "EDOOFUS", + "EDOTDOT", + "EDQUOT", + "EEXIST", + "EFAULT", + "EFBIG", + "EFER_LMA", + "EFER_LME", + "EFER_NXE", + "EFER_SCE", + "EFTYPE", + "EHOSTDOWN", + "EHOSTUNREACH", + "EHWPOISON", + "EIDRM", + "EILSEQ", + "EINPROGRESS", + "EINTR", + "EINVAL", + "EIO", + "EIPSEC", + "EISCONN", + "EISDIR", + "EISNAM", + "EKEYEXPIRED", + "EKEYREJECTED", + "EKEYREVOKED", + "EL2HLT", + "EL2NSYNC", + "EL3HLT", + "EL3RST", + "ELAST", + "ELF_NGREG", + "ELF_PRARGSZ", + "ELIBACC", + "ELIBBAD", + "ELIBEXEC", + "ELIBMAX", + "ELIBSCN", + "ELNRNG", + "ELOOP", + "EMEDIUMTYPE", + "EMFILE", + "EMLINK", + "EMSGSIZE", + "EMT_TAGOVF", + "EMULTIHOP", + "EMUL_ENABLED", + "EMUL_LINUX", + "EMUL_LINUX32", + "EMUL_MAXID", + "EMUL_NATIVE", + "ENAMETOOLONG", + "ENAVAIL", + "ENDRUNDISC", + "ENEEDAUTH", + "ENETDOWN", + "ENETRESET", + "ENETUNREACH", + "ENFILE", + "ENOANO", + "ENOATTR", + "ENOBUFS", + "ENOCSI", + "ENODATA", + "ENODEV", + "ENOENT", + "ENOEXEC", + "ENOKEY", + "ENOLCK", + "ENOLINK", + "ENOMEDIUM", + "ENOMEM", + "ENOMSG", + "ENONET", + "ENOPKG", + "ENOPOLICY", + "ENOPROTOOPT", + "ENOSPC", + "ENOSR", + "ENOSTR", + "ENOSYS", + "ENOTBLK", + "ENOTCAPABLE", + "ENOTCONN", + "ENOTDIR", + "ENOTEMPTY", + "ENOTNAM", + "ENOTRECOVERABLE", + "ENOTSOCK", + "ENOTSUP", + "ENOTTY", + "ENOTUNIQ", + "ENXIO", + "EN_SW_CTL_INF", + "EN_SW_CTL_PREC", + "EN_SW_CTL_ROUND", + "EN_SW_DATACHAIN", + "EN_SW_DENORM", + "EN_SW_INVOP", + "EN_SW_OVERFLOW", + "EN_SW_PRECLOSS", + "EN_SW_UNDERFLOW", + "EN_SW_ZERODIV", + "EOPNOTSUPP", + "EOVERFLOW", + "EOWNERDEAD", + "EPERM", + "EPFNOSUPPORT", + "EPIPE", + "EPOLLERR", + "EPOLLET", + "EPOLLHUP", + "EPOLLIN", + "EPOLLMSG", + "EPOLLONESHOT", + "EPOLLOUT", + "EPOLLPRI", + "EPOLLRDBAND", + "EPOLLRDHUP", + "EPOLLRDNORM", + "EPOLLWRBAND", + "EPOLLWRNORM", + "EPOLL_CLOEXEC", + "EPOLL_CTL_ADD", + "EPOLL_CTL_DEL", + "EPOLL_CTL_MOD", + "EPOLL_NONBLOCK", + "EPROCLIM", + "EPROCUNAVAIL", + "EPROGMISMATCH", + "EPROGUNAVAIL", + "EPROTO", + "EPROTONOSUPPORT", + "EPROTOTYPE", + "EPWROFF", + "ERANGE", + "EREMCHG", + "EREMOTE", + "EREMOTEIO", + "ERESTART", + "ERFKILL", + "EROFS", + "ERPCMISMATCH", + "ERROR_ACCESS_DENIED", + "ERROR_ALREADY_EXISTS", + "ERROR_BROKEN_PIPE", + "ERROR_BUFFER_OVERFLOW", + "ERROR_DIR_NOT_EMPTY", + "ERROR_ENVVAR_NOT_FOUND", + "ERROR_FILE_EXISTS", + "ERROR_FILE_NOT_FOUND", + "ERROR_HANDLE_EOF", + "ERROR_INSUFFICIENT_BUFFER", + "ERROR_IO_PENDING", + "ERROR_MOD_NOT_FOUND", + "ERROR_MORE_DATA", + "ERROR_NETNAME_DELETED", + "ERROR_NOT_FOUND", + "ERROR_NO_MORE_FILES", + "ERROR_OPERATION_ABORTED", + "ERROR_PATH_NOT_FOUND", + "ERROR_PRIVILEGE_NOT_HELD", + "ERROR_PROC_NOT_FOUND", + "ESHLIBVERS", + "ESHUTDOWN", + "ESOCKTNOSUPPORT", + "ESPIPE", + "ESRCH", + "ESRMNT", + "ESTALE", + "ESTRPIPE", + "ETHERCAP_JUMBO_MTU", + "ETHERCAP_VLAN_HWTAGGING", + "ETHERCAP_VLAN_MTU", + "ETHERMIN", + "ETHERMTU", + "ETHERMTU_JUMBO", + "ETHERTYPE_8023", + "ETHERTYPE_AARP", + "ETHERTYPE_ACCTON", + "ETHERTYPE_AEONIC", + "ETHERTYPE_ALPHA", + "ETHERTYPE_AMBER", + "ETHERTYPE_AMOEBA", + "ETHERTYPE_AOE", + "ETHERTYPE_APOLLO", + "ETHERTYPE_APOLLODOMAIN", + "ETHERTYPE_APPLETALK", + "ETHERTYPE_APPLITEK", + "ETHERTYPE_ARGONAUT", + "ETHERTYPE_ARP", + "ETHERTYPE_AT", + "ETHERTYPE_ATALK", + "ETHERTYPE_ATOMIC", + "ETHERTYPE_ATT", + "ETHERTYPE_ATTSTANFORD", + "ETHERTYPE_AUTOPHON", + "ETHERTYPE_AXIS", + "ETHERTYPE_BCLOOP", + "ETHERTYPE_BOFL", + "ETHERTYPE_CABLETRON", + "ETHERTYPE_CHAOS", + "ETHERTYPE_COMDESIGN", + "ETHERTYPE_COMPUGRAPHIC", + "ETHERTYPE_COUNTERPOINT", + "ETHERTYPE_CRONUS", + "ETHERTYPE_CRONUSVLN", + "ETHERTYPE_DCA", + "ETHERTYPE_DDE", + "ETHERTYPE_DEBNI", + "ETHERTYPE_DECAM", + "ETHERTYPE_DECCUST", + "ETHERTYPE_DECDIAG", + "ETHERTYPE_DECDNS", + "ETHERTYPE_DECDTS", + "ETHERTYPE_DECEXPER", + "ETHERTYPE_DECLAST", + "ETHERTYPE_DECLTM", + "ETHERTYPE_DECMUMPS", + "ETHERTYPE_DECNETBIOS", + "ETHERTYPE_DELTACON", + "ETHERTYPE_DIDDLE", + "ETHERTYPE_DLOG1", + "ETHERTYPE_DLOG2", + "ETHERTYPE_DN", + "ETHERTYPE_DOGFIGHT", + "ETHERTYPE_DSMD", + "ETHERTYPE_ECMA", + "ETHERTYPE_ENCRYPT", + "ETHERTYPE_ES", + "ETHERTYPE_EXCELAN", + "ETHERTYPE_EXPERDATA", + "ETHERTYPE_FLIP", + "ETHERTYPE_FLOWCONTROL", + "ETHERTYPE_FRARP", + "ETHERTYPE_GENDYN", + "ETHERTYPE_HAYES", + "ETHERTYPE_HIPPI_FP", + "ETHERTYPE_HITACHI", + "ETHERTYPE_HP", + "ETHERTYPE_IEEEPUP", + "ETHERTYPE_IEEEPUPAT", + "ETHERTYPE_IMLBL", + "ETHERTYPE_IMLBLDIAG", + "ETHERTYPE_IP", + "ETHERTYPE_IPAS", + "ETHERTYPE_IPV6", + "ETHERTYPE_IPX", + "ETHERTYPE_IPXNEW", + "ETHERTYPE_KALPANA", + "ETHERTYPE_LANBRIDGE", + "ETHERTYPE_LANPROBE", + "ETHERTYPE_LAT", + "ETHERTYPE_LBACK", + "ETHERTYPE_LITTLE", + "ETHERTYPE_LLDP", + "ETHERTYPE_LOGICRAFT", + "ETHERTYPE_LOOPBACK", + "ETHERTYPE_MATRA", + "ETHERTYPE_MAX", + "ETHERTYPE_MERIT", + "ETHERTYPE_MICP", + "ETHERTYPE_MOPDL", + "ETHERTYPE_MOPRC", + "ETHERTYPE_MOTOROLA", + "ETHERTYPE_MPLS", + "ETHERTYPE_MPLS_MCAST", + "ETHERTYPE_MUMPS", + "ETHERTYPE_NBPCC", + "ETHERTYPE_NBPCLAIM", + "ETHERTYPE_NBPCLREQ", + "ETHERTYPE_NBPCLRSP", + "ETHERTYPE_NBPCREQ", + "ETHERTYPE_NBPCRSP", + "ETHERTYPE_NBPDG", + "ETHERTYPE_NBPDGB", + "ETHERTYPE_NBPDLTE", + "ETHERTYPE_NBPRAR", + "ETHERTYPE_NBPRAS", + "ETHERTYPE_NBPRST", + "ETHERTYPE_NBPSCD", + "ETHERTYPE_NBPVCD", + "ETHERTYPE_NBS", + "ETHERTYPE_NCD", + "ETHERTYPE_NESTAR", + "ETHERTYPE_NETBEUI", + "ETHERTYPE_NOVELL", + "ETHERTYPE_NS", + "ETHERTYPE_NSAT", + "ETHERTYPE_NSCOMPAT", + "ETHERTYPE_NTRAILER", + "ETHERTYPE_OS9", + "ETHERTYPE_OS9NET", + "ETHERTYPE_PACER", + "ETHERTYPE_PAE", + "ETHERTYPE_PCS", + "ETHERTYPE_PLANNING", + "ETHERTYPE_PPP", + "ETHERTYPE_PPPOE", + "ETHERTYPE_PPPOEDISC", + "ETHERTYPE_PRIMENTS", + "ETHERTYPE_PUP", + "ETHERTYPE_PUPAT", + "ETHERTYPE_QINQ", + "ETHERTYPE_RACAL", + "ETHERTYPE_RATIONAL", + "ETHERTYPE_RAWFR", + "ETHERTYPE_RCL", + "ETHERTYPE_RDP", + "ETHERTYPE_RETIX", + "ETHERTYPE_REVARP", + "ETHERTYPE_SCA", + "ETHERTYPE_SECTRA", + "ETHERTYPE_SECUREDATA", + "ETHERTYPE_SGITW", + "ETHERTYPE_SG_BOUNCE", + "ETHERTYPE_SG_DIAG", + "ETHERTYPE_SG_NETGAMES", + "ETHERTYPE_SG_RESV", + "ETHERTYPE_SIMNET", + "ETHERTYPE_SLOW", + "ETHERTYPE_SLOWPROTOCOLS", + "ETHERTYPE_SNA", + "ETHERTYPE_SNMP", + "ETHERTYPE_SONIX", + "ETHERTYPE_SPIDER", + "ETHERTYPE_SPRITE", + "ETHERTYPE_STP", + "ETHERTYPE_TALARIS", + "ETHERTYPE_TALARISMC", + "ETHERTYPE_TCPCOMP", + "ETHERTYPE_TCPSM", + "ETHERTYPE_TEC", + "ETHERTYPE_TIGAN", + "ETHERTYPE_TRAIL", + "ETHERTYPE_TRANSETHER", + "ETHERTYPE_TYMSHARE", + "ETHERTYPE_UBBST", + "ETHERTYPE_UBDEBUG", + "ETHERTYPE_UBDIAGLOOP", + "ETHERTYPE_UBDL", + "ETHERTYPE_UBNIU", + "ETHERTYPE_UBNMC", + "ETHERTYPE_VALID", + "ETHERTYPE_VARIAN", + "ETHERTYPE_VAXELN", + "ETHERTYPE_VEECO", + "ETHERTYPE_VEXP", + "ETHERTYPE_VGLAB", + "ETHERTYPE_VINES", + "ETHERTYPE_VINESECHO", + "ETHERTYPE_VINESLOOP", + "ETHERTYPE_VITAL", + "ETHERTYPE_VLAN", + "ETHERTYPE_VLTLMAN", + "ETHERTYPE_VPROD", + "ETHERTYPE_VURESERVED", + "ETHERTYPE_WATERLOO", + "ETHERTYPE_WELLFLEET", + "ETHERTYPE_X25", + "ETHERTYPE_X75", + "ETHERTYPE_XNSSM", + "ETHERTYPE_XTP", + "ETHER_ADDR_LEN", + "ETHER_ALIGN", + "ETHER_CRC_LEN", + "ETHER_CRC_POLY_BE", + "ETHER_CRC_POLY_LE", + "ETHER_HDR_LEN", + "ETHER_MAX_DIX_LEN", + "ETHER_MAX_LEN", + "ETHER_MAX_LEN_JUMBO", + "ETHER_MIN_LEN", + "ETHER_PPPOE_ENCAP_LEN", + "ETHER_TYPE_LEN", + "ETHER_VLAN_ENCAP_LEN", + "ETH_P_1588", + "ETH_P_8021Q", + "ETH_P_802_2", + "ETH_P_802_3", + "ETH_P_AARP", + "ETH_P_ALL", + "ETH_P_AOE", + "ETH_P_ARCNET", + "ETH_P_ARP", + "ETH_P_ATALK", + "ETH_P_ATMFATE", + "ETH_P_ATMMPOA", + "ETH_P_AX25", + "ETH_P_BPQ", + "ETH_P_CAIF", + "ETH_P_CAN", + "ETH_P_CONTROL", + "ETH_P_CUST", + "ETH_P_DDCMP", + "ETH_P_DEC", + "ETH_P_DIAG", + "ETH_P_DNA_DL", + "ETH_P_DNA_RC", + "ETH_P_DNA_RT", + "ETH_P_DSA", + "ETH_P_ECONET", + "ETH_P_EDSA", + "ETH_P_FCOE", + "ETH_P_FIP", + "ETH_P_HDLC", + "ETH_P_IEEE802154", + "ETH_P_IEEEPUP", + "ETH_P_IEEEPUPAT", + "ETH_P_IP", + "ETH_P_IPV6", + "ETH_P_IPX", + "ETH_P_IRDA", + "ETH_P_LAT", + "ETH_P_LINK_CTL", + "ETH_P_LOCALTALK", + "ETH_P_LOOP", + "ETH_P_MOBITEX", + "ETH_P_MPLS_MC", + "ETH_P_MPLS_UC", + "ETH_P_PAE", + "ETH_P_PAUSE", + "ETH_P_PHONET", + "ETH_P_PPPTALK", + "ETH_P_PPP_DISC", + "ETH_P_PPP_MP", + "ETH_P_PPP_SES", + "ETH_P_PUP", + "ETH_P_PUPAT", + "ETH_P_RARP", + "ETH_P_SCA", + "ETH_P_SLOW", + "ETH_P_SNAP", + "ETH_P_TEB", + "ETH_P_TIPC", + "ETH_P_TRAILER", + "ETH_P_TR_802_2", + "ETH_P_WAN_PPP", + "ETH_P_WCCP", + "ETH_P_X25", + "ETIME", + "ETIMEDOUT", + "ETOOMANYREFS", + "ETXTBSY", + "EUCLEAN", + "EUNATCH", + "EUSERS", + "EVFILT_AIO", + "EVFILT_FS", + "EVFILT_LIO", + "EVFILT_MACHPORT", + "EVFILT_PROC", + "EVFILT_READ", + "EVFILT_SIGNAL", + "EVFILT_SYSCOUNT", + "EVFILT_THREADMARKER", + "EVFILT_TIMER", + "EVFILT_USER", + "EVFILT_VM", + "EVFILT_VNODE", + "EVFILT_WRITE", + "EV_ADD", + "EV_CLEAR", + "EV_DELETE", + "EV_DISABLE", + "EV_DISPATCH", + "EV_DROP", + "EV_ENABLE", + "EV_EOF", + "EV_ERROR", + "EV_FLAG0", + "EV_FLAG1", + "EV_ONESHOT", + "EV_OOBAND", + "EV_POLL", + "EV_RECEIPT", + "EV_SYSFLAGS", + "EWINDOWS", + "EWOULDBLOCK", + "EXDEV", + "EXFULL", + "EXTA", + "EXTB", + "EXTPROC", + "Environ", + "EpollCreate", + "EpollCreate1", + "EpollCtl", + "EpollEvent", + "EpollWait", + "Errno", + "EscapeArg", + "Exchangedata", + "Exec", + "Exit", + "ExitProcess", + "FD_CLOEXEC", + "FD_SETSIZE", + "FILE_ACTION_ADDED", + "FILE_ACTION_MODIFIED", + "FILE_ACTION_REMOVED", + "FILE_ACTION_RENAMED_NEW_NAME", + "FILE_ACTION_RENAMED_OLD_NAME", + "FILE_APPEND_DATA", + "FILE_ATTRIBUTE_ARCHIVE", + "FILE_ATTRIBUTE_DIRECTORY", + "FILE_ATTRIBUTE_HIDDEN", + "FILE_ATTRIBUTE_NORMAL", + "FILE_ATTRIBUTE_READONLY", + "FILE_ATTRIBUTE_REPARSE_POINT", + "FILE_ATTRIBUTE_SYSTEM", + "FILE_BEGIN", + "FILE_CURRENT", + "FILE_END", + "FILE_FLAG_BACKUP_SEMANTICS", + "FILE_FLAG_OPEN_REPARSE_POINT", + "FILE_FLAG_OVERLAPPED", + "FILE_LIST_DIRECTORY", + "FILE_MAP_COPY", + "FILE_MAP_EXECUTE", + "FILE_MAP_READ", + "FILE_MAP_WRITE", + "FILE_NOTIFY_CHANGE_ATTRIBUTES", + "FILE_NOTIFY_CHANGE_CREATION", + "FILE_NOTIFY_CHANGE_DIR_NAME", + "FILE_NOTIFY_CHANGE_FILE_NAME", + "FILE_NOTIFY_CHANGE_LAST_ACCESS", + "FILE_NOTIFY_CHANGE_LAST_WRITE", + "FILE_NOTIFY_CHANGE_SIZE", + "FILE_SHARE_DELETE", + "FILE_SHARE_READ", + "FILE_SHARE_WRITE", + "FILE_SKIP_COMPLETION_PORT_ON_SUCCESS", + "FILE_SKIP_SET_EVENT_ON_HANDLE", + "FILE_TYPE_CHAR", + "FILE_TYPE_DISK", + "FILE_TYPE_PIPE", + "FILE_TYPE_REMOTE", + "FILE_TYPE_UNKNOWN", + "FILE_WRITE_ATTRIBUTES", + "FLUSHO", + "FORMAT_MESSAGE_ALLOCATE_BUFFER", + "FORMAT_MESSAGE_ARGUMENT_ARRAY", + "FORMAT_MESSAGE_FROM_HMODULE", + "FORMAT_MESSAGE_FROM_STRING", + "FORMAT_MESSAGE_FROM_SYSTEM", + "FORMAT_MESSAGE_IGNORE_INSERTS", + "FORMAT_MESSAGE_MAX_WIDTH_MASK", + "FSCTL_GET_REPARSE_POINT", + "F_ADDFILESIGS", + "F_ADDSIGS", + "F_ALLOCATEALL", + "F_ALLOCATECONTIG", + "F_CANCEL", + "F_CHKCLEAN", + "F_CLOSEM", + "F_DUP2FD", + "F_DUP2FD_CLOEXEC", + "F_DUPFD", + "F_DUPFD_CLOEXEC", + "F_EXLCK", + "F_FLUSH_DATA", + "F_FREEZE_FS", + "F_FSCTL", + "F_FSDIRMASK", + "F_FSIN", + "F_FSINOUT", + "F_FSOUT", + "F_FSPRIV", + "F_FSVOID", + "F_FULLFSYNC", + "F_GETFD", + "F_GETFL", + "F_GETLEASE", + "F_GETLK", + "F_GETLK64", + "F_GETLKPID", + "F_GETNOSIGPIPE", + "F_GETOWN", + "F_GETOWN_EX", + "F_GETPATH", + "F_GETPATH_MTMINFO", + "F_GETPIPE_SZ", + "F_GETPROTECTIONCLASS", + "F_GETSIG", + "F_GLOBAL_NOCACHE", + "F_LOCK", + "F_LOG2PHYS", + "F_LOG2PHYS_EXT", + "F_MARKDEPENDENCY", + "F_MAXFD", + "F_NOCACHE", + "F_NODIRECT", + "F_NOTIFY", + "F_OGETLK", + "F_OK", + "F_OSETLK", + "F_OSETLKW", + "F_PARAM_MASK", + "F_PARAM_MAX", + "F_PATHPKG_CHECK", + "F_PEOFPOSMODE", + "F_PREALLOCATE", + "F_RDADVISE", + "F_RDAHEAD", + "F_RDLCK", + "F_READAHEAD", + "F_READBOOTSTRAP", + "F_SETBACKINGSTORE", + "F_SETFD", + "F_SETFL", + "F_SETLEASE", + "F_SETLK", + "F_SETLK64", + "F_SETLKW", + "F_SETLKW64", + "F_SETLK_REMOTE", + "F_SETNOSIGPIPE", + "F_SETOWN", + "F_SETOWN_EX", + "F_SETPIPE_SZ", + "F_SETPROTECTIONCLASS", + "F_SETSIG", + "F_SETSIZE", + "F_SHLCK", + "F_TEST", + "F_THAW_FS", + "F_TLOCK", + "F_ULOCK", + "F_UNLCK", + "F_UNLCKSYS", + "F_VOLPOSMODE", + "F_WRITEBOOTSTRAP", + "F_WRLCK", + "Faccessat", + "Fallocate", + "Fbootstraptransfer_t", + "Fchdir", + "Fchflags", + "Fchmod", + "Fchmodat", + "Fchown", + "Fchownat", + "FcntlFlock", + "FdSet", + "Fdatasync", + "FileNotifyInformation", + "Filetime", + "FindClose", + "FindFirstFile", + "FindNextFile", + "Flock", + "Flock_t", + "FlushBpf", + "FlushFileBuffers", + "FlushViewOfFile", + "ForkExec", + "ForkLock", + "FormatMessage", + "Fpathconf", + "FreeAddrInfoW", + "FreeEnvironmentStrings", + "FreeLibrary", + "Fsid", + "Fstat", + "Fstatat", + "Fstatfs", + "Fstore_t", + "Fsync", + "Ftruncate", + "FullPath", + "Futimes", + "Futimesat", + "GENERIC_ALL", + "GENERIC_EXECUTE", + "GENERIC_READ", + "GENERIC_WRITE", + "GUID", + "GetAcceptExSockaddrs", + "GetAdaptersInfo", + "GetAddrInfoW", + "GetCommandLine", + "GetComputerName", + "GetConsoleMode", + "GetCurrentDirectory", + "GetCurrentProcess", + "GetEnvironmentStrings", + "GetEnvironmentVariable", + "GetExitCodeProcess", + "GetFileAttributes", + "GetFileAttributesEx", + "GetFileExInfoStandard", + "GetFileExMaxInfoLevel", + "GetFileInformationByHandle", + "GetFileType", + "GetFullPathName", + "GetHostByName", + "GetIfEntry", + "GetLastError", + "GetLengthSid", + "GetLongPathName", + "GetProcAddress", + "GetProcessTimes", + "GetProtoByName", + "GetQueuedCompletionStatus", + "GetServByName", + "GetShortPathName", + "GetStartupInfo", + "GetStdHandle", + "GetSystemTimeAsFileTime", + "GetTempPath", + "GetTimeZoneInformation", + "GetTokenInformation", + "GetUserNameEx", + "GetUserProfileDirectory", + "GetVersion", + "Getcwd", + "Getdents", + "Getdirentries", + "Getdtablesize", + "Getegid", + "Getenv", + "Geteuid", + "Getfsstat", + "Getgid", + "Getgroups", + "Getpagesize", + "Getpeername", + "Getpgid", + "Getpgrp", + "Getpid", + "Getppid", + "Getpriority", + "Getrlimit", + "Getrusage", + "Getsid", + "Getsockname", + "Getsockopt", + "GetsockoptByte", + "GetsockoptICMPv6Filter", + "GetsockoptIPMreq", + "GetsockoptIPMreqn", + "GetsockoptIPv6MTUInfo", + "GetsockoptIPv6Mreq", + "GetsockoptInet4Addr", + "GetsockoptInt", + "GetsockoptUcred", + "Gettid", + "Gettimeofday", + "Getuid", + "Getwd", + "Getxattr", + "HANDLE_FLAG_INHERIT", + "HKEY_CLASSES_ROOT", + "HKEY_CURRENT_CONFIG", + "HKEY_CURRENT_USER", + "HKEY_DYN_DATA", + "HKEY_LOCAL_MACHINE", + "HKEY_PERFORMANCE_DATA", + "HKEY_USERS", + "HUPCL", + "Handle", + "Hostent", + "ICANON", + "ICMP6_FILTER", + "ICMPV6_FILTER", + "ICMPv6Filter", + "ICRNL", + "IEXTEN", + "IFAN_ARRIVAL", + "IFAN_DEPARTURE", + "IFA_ADDRESS", + "IFA_ANYCAST", + "IFA_BROADCAST", + "IFA_CACHEINFO", + "IFA_F_DADFAILED", + "IFA_F_DEPRECATED", + "IFA_F_HOMEADDRESS", + "IFA_F_NODAD", + "IFA_F_OPTIMISTIC", + "IFA_F_PERMANENT", + "IFA_F_SECONDARY", + "IFA_F_TEMPORARY", + "IFA_F_TENTATIVE", + "IFA_LABEL", + "IFA_LOCAL", + "IFA_MAX", + "IFA_MULTICAST", + "IFA_ROUTE", + "IFA_UNSPEC", + "IFF_ALLMULTI", + "IFF_ALTPHYS", + "IFF_AUTOMEDIA", + "IFF_BROADCAST", + "IFF_CANTCHANGE", + "IFF_CANTCONFIG", + "IFF_DEBUG", + "IFF_DRV_OACTIVE", + "IFF_DRV_RUNNING", + "IFF_DYING", + "IFF_DYNAMIC", + "IFF_LINK0", + "IFF_LINK1", + "IFF_LINK2", + "IFF_LOOPBACK", + "IFF_MASTER", + "IFF_MONITOR", + "IFF_MULTICAST", + "IFF_NOARP", + "IFF_NOTRAILERS", + "IFF_NO_PI", + "IFF_OACTIVE", + "IFF_ONE_QUEUE", + "IFF_POINTOPOINT", + "IFF_POINTTOPOINT", + "IFF_PORTSEL", + "IFF_PPROMISC", + "IFF_PROMISC", + "IFF_RENAMING", + "IFF_RUNNING", + "IFF_SIMPLEX", + "IFF_SLAVE", + "IFF_SMART", + "IFF_STATICARP", + "IFF_TAP", + "IFF_TUN", + "IFF_TUN_EXCL", + "IFF_UP", + "IFF_VNET_HDR", + "IFLA_ADDRESS", + "IFLA_BROADCAST", + "IFLA_COST", + "IFLA_IFALIAS", + "IFLA_IFNAME", + "IFLA_LINK", + "IFLA_LINKINFO", + "IFLA_LINKMODE", + "IFLA_MAP", + "IFLA_MASTER", + "IFLA_MAX", + "IFLA_MTU", + "IFLA_NET_NS_PID", + "IFLA_OPERSTATE", + "IFLA_PRIORITY", + "IFLA_PROTINFO", + "IFLA_QDISC", + "IFLA_STATS", + "IFLA_TXQLEN", + "IFLA_UNSPEC", + "IFLA_WEIGHT", + "IFLA_WIRELESS", + "IFNAMSIZ", + "IFT_1822", + "IFT_A12MPPSWITCH", + "IFT_AAL2", + "IFT_AAL5", + "IFT_ADSL", + "IFT_AFLANE8023", + "IFT_AFLANE8025", + "IFT_ARAP", + "IFT_ARCNET", + "IFT_ARCNETPLUS", + "IFT_ASYNC", + "IFT_ATM", + "IFT_ATMDXI", + "IFT_ATMFUNI", + "IFT_ATMIMA", + "IFT_ATMLOGICAL", + "IFT_ATMRADIO", + "IFT_ATMSUBINTERFACE", + "IFT_ATMVCIENDPT", + "IFT_ATMVIRTUAL", + "IFT_BGPPOLICYACCOUNTING", + "IFT_BLUETOOTH", + "IFT_BRIDGE", + "IFT_BSC", + "IFT_CARP", + "IFT_CCTEMUL", + "IFT_CELLULAR", + "IFT_CEPT", + "IFT_CES", + "IFT_CHANNEL", + "IFT_CNR", + "IFT_COFFEE", + "IFT_COMPOSITELINK", + "IFT_DCN", + "IFT_DIGITALPOWERLINE", + "IFT_DIGITALWRAPPEROVERHEADCHANNEL", + "IFT_DLSW", + "IFT_DOCSCABLEDOWNSTREAM", + "IFT_DOCSCABLEMACLAYER", + "IFT_DOCSCABLEUPSTREAM", + "IFT_DOCSCABLEUPSTREAMCHANNEL", + "IFT_DS0", + "IFT_DS0BUNDLE", + "IFT_DS1FDL", + "IFT_DS3", + "IFT_DTM", + "IFT_DUMMY", + "IFT_DVBASILN", + "IFT_DVBASIOUT", + "IFT_DVBRCCDOWNSTREAM", + "IFT_DVBRCCMACLAYER", + "IFT_DVBRCCUPSTREAM", + "IFT_ECONET", + "IFT_ENC", + "IFT_EON", + "IFT_EPLRS", + "IFT_ESCON", + "IFT_ETHER", + "IFT_FAITH", + "IFT_FAST", + "IFT_FASTETHER", + "IFT_FASTETHERFX", + "IFT_FDDI", + "IFT_FIBRECHANNEL", + "IFT_FRAMERELAYINTERCONNECT", + "IFT_FRAMERELAYMPI", + "IFT_FRDLCIENDPT", + "IFT_FRELAY", + "IFT_FRELAYDCE", + "IFT_FRF16MFRBUNDLE", + "IFT_FRFORWARD", + "IFT_G703AT2MB", + "IFT_G703AT64K", + "IFT_GIF", + "IFT_GIGABITETHERNET", + "IFT_GR303IDT", + "IFT_GR303RDT", + "IFT_H323GATEKEEPER", + "IFT_H323PROXY", + "IFT_HDH1822", + "IFT_HDLC", + "IFT_HDSL2", + "IFT_HIPERLAN2", + "IFT_HIPPI", + "IFT_HIPPIINTERFACE", + "IFT_HOSTPAD", + "IFT_HSSI", + "IFT_HY", + "IFT_IBM370PARCHAN", + "IFT_IDSL", + "IFT_IEEE1394", + "IFT_IEEE80211", + "IFT_IEEE80212", + "IFT_IEEE8023ADLAG", + "IFT_IFGSN", + "IFT_IMT", + "IFT_INFINIBAND", + "IFT_INTERLEAVE", + "IFT_IP", + "IFT_IPFORWARD", + "IFT_IPOVERATM", + "IFT_IPOVERCDLC", + "IFT_IPOVERCLAW", + "IFT_IPSWITCH", + "IFT_IPXIP", + "IFT_ISDN", + "IFT_ISDNBASIC", + "IFT_ISDNPRIMARY", + "IFT_ISDNS", + "IFT_ISDNU", + "IFT_ISO88022LLC", + "IFT_ISO88023", + "IFT_ISO88024", + "IFT_ISO88025", + "IFT_ISO88025CRFPINT", + "IFT_ISO88025DTR", + "IFT_ISO88025FIBER", + "IFT_ISO88026", + "IFT_ISUP", + "IFT_L2VLAN", + "IFT_L3IPVLAN", + "IFT_L3IPXVLAN", + "IFT_LAPB", + "IFT_LAPD", + "IFT_LAPF", + "IFT_LINEGROUP", + "IFT_LOCALTALK", + "IFT_LOOP", + "IFT_MEDIAMAILOVERIP", + "IFT_MFSIGLINK", + "IFT_MIOX25", + "IFT_MODEM", + "IFT_MPC", + "IFT_MPLS", + "IFT_MPLSTUNNEL", + "IFT_MSDSL", + "IFT_MVL", + "IFT_MYRINET", + "IFT_NFAS", + "IFT_NSIP", + "IFT_OPTICALCHANNEL", + "IFT_OPTICALTRANSPORT", + "IFT_OTHER", + "IFT_P10", + "IFT_P80", + "IFT_PARA", + "IFT_PDP", + "IFT_PFLOG", + "IFT_PFLOW", + "IFT_PFSYNC", + "IFT_PLC", + "IFT_PON155", + "IFT_PON622", + "IFT_POS", + "IFT_PPP", + "IFT_PPPMULTILINKBUNDLE", + "IFT_PROPATM", + "IFT_PROPBWAP2MP", + "IFT_PROPCNLS", + "IFT_PROPDOCSWIRELESSDOWNSTREAM", + "IFT_PROPDOCSWIRELESSMACLAYER", + "IFT_PROPDOCSWIRELESSUPSTREAM", + "IFT_PROPMUX", + "IFT_PROPVIRTUAL", + "IFT_PROPWIRELESSP2P", + "IFT_PTPSERIAL", + "IFT_PVC", + "IFT_Q2931", + "IFT_QLLC", + "IFT_RADIOMAC", + "IFT_RADSL", + "IFT_REACHDSL", + "IFT_RFC1483", + "IFT_RS232", + "IFT_RSRB", + "IFT_SDLC", + "IFT_SDSL", + "IFT_SHDSL", + "IFT_SIP", + "IFT_SIPSIG", + "IFT_SIPTG", + "IFT_SLIP", + "IFT_SMDSDXI", + "IFT_SMDSICIP", + "IFT_SONET", + "IFT_SONETOVERHEADCHANNEL", + "IFT_SONETPATH", + "IFT_SONETVT", + "IFT_SRP", + "IFT_SS7SIGLINK", + "IFT_STACKTOSTACK", + "IFT_STARLAN", + "IFT_STF", + "IFT_T1", + "IFT_TDLC", + "IFT_TELINK", + "IFT_TERMPAD", + "IFT_TR008", + "IFT_TRANSPHDLC", + "IFT_TUNNEL", + "IFT_ULTRA", + "IFT_USB", + "IFT_V11", + "IFT_V35", + "IFT_V36", + "IFT_V37", + "IFT_VDSL", + "IFT_VIRTUALIPADDRESS", + "IFT_VIRTUALTG", + "IFT_VOICEDID", + "IFT_VOICEEM", + "IFT_VOICEEMFGD", + "IFT_VOICEENCAP", + "IFT_VOICEFGDEANA", + "IFT_VOICEFXO", + "IFT_VOICEFXS", + "IFT_VOICEOVERATM", + "IFT_VOICEOVERCABLE", + "IFT_VOICEOVERFRAMERELAY", + "IFT_VOICEOVERIP", + "IFT_X213", + "IFT_X25", + "IFT_X25DDN", + "IFT_X25HUNTGROUP", + "IFT_X25MLP", + "IFT_X25PLE", + "IFT_XETHER", + "IGNBRK", + "IGNCR", + "IGNORE", + "IGNPAR", + "IMAXBEL", + "INFINITE", + "INLCR", + "INPCK", + "INVALID_FILE_ATTRIBUTES", + "IN_ACCESS", + "IN_ALL_EVENTS", + "IN_ATTRIB", + "IN_CLASSA_HOST", + "IN_CLASSA_MAX", + "IN_CLASSA_NET", + "IN_CLASSA_NSHIFT", + "IN_CLASSB_HOST", + "IN_CLASSB_MAX", + "IN_CLASSB_NET", + "IN_CLASSB_NSHIFT", + "IN_CLASSC_HOST", + "IN_CLASSC_NET", + "IN_CLASSC_NSHIFT", + "IN_CLASSD_HOST", + "IN_CLASSD_NET", + "IN_CLASSD_NSHIFT", + "IN_CLOEXEC", + "IN_CLOSE", + "IN_CLOSE_NOWRITE", + "IN_CLOSE_WRITE", + "IN_CREATE", + "IN_DELETE", + "IN_DELETE_SELF", + "IN_DONT_FOLLOW", + "IN_EXCL_UNLINK", + "IN_IGNORED", + "IN_ISDIR", + "IN_LINKLOCALNETNUM", + "IN_LOOPBACKNET", + "IN_MASK_ADD", + "IN_MODIFY", + "IN_MOVE", + "IN_MOVED_FROM", + "IN_MOVED_TO", + "IN_MOVE_SELF", + "IN_NONBLOCK", + "IN_ONESHOT", + "IN_ONLYDIR", + "IN_OPEN", + "IN_Q_OVERFLOW", + "IN_RFC3021_HOST", + "IN_RFC3021_MASK", + "IN_RFC3021_NET", + "IN_RFC3021_NSHIFT", + "IN_UNMOUNT", + "IOC_IN", + "IOC_INOUT", + "IOC_OUT", + "IOC_VENDOR", + "IOC_WS2", + "IO_REPARSE_TAG_SYMLINK", + "IPMreq", + "IPMreqn", + "IPPROTO_3PC", + "IPPROTO_ADFS", + "IPPROTO_AH", + "IPPROTO_AHIP", + "IPPROTO_APES", + "IPPROTO_ARGUS", + "IPPROTO_AX25", + "IPPROTO_BHA", + "IPPROTO_BLT", + "IPPROTO_BRSATMON", + "IPPROTO_CARP", + "IPPROTO_CFTP", + "IPPROTO_CHAOS", + "IPPROTO_CMTP", + "IPPROTO_COMP", + "IPPROTO_CPHB", + "IPPROTO_CPNX", + "IPPROTO_DCCP", + "IPPROTO_DDP", + "IPPROTO_DGP", + "IPPROTO_DIVERT", + "IPPROTO_DIVERT_INIT", + "IPPROTO_DIVERT_RESP", + "IPPROTO_DONE", + "IPPROTO_DSTOPTS", + "IPPROTO_EGP", + "IPPROTO_EMCON", + "IPPROTO_ENCAP", + "IPPROTO_EON", + "IPPROTO_ESP", + "IPPROTO_ETHERIP", + "IPPROTO_FRAGMENT", + "IPPROTO_GGP", + "IPPROTO_GMTP", + "IPPROTO_GRE", + "IPPROTO_HELLO", + "IPPROTO_HMP", + "IPPROTO_HOPOPTS", + "IPPROTO_ICMP", + "IPPROTO_ICMPV6", + "IPPROTO_IDP", + "IPPROTO_IDPR", + "IPPROTO_IDRP", + "IPPROTO_IGMP", + "IPPROTO_IGP", + "IPPROTO_IGRP", + "IPPROTO_IL", + "IPPROTO_INLSP", + "IPPROTO_INP", + "IPPROTO_IP", + "IPPROTO_IPCOMP", + "IPPROTO_IPCV", + "IPPROTO_IPEIP", + "IPPROTO_IPIP", + "IPPROTO_IPPC", + "IPPROTO_IPV4", + "IPPROTO_IPV6", + "IPPROTO_IPV6_ICMP", + "IPPROTO_IRTP", + "IPPROTO_KRYPTOLAN", + "IPPROTO_LARP", + "IPPROTO_LEAF1", + "IPPROTO_LEAF2", + "IPPROTO_MAX", + "IPPROTO_MAXID", + "IPPROTO_MEAS", + "IPPROTO_MH", + "IPPROTO_MHRP", + "IPPROTO_MICP", + "IPPROTO_MOBILE", + "IPPROTO_MPLS", + "IPPROTO_MTP", + "IPPROTO_MUX", + "IPPROTO_ND", + "IPPROTO_NHRP", + "IPPROTO_NONE", + "IPPROTO_NSP", + "IPPROTO_NVPII", + "IPPROTO_OLD_DIVERT", + "IPPROTO_OSPFIGP", + "IPPROTO_PFSYNC", + "IPPROTO_PGM", + "IPPROTO_PIGP", + "IPPROTO_PIM", + "IPPROTO_PRM", + "IPPROTO_PUP", + "IPPROTO_PVP", + "IPPROTO_RAW", + "IPPROTO_RCCMON", + "IPPROTO_RDP", + "IPPROTO_ROUTING", + "IPPROTO_RSVP", + "IPPROTO_RVD", + "IPPROTO_SATEXPAK", + "IPPROTO_SATMON", + "IPPROTO_SCCSP", + "IPPROTO_SCTP", + "IPPROTO_SDRP", + "IPPROTO_SEND", + "IPPROTO_SEP", + "IPPROTO_SKIP", + "IPPROTO_SPACER", + "IPPROTO_SRPC", + "IPPROTO_ST", + "IPPROTO_SVMTP", + "IPPROTO_SWIPE", + "IPPROTO_TCF", + "IPPROTO_TCP", + "IPPROTO_TLSP", + "IPPROTO_TP", + "IPPROTO_TPXX", + "IPPROTO_TRUNK1", + "IPPROTO_TRUNK2", + "IPPROTO_TTP", + "IPPROTO_UDP", + "IPPROTO_UDPLITE", + "IPPROTO_VINES", + "IPPROTO_VISA", + "IPPROTO_VMTP", + "IPPROTO_VRRP", + "IPPROTO_WBEXPAK", + "IPPROTO_WBMON", + "IPPROTO_WSN", + "IPPROTO_XNET", + "IPPROTO_XTP", + "IPV6_2292DSTOPTS", + "IPV6_2292HOPLIMIT", + "IPV6_2292HOPOPTS", + "IPV6_2292NEXTHOP", + "IPV6_2292PKTINFO", + "IPV6_2292PKTOPTIONS", + "IPV6_2292RTHDR", + "IPV6_ADDRFORM", + "IPV6_ADD_MEMBERSHIP", + "IPV6_AUTHHDR", + "IPV6_AUTH_LEVEL", + "IPV6_AUTOFLOWLABEL", + "IPV6_BINDANY", + "IPV6_BINDV6ONLY", + "IPV6_BOUND_IF", + "IPV6_CHECKSUM", + "IPV6_DEFAULT_MULTICAST_HOPS", + "IPV6_DEFAULT_MULTICAST_LOOP", + "IPV6_DEFHLIM", + "IPV6_DONTFRAG", + "IPV6_DROP_MEMBERSHIP", + "IPV6_DSTOPTS", + "IPV6_ESP_NETWORK_LEVEL", + "IPV6_ESP_TRANS_LEVEL", + "IPV6_FAITH", + "IPV6_FLOWINFO_MASK", + "IPV6_FLOWLABEL_MASK", + "IPV6_FRAGTTL", + "IPV6_FW_ADD", + "IPV6_FW_DEL", + "IPV6_FW_FLUSH", + "IPV6_FW_GET", + "IPV6_FW_ZERO", + "IPV6_HLIMDEC", + "IPV6_HOPLIMIT", + "IPV6_HOPOPTS", + "IPV6_IPCOMP_LEVEL", + "IPV6_IPSEC_POLICY", + "IPV6_JOIN_ANYCAST", + "IPV6_JOIN_GROUP", + "IPV6_LEAVE_ANYCAST", + "IPV6_LEAVE_GROUP", + "IPV6_MAXHLIM", + "IPV6_MAXOPTHDR", + "IPV6_MAXPACKET", + "IPV6_MAX_GROUP_SRC_FILTER", + "IPV6_MAX_MEMBERSHIPS", + "IPV6_MAX_SOCK_SRC_FILTER", + "IPV6_MIN_MEMBERSHIPS", + "IPV6_MMTU", + "IPV6_MSFILTER", + "IPV6_MTU", + "IPV6_MTU_DISCOVER", + "IPV6_MULTICAST_HOPS", + "IPV6_MULTICAST_IF", + "IPV6_MULTICAST_LOOP", + "IPV6_NEXTHOP", + "IPV6_OPTIONS", + "IPV6_PATHMTU", + "IPV6_PIPEX", + "IPV6_PKTINFO", + "IPV6_PMTUDISC_DO", + "IPV6_PMTUDISC_DONT", + "IPV6_PMTUDISC_PROBE", + "IPV6_PMTUDISC_WANT", + "IPV6_PORTRANGE", + "IPV6_PORTRANGE_DEFAULT", + "IPV6_PORTRANGE_HIGH", + "IPV6_PORTRANGE_LOW", + "IPV6_PREFER_TEMPADDR", + "IPV6_RECVDSTOPTS", + "IPV6_RECVDSTPORT", + "IPV6_RECVERR", + "IPV6_RECVHOPLIMIT", + "IPV6_RECVHOPOPTS", + "IPV6_RECVPATHMTU", + "IPV6_RECVPKTINFO", + "IPV6_RECVRTHDR", + "IPV6_RECVTCLASS", + "IPV6_ROUTER_ALERT", + "IPV6_RTABLE", + "IPV6_RTHDR", + "IPV6_RTHDRDSTOPTS", + "IPV6_RTHDR_LOOSE", + "IPV6_RTHDR_STRICT", + "IPV6_RTHDR_TYPE_0", + "IPV6_RXDSTOPTS", + "IPV6_RXHOPOPTS", + "IPV6_SOCKOPT_RESERVED1", + "IPV6_TCLASS", + "IPV6_UNICAST_HOPS", + "IPV6_USE_MIN_MTU", + "IPV6_V6ONLY", + "IPV6_VERSION", + "IPV6_VERSION_MASK", + "IPV6_XFRM_POLICY", + "IP_ADD_MEMBERSHIP", + "IP_ADD_SOURCE_MEMBERSHIP", + "IP_AUTH_LEVEL", + "IP_BINDANY", + "IP_BLOCK_SOURCE", + "IP_BOUND_IF", + "IP_DEFAULT_MULTICAST_LOOP", + "IP_DEFAULT_MULTICAST_TTL", + "IP_DF", + "IP_DIVERTFL", + "IP_DONTFRAG", + "IP_DROP_MEMBERSHIP", + "IP_DROP_SOURCE_MEMBERSHIP", + "IP_DUMMYNET3", + "IP_DUMMYNET_CONFIGURE", + "IP_DUMMYNET_DEL", + "IP_DUMMYNET_FLUSH", + "IP_DUMMYNET_GET", + "IP_EF", + "IP_ERRORMTU", + "IP_ESP_NETWORK_LEVEL", + "IP_ESP_TRANS_LEVEL", + "IP_FAITH", + "IP_FREEBIND", + "IP_FW3", + "IP_FW_ADD", + "IP_FW_DEL", + "IP_FW_FLUSH", + "IP_FW_GET", + "IP_FW_NAT_CFG", + "IP_FW_NAT_DEL", + "IP_FW_NAT_GET_CONFIG", + "IP_FW_NAT_GET_LOG", + "IP_FW_RESETLOG", + "IP_FW_TABLE_ADD", + "IP_FW_TABLE_DEL", + "IP_FW_TABLE_FLUSH", + "IP_FW_TABLE_GETSIZE", + "IP_FW_TABLE_LIST", + "IP_FW_ZERO", + "IP_HDRINCL", + "IP_IPCOMP_LEVEL", + "IP_IPSECFLOWINFO", + "IP_IPSEC_LOCAL_AUTH", + "IP_IPSEC_LOCAL_CRED", + "IP_IPSEC_LOCAL_ID", + "IP_IPSEC_POLICY", + "IP_IPSEC_REMOTE_AUTH", + "IP_IPSEC_REMOTE_CRED", + "IP_IPSEC_REMOTE_ID", + "IP_MAXPACKET", + "IP_MAX_GROUP_SRC_FILTER", + "IP_MAX_MEMBERSHIPS", + "IP_MAX_SOCK_MUTE_FILTER", + "IP_MAX_SOCK_SRC_FILTER", + "IP_MAX_SOURCE_FILTER", + "IP_MF", + "IP_MINFRAGSIZE", + "IP_MINTTL", + "IP_MIN_MEMBERSHIPS", + "IP_MSFILTER", + "IP_MSS", + "IP_MTU", + "IP_MTU_DISCOVER", + "IP_MULTICAST_IF", + "IP_MULTICAST_IFINDEX", + "IP_MULTICAST_LOOP", + "IP_MULTICAST_TTL", + "IP_MULTICAST_VIF", + "IP_NAT__XXX", + "IP_OFFMASK", + "IP_OLD_FW_ADD", + "IP_OLD_FW_DEL", + "IP_OLD_FW_FLUSH", + "IP_OLD_FW_GET", + "IP_OLD_FW_RESETLOG", + "IP_OLD_FW_ZERO", + "IP_ONESBCAST", + "IP_OPTIONS", + "IP_ORIGDSTADDR", + "IP_PASSSEC", + "IP_PIPEX", + "IP_PKTINFO", + "IP_PKTOPTIONS", + "IP_PMTUDISC", + "IP_PMTUDISC_DO", + "IP_PMTUDISC_DONT", + "IP_PMTUDISC_PROBE", + "IP_PMTUDISC_WANT", + "IP_PORTRANGE", + "IP_PORTRANGE_DEFAULT", + "IP_PORTRANGE_HIGH", + "IP_PORTRANGE_LOW", + "IP_RECVDSTADDR", + "IP_RECVDSTPORT", + "IP_RECVERR", + "IP_RECVIF", + "IP_RECVOPTS", + "IP_RECVORIGDSTADDR", + "IP_RECVPKTINFO", + "IP_RECVRETOPTS", + "IP_RECVRTABLE", + "IP_RECVTOS", + "IP_RECVTTL", + "IP_RETOPTS", + "IP_RF", + "IP_ROUTER_ALERT", + "IP_RSVP_OFF", + "IP_RSVP_ON", + "IP_RSVP_VIF_OFF", + "IP_RSVP_VIF_ON", + "IP_RTABLE", + "IP_SENDSRCADDR", + "IP_STRIPHDR", + "IP_TOS", + "IP_TRAFFIC_MGT_BACKGROUND", + "IP_TRANSPARENT", + "IP_TTL", + "IP_UNBLOCK_SOURCE", + "IP_XFRM_POLICY", + "IPv6MTUInfo", + "IPv6Mreq", + "ISIG", + "ISTRIP", + "IUCLC", + "IUTF8", + "IXANY", + "IXOFF", + "IXON", + "IfAddrmsg", + "IfAnnounceMsghdr", + "IfData", + "IfInfomsg", + "IfMsghdr", + "IfaMsghdr", + "IfmaMsghdr", + "IfmaMsghdr2", + "ImplementsGetwd", + "Inet4Pktinfo", + "Inet6Pktinfo", + "InotifyAddWatch", + "InotifyEvent", + "InotifyInit", + "InotifyInit1", + "InotifyRmWatch", + "InterfaceAddrMessage", + "InterfaceAnnounceMessage", + "InterfaceInfo", + "InterfaceMessage", + "InterfaceMulticastAddrMessage", + "InvalidHandle", + "Ioperm", + "Iopl", + "Iovec", + "IpAdapterInfo", + "IpAddrString", + "IpAddressString", + "IpMaskString", + "Issetugid", + "KEY_ALL_ACCESS", + "KEY_CREATE_LINK", + "KEY_CREATE_SUB_KEY", + "KEY_ENUMERATE_SUB_KEYS", + "KEY_EXECUTE", + "KEY_NOTIFY", + "KEY_QUERY_VALUE", + "KEY_READ", + "KEY_SET_VALUE", + "KEY_WOW64_32KEY", + "KEY_WOW64_64KEY", + "KEY_WRITE", + "Kevent", + "Kevent_t", + "Kill", + "Klogctl", + "Kqueue", + "LANG_ENGLISH", + "LAYERED_PROTOCOL", + "LCNT_OVERLOAD_FLUSH", + "LINUX_REBOOT_CMD_CAD_OFF", + "LINUX_REBOOT_CMD_CAD_ON", + "LINUX_REBOOT_CMD_HALT", + "LINUX_REBOOT_CMD_KEXEC", + "LINUX_REBOOT_CMD_POWER_OFF", + "LINUX_REBOOT_CMD_RESTART", + "LINUX_REBOOT_CMD_RESTART2", + "LINUX_REBOOT_CMD_SW_SUSPEND", + "LINUX_REBOOT_MAGIC1", + "LINUX_REBOOT_MAGIC2", + "LOCK_EX", + "LOCK_NB", + "LOCK_SH", + "LOCK_UN", + "LazyDLL", + "LazyProc", + "Lchown", + "Linger", + "Link", + "Listen", + "Listxattr", + "LoadCancelIoEx", + "LoadConnectEx", + "LoadCreateSymbolicLink", + "LoadDLL", + "LoadGetAddrInfo", + "LoadLibrary", + "LoadSetFileCompletionNotificationModes", + "LocalFree", + "Log2phys_t", + "LookupAccountName", + "LookupAccountSid", + "LookupSID", + "LsfJump", + "LsfSocket", + "LsfStmt", + "Lstat", + "MADV_AUTOSYNC", + "MADV_CAN_REUSE", + "MADV_CORE", + "MADV_DOFORK", + "MADV_DONTFORK", + "MADV_DONTNEED", + "MADV_FREE", + "MADV_FREE_REUSABLE", + "MADV_FREE_REUSE", + "MADV_HUGEPAGE", + "MADV_HWPOISON", + "MADV_MERGEABLE", + "MADV_NOCORE", + "MADV_NOHUGEPAGE", + "MADV_NORMAL", + "MADV_NOSYNC", + "MADV_PROTECT", + "MADV_RANDOM", + "MADV_REMOVE", + "MADV_SEQUENTIAL", + "MADV_SPACEAVAIL", + "MADV_UNMERGEABLE", + "MADV_WILLNEED", + "MADV_ZERO_WIRED_PAGES", + "MAP_32BIT", + "MAP_ALIGNED_SUPER", + "MAP_ALIGNMENT_16MB", + "MAP_ALIGNMENT_1TB", + "MAP_ALIGNMENT_256TB", + "MAP_ALIGNMENT_4GB", + "MAP_ALIGNMENT_64KB", + "MAP_ALIGNMENT_64PB", + "MAP_ALIGNMENT_MASK", + "MAP_ALIGNMENT_SHIFT", + "MAP_ANON", + "MAP_ANONYMOUS", + "MAP_COPY", + "MAP_DENYWRITE", + "MAP_EXECUTABLE", + "MAP_FILE", + "MAP_FIXED", + "MAP_FLAGMASK", + "MAP_GROWSDOWN", + "MAP_HASSEMAPHORE", + "MAP_HUGETLB", + "MAP_INHERIT", + "MAP_INHERIT_COPY", + "MAP_INHERIT_DEFAULT", + "MAP_INHERIT_DONATE_COPY", + "MAP_INHERIT_NONE", + "MAP_INHERIT_SHARE", + "MAP_JIT", + "MAP_LOCKED", + "MAP_NOCACHE", + "MAP_NOCORE", + "MAP_NOEXTEND", + "MAP_NONBLOCK", + "MAP_NORESERVE", + "MAP_NOSYNC", + "MAP_POPULATE", + "MAP_PREFAULT_READ", + "MAP_PRIVATE", + "MAP_RENAME", + "MAP_RESERVED0080", + "MAP_RESERVED0100", + "MAP_SHARED", + "MAP_STACK", + "MAP_TRYFIXED", + "MAP_TYPE", + "MAP_WIRED", + "MAXIMUM_REPARSE_DATA_BUFFER_SIZE", + "MAXLEN_IFDESCR", + "MAXLEN_PHYSADDR", + "MAX_ADAPTER_ADDRESS_LENGTH", + "MAX_ADAPTER_DESCRIPTION_LENGTH", + "MAX_ADAPTER_NAME_LENGTH", + "MAX_COMPUTERNAME_LENGTH", + "MAX_INTERFACE_NAME_LEN", + "MAX_LONG_PATH", + "MAX_PATH", + "MAX_PROTOCOL_CHAIN", + "MCL_CURRENT", + "MCL_FUTURE", + "MNT_DETACH", + "MNT_EXPIRE", + "MNT_FORCE", + "MSG_BCAST", + "MSG_CMSG_CLOEXEC", + "MSG_COMPAT", + "MSG_CONFIRM", + "MSG_CONTROLMBUF", + "MSG_CTRUNC", + "MSG_DONTROUTE", + "MSG_DONTWAIT", + "MSG_EOF", + "MSG_EOR", + "MSG_ERRQUEUE", + "MSG_FASTOPEN", + "MSG_FIN", + "MSG_FLUSH", + "MSG_HAVEMORE", + "MSG_HOLD", + "MSG_IOVUSRSPACE", + "MSG_LENUSRSPACE", + "MSG_MCAST", + "MSG_MORE", + "MSG_NAMEMBUF", + "MSG_NBIO", + "MSG_NEEDSA", + "MSG_NOSIGNAL", + "MSG_NOTIFICATION", + "MSG_OOB", + "MSG_PEEK", + "MSG_PROXY", + "MSG_RCVMORE", + "MSG_RST", + "MSG_SEND", + "MSG_SYN", + "MSG_TRUNC", + "MSG_TRYHARD", + "MSG_USERFLAGS", + "MSG_WAITALL", + "MSG_WAITFORONE", + "MSG_WAITSTREAM", + "MS_ACTIVE", + "MS_ASYNC", + "MS_BIND", + "MS_DEACTIVATE", + "MS_DIRSYNC", + "MS_INVALIDATE", + "MS_I_VERSION", + "MS_KERNMOUNT", + "MS_KILLPAGES", + "MS_MANDLOCK", + "MS_MGC_MSK", + "MS_MGC_VAL", + "MS_MOVE", + "MS_NOATIME", + "MS_NODEV", + "MS_NODIRATIME", + "MS_NOEXEC", + "MS_NOSUID", + "MS_NOUSER", + "MS_POSIXACL", + "MS_PRIVATE", + "MS_RDONLY", + "MS_REC", + "MS_RELATIME", + "MS_REMOUNT", + "MS_RMT_MASK", + "MS_SHARED", + "MS_SILENT", + "MS_SLAVE", + "MS_STRICTATIME", + "MS_SYNC", + "MS_SYNCHRONOUS", + "MS_UNBINDABLE", + "Madvise", + "MapViewOfFile", + "MaxTokenInfoClass", + "Mclpool", + "MibIfRow", + "Mkdir", + "Mkdirat", + "Mkfifo", + "Mknod", + "Mknodat", + "Mlock", + "Mlockall", + "Mmap", + "Mount", + "MoveFile", + "Mprotect", + "Msghdr", + "Munlock", + "Munlockall", + "Munmap", + "MustLoadDLL", + "NAME_MAX", + "NETLINK_ADD_MEMBERSHIP", + "NETLINK_AUDIT", + "NETLINK_BROADCAST_ERROR", + "NETLINK_CONNECTOR", + "NETLINK_DNRTMSG", + "NETLINK_DROP_MEMBERSHIP", + "NETLINK_ECRYPTFS", + "NETLINK_FIB_LOOKUP", + "NETLINK_FIREWALL", + "NETLINK_GENERIC", + "NETLINK_INET_DIAG", + "NETLINK_IP6_FW", + "NETLINK_ISCSI", + "NETLINK_KOBJECT_UEVENT", + "NETLINK_NETFILTER", + "NETLINK_NFLOG", + "NETLINK_NO_ENOBUFS", + "NETLINK_PKTINFO", + "NETLINK_RDMA", + "NETLINK_ROUTE", + "NETLINK_SCSITRANSPORT", + "NETLINK_SELINUX", + "NETLINK_UNUSED", + "NETLINK_USERSOCK", + "NETLINK_XFRM", + "NET_RT_DUMP", + "NET_RT_DUMP2", + "NET_RT_FLAGS", + "NET_RT_IFLIST", + "NET_RT_IFLIST2", + "NET_RT_IFLISTL", + "NET_RT_IFMALIST", + "NET_RT_MAXID", + "NET_RT_OIFLIST", + "NET_RT_OOIFLIST", + "NET_RT_STAT", + "NET_RT_STATS", + "NET_RT_TABLE", + "NET_RT_TRASH", + "NLA_ALIGNTO", + "NLA_F_NESTED", + "NLA_F_NET_BYTEORDER", + "NLA_HDRLEN", + "NLMSG_ALIGNTO", + "NLMSG_DONE", + "NLMSG_ERROR", + "NLMSG_HDRLEN", + "NLMSG_MIN_TYPE", + "NLMSG_NOOP", + "NLMSG_OVERRUN", + "NLM_F_ACK", + "NLM_F_APPEND", + "NLM_F_ATOMIC", + "NLM_F_CREATE", + "NLM_F_DUMP", + "NLM_F_ECHO", + "NLM_F_EXCL", + "NLM_F_MATCH", + "NLM_F_MULTI", + "NLM_F_REPLACE", + "NLM_F_REQUEST", + "NLM_F_ROOT", + "NOFLSH", + "NOTE_ABSOLUTE", + "NOTE_ATTRIB", + "NOTE_CHILD", + "NOTE_DELETE", + "NOTE_EOF", + "NOTE_EXEC", + "NOTE_EXIT", + "NOTE_EXITSTATUS", + "NOTE_EXTEND", + "NOTE_FFAND", + "NOTE_FFCOPY", + "NOTE_FFCTRLMASK", + "NOTE_FFLAGSMASK", + "NOTE_FFNOP", + "NOTE_FFOR", + "NOTE_FORK", + "NOTE_LINK", + "NOTE_LOWAT", + "NOTE_NONE", + "NOTE_NSECONDS", + "NOTE_PCTRLMASK", + "NOTE_PDATAMASK", + "NOTE_REAP", + "NOTE_RENAME", + "NOTE_RESOURCEEND", + "NOTE_REVOKE", + "NOTE_SECONDS", + "NOTE_SIGNAL", + "NOTE_TRACK", + "NOTE_TRACKERR", + "NOTE_TRIGGER", + "NOTE_TRUNCATE", + "NOTE_USECONDS", + "NOTE_VM_ERROR", + "NOTE_VM_PRESSURE", + "NOTE_VM_PRESSURE_SUDDEN_TERMINATE", + "NOTE_VM_PRESSURE_TERMINATE", + "NOTE_WRITE", + "NameCanonical", + "NameCanonicalEx", + "NameDisplay", + "NameDnsDomain", + "NameFullyQualifiedDN", + "NameSamCompatible", + "NameServicePrincipal", + "NameUniqueId", + "NameUnknown", + "NameUserPrincipal", + "Nanosleep", + "NetApiBufferFree", + "NetGetJoinInformation", + "NetSetupDomainName", + "NetSetupUnjoined", + "NetSetupUnknownStatus", + "NetSetupWorkgroupName", + "NetUserGetInfo", + "NetlinkMessage", + "NetlinkRIB", + "NetlinkRouteAttr", + "NetlinkRouteRequest", + "NewCallback", + "NewCallbackCDecl", + "NewLazyDLL", + "NlAttr", + "NlMsgerr", + "NlMsghdr", + "NsecToFiletime", + "NsecToTimespec", + "NsecToTimeval", + "Ntohs", + "OCRNL", + "OFDEL", + "OFILL", + "OFIOGETBMAP", + "OID_PKIX_KP_SERVER_AUTH", + "OID_SERVER_GATED_CRYPTO", + "OID_SGC_NETSCAPE", + "OLCUC", + "ONLCR", + "ONLRET", + "ONOCR", + "ONOEOT", + "OPEN_ALWAYS", + "OPEN_EXISTING", + "OPOST", + "O_ACCMODE", + "O_ALERT", + "O_ALT_IO", + "O_APPEND", + "O_ASYNC", + "O_CLOEXEC", + "O_CREAT", + "O_DIRECT", + "O_DIRECTORY", + "O_DSYNC", + "O_EVTONLY", + "O_EXCL", + "O_EXEC", + "O_EXLOCK", + "O_FSYNC", + "O_LARGEFILE", + "O_NDELAY", + "O_NOATIME", + "O_NOCTTY", + "O_NOFOLLOW", + "O_NONBLOCK", + "O_NOSIGPIPE", + "O_POPUP", + "O_RDONLY", + "O_RDWR", + "O_RSYNC", + "O_SHLOCK", + "O_SYMLINK", + "O_SYNC", + "O_TRUNC", + "O_TTY_INIT", + "O_WRONLY", + "Open", + "OpenCurrentProcessToken", + "OpenProcess", + "OpenProcessToken", + "Openat", + "Overlapped", + "PACKET_ADD_MEMBERSHIP", + "PACKET_BROADCAST", + "PACKET_DROP_MEMBERSHIP", + "PACKET_FASTROUTE", + "PACKET_HOST", + "PACKET_LOOPBACK", + "PACKET_MR_ALLMULTI", + "PACKET_MR_MULTICAST", + "PACKET_MR_PROMISC", + "PACKET_MULTICAST", + "PACKET_OTHERHOST", + "PACKET_OUTGOING", + "PACKET_RECV_OUTPUT", + "PACKET_RX_RING", + "PACKET_STATISTICS", + "PAGE_EXECUTE_READ", + "PAGE_EXECUTE_READWRITE", + "PAGE_EXECUTE_WRITECOPY", + "PAGE_READONLY", + "PAGE_READWRITE", + "PAGE_WRITECOPY", + "PARENB", + "PARMRK", + "PARODD", + "PENDIN", + "PFL_HIDDEN", + "PFL_MATCHES_PROTOCOL_ZERO", + "PFL_MULTIPLE_PROTO_ENTRIES", + "PFL_NETWORKDIRECT_PROVIDER", + "PFL_RECOMMENDED_PROTO_ENTRY", + "PF_FLUSH", + "PKCS_7_ASN_ENCODING", + "PMC5_PIPELINE_FLUSH", + "PRIO_PGRP", + "PRIO_PROCESS", + "PRIO_USER", + "PRI_IOFLUSH", + "PROCESS_QUERY_INFORMATION", + "PROCESS_TERMINATE", + "PROT_EXEC", + "PROT_GROWSDOWN", + "PROT_GROWSUP", + "PROT_NONE", + "PROT_READ", + "PROT_WRITE", + "PROV_DH_SCHANNEL", + "PROV_DSS", + "PROV_DSS_DH", + "PROV_EC_ECDSA_FULL", + "PROV_EC_ECDSA_SIG", + "PROV_EC_ECNRA_FULL", + "PROV_EC_ECNRA_SIG", + "PROV_FORTEZZA", + "PROV_INTEL_SEC", + "PROV_MS_EXCHANGE", + "PROV_REPLACE_OWF", + "PROV_RNG", + "PROV_RSA_AES", + "PROV_RSA_FULL", + "PROV_RSA_SCHANNEL", + "PROV_RSA_SIG", + "PROV_SPYRUS_LYNKS", + "PROV_SSL", + "PR_CAPBSET_DROP", + "PR_CAPBSET_READ", + "PR_CLEAR_SECCOMP_FILTER", + "PR_ENDIAN_BIG", + "PR_ENDIAN_LITTLE", + "PR_ENDIAN_PPC_LITTLE", + "PR_FPEMU_NOPRINT", + "PR_FPEMU_SIGFPE", + "PR_FP_EXC_ASYNC", + "PR_FP_EXC_DISABLED", + "PR_FP_EXC_DIV", + "PR_FP_EXC_INV", + "PR_FP_EXC_NONRECOV", + "PR_FP_EXC_OVF", + "PR_FP_EXC_PRECISE", + "PR_FP_EXC_RES", + "PR_FP_EXC_SW_ENABLE", + "PR_FP_EXC_UND", + "PR_GET_DUMPABLE", + "PR_GET_ENDIAN", + "PR_GET_FPEMU", + "PR_GET_FPEXC", + "PR_GET_KEEPCAPS", + "PR_GET_NAME", + "PR_GET_PDEATHSIG", + "PR_GET_SECCOMP", + "PR_GET_SECCOMP_FILTER", + "PR_GET_SECUREBITS", + "PR_GET_TIMERSLACK", + "PR_GET_TIMING", + "PR_GET_TSC", + "PR_GET_UNALIGN", + "PR_MCE_KILL", + "PR_MCE_KILL_CLEAR", + "PR_MCE_KILL_DEFAULT", + "PR_MCE_KILL_EARLY", + "PR_MCE_KILL_GET", + "PR_MCE_KILL_LATE", + "PR_MCE_KILL_SET", + "PR_SECCOMP_FILTER_EVENT", + "PR_SECCOMP_FILTER_SYSCALL", + "PR_SET_DUMPABLE", + "PR_SET_ENDIAN", + "PR_SET_FPEMU", + "PR_SET_FPEXC", + "PR_SET_KEEPCAPS", + "PR_SET_NAME", + "PR_SET_PDEATHSIG", + "PR_SET_PTRACER", + "PR_SET_SECCOMP", + "PR_SET_SECCOMP_FILTER", + "PR_SET_SECUREBITS", + "PR_SET_TIMERSLACK", + "PR_SET_TIMING", + "PR_SET_TSC", + "PR_SET_UNALIGN", + "PR_TASK_PERF_EVENTS_DISABLE", + "PR_TASK_PERF_EVENTS_ENABLE", + "PR_TIMING_STATISTICAL", + "PR_TIMING_TIMESTAMP", + "PR_TSC_ENABLE", + "PR_TSC_SIGSEGV", + "PR_UNALIGN_NOPRINT", + "PR_UNALIGN_SIGBUS", + "PTRACE_ARCH_PRCTL", + "PTRACE_ATTACH", + "PTRACE_CONT", + "PTRACE_DETACH", + "PTRACE_EVENT_CLONE", + "PTRACE_EVENT_EXEC", + "PTRACE_EVENT_EXIT", + "PTRACE_EVENT_FORK", + "PTRACE_EVENT_VFORK", + "PTRACE_EVENT_VFORK_DONE", + "PTRACE_GETCRUNCHREGS", + "PTRACE_GETEVENTMSG", + "PTRACE_GETFPREGS", + "PTRACE_GETFPXREGS", + "PTRACE_GETHBPREGS", + "PTRACE_GETREGS", + "PTRACE_GETREGSET", + "PTRACE_GETSIGINFO", + "PTRACE_GETVFPREGS", + "PTRACE_GETWMMXREGS", + "PTRACE_GET_THREAD_AREA", + "PTRACE_KILL", + "PTRACE_OLDSETOPTIONS", + "PTRACE_O_MASK", + "PTRACE_O_TRACECLONE", + "PTRACE_O_TRACEEXEC", + "PTRACE_O_TRACEEXIT", + "PTRACE_O_TRACEFORK", + "PTRACE_O_TRACESYSGOOD", + "PTRACE_O_TRACEVFORK", + "PTRACE_O_TRACEVFORKDONE", + "PTRACE_PEEKDATA", + "PTRACE_PEEKTEXT", + "PTRACE_PEEKUSR", + "PTRACE_POKEDATA", + "PTRACE_POKETEXT", + "PTRACE_POKEUSR", + "PTRACE_SETCRUNCHREGS", + "PTRACE_SETFPREGS", + "PTRACE_SETFPXREGS", + "PTRACE_SETHBPREGS", + "PTRACE_SETOPTIONS", + "PTRACE_SETREGS", + "PTRACE_SETREGSET", + "PTRACE_SETSIGINFO", + "PTRACE_SETVFPREGS", + "PTRACE_SETWMMXREGS", + "PTRACE_SET_SYSCALL", + "PTRACE_SET_THREAD_AREA", + "PTRACE_SINGLEBLOCK", + "PTRACE_SINGLESTEP", + "PTRACE_SYSCALL", + "PTRACE_SYSEMU", + "PTRACE_SYSEMU_SINGLESTEP", + "PTRACE_TRACEME", + "PT_ATTACH", + "PT_ATTACHEXC", + "PT_CONTINUE", + "PT_DATA_ADDR", + "PT_DENY_ATTACH", + "PT_DETACH", + "PT_FIRSTMACH", + "PT_FORCEQUOTA", + "PT_KILL", + "PT_MASK", + "PT_READ_D", + "PT_READ_I", + "PT_READ_U", + "PT_SIGEXC", + "PT_STEP", + "PT_TEXT_ADDR", + "PT_TEXT_END_ADDR", + "PT_THUPDATE", + "PT_TRACE_ME", + "PT_WRITE_D", + "PT_WRITE_I", + "PT_WRITE_U", + "ParseDirent", + "ParseNetlinkMessage", + "ParseNetlinkRouteAttr", + "ParseRoutingMessage", + "ParseRoutingSockaddr", + "ParseSocketControlMessage", + "ParseUnixCredentials", + "ParseUnixRights", + "PathMax", + "Pathconf", + "Pause", + "Pipe", + "Pipe2", + "PivotRoot", + "Pointer", + "PostQueuedCompletionStatus", + "Pread", + "Proc", + "ProcAttr", + "Process32First", + "Process32Next", + "ProcessEntry32", + "ProcessInformation", + "Protoent", + "PtraceAttach", + "PtraceCont", + "PtraceDetach", + "PtraceGetEventMsg", + "PtraceGetRegs", + "PtracePeekData", + "PtracePeekText", + "PtracePokeData", + "PtracePokeText", + "PtraceRegs", + "PtraceSetOptions", + "PtraceSetRegs", + "PtraceSingleStep", + "PtraceSyscall", + "Pwrite", + "REG_BINARY", + "REG_DWORD", + "REG_DWORD_BIG_ENDIAN", + "REG_DWORD_LITTLE_ENDIAN", + "REG_EXPAND_SZ", + "REG_FULL_RESOURCE_DESCRIPTOR", + "REG_LINK", + "REG_MULTI_SZ", + "REG_NONE", + "REG_QWORD", + "REG_QWORD_LITTLE_ENDIAN", + "REG_RESOURCE_LIST", + "REG_RESOURCE_REQUIREMENTS_LIST", + "REG_SZ", + "RLIMIT_AS", + "RLIMIT_CORE", + "RLIMIT_CPU", + "RLIMIT_DATA", + "RLIMIT_FSIZE", + "RLIMIT_NOFILE", + "RLIMIT_STACK", + "RLIM_INFINITY", + "RTAX_ADVMSS", + "RTAX_AUTHOR", + "RTAX_BRD", + "RTAX_CWND", + "RTAX_DST", + "RTAX_FEATURES", + "RTAX_FEATURE_ALLFRAG", + "RTAX_FEATURE_ECN", + "RTAX_FEATURE_SACK", + "RTAX_FEATURE_TIMESTAMP", + "RTAX_GATEWAY", + "RTAX_GENMASK", + "RTAX_HOPLIMIT", + "RTAX_IFA", + "RTAX_IFP", + "RTAX_INITCWND", + "RTAX_INITRWND", + "RTAX_LABEL", + "RTAX_LOCK", + "RTAX_MAX", + "RTAX_MTU", + "RTAX_NETMASK", + "RTAX_REORDERING", + "RTAX_RTO_MIN", + "RTAX_RTT", + "RTAX_RTTVAR", + "RTAX_SRC", + "RTAX_SRCMASK", + "RTAX_SSTHRESH", + "RTAX_TAG", + "RTAX_UNSPEC", + "RTAX_WINDOW", + "RTA_ALIGNTO", + "RTA_AUTHOR", + "RTA_BRD", + "RTA_CACHEINFO", + "RTA_DST", + "RTA_FLOW", + "RTA_GATEWAY", + "RTA_GENMASK", + "RTA_IFA", + "RTA_IFP", + "RTA_IIF", + "RTA_LABEL", + "RTA_MAX", + "RTA_METRICS", + "RTA_MULTIPATH", + "RTA_NETMASK", + "RTA_OIF", + "RTA_PREFSRC", + "RTA_PRIORITY", + "RTA_SRC", + "RTA_SRCMASK", + "RTA_TABLE", + "RTA_TAG", + "RTA_UNSPEC", + "RTCF_DIRECTSRC", + "RTCF_DOREDIRECT", + "RTCF_LOG", + "RTCF_MASQ", + "RTCF_NAT", + "RTCF_VALVE", + "RTF_ADDRCLASSMASK", + "RTF_ADDRCONF", + "RTF_ALLONLINK", + "RTF_ANNOUNCE", + "RTF_BLACKHOLE", + "RTF_BROADCAST", + "RTF_CACHE", + "RTF_CLONED", + "RTF_CLONING", + "RTF_CONDEMNED", + "RTF_DEFAULT", + "RTF_DELCLONE", + "RTF_DONE", + "RTF_DYNAMIC", + "RTF_FLOW", + "RTF_FMASK", + "RTF_GATEWAY", + "RTF_GWFLAG_COMPAT", + "RTF_HOST", + "RTF_IFREF", + "RTF_IFSCOPE", + "RTF_INTERFACE", + "RTF_IRTT", + "RTF_LINKRT", + "RTF_LLDATA", + "RTF_LLINFO", + "RTF_LOCAL", + "RTF_MASK", + "RTF_MODIFIED", + "RTF_MPATH", + "RTF_MPLS", + "RTF_MSS", + "RTF_MTU", + "RTF_MULTICAST", + "RTF_NAT", + "RTF_NOFORWARD", + "RTF_NONEXTHOP", + "RTF_NOPMTUDISC", + "RTF_PERMANENT_ARP", + "RTF_PINNED", + "RTF_POLICY", + "RTF_PRCLONING", + "RTF_PROTO1", + "RTF_PROTO2", + "RTF_PROTO3", + "RTF_REINSTATE", + "RTF_REJECT", + "RTF_RNH_LOCKED", + "RTF_SOURCE", + "RTF_SRC", + "RTF_STATIC", + "RTF_STICKY", + "RTF_THROW", + "RTF_TUNNEL", + "RTF_UP", + "RTF_USETRAILERS", + "RTF_WASCLONED", + "RTF_WINDOW", + "RTF_XRESOLVE", + "RTM_ADD", + "RTM_BASE", + "RTM_CHANGE", + "RTM_CHGADDR", + "RTM_DELACTION", + "RTM_DELADDR", + "RTM_DELADDRLABEL", + "RTM_DELETE", + "RTM_DELLINK", + "RTM_DELMADDR", + "RTM_DELNEIGH", + "RTM_DELQDISC", + "RTM_DELROUTE", + "RTM_DELRULE", + "RTM_DELTCLASS", + "RTM_DELTFILTER", + "RTM_DESYNC", + "RTM_F_CLONED", + "RTM_F_EQUALIZE", + "RTM_F_NOTIFY", + "RTM_F_PREFIX", + "RTM_GET", + "RTM_GET2", + "RTM_GETACTION", + "RTM_GETADDR", + "RTM_GETADDRLABEL", + "RTM_GETANYCAST", + "RTM_GETDCB", + "RTM_GETLINK", + "RTM_GETMULTICAST", + "RTM_GETNEIGH", + "RTM_GETNEIGHTBL", + "RTM_GETQDISC", + "RTM_GETROUTE", + "RTM_GETRULE", + "RTM_GETTCLASS", + "RTM_GETTFILTER", + "RTM_IEEE80211", + "RTM_IFANNOUNCE", + "RTM_IFINFO", + "RTM_IFINFO2", + "RTM_LLINFO_UPD", + "RTM_LOCK", + "RTM_LOSING", + "RTM_MAX", + "RTM_MAXSIZE", + "RTM_MISS", + "RTM_NEWACTION", + "RTM_NEWADDR", + "RTM_NEWADDRLABEL", + "RTM_NEWLINK", + "RTM_NEWMADDR", + "RTM_NEWMADDR2", + "RTM_NEWNDUSEROPT", + "RTM_NEWNEIGH", + "RTM_NEWNEIGHTBL", + "RTM_NEWPREFIX", + "RTM_NEWQDISC", + "RTM_NEWROUTE", + "RTM_NEWRULE", + "RTM_NEWTCLASS", + "RTM_NEWTFILTER", + "RTM_NR_FAMILIES", + "RTM_NR_MSGTYPES", + "RTM_OIFINFO", + "RTM_OLDADD", + "RTM_OLDDEL", + "RTM_OOIFINFO", + "RTM_REDIRECT", + "RTM_RESOLVE", + "RTM_RTTUNIT", + "RTM_SETDCB", + "RTM_SETGATE", + "RTM_SETLINK", + "RTM_SETNEIGHTBL", + "RTM_VERSION", + "RTNH_ALIGNTO", + "RTNH_F_DEAD", + "RTNH_F_ONLINK", + "RTNH_F_PERVASIVE", + "RTNLGRP_IPV4_IFADDR", + "RTNLGRP_IPV4_MROUTE", + "RTNLGRP_IPV4_ROUTE", + "RTNLGRP_IPV4_RULE", + "RTNLGRP_IPV6_IFADDR", + "RTNLGRP_IPV6_IFINFO", + "RTNLGRP_IPV6_MROUTE", + "RTNLGRP_IPV6_PREFIX", + "RTNLGRP_IPV6_ROUTE", + "RTNLGRP_IPV6_RULE", + "RTNLGRP_LINK", + "RTNLGRP_ND_USEROPT", + "RTNLGRP_NEIGH", + "RTNLGRP_NONE", + "RTNLGRP_NOTIFY", + "RTNLGRP_TC", + "RTN_ANYCAST", + "RTN_BLACKHOLE", + "RTN_BROADCAST", + "RTN_LOCAL", + "RTN_MAX", + "RTN_MULTICAST", + "RTN_NAT", + "RTN_PROHIBIT", + "RTN_THROW", + "RTN_UNICAST", + "RTN_UNREACHABLE", + "RTN_UNSPEC", + "RTN_XRESOLVE", + "RTPROT_BIRD", + "RTPROT_BOOT", + "RTPROT_DHCP", + "RTPROT_DNROUTED", + "RTPROT_GATED", + "RTPROT_KERNEL", + "RTPROT_MRT", + "RTPROT_NTK", + "RTPROT_RA", + "RTPROT_REDIRECT", + "RTPROT_STATIC", + "RTPROT_UNSPEC", + "RTPROT_XORP", + "RTPROT_ZEBRA", + "RTV_EXPIRE", + "RTV_HOPCOUNT", + "RTV_MTU", + "RTV_RPIPE", + "RTV_RTT", + "RTV_RTTVAR", + "RTV_SPIPE", + "RTV_SSTHRESH", + "RTV_WEIGHT", + "RT_CACHING_CONTEXT", + "RT_CLASS_DEFAULT", + "RT_CLASS_LOCAL", + "RT_CLASS_MAIN", + "RT_CLASS_MAX", + "RT_CLASS_UNSPEC", + "RT_DEFAULT_FIB", + "RT_NORTREF", + "RT_SCOPE_HOST", + "RT_SCOPE_LINK", + "RT_SCOPE_NOWHERE", + "RT_SCOPE_SITE", + "RT_SCOPE_UNIVERSE", + "RT_TABLEID_MAX", + "RT_TABLE_COMPAT", + "RT_TABLE_DEFAULT", + "RT_TABLE_LOCAL", + "RT_TABLE_MAIN", + "RT_TABLE_MAX", + "RT_TABLE_UNSPEC", + "RUSAGE_CHILDREN", + "RUSAGE_SELF", + "RUSAGE_THREAD", + "Radvisory_t", + "RawConn", + "RawSockaddr", + "RawSockaddrAny", + "RawSockaddrDatalink", + "RawSockaddrInet4", + "RawSockaddrInet6", + "RawSockaddrLinklayer", + "RawSockaddrNetlink", + "RawSockaddrUnix", + "RawSyscall", + "RawSyscall6", + "Read", + "ReadConsole", + "ReadDirectoryChanges", + "ReadDirent", + "ReadFile", + "Readlink", + "Reboot", + "Recvfrom", + "Recvmsg", + "RegCloseKey", + "RegEnumKeyEx", + "RegOpenKeyEx", + "RegQueryInfoKey", + "RegQueryValueEx", + "RemoveDirectory", + "Removexattr", + "Rename", + "Renameat", + "Revoke", + "Rlimit", + "Rmdir", + "RouteMessage", + "RouteRIB", + "RoutingMessage", + "RtAttr", + "RtGenmsg", + "RtMetrics", + "RtMsg", + "RtMsghdr", + "RtNexthop", + "Rusage", + "SCM_BINTIME", + "SCM_CREDENTIALS", + "SCM_CREDS", + "SCM_RIGHTS", + "SCM_TIMESTAMP", + "SCM_TIMESTAMPING", + "SCM_TIMESTAMPNS", + "SCM_TIMESTAMP_MONOTONIC", + "SHUT_RD", + "SHUT_RDWR", + "SHUT_WR", + "SID", + "SIDAndAttributes", + "SIGABRT", + "SIGALRM", + "SIGBUS", + "SIGCHLD", + "SIGCLD", + "SIGCONT", + "SIGEMT", + "SIGFPE", + "SIGHUP", + "SIGILL", + "SIGINFO", + "SIGINT", + "SIGIO", + "SIGIOT", + "SIGKILL", + "SIGLIBRT", + "SIGLWP", + "SIGPIPE", + "SIGPOLL", + "SIGPROF", + "SIGPWR", + "SIGQUIT", + "SIGSEGV", + "SIGSTKFLT", + "SIGSTOP", + "SIGSYS", + "SIGTERM", + "SIGTHR", + "SIGTRAP", + "SIGTSTP", + "SIGTTIN", + "SIGTTOU", + "SIGUNUSED", + "SIGURG", + "SIGUSR1", + "SIGUSR2", + "SIGVTALRM", + "SIGWINCH", + "SIGXCPU", + "SIGXFSZ", + "SIOCADDDLCI", + "SIOCADDMULTI", + "SIOCADDRT", + "SIOCAIFADDR", + "SIOCAIFGROUP", + "SIOCALIFADDR", + "SIOCARPIPLL", + "SIOCATMARK", + "SIOCAUTOADDR", + "SIOCAUTONETMASK", + "SIOCBRDGADD", + "SIOCBRDGADDS", + "SIOCBRDGARL", + "SIOCBRDGDADDR", + "SIOCBRDGDEL", + "SIOCBRDGDELS", + "SIOCBRDGFLUSH", + "SIOCBRDGFRL", + "SIOCBRDGGCACHE", + "SIOCBRDGGFD", + "SIOCBRDGGHT", + "SIOCBRDGGIFFLGS", + "SIOCBRDGGMA", + "SIOCBRDGGPARAM", + "SIOCBRDGGPRI", + "SIOCBRDGGRL", + "SIOCBRDGGSIFS", + "SIOCBRDGGTO", + "SIOCBRDGIFS", + "SIOCBRDGRTS", + "SIOCBRDGSADDR", + "SIOCBRDGSCACHE", + "SIOCBRDGSFD", + "SIOCBRDGSHT", + "SIOCBRDGSIFCOST", + "SIOCBRDGSIFFLGS", + "SIOCBRDGSIFPRIO", + "SIOCBRDGSMA", + "SIOCBRDGSPRI", + "SIOCBRDGSPROTO", + "SIOCBRDGSTO", + "SIOCBRDGSTXHC", + "SIOCDARP", + "SIOCDELDLCI", + "SIOCDELMULTI", + "SIOCDELRT", + "SIOCDEVPRIVATE", + "SIOCDIFADDR", + "SIOCDIFGROUP", + "SIOCDIFPHYADDR", + "SIOCDLIFADDR", + "SIOCDRARP", + "SIOCGARP", + "SIOCGDRVSPEC", + "SIOCGETKALIVE", + "SIOCGETLABEL", + "SIOCGETPFLOW", + "SIOCGETPFSYNC", + "SIOCGETSGCNT", + "SIOCGETVIFCNT", + "SIOCGETVLAN", + "SIOCGHIWAT", + "SIOCGIFADDR", + "SIOCGIFADDRPREF", + "SIOCGIFALIAS", + "SIOCGIFALTMTU", + "SIOCGIFASYNCMAP", + "SIOCGIFBOND", + "SIOCGIFBR", + "SIOCGIFBRDADDR", + "SIOCGIFCAP", + "SIOCGIFCONF", + "SIOCGIFCOUNT", + "SIOCGIFDATA", + "SIOCGIFDESCR", + "SIOCGIFDEVMTU", + "SIOCGIFDLT", + "SIOCGIFDSTADDR", + "SIOCGIFENCAP", + "SIOCGIFFIB", + "SIOCGIFFLAGS", + "SIOCGIFGATTR", + "SIOCGIFGENERIC", + "SIOCGIFGMEMB", + "SIOCGIFGROUP", + "SIOCGIFHARDMTU", + "SIOCGIFHWADDR", + "SIOCGIFINDEX", + "SIOCGIFKPI", + "SIOCGIFMAC", + "SIOCGIFMAP", + "SIOCGIFMEDIA", + "SIOCGIFMEM", + "SIOCGIFMETRIC", + "SIOCGIFMTU", + "SIOCGIFNAME", + "SIOCGIFNETMASK", + "SIOCGIFPDSTADDR", + "SIOCGIFPFLAGS", + "SIOCGIFPHYS", + "SIOCGIFPRIORITY", + "SIOCGIFPSRCADDR", + "SIOCGIFRDOMAIN", + "SIOCGIFRTLABEL", + "SIOCGIFSLAVE", + "SIOCGIFSTATUS", + "SIOCGIFTIMESLOT", + "SIOCGIFTXQLEN", + "SIOCGIFVLAN", + "SIOCGIFWAKEFLAGS", + "SIOCGIFXFLAGS", + "SIOCGLIFADDR", + "SIOCGLIFPHYADDR", + "SIOCGLIFPHYRTABLE", + "SIOCGLIFPHYTTL", + "SIOCGLINKSTR", + "SIOCGLOWAT", + "SIOCGPGRP", + "SIOCGPRIVATE_0", + "SIOCGPRIVATE_1", + "SIOCGRARP", + "SIOCGSPPPPARAMS", + "SIOCGSTAMP", + "SIOCGSTAMPNS", + "SIOCGVH", + "SIOCGVNETID", + "SIOCIFCREATE", + "SIOCIFCREATE2", + "SIOCIFDESTROY", + "SIOCIFGCLONERS", + "SIOCINITIFADDR", + "SIOCPROTOPRIVATE", + "SIOCRSLVMULTI", + "SIOCRTMSG", + "SIOCSARP", + "SIOCSDRVSPEC", + "SIOCSETKALIVE", + "SIOCSETLABEL", + "SIOCSETPFLOW", + "SIOCSETPFSYNC", + "SIOCSETVLAN", + "SIOCSHIWAT", + "SIOCSIFADDR", + "SIOCSIFADDRPREF", + "SIOCSIFALTMTU", + "SIOCSIFASYNCMAP", + "SIOCSIFBOND", + "SIOCSIFBR", + "SIOCSIFBRDADDR", + "SIOCSIFCAP", + "SIOCSIFDESCR", + "SIOCSIFDSTADDR", + "SIOCSIFENCAP", + "SIOCSIFFIB", + "SIOCSIFFLAGS", + "SIOCSIFGATTR", + "SIOCSIFGENERIC", + "SIOCSIFHWADDR", + "SIOCSIFHWBROADCAST", + "SIOCSIFKPI", + "SIOCSIFLINK", + "SIOCSIFLLADDR", + "SIOCSIFMAC", + "SIOCSIFMAP", + "SIOCSIFMEDIA", + "SIOCSIFMEM", + "SIOCSIFMETRIC", + "SIOCSIFMTU", + "SIOCSIFNAME", + "SIOCSIFNETMASK", + "SIOCSIFPFLAGS", + "SIOCSIFPHYADDR", + "SIOCSIFPHYS", + "SIOCSIFPRIORITY", + "SIOCSIFRDOMAIN", + "SIOCSIFRTLABEL", + "SIOCSIFRVNET", + "SIOCSIFSLAVE", + "SIOCSIFTIMESLOT", + "SIOCSIFTXQLEN", + "SIOCSIFVLAN", + "SIOCSIFVNET", + "SIOCSIFXFLAGS", + "SIOCSLIFPHYADDR", + "SIOCSLIFPHYRTABLE", + "SIOCSLIFPHYTTL", + "SIOCSLINKSTR", + "SIOCSLOWAT", + "SIOCSPGRP", + "SIOCSRARP", + "SIOCSSPPPPARAMS", + "SIOCSVH", + "SIOCSVNETID", + "SIOCZIFDATA", + "SIO_GET_EXTENSION_FUNCTION_POINTER", + "SIO_GET_INTERFACE_LIST", + "SIO_KEEPALIVE_VALS", + "SIO_UDP_CONNRESET", + "SOCK_CLOEXEC", + "SOCK_DCCP", + "SOCK_DGRAM", + "SOCK_FLAGS_MASK", + "SOCK_MAXADDRLEN", + "SOCK_NONBLOCK", + "SOCK_NOSIGPIPE", + "SOCK_PACKET", + "SOCK_RAW", + "SOCK_RDM", + "SOCK_SEQPACKET", + "SOCK_STREAM", + "SOL_AAL", + "SOL_ATM", + "SOL_DECNET", + "SOL_ICMPV6", + "SOL_IP", + "SOL_IPV6", + "SOL_IRDA", + "SOL_PACKET", + "SOL_RAW", + "SOL_SOCKET", + "SOL_TCP", + "SOL_X25", + "SOMAXCONN", + "SO_ACCEPTCONN", + "SO_ACCEPTFILTER", + "SO_ATTACH_FILTER", + "SO_BINDANY", + "SO_BINDTODEVICE", + "SO_BINTIME", + "SO_BROADCAST", + "SO_BSDCOMPAT", + "SO_DEBUG", + "SO_DETACH_FILTER", + "SO_DOMAIN", + "SO_DONTROUTE", + "SO_DONTTRUNC", + "SO_ERROR", + "SO_KEEPALIVE", + "SO_LABEL", + "SO_LINGER", + "SO_LINGER_SEC", + "SO_LISTENINCQLEN", + "SO_LISTENQLEN", + "SO_LISTENQLIMIT", + "SO_MARK", + "SO_NETPROC", + "SO_NKE", + "SO_NOADDRERR", + "SO_NOHEADER", + "SO_NOSIGPIPE", + "SO_NOTIFYCONFLICT", + "SO_NO_CHECK", + "SO_NO_DDP", + "SO_NO_OFFLOAD", + "SO_NP_EXTENSIONS", + "SO_NREAD", + "SO_NWRITE", + "SO_OOBINLINE", + "SO_OVERFLOWED", + "SO_PASSCRED", + "SO_PASSSEC", + "SO_PEERCRED", + "SO_PEERLABEL", + "SO_PEERNAME", + "SO_PEERSEC", + "SO_PRIORITY", + "SO_PROTOCOL", + "SO_PROTOTYPE", + "SO_RANDOMPORT", + "SO_RCVBUF", + "SO_RCVBUFFORCE", + "SO_RCVLOWAT", + "SO_RCVTIMEO", + "SO_RESTRICTIONS", + "SO_RESTRICT_DENYIN", + "SO_RESTRICT_DENYOUT", + "SO_RESTRICT_DENYSET", + "SO_REUSEADDR", + "SO_REUSEPORT", + "SO_REUSESHAREUID", + "SO_RTABLE", + "SO_RXQ_OVFL", + "SO_SECURITY_AUTHENTICATION", + "SO_SECURITY_ENCRYPTION_NETWORK", + "SO_SECURITY_ENCRYPTION_TRANSPORT", + "SO_SETFIB", + "SO_SNDBUF", + "SO_SNDBUFFORCE", + "SO_SNDLOWAT", + "SO_SNDTIMEO", + "SO_SPLICE", + "SO_TIMESTAMP", + "SO_TIMESTAMPING", + "SO_TIMESTAMPNS", + "SO_TIMESTAMP_MONOTONIC", + "SO_TYPE", + "SO_UPCALLCLOSEWAIT", + "SO_UPDATE_ACCEPT_CONTEXT", + "SO_UPDATE_CONNECT_CONTEXT", + "SO_USELOOPBACK", + "SO_USER_COOKIE", + "SO_VENDOR", + "SO_WANTMORE", + "SO_WANTOOBFLAG", + "SSLExtraCertChainPolicyPara", + "STANDARD_RIGHTS_ALL", + "STANDARD_RIGHTS_EXECUTE", + "STANDARD_RIGHTS_READ", + "STANDARD_RIGHTS_REQUIRED", + "STANDARD_RIGHTS_WRITE", + "STARTF_USESHOWWINDOW", + "STARTF_USESTDHANDLES", + "STD_ERROR_HANDLE", + "STD_INPUT_HANDLE", + "STD_OUTPUT_HANDLE", + "SUBLANG_ENGLISH_US", + "SW_FORCEMINIMIZE", + "SW_HIDE", + "SW_MAXIMIZE", + "SW_MINIMIZE", + "SW_NORMAL", + "SW_RESTORE", + "SW_SHOW", + "SW_SHOWDEFAULT", + "SW_SHOWMAXIMIZED", + "SW_SHOWMINIMIZED", + "SW_SHOWMINNOACTIVE", + "SW_SHOWNA", + "SW_SHOWNOACTIVATE", + "SW_SHOWNORMAL", + "SYMBOLIC_LINK_FLAG_DIRECTORY", + "SYNCHRONIZE", + "SYSCTL_VERSION", + "SYSCTL_VERS_0", + "SYSCTL_VERS_1", + "SYSCTL_VERS_MASK", + "SYS_ABORT2", + "SYS_ACCEPT", + "SYS_ACCEPT4", + "SYS_ACCEPT_NOCANCEL", + "SYS_ACCESS", + "SYS_ACCESS_EXTENDED", + "SYS_ACCT", + "SYS_ADD_KEY", + "SYS_ADD_PROFIL", + "SYS_ADJFREQ", + "SYS_ADJTIME", + "SYS_ADJTIMEX", + "SYS_AFS_SYSCALL", + "SYS_AIO_CANCEL", + "SYS_AIO_ERROR", + "SYS_AIO_FSYNC", + "SYS_AIO_READ", + "SYS_AIO_RETURN", + "SYS_AIO_SUSPEND", + "SYS_AIO_SUSPEND_NOCANCEL", + "SYS_AIO_WRITE", + "SYS_ALARM", + "SYS_ARCH_PRCTL", + "SYS_ARM_FADVISE64_64", + "SYS_ARM_SYNC_FILE_RANGE", + "SYS_ATGETMSG", + "SYS_ATPGETREQ", + "SYS_ATPGETRSP", + "SYS_ATPSNDREQ", + "SYS_ATPSNDRSP", + "SYS_ATPUTMSG", + "SYS_ATSOCKET", + "SYS_AUDIT", + "SYS_AUDITCTL", + "SYS_AUDITON", + "SYS_AUDIT_SESSION_JOIN", + "SYS_AUDIT_SESSION_PORT", + "SYS_AUDIT_SESSION_SELF", + "SYS_BDFLUSH", + "SYS_BIND", + "SYS_BINDAT", + "SYS_BREAK", + "SYS_BRK", + "SYS_BSDTHREAD_CREATE", + "SYS_BSDTHREAD_REGISTER", + "SYS_BSDTHREAD_TERMINATE", + "SYS_CAPGET", + "SYS_CAPSET", + "SYS_CAP_ENTER", + "SYS_CAP_FCNTLS_GET", + "SYS_CAP_FCNTLS_LIMIT", + "SYS_CAP_GETMODE", + "SYS_CAP_GETRIGHTS", + "SYS_CAP_IOCTLS_GET", + "SYS_CAP_IOCTLS_LIMIT", + "SYS_CAP_NEW", + "SYS_CAP_RIGHTS_GET", + "SYS_CAP_RIGHTS_LIMIT", + "SYS_CHDIR", + "SYS_CHFLAGS", + "SYS_CHFLAGSAT", + "SYS_CHMOD", + "SYS_CHMOD_EXTENDED", + "SYS_CHOWN", + "SYS_CHOWN32", + "SYS_CHROOT", + "SYS_CHUD", + "SYS_CLOCK_ADJTIME", + "SYS_CLOCK_GETCPUCLOCKID2", + "SYS_CLOCK_GETRES", + "SYS_CLOCK_GETTIME", + "SYS_CLOCK_NANOSLEEP", + "SYS_CLOCK_SETTIME", + "SYS_CLONE", + "SYS_CLOSE", + "SYS_CLOSEFROM", + "SYS_CLOSE_NOCANCEL", + "SYS_CONNECT", + "SYS_CONNECTAT", + "SYS_CONNECT_NOCANCEL", + "SYS_COPYFILE", + "SYS_CPUSET", + "SYS_CPUSET_GETAFFINITY", + "SYS_CPUSET_GETID", + "SYS_CPUSET_SETAFFINITY", + "SYS_CPUSET_SETID", + "SYS_CREAT", + "SYS_CREATE_MODULE", + "SYS_CSOPS", + "SYS_DELETE", + "SYS_DELETE_MODULE", + "SYS_DUP", + "SYS_DUP2", + "SYS_DUP3", + "SYS_EACCESS", + "SYS_EPOLL_CREATE", + "SYS_EPOLL_CREATE1", + "SYS_EPOLL_CTL", + "SYS_EPOLL_CTL_OLD", + "SYS_EPOLL_PWAIT", + "SYS_EPOLL_WAIT", + "SYS_EPOLL_WAIT_OLD", + "SYS_EVENTFD", + "SYS_EVENTFD2", + "SYS_EXCHANGEDATA", + "SYS_EXECVE", + "SYS_EXIT", + "SYS_EXIT_GROUP", + "SYS_EXTATTRCTL", + "SYS_EXTATTR_DELETE_FD", + "SYS_EXTATTR_DELETE_FILE", + "SYS_EXTATTR_DELETE_LINK", + "SYS_EXTATTR_GET_FD", + "SYS_EXTATTR_GET_FILE", + "SYS_EXTATTR_GET_LINK", + "SYS_EXTATTR_LIST_FD", + "SYS_EXTATTR_LIST_FILE", + "SYS_EXTATTR_LIST_LINK", + "SYS_EXTATTR_SET_FD", + "SYS_EXTATTR_SET_FILE", + "SYS_EXTATTR_SET_LINK", + "SYS_FACCESSAT", + "SYS_FADVISE64", + "SYS_FADVISE64_64", + "SYS_FALLOCATE", + "SYS_FANOTIFY_INIT", + "SYS_FANOTIFY_MARK", + "SYS_FCHDIR", + "SYS_FCHFLAGS", + "SYS_FCHMOD", + "SYS_FCHMODAT", + "SYS_FCHMOD_EXTENDED", + "SYS_FCHOWN", + "SYS_FCHOWN32", + "SYS_FCHOWNAT", + "SYS_FCHROOT", + "SYS_FCNTL", + "SYS_FCNTL64", + "SYS_FCNTL_NOCANCEL", + "SYS_FDATASYNC", + "SYS_FEXECVE", + "SYS_FFCLOCK_GETCOUNTER", + "SYS_FFCLOCK_GETESTIMATE", + "SYS_FFCLOCK_SETESTIMATE", + "SYS_FFSCTL", + "SYS_FGETATTRLIST", + "SYS_FGETXATTR", + "SYS_FHOPEN", + "SYS_FHSTAT", + "SYS_FHSTATFS", + "SYS_FILEPORT_MAKEFD", + "SYS_FILEPORT_MAKEPORT", + "SYS_FKTRACE", + "SYS_FLISTXATTR", + "SYS_FLOCK", + "SYS_FORK", + "SYS_FPATHCONF", + "SYS_FREEBSD6_FTRUNCATE", + "SYS_FREEBSD6_LSEEK", + "SYS_FREEBSD6_MMAP", + "SYS_FREEBSD6_PREAD", + "SYS_FREEBSD6_PWRITE", + "SYS_FREEBSD6_TRUNCATE", + "SYS_FREMOVEXATTR", + "SYS_FSCTL", + "SYS_FSETATTRLIST", + "SYS_FSETXATTR", + "SYS_FSGETPATH", + "SYS_FSTAT", + "SYS_FSTAT64", + "SYS_FSTAT64_EXTENDED", + "SYS_FSTATAT", + "SYS_FSTATAT64", + "SYS_FSTATFS", + "SYS_FSTATFS64", + "SYS_FSTATV", + "SYS_FSTATVFS1", + "SYS_FSTAT_EXTENDED", + "SYS_FSYNC", + "SYS_FSYNC_NOCANCEL", + "SYS_FSYNC_RANGE", + "SYS_FTIME", + "SYS_FTRUNCATE", + "SYS_FTRUNCATE64", + "SYS_FUTEX", + "SYS_FUTIMENS", + "SYS_FUTIMES", + "SYS_FUTIMESAT", + "SYS_GETATTRLIST", + "SYS_GETAUDIT", + "SYS_GETAUDIT_ADDR", + "SYS_GETAUID", + "SYS_GETCONTEXT", + "SYS_GETCPU", + "SYS_GETCWD", + "SYS_GETDENTS", + "SYS_GETDENTS64", + "SYS_GETDIRENTRIES", + "SYS_GETDIRENTRIES64", + "SYS_GETDIRENTRIESATTR", + "SYS_GETDTABLECOUNT", + "SYS_GETDTABLESIZE", + "SYS_GETEGID", + "SYS_GETEGID32", + "SYS_GETEUID", + "SYS_GETEUID32", + "SYS_GETFH", + "SYS_GETFSSTAT", + "SYS_GETFSSTAT64", + "SYS_GETGID", + "SYS_GETGID32", + "SYS_GETGROUPS", + "SYS_GETGROUPS32", + "SYS_GETHOSTUUID", + "SYS_GETITIMER", + "SYS_GETLCID", + "SYS_GETLOGIN", + "SYS_GETLOGINCLASS", + "SYS_GETPEERNAME", + "SYS_GETPGID", + "SYS_GETPGRP", + "SYS_GETPID", + "SYS_GETPMSG", + "SYS_GETPPID", + "SYS_GETPRIORITY", + "SYS_GETRESGID", + "SYS_GETRESGID32", + "SYS_GETRESUID", + "SYS_GETRESUID32", + "SYS_GETRLIMIT", + "SYS_GETRTABLE", + "SYS_GETRUSAGE", + "SYS_GETSGROUPS", + "SYS_GETSID", + "SYS_GETSOCKNAME", + "SYS_GETSOCKOPT", + "SYS_GETTHRID", + "SYS_GETTID", + "SYS_GETTIMEOFDAY", + "SYS_GETUID", + "SYS_GETUID32", + "SYS_GETVFSSTAT", + "SYS_GETWGROUPS", + "SYS_GETXATTR", + "SYS_GET_KERNEL_SYMS", + "SYS_GET_MEMPOLICY", + "SYS_GET_ROBUST_LIST", + "SYS_GET_THREAD_AREA", + "SYS_GTTY", + "SYS_IDENTITYSVC", + "SYS_IDLE", + "SYS_INITGROUPS", + "SYS_INIT_MODULE", + "SYS_INOTIFY_ADD_WATCH", + "SYS_INOTIFY_INIT", + "SYS_INOTIFY_INIT1", + "SYS_INOTIFY_RM_WATCH", + "SYS_IOCTL", + "SYS_IOPERM", + "SYS_IOPL", + "SYS_IOPOLICYSYS", + "SYS_IOPRIO_GET", + "SYS_IOPRIO_SET", + "SYS_IO_CANCEL", + "SYS_IO_DESTROY", + "SYS_IO_GETEVENTS", + "SYS_IO_SETUP", + "SYS_IO_SUBMIT", + "SYS_IPC", + "SYS_ISSETUGID", + "SYS_JAIL", + "SYS_JAIL_ATTACH", + "SYS_JAIL_GET", + "SYS_JAIL_REMOVE", + "SYS_JAIL_SET", + "SYS_KDEBUG_TRACE", + "SYS_KENV", + "SYS_KEVENT", + "SYS_KEVENT64", + "SYS_KEXEC_LOAD", + "SYS_KEYCTL", + "SYS_KILL", + "SYS_KLDFIND", + "SYS_KLDFIRSTMOD", + "SYS_KLDLOAD", + "SYS_KLDNEXT", + "SYS_KLDSTAT", + "SYS_KLDSYM", + "SYS_KLDUNLOAD", + "SYS_KLDUNLOADF", + "SYS_KQUEUE", + "SYS_KQUEUE1", + "SYS_KTIMER_CREATE", + "SYS_KTIMER_DELETE", + "SYS_KTIMER_GETOVERRUN", + "SYS_KTIMER_GETTIME", + "SYS_KTIMER_SETTIME", + "SYS_KTRACE", + "SYS_LCHFLAGS", + "SYS_LCHMOD", + "SYS_LCHOWN", + "SYS_LCHOWN32", + "SYS_LGETFH", + "SYS_LGETXATTR", + "SYS_LINK", + "SYS_LINKAT", + "SYS_LIO_LISTIO", + "SYS_LISTEN", + "SYS_LISTXATTR", + "SYS_LLISTXATTR", + "SYS_LOCK", + "SYS_LOOKUP_DCOOKIE", + "SYS_LPATHCONF", + "SYS_LREMOVEXATTR", + "SYS_LSEEK", + "SYS_LSETXATTR", + "SYS_LSTAT", + "SYS_LSTAT64", + "SYS_LSTAT64_EXTENDED", + "SYS_LSTATV", + "SYS_LSTAT_EXTENDED", + "SYS_LUTIMES", + "SYS_MAC_SYSCALL", + "SYS_MADVISE", + "SYS_MADVISE1", + "SYS_MAXSYSCALL", + "SYS_MBIND", + "SYS_MIGRATE_PAGES", + "SYS_MINCORE", + "SYS_MINHERIT", + "SYS_MKCOMPLEX", + "SYS_MKDIR", + "SYS_MKDIRAT", + "SYS_MKDIR_EXTENDED", + "SYS_MKFIFO", + "SYS_MKFIFOAT", + "SYS_MKFIFO_EXTENDED", + "SYS_MKNOD", + "SYS_MKNODAT", + "SYS_MLOCK", + "SYS_MLOCKALL", + "SYS_MMAP", + "SYS_MMAP2", + "SYS_MODCTL", + "SYS_MODFIND", + "SYS_MODFNEXT", + "SYS_MODIFY_LDT", + "SYS_MODNEXT", + "SYS_MODSTAT", + "SYS_MODWATCH", + "SYS_MOUNT", + "SYS_MOVE_PAGES", + "SYS_MPROTECT", + "SYS_MPX", + "SYS_MQUERY", + "SYS_MQ_GETSETATTR", + "SYS_MQ_NOTIFY", + "SYS_MQ_OPEN", + "SYS_MQ_TIMEDRECEIVE", + "SYS_MQ_TIMEDSEND", + "SYS_MQ_UNLINK", + "SYS_MREMAP", + "SYS_MSGCTL", + "SYS_MSGGET", + "SYS_MSGRCV", + "SYS_MSGRCV_NOCANCEL", + "SYS_MSGSND", + "SYS_MSGSND_NOCANCEL", + "SYS_MSGSYS", + "SYS_MSYNC", + "SYS_MSYNC_NOCANCEL", + "SYS_MUNLOCK", + "SYS_MUNLOCKALL", + "SYS_MUNMAP", + "SYS_NAME_TO_HANDLE_AT", + "SYS_NANOSLEEP", + "SYS_NEWFSTATAT", + "SYS_NFSCLNT", + "SYS_NFSSERVCTL", + "SYS_NFSSVC", + "SYS_NFSTAT", + "SYS_NICE", + "SYS_NLSTAT", + "SYS_NMOUNT", + "SYS_NSTAT", + "SYS_NTP_ADJTIME", + "SYS_NTP_GETTIME", + "SYS_OABI_SYSCALL_BASE", + "SYS_OBREAK", + "SYS_OLDFSTAT", + "SYS_OLDLSTAT", + "SYS_OLDOLDUNAME", + "SYS_OLDSTAT", + "SYS_OLDUNAME", + "SYS_OPEN", + "SYS_OPENAT", + "SYS_OPENBSD_POLL", + "SYS_OPEN_BY_HANDLE_AT", + "SYS_OPEN_EXTENDED", + "SYS_OPEN_NOCANCEL", + "SYS_OVADVISE", + "SYS_PACCEPT", + "SYS_PATHCONF", + "SYS_PAUSE", + "SYS_PCICONFIG_IOBASE", + "SYS_PCICONFIG_READ", + "SYS_PCICONFIG_WRITE", + "SYS_PDFORK", + "SYS_PDGETPID", + "SYS_PDKILL", + "SYS_PERF_EVENT_OPEN", + "SYS_PERSONALITY", + "SYS_PID_HIBERNATE", + "SYS_PID_RESUME", + "SYS_PID_SHUTDOWN_SOCKETS", + "SYS_PID_SUSPEND", + "SYS_PIPE", + "SYS_PIPE2", + "SYS_PIVOT_ROOT", + "SYS_PMC_CONTROL", + "SYS_PMC_GET_INFO", + "SYS_POLL", + "SYS_POLLTS", + "SYS_POLL_NOCANCEL", + "SYS_POSIX_FADVISE", + "SYS_POSIX_FALLOCATE", + "SYS_POSIX_OPENPT", + "SYS_POSIX_SPAWN", + "SYS_PPOLL", + "SYS_PRCTL", + "SYS_PREAD", + "SYS_PREAD64", + "SYS_PREADV", + "SYS_PREAD_NOCANCEL", + "SYS_PRLIMIT64", + "SYS_PROCCTL", + "SYS_PROCESS_POLICY", + "SYS_PROCESS_VM_READV", + "SYS_PROCESS_VM_WRITEV", + "SYS_PROC_INFO", + "SYS_PROF", + "SYS_PROFIL", + "SYS_PSELECT", + "SYS_PSELECT6", + "SYS_PSET_ASSIGN", + "SYS_PSET_CREATE", + "SYS_PSET_DESTROY", + "SYS_PSYNCH_CVBROAD", + "SYS_PSYNCH_CVCLRPREPOST", + "SYS_PSYNCH_CVSIGNAL", + "SYS_PSYNCH_CVWAIT", + "SYS_PSYNCH_MUTEXDROP", + "SYS_PSYNCH_MUTEXWAIT", + "SYS_PSYNCH_RW_DOWNGRADE", + "SYS_PSYNCH_RW_LONGRDLOCK", + "SYS_PSYNCH_RW_RDLOCK", + "SYS_PSYNCH_RW_UNLOCK", + "SYS_PSYNCH_RW_UNLOCK2", + "SYS_PSYNCH_RW_UPGRADE", + "SYS_PSYNCH_RW_WRLOCK", + "SYS_PSYNCH_RW_YIELDWRLOCK", + "SYS_PTRACE", + "SYS_PUTPMSG", + "SYS_PWRITE", + "SYS_PWRITE64", + "SYS_PWRITEV", + "SYS_PWRITE_NOCANCEL", + "SYS_QUERY_MODULE", + "SYS_QUOTACTL", + "SYS_RASCTL", + "SYS_RCTL_ADD_RULE", + "SYS_RCTL_GET_LIMITS", + "SYS_RCTL_GET_RACCT", + "SYS_RCTL_GET_RULES", + "SYS_RCTL_REMOVE_RULE", + "SYS_READ", + "SYS_READAHEAD", + "SYS_READDIR", + "SYS_READLINK", + "SYS_READLINKAT", + "SYS_READV", + "SYS_READV_NOCANCEL", + "SYS_READ_NOCANCEL", + "SYS_REBOOT", + "SYS_RECV", + "SYS_RECVFROM", + "SYS_RECVFROM_NOCANCEL", + "SYS_RECVMMSG", + "SYS_RECVMSG", + "SYS_RECVMSG_NOCANCEL", + "SYS_REMAP_FILE_PAGES", + "SYS_REMOVEXATTR", + "SYS_RENAME", + "SYS_RENAMEAT", + "SYS_REQUEST_KEY", + "SYS_RESTART_SYSCALL", + "SYS_REVOKE", + "SYS_RFORK", + "SYS_RMDIR", + "SYS_RTPRIO", + "SYS_RTPRIO_THREAD", + "SYS_RT_SIGACTION", + "SYS_RT_SIGPENDING", + "SYS_RT_SIGPROCMASK", + "SYS_RT_SIGQUEUEINFO", + "SYS_RT_SIGRETURN", + "SYS_RT_SIGSUSPEND", + "SYS_RT_SIGTIMEDWAIT", + "SYS_RT_TGSIGQUEUEINFO", + "SYS_SBRK", + "SYS_SCHED_GETAFFINITY", + "SYS_SCHED_GETPARAM", + "SYS_SCHED_GETSCHEDULER", + "SYS_SCHED_GET_PRIORITY_MAX", + "SYS_SCHED_GET_PRIORITY_MIN", + "SYS_SCHED_RR_GET_INTERVAL", + "SYS_SCHED_SETAFFINITY", + "SYS_SCHED_SETPARAM", + "SYS_SCHED_SETSCHEDULER", + "SYS_SCHED_YIELD", + "SYS_SCTP_GENERIC_RECVMSG", + "SYS_SCTP_GENERIC_SENDMSG", + "SYS_SCTP_GENERIC_SENDMSG_IOV", + "SYS_SCTP_PEELOFF", + "SYS_SEARCHFS", + "SYS_SECURITY", + "SYS_SELECT", + "SYS_SELECT_NOCANCEL", + "SYS_SEMCONFIG", + "SYS_SEMCTL", + "SYS_SEMGET", + "SYS_SEMOP", + "SYS_SEMSYS", + "SYS_SEMTIMEDOP", + "SYS_SEM_CLOSE", + "SYS_SEM_DESTROY", + "SYS_SEM_GETVALUE", + "SYS_SEM_INIT", + "SYS_SEM_OPEN", + "SYS_SEM_POST", + "SYS_SEM_TRYWAIT", + "SYS_SEM_UNLINK", + "SYS_SEM_WAIT", + "SYS_SEM_WAIT_NOCANCEL", + "SYS_SEND", + "SYS_SENDFILE", + "SYS_SENDFILE64", + "SYS_SENDMMSG", + "SYS_SENDMSG", + "SYS_SENDMSG_NOCANCEL", + "SYS_SENDTO", + "SYS_SENDTO_NOCANCEL", + "SYS_SETATTRLIST", + "SYS_SETAUDIT", + "SYS_SETAUDIT_ADDR", + "SYS_SETAUID", + "SYS_SETCONTEXT", + "SYS_SETDOMAINNAME", + "SYS_SETEGID", + "SYS_SETEUID", + "SYS_SETFIB", + "SYS_SETFSGID", + "SYS_SETFSGID32", + "SYS_SETFSUID", + "SYS_SETFSUID32", + "SYS_SETGID", + "SYS_SETGID32", + "SYS_SETGROUPS", + "SYS_SETGROUPS32", + "SYS_SETHOSTNAME", + "SYS_SETITIMER", + "SYS_SETLCID", + "SYS_SETLOGIN", + "SYS_SETLOGINCLASS", + "SYS_SETNS", + "SYS_SETPGID", + "SYS_SETPRIORITY", + "SYS_SETPRIVEXEC", + "SYS_SETREGID", + "SYS_SETREGID32", + "SYS_SETRESGID", + "SYS_SETRESGID32", + "SYS_SETRESUID", + "SYS_SETRESUID32", + "SYS_SETREUID", + "SYS_SETREUID32", + "SYS_SETRLIMIT", + "SYS_SETRTABLE", + "SYS_SETSGROUPS", + "SYS_SETSID", + "SYS_SETSOCKOPT", + "SYS_SETTID", + "SYS_SETTID_WITH_PID", + "SYS_SETTIMEOFDAY", + "SYS_SETUID", + "SYS_SETUID32", + "SYS_SETWGROUPS", + "SYS_SETXATTR", + "SYS_SET_MEMPOLICY", + "SYS_SET_ROBUST_LIST", + "SYS_SET_THREAD_AREA", + "SYS_SET_TID_ADDRESS", + "SYS_SGETMASK", + "SYS_SHARED_REGION_CHECK_NP", + "SYS_SHARED_REGION_MAP_AND_SLIDE_NP", + "SYS_SHMAT", + "SYS_SHMCTL", + "SYS_SHMDT", + "SYS_SHMGET", + "SYS_SHMSYS", + "SYS_SHM_OPEN", + "SYS_SHM_UNLINK", + "SYS_SHUTDOWN", + "SYS_SIGACTION", + "SYS_SIGALTSTACK", + "SYS_SIGNAL", + "SYS_SIGNALFD", + "SYS_SIGNALFD4", + "SYS_SIGPENDING", + "SYS_SIGPROCMASK", + "SYS_SIGQUEUE", + "SYS_SIGQUEUEINFO", + "SYS_SIGRETURN", + "SYS_SIGSUSPEND", + "SYS_SIGSUSPEND_NOCANCEL", + "SYS_SIGTIMEDWAIT", + "SYS_SIGWAIT", + "SYS_SIGWAITINFO", + "SYS_SOCKET", + "SYS_SOCKETCALL", + "SYS_SOCKETPAIR", + "SYS_SPLICE", + "SYS_SSETMASK", + "SYS_SSTK", + "SYS_STACK_SNAPSHOT", + "SYS_STAT", + "SYS_STAT64", + "SYS_STAT64_EXTENDED", + "SYS_STATFS", + "SYS_STATFS64", + "SYS_STATV", + "SYS_STATVFS1", + "SYS_STAT_EXTENDED", + "SYS_STIME", + "SYS_STTY", + "SYS_SWAPCONTEXT", + "SYS_SWAPCTL", + "SYS_SWAPOFF", + "SYS_SWAPON", + "SYS_SYMLINK", + "SYS_SYMLINKAT", + "SYS_SYNC", + "SYS_SYNCFS", + "SYS_SYNC_FILE_RANGE", + "SYS_SYSARCH", + "SYS_SYSCALL", + "SYS_SYSCALL_BASE", + "SYS_SYSFS", + "SYS_SYSINFO", + "SYS_SYSLOG", + "SYS_TEE", + "SYS_TGKILL", + "SYS_THREAD_SELFID", + "SYS_THR_CREATE", + "SYS_THR_EXIT", + "SYS_THR_KILL", + "SYS_THR_KILL2", + "SYS_THR_NEW", + "SYS_THR_SELF", + "SYS_THR_SET_NAME", + "SYS_THR_SUSPEND", + "SYS_THR_WAKE", + "SYS_TIME", + "SYS_TIMERFD_CREATE", + "SYS_TIMERFD_GETTIME", + "SYS_TIMERFD_SETTIME", + "SYS_TIMER_CREATE", + "SYS_TIMER_DELETE", + "SYS_TIMER_GETOVERRUN", + "SYS_TIMER_GETTIME", + "SYS_TIMER_SETTIME", + "SYS_TIMES", + "SYS_TKILL", + "SYS_TRUNCATE", + "SYS_TRUNCATE64", + "SYS_TUXCALL", + "SYS_UGETRLIMIT", + "SYS_ULIMIT", + "SYS_UMASK", + "SYS_UMASK_EXTENDED", + "SYS_UMOUNT", + "SYS_UMOUNT2", + "SYS_UNAME", + "SYS_UNDELETE", + "SYS_UNLINK", + "SYS_UNLINKAT", + "SYS_UNMOUNT", + "SYS_UNSHARE", + "SYS_USELIB", + "SYS_USTAT", + "SYS_UTIME", + "SYS_UTIMENSAT", + "SYS_UTIMES", + "SYS_UTRACE", + "SYS_UUIDGEN", + "SYS_VADVISE", + "SYS_VFORK", + "SYS_VHANGUP", + "SYS_VM86", + "SYS_VM86OLD", + "SYS_VMSPLICE", + "SYS_VM_PRESSURE_MONITOR", + "SYS_VSERVER", + "SYS_WAIT4", + "SYS_WAIT4_NOCANCEL", + "SYS_WAIT6", + "SYS_WAITEVENT", + "SYS_WAITID", + "SYS_WAITID_NOCANCEL", + "SYS_WAITPID", + "SYS_WATCHEVENT", + "SYS_WORKQ_KERNRETURN", + "SYS_WORKQ_OPEN", + "SYS_WRITE", + "SYS_WRITEV", + "SYS_WRITEV_NOCANCEL", + "SYS_WRITE_NOCANCEL", + "SYS_YIELD", + "SYS__LLSEEK", + "SYS__LWP_CONTINUE", + "SYS__LWP_CREATE", + "SYS__LWP_CTL", + "SYS__LWP_DETACH", + "SYS__LWP_EXIT", + "SYS__LWP_GETNAME", + "SYS__LWP_GETPRIVATE", + "SYS__LWP_KILL", + "SYS__LWP_PARK", + "SYS__LWP_SELF", + "SYS__LWP_SETNAME", + "SYS__LWP_SETPRIVATE", + "SYS__LWP_SUSPEND", + "SYS__LWP_UNPARK", + "SYS__LWP_UNPARK_ALL", + "SYS__LWP_WAIT", + "SYS__LWP_WAKEUP", + "SYS__NEWSELECT", + "SYS__PSET_BIND", + "SYS__SCHED_GETAFFINITY", + "SYS__SCHED_GETPARAM", + "SYS__SCHED_SETAFFINITY", + "SYS__SCHED_SETPARAM", + "SYS__SYSCTL", + "SYS__UMTX_LOCK", + "SYS__UMTX_OP", + "SYS__UMTX_UNLOCK", + "SYS___ACL_ACLCHECK_FD", + "SYS___ACL_ACLCHECK_FILE", + "SYS___ACL_ACLCHECK_LINK", + "SYS___ACL_DELETE_FD", + "SYS___ACL_DELETE_FILE", + "SYS___ACL_DELETE_LINK", + "SYS___ACL_GET_FD", + "SYS___ACL_GET_FILE", + "SYS___ACL_GET_LINK", + "SYS___ACL_SET_FD", + "SYS___ACL_SET_FILE", + "SYS___ACL_SET_LINK", + "SYS___CLONE", + "SYS___DISABLE_THREADSIGNAL", + "SYS___GETCWD", + "SYS___GETLOGIN", + "SYS___GET_TCB", + "SYS___MAC_EXECVE", + "SYS___MAC_GETFSSTAT", + "SYS___MAC_GET_FD", + "SYS___MAC_GET_FILE", + "SYS___MAC_GET_LCID", + "SYS___MAC_GET_LCTX", + "SYS___MAC_GET_LINK", + "SYS___MAC_GET_MOUNT", + "SYS___MAC_GET_PID", + "SYS___MAC_GET_PROC", + "SYS___MAC_MOUNT", + "SYS___MAC_SET_FD", + "SYS___MAC_SET_FILE", + "SYS___MAC_SET_LCTX", + "SYS___MAC_SET_LINK", + "SYS___MAC_SET_PROC", + "SYS___MAC_SYSCALL", + "SYS___OLD_SEMWAIT_SIGNAL", + "SYS___OLD_SEMWAIT_SIGNAL_NOCANCEL", + "SYS___POSIX_CHOWN", + "SYS___POSIX_FCHOWN", + "SYS___POSIX_LCHOWN", + "SYS___POSIX_RENAME", + "SYS___PTHREAD_CANCELED", + "SYS___PTHREAD_CHDIR", + "SYS___PTHREAD_FCHDIR", + "SYS___PTHREAD_KILL", + "SYS___PTHREAD_MARKCANCEL", + "SYS___PTHREAD_SIGMASK", + "SYS___QUOTACTL", + "SYS___SEMCTL", + "SYS___SEMWAIT_SIGNAL", + "SYS___SEMWAIT_SIGNAL_NOCANCEL", + "SYS___SETLOGIN", + "SYS___SETUGID", + "SYS___SET_TCB", + "SYS___SIGACTION_SIGTRAMP", + "SYS___SIGTIMEDWAIT", + "SYS___SIGWAIT", + "SYS___SIGWAIT_NOCANCEL", + "SYS___SYSCTL", + "SYS___TFORK", + "SYS___THREXIT", + "SYS___THRSIGDIVERT", + "SYS___THRSLEEP", + "SYS___THRWAKEUP", + "S_ARCH1", + "S_ARCH2", + "S_BLKSIZE", + "S_IEXEC", + "S_IFBLK", + "S_IFCHR", + "S_IFDIR", + "S_IFIFO", + "S_IFLNK", + "S_IFMT", + "S_IFREG", + "S_IFSOCK", + "S_IFWHT", + "S_IREAD", + "S_IRGRP", + "S_IROTH", + "S_IRUSR", + "S_IRWXG", + "S_IRWXO", + "S_IRWXU", + "S_ISGID", + "S_ISTXT", + "S_ISUID", + "S_ISVTX", + "S_IWGRP", + "S_IWOTH", + "S_IWRITE", + "S_IWUSR", + "S_IXGRP", + "S_IXOTH", + "S_IXUSR", + "S_LOGIN_SET", + "SecurityAttributes", + "Seek", + "Select", + "Sendfile", + "Sendmsg", + "SendmsgN", + "Sendto", + "Servent", + "SetBpf", + "SetBpfBuflen", + "SetBpfDatalink", + "SetBpfHeadercmpl", + "SetBpfImmediate", + "SetBpfInterface", + "SetBpfPromisc", + "SetBpfTimeout", + "SetCurrentDirectory", + "SetEndOfFile", + "SetEnvironmentVariable", + "SetFileAttributes", + "SetFileCompletionNotificationModes", + "SetFilePointer", + "SetFileTime", + "SetHandleInformation", + "SetKevent", + "SetLsfPromisc", + "SetNonblock", + "Setdomainname", + "Setegid", + "Setenv", + "Seteuid", + "Setfsgid", + "Setfsuid", + "Setgid", + "Setgroups", + "Sethostname", + "Setlogin", + "Setpgid", + "Setpriority", + "Setprivexec", + "Setregid", + "Setresgid", + "Setresuid", + "Setreuid", + "Setrlimit", + "Setsid", + "Setsockopt", + "SetsockoptByte", + "SetsockoptICMPv6Filter", + "SetsockoptIPMreq", + "SetsockoptIPMreqn", + "SetsockoptIPv6Mreq", + "SetsockoptInet4Addr", + "SetsockoptInt", + "SetsockoptLinger", + "SetsockoptString", + "SetsockoptTimeval", + "Settimeofday", + "Setuid", + "Setxattr", + "Shutdown", + "SidTypeAlias", + "SidTypeComputer", + "SidTypeDeletedAccount", + "SidTypeDomain", + "SidTypeGroup", + "SidTypeInvalid", + "SidTypeLabel", + "SidTypeUnknown", + "SidTypeUser", + "SidTypeWellKnownGroup", + "Signal", + "SizeofBpfHdr", + "SizeofBpfInsn", + "SizeofBpfProgram", + "SizeofBpfStat", + "SizeofBpfVersion", + "SizeofBpfZbuf", + "SizeofBpfZbufHeader", + "SizeofCmsghdr", + "SizeofICMPv6Filter", + "SizeofIPMreq", + "SizeofIPMreqn", + "SizeofIPv6MTUInfo", + "SizeofIPv6Mreq", + "SizeofIfAddrmsg", + "SizeofIfAnnounceMsghdr", + "SizeofIfData", + "SizeofIfInfomsg", + "SizeofIfMsghdr", + "SizeofIfaMsghdr", + "SizeofIfmaMsghdr", + "SizeofIfmaMsghdr2", + "SizeofInet4Pktinfo", + "SizeofInet6Pktinfo", + "SizeofInotifyEvent", + "SizeofLinger", + "SizeofMsghdr", + "SizeofNlAttr", + "SizeofNlMsgerr", + "SizeofNlMsghdr", + "SizeofRtAttr", + "SizeofRtGenmsg", + "SizeofRtMetrics", + "SizeofRtMsg", + "SizeofRtMsghdr", + "SizeofRtNexthop", + "SizeofSockFilter", + "SizeofSockFprog", + "SizeofSockaddrAny", + "SizeofSockaddrDatalink", + "SizeofSockaddrInet4", + "SizeofSockaddrInet6", + "SizeofSockaddrLinklayer", + "SizeofSockaddrNetlink", + "SizeofSockaddrUnix", + "SizeofTCPInfo", + "SizeofUcred", + "SlicePtrFromStrings", + "SockFilter", + "SockFprog", + "Sockaddr", + "SockaddrDatalink", + "SockaddrGen", + "SockaddrInet4", + "SockaddrInet6", + "SockaddrLinklayer", + "SockaddrNetlink", + "SockaddrUnix", + "Socket", + "SocketControlMessage", + "SocketDisableIPv6", + "Socketpair", + "Splice", + "StartProcess", + "StartupInfo", + "Stat", + "Stat_t", + "Statfs", + "Statfs_t", + "Stderr", + "Stdin", + "Stdout", + "StringBytePtr", + "StringByteSlice", + "StringSlicePtr", + "StringToSid", + "StringToUTF16", + "StringToUTF16Ptr", + "Symlink", + "Sync", + "SyncFileRange", + "SysProcAttr", + "SysProcIDMap", + "Syscall", + "Syscall12", + "Syscall15", + "Syscall18", + "Syscall6", + "Syscall9", + "Sysctl", + "SysctlUint32", + "Sysctlnode", + "Sysinfo", + "Sysinfo_t", + "Systemtime", + "TCGETS", + "TCIFLUSH", + "TCIOFLUSH", + "TCOFLUSH", + "TCPInfo", + "TCPKeepalive", + "TCP_CA_NAME_MAX", + "TCP_CONGCTL", + "TCP_CONGESTION", + "TCP_CONNECTIONTIMEOUT", + "TCP_CORK", + "TCP_DEFER_ACCEPT", + "TCP_INFO", + "TCP_KEEPALIVE", + "TCP_KEEPCNT", + "TCP_KEEPIDLE", + "TCP_KEEPINIT", + "TCP_KEEPINTVL", + "TCP_LINGER2", + "TCP_MAXBURST", + "TCP_MAXHLEN", + "TCP_MAXOLEN", + "TCP_MAXSEG", + "TCP_MAXWIN", + "TCP_MAX_SACK", + "TCP_MAX_WINSHIFT", + "TCP_MD5SIG", + "TCP_MD5SIG_MAXKEYLEN", + "TCP_MINMSS", + "TCP_MINMSSOVERLOAD", + "TCP_MSS", + "TCP_NODELAY", + "TCP_NOOPT", + "TCP_NOPUSH", + "TCP_NSTATES", + "TCP_QUICKACK", + "TCP_RXT_CONNDROPTIME", + "TCP_RXT_FINDROP", + "TCP_SACK_ENABLE", + "TCP_SYNCNT", + "TCP_VENDOR", + "TCP_WINDOW_CLAMP", + "TCSAFLUSH", + "TCSETS", + "TF_DISCONNECT", + "TF_REUSE_SOCKET", + "TF_USE_DEFAULT_WORKER", + "TF_USE_KERNEL_APC", + "TF_USE_SYSTEM_THREAD", + "TF_WRITE_BEHIND", + "TH32CS_INHERIT", + "TH32CS_SNAPALL", + "TH32CS_SNAPHEAPLIST", + "TH32CS_SNAPMODULE", + "TH32CS_SNAPMODULE32", + "TH32CS_SNAPPROCESS", + "TH32CS_SNAPTHREAD", + "TIME_ZONE_ID_DAYLIGHT", + "TIME_ZONE_ID_STANDARD", + "TIME_ZONE_ID_UNKNOWN", + "TIOCCBRK", + "TIOCCDTR", + "TIOCCONS", + "TIOCDCDTIMESTAMP", + "TIOCDRAIN", + "TIOCDSIMICROCODE", + "TIOCEXCL", + "TIOCEXT", + "TIOCFLAG_CDTRCTS", + "TIOCFLAG_CLOCAL", + "TIOCFLAG_CRTSCTS", + "TIOCFLAG_MDMBUF", + "TIOCFLAG_PPS", + "TIOCFLAG_SOFTCAR", + "TIOCFLUSH", + "TIOCGDEV", + "TIOCGDRAINWAIT", + "TIOCGETA", + "TIOCGETD", + "TIOCGFLAGS", + "TIOCGICOUNT", + "TIOCGLCKTRMIOS", + "TIOCGLINED", + "TIOCGPGRP", + "TIOCGPTN", + "TIOCGQSIZE", + "TIOCGRANTPT", + "TIOCGRS485", + "TIOCGSERIAL", + "TIOCGSID", + "TIOCGSIZE", + "TIOCGSOFTCAR", + "TIOCGTSTAMP", + "TIOCGWINSZ", + "TIOCINQ", + "TIOCIXOFF", + "TIOCIXON", + "TIOCLINUX", + "TIOCMBIC", + "TIOCMBIS", + "TIOCMGDTRWAIT", + "TIOCMGET", + "TIOCMIWAIT", + "TIOCMODG", + "TIOCMODS", + "TIOCMSDTRWAIT", + "TIOCMSET", + "TIOCM_CAR", + "TIOCM_CD", + "TIOCM_CTS", + "TIOCM_DCD", + "TIOCM_DSR", + "TIOCM_DTR", + "TIOCM_LE", + "TIOCM_RI", + "TIOCM_RNG", + "TIOCM_RTS", + "TIOCM_SR", + "TIOCM_ST", + "TIOCNOTTY", + "TIOCNXCL", + "TIOCOUTQ", + "TIOCPKT", + "TIOCPKT_DATA", + "TIOCPKT_DOSTOP", + "TIOCPKT_FLUSHREAD", + "TIOCPKT_FLUSHWRITE", + "TIOCPKT_IOCTL", + "TIOCPKT_NOSTOP", + "TIOCPKT_START", + "TIOCPKT_STOP", + "TIOCPTMASTER", + "TIOCPTMGET", + "TIOCPTSNAME", + "TIOCPTYGNAME", + "TIOCPTYGRANT", + "TIOCPTYUNLK", + "TIOCRCVFRAME", + "TIOCREMOTE", + "TIOCSBRK", + "TIOCSCONS", + "TIOCSCTTY", + "TIOCSDRAINWAIT", + "TIOCSDTR", + "TIOCSERCONFIG", + "TIOCSERGETLSR", + "TIOCSERGETMULTI", + "TIOCSERGSTRUCT", + "TIOCSERGWILD", + "TIOCSERSETMULTI", + "TIOCSERSWILD", + "TIOCSER_TEMT", + "TIOCSETA", + "TIOCSETAF", + "TIOCSETAW", + "TIOCSETD", + "TIOCSFLAGS", + "TIOCSIG", + "TIOCSLCKTRMIOS", + "TIOCSLINED", + "TIOCSPGRP", + "TIOCSPTLCK", + "TIOCSQSIZE", + "TIOCSRS485", + "TIOCSSERIAL", + "TIOCSSIZE", + "TIOCSSOFTCAR", + "TIOCSTART", + "TIOCSTAT", + "TIOCSTI", + "TIOCSTOP", + "TIOCSTSTAMP", + "TIOCSWINSZ", + "TIOCTIMESTAMP", + "TIOCUCNTL", + "TIOCVHANGUP", + "TIOCXMTFRAME", + "TOKEN_ADJUST_DEFAULT", + "TOKEN_ADJUST_GROUPS", + "TOKEN_ADJUST_PRIVILEGES", + "TOKEN_ADJUST_SESSIONID", + "TOKEN_ALL_ACCESS", + "TOKEN_ASSIGN_PRIMARY", + "TOKEN_DUPLICATE", + "TOKEN_EXECUTE", + "TOKEN_IMPERSONATE", + "TOKEN_QUERY", + "TOKEN_QUERY_SOURCE", + "TOKEN_READ", + "TOKEN_WRITE", + "TOSTOP", + "TRUNCATE_EXISTING", + "TUNATTACHFILTER", + "TUNDETACHFILTER", + "TUNGETFEATURES", + "TUNGETIFF", + "TUNGETSNDBUF", + "TUNGETVNETHDRSZ", + "TUNSETDEBUG", + "TUNSETGROUP", + "TUNSETIFF", + "TUNSETLINK", + "TUNSETNOCSUM", + "TUNSETOFFLOAD", + "TUNSETOWNER", + "TUNSETPERSIST", + "TUNSETSNDBUF", + "TUNSETTXFILTER", + "TUNSETVNETHDRSZ", + "Tee", + "TerminateProcess", + "Termios", + "Tgkill", + "Time", + "Time_t", + "Times", + "Timespec", + "TimespecToNsec", + "Timeval", + "Timeval32", + "TimevalToNsec", + "Timex", + "Timezoneinformation", + "Tms", + "Token", + "TokenAccessInformation", + "TokenAuditPolicy", + "TokenDefaultDacl", + "TokenElevation", + "TokenElevationType", + "TokenGroups", + "TokenGroupsAndPrivileges", + "TokenHasRestrictions", + "TokenImpersonationLevel", + "TokenIntegrityLevel", + "TokenLinkedToken", + "TokenLogonSid", + "TokenMandatoryPolicy", + "TokenOrigin", + "TokenOwner", + "TokenPrimaryGroup", + "TokenPrivileges", + "TokenRestrictedSids", + "TokenSandBoxInert", + "TokenSessionId", + "TokenSessionReference", + "TokenSource", + "TokenStatistics", + "TokenType", + "TokenUIAccess", + "TokenUser", + "TokenVirtualizationAllowed", + "TokenVirtualizationEnabled", + "Tokenprimarygroup", + "Tokenuser", + "TranslateAccountName", + "TranslateName", + "TransmitFile", + "TransmitFileBuffers", + "Truncate", + "UNIX_PATH_MAX", + "USAGE_MATCH_TYPE_AND", + "USAGE_MATCH_TYPE_OR", + "UTF16FromString", + "UTF16PtrFromString", + "UTF16ToString", + "Ucred", + "Umask", + "Uname", + "Undelete", + "UnixCredentials", + "UnixRights", + "Unlink", + "Unlinkat", + "UnmapViewOfFile", + "Unmount", + "Unsetenv", + "Unshare", + "UserInfo10", + "Ustat", + "Ustat_t", + "Utimbuf", + "Utime", + "Utimes", + "UtimesNano", + "Utsname", + "VDISCARD", + "VDSUSP", + "VEOF", + "VEOL", + "VEOL2", + "VERASE", + "VERASE2", + "VINTR", + "VKILL", + "VLNEXT", + "VMIN", + "VQUIT", + "VREPRINT", + "VSTART", + "VSTATUS", + "VSTOP", + "VSUSP", + "VSWTC", + "VT0", + "VT1", + "VTDLY", + "VTIME", + "VWERASE", + "VirtualLock", + "VirtualUnlock", + "WAIT_ABANDONED", + "WAIT_FAILED", + "WAIT_OBJECT_0", + "WAIT_TIMEOUT", + "WALL", + "WALLSIG", + "WALTSIG", + "WCLONE", + "WCONTINUED", + "WCOREFLAG", + "WEXITED", + "WLINUXCLONE", + "WNOHANG", + "WNOTHREAD", + "WNOWAIT", + "WNOZOMBIE", + "WOPTSCHECKED", + "WORDSIZE", + "WSABuf", + "WSACleanup", + "WSADESCRIPTION_LEN", + "WSAData", + "WSAEACCES", + "WSAECONNABORTED", + "WSAECONNRESET", + "WSAEnumProtocols", + "WSAID_CONNECTEX", + "WSAIoctl", + "WSAPROTOCOL_LEN", + "WSAProtocolChain", + "WSAProtocolInfo", + "WSARecv", + "WSARecvFrom", + "WSASYS_STATUS_LEN", + "WSASend", + "WSASendTo", + "WSASendto", + "WSAStartup", + "WSTOPPED", + "WTRAPPED", + "WUNTRACED", + "Wait4", + "WaitForSingleObject", + "WaitStatus", + "Win32FileAttributeData", + "Win32finddata", + "Write", + "WriteConsole", + "WriteFile", + "X509_ASN_ENCODING", + "XCASE", + "XP1_CONNECTIONLESS", + "XP1_CONNECT_DATA", + "XP1_DISCONNECT_DATA", + "XP1_EXPEDITED_DATA", + "XP1_GRACEFUL_CLOSE", + "XP1_GUARANTEED_DELIVERY", + "XP1_GUARANTEED_ORDER", + "XP1_IFS_HANDLES", + "XP1_MESSAGE_ORIENTED", + "XP1_MULTIPOINT_CONTROL_PLANE", + "XP1_MULTIPOINT_DATA_PLANE", + "XP1_PARTIAL_MESSAGE", + "XP1_PSEUDO_STREAM", + "XP1_QOS_SUPPORTED", + "XP1_SAN_SUPPORT_SDP", + "XP1_SUPPORT_BROADCAST", + "XP1_SUPPORT_MULTIPOINT", + "XP1_UNI_RECV", + "XP1_UNI_SEND", + }, + "syscall/js": []string{ + "CopyBytesToGo", + "CopyBytesToJS", + "Error", + "Func", + "FuncOf", + "Global", + "Null", + "Type", + "TypeBoolean", + "TypeFunction", + "TypeNull", + "TypeNumber", + "TypeObject", + "TypeString", + "TypeSymbol", + "TypeUndefined", + "Undefined", + "Value", + "ValueError", + "ValueOf", + "Wrapper", + }, + "testing": []string{ + "AllocsPerRun", + "B", + "Benchmark", + "BenchmarkResult", + "Cover", + "CoverBlock", + "CoverMode", + "Coverage", + "Init", + "InternalBenchmark", + "InternalExample", + "InternalTest", + "M", + "Main", + "MainStart", + "PB", + "RegisterCover", + "RunBenchmarks", + "RunExamples", + "RunTests", + "Short", + "T", + "TB", + "Verbose", + }, + "testing/iotest": []string{ + "DataErrReader", + "ErrTimeout", + "HalfReader", + "NewReadLogger", + "NewWriteLogger", + "OneByteReader", + "TimeoutReader", + "TruncateWriter", + }, + "testing/quick": []string{ + "Check", + "CheckEqual", + "CheckEqualError", + "CheckError", + "Config", + "Generator", + "SetupError", + "Value", + }, + "text/scanner": []string{ + "Char", + "Comment", + "EOF", + "Float", + "GoTokens", + "GoWhitespace", + "Ident", + "Int", + "Position", + "RawString", + "ScanChars", + "ScanComments", + "ScanFloats", + "ScanIdents", + "ScanInts", + "ScanRawStrings", + "ScanStrings", + "Scanner", + "SkipComments", + "String", + "TokenString", + }, + "text/tabwriter": []string{ + "AlignRight", + "Debug", + "DiscardEmptyColumns", + "Escape", + "FilterHTML", + "NewWriter", + "StripEscape", + "TabIndent", + "Writer", + }, + "text/template": []string{ + "ExecError", + "FuncMap", + "HTMLEscape", + "HTMLEscapeString", + "HTMLEscaper", + "IsTrue", + "JSEscape", + "JSEscapeString", + "JSEscaper", + "Must", + "New", + "ParseFiles", + "ParseGlob", + "Template", + "URLQueryEscaper", + }, + "text/template/parse": []string{ + "ActionNode", + "BoolNode", + "BranchNode", + "ChainNode", + "CommandNode", + "DotNode", + "FieldNode", + "IdentifierNode", + "IfNode", + "IsEmptyTree", + "ListNode", + "New", + "NewIdentifier", + "NilNode", + "Node", + "NodeAction", + "NodeBool", + "NodeChain", + "NodeCommand", + "NodeDot", + "NodeField", + "NodeIdentifier", + "NodeIf", + "NodeList", + "NodeNil", + "NodeNumber", + "NodePipe", + "NodeRange", + "NodeString", + "NodeTemplate", + "NodeText", + "NodeType", + "NodeVariable", + "NodeWith", + "NumberNode", + "Parse", + "PipeNode", + "Pos", + "RangeNode", + "StringNode", + "TemplateNode", + "TextNode", + "Tree", + "VariableNode", + "WithNode", + }, + "time": []string{ + "ANSIC", + "After", + "AfterFunc", + "April", + "August", + "Date", + "December", + "Duration", + "February", + "FixedZone", + "Friday", + "Hour", + "January", + "July", + "June", + "Kitchen", + "LoadLocation", + "LoadLocationFromTZData", + "Local", + "Location", + "March", + "May", + "Microsecond", + "Millisecond", + "Minute", + "Monday", + "Month", + "Nanosecond", + "NewTicker", + "NewTimer", + "November", + "Now", + "October", + "Parse", + "ParseDuration", + "ParseError", + "ParseInLocation", + "RFC1123", + "RFC1123Z", + "RFC3339", + "RFC3339Nano", + "RFC822", + "RFC822Z", + "RFC850", + "RubyDate", + "Saturday", + "Second", + "September", + "Since", + "Sleep", + "Stamp", + "StampMicro", + "StampMilli", + "StampNano", + "Sunday", + "Thursday", + "Tick", + "Ticker", + "Time", + "Timer", + "Tuesday", + "UTC", + "Unix", + "UnixDate", + "Until", + "Wednesday", + "Weekday", + }, + "unicode": []string{ + "ASCII_Hex_Digit", + "Adlam", + "Ahom", + "Anatolian_Hieroglyphs", + "Arabic", + "Armenian", + "Avestan", + "AzeriCase", + "Balinese", + "Bamum", + "Bassa_Vah", + "Batak", + "Bengali", + "Bhaiksuki", + "Bidi_Control", + "Bopomofo", + "Brahmi", + "Braille", + "Buginese", + "Buhid", + "C", + "Canadian_Aboriginal", + "Carian", + "CaseRange", + "CaseRanges", + "Categories", + "Caucasian_Albanian", + "Cc", + "Cf", + "Chakma", + "Cham", + "Cherokee", + "Co", + "Common", + "Coptic", + "Cs", + "Cuneiform", + "Cypriot", + "Cyrillic", + "Dash", + "Deprecated", + "Deseret", + "Devanagari", + "Diacritic", + "Digit", + "Dogra", + "Duployan", + "Egyptian_Hieroglyphs", + "Elbasan", + "Elymaic", + "Ethiopic", + "Extender", + "FoldCategory", + "FoldScript", + "Georgian", + "Glagolitic", + "Gothic", + "Grantha", + "GraphicRanges", + "Greek", + "Gujarati", + "Gunjala_Gondi", + "Gurmukhi", + "Han", + "Hangul", + "Hanifi_Rohingya", + "Hanunoo", + "Hatran", + "Hebrew", + "Hex_Digit", + "Hiragana", + "Hyphen", + "IDS_Binary_Operator", + "IDS_Trinary_Operator", + "Ideographic", + "Imperial_Aramaic", + "In", + "Inherited", + "Inscriptional_Pahlavi", + "Inscriptional_Parthian", + "Is", + "IsControl", + "IsDigit", + "IsGraphic", + "IsLetter", + "IsLower", + "IsMark", + "IsNumber", + "IsOneOf", + "IsPrint", + "IsPunct", + "IsSpace", + "IsSymbol", + "IsTitle", + "IsUpper", + "Javanese", + "Join_Control", + "Kaithi", + "Kannada", + "Katakana", + "Kayah_Li", + "Kharoshthi", + "Khmer", + "Khojki", + "Khudawadi", + "L", + "Lao", + "Latin", + "Lepcha", + "Letter", + "Limbu", + "Linear_A", + "Linear_B", + "Lisu", + "Ll", + "Lm", + "Lo", + "Logical_Order_Exception", + "Lower", + "LowerCase", + "Lt", + "Lu", + "Lycian", + "Lydian", + "M", + "Mahajani", + "Makasar", + "Malayalam", + "Mandaic", + "Manichaean", + "Marchen", + "Mark", + "Masaram_Gondi", + "MaxASCII", + "MaxCase", + "MaxLatin1", + "MaxRune", + "Mc", + "Me", + "Medefaidrin", + "Meetei_Mayek", + "Mende_Kikakui", + "Meroitic_Cursive", + "Meroitic_Hieroglyphs", + "Miao", + "Mn", + "Modi", + "Mongolian", + "Mro", + "Multani", + "Myanmar", + "N", + "Nabataean", + "Nandinagari", + "Nd", + "New_Tai_Lue", + "Newa", + "Nko", + "Nl", + "No", + "Noncharacter_Code_Point", + "Number", + "Nushu", + "Nyiakeng_Puachue_Hmong", + "Ogham", + "Ol_Chiki", + "Old_Hungarian", + "Old_Italic", + "Old_North_Arabian", + "Old_Permic", + "Old_Persian", + "Old_Sogdian", + "Old_South_Arabian", + "Old_Turkic", + "Oriya", + "Osage", + "Osmanya", + "Other", + "Other_Alphabetic", + "Other_Default_Ignorable_Code_Point", + "Other_Grapheme_Extend", + "Other_ID_Continue", + "Other_ID_Start", + "Other_Lowercase", + "Other_Math", + "Other_Uppercase", + "P", + "Pahawh_Hmong", + "Palmyrene", + "Pattern_Syntax", + "Pattern_White_Space", + "Pau_Cin_Hau", + "Pc", + "Pd", + "Pe", + "Pf", + "Phags_Pa", + "Phoenician", + "Pi", + "Po", + "Prepended_Concatenation_Mark", + "PrintRanges", + "Properties", + "Ps", + "Psalter_Pahlavi", + "Punct", + "Quotation_Mark", + "Radical", + "Range16", + "Range32", + "RangeTable", + "Regional_Indicator", + "Rejang", + "ReplacementChar", + "Runic", + "S", + "STerm", + "Samaritan", + "Saurashtra", + "Sc", + "Scripts", + "Sentence_Terminal", + "Sharada", + "Shavian", + "Siddham", + "SignWriting", + "SimpleFold", + "Sinhala", + "Sk", + "Sm", + "So", + "Soft_Dotted", + "Sogdian", + "Sora_Sompeng", + "Soyombo", + "Space", + "SpecialCase", + "Sundanese", + "Syloti_Nagri", + "Symbol", + "Syriac", + "Tagalog", + "Tagbanwa", + "Tai_Le", + "Tai_Tham", + "Tai_Viet", + "Takri", + "Tamil", + "Tangut", + "Telugu", + "Terminal_Punctuation", + "Thaana", + "Thai", + "Tibetan", + "Tifinagh", + "Tirhuta", + "Title", + "TitleCase", + "To", + "ToLower", + "ToTitle", + "ToUpper", + "TurkishCase", + "Ugaritic", + "Unified_Ideograph", + "Upper", + "UpperCase", + "UpperLower", + "Vai", + "Variation_Selector", + "Version", + "Wancho", + "Warang_Citi", + "White_Space", + "Yi", + "Z", + "Zanabazar_Square", + "Zl", + "Zp", + "Zs", + }, + "unicode/utf16": []string{ + "Decode", + "DecodeRune", + "Encode", + "EncodeRune", + "IsSurrogate", + }, + "unicode/utf8": []string{ + "DecodeLastRune", + "DecodeLastRuneInString", + "DecodeRune", + "DecodeRuneInString", + "EncodeRune", + "FullRune", + "FullRuneInString", + "MaxRune", + "RuneCount", + "RuneCountInString", + "RuneError", + "RuneLen", + "RuneSelf", + "RuneStart", + "UTFMax", + "Valid", + "ValidRune", + "ValidString", + }, + "unsafe": []string{ + "Alignof", + "ArbitraryType", + "Offsetof", + "Pointer", + "Sizeof", + }, +} diff --git a/vendor/golang.org/x/tools/internal/packagesinternal/packages.go b/vendor/golang.org/x/tools/internal/packagesinternal/packages.go new file mode 100644 index 000000000..2c4527f24 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/packagesinternal/packages.go @@ -0,0 +1,14 @@ +// Package packagesinternal exposes internal-only fields from go/packages. +package packagesinternal + +import ( + "golang.org/x/tools/internal/gocommand" +) + +var GetForTest = func(p interface{}) string { return "" } + +var GetGoCmdRunner = func(config interface{}) *gocommand.Runner { return nil } + +var SetGoCmdRunner = func(config interface{}, runner *gocommand.Runner) {} + +var TypecheckCgo int diff --git a/vendor/golang.org/x/tools/internal/typesinternal/types.go b/vendor/golang.org/x/tools/internal/typesinternal/types.go new file mode 100644 index 000000000..a5bb408e2 --- /dev/null +++ b/vendor/golang.org/x/tools/internal/typesinternal/types.go @@ -0,0 +1,28 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package typesinternal + +import ( + "go/types" + "reflect" + "unsafe" +) + +func SetUsesCgo(conf *types.Config) bool { + v := reflect.ValueOf(conf).Elem() + + f := v.FieldByName("go115UsesCgo") + if !f.IsValid() { + f = v.FieldByName("UsesCgo") + if !f.IsValid() { + return false + } + } + + addr := unsafe.Pointer(f.UnsafeAddr()) + *(*bool)(addr) = true + + return true +} diff --git a/vendor/golang.org/x/xerrors/LICENSE b/vendor/golang.org/x/xerrors/LICENSE new file mode 100644 index 000000000..e4a47e17f --- /dev/null +++ b/vendor/golang.org/x/xerrors/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2019 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/golang.org/x/xerrors/PATENTS b/vendor/golang.org/x/xerrors/PATENTS new file mode 100644 index 000000000..733099041 --- /dev/null +++ b/vendor/golang.org/x/xerrors/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/vendor/golang.org/x/xerrors/README b/vendor/golang.org/x/xerrors/README new file mode 100644 index 000000000..aac7867a5 --- /dev/null +++ b/vendor/golang.org/x/xerrors/README @@ -0,0 +1,2 @@ +This repository holds the transition packages for the new Go 1.13 error values. +See golang.org/design/29934-error-values. diff --git a/vendor/golang.org/x/xerrors/adaptor.go b/vendor/golang.org/x/xerrors/adaptor.go new file mode 100644 index 000000000..4317f2483 --- /dev/null +++ b/vendor/golang.org/x/xerrors/adaptor.go @@ -0,0 +1,193 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xerrors + +import ( + "bytes" + "fmt" + "io" + "reflect" + "strconv" +) + +// FormatError calls the FormatError method of f with an errors.Printer +// configured according to s and verb, and writes the result to s. +func FormatError(f Formatter, s fmt.State, verb rune) { + // Assuming this function is only called from the Format method, and given + // that FormatError takes precedence over Format, it cannot be called from + // any package that supports errors.Formatter. It is therefore safe to + // disregard that State may be a specific printer implementation and use one + // of our choice instead. + + // limitations: does not support printing error as Go struct. + + var ( + sep = " " // separator before next error + p = &state{State: s} + direct = true + ) + + var err error = f + + switch verb { + // Note that this switch must match the preference order + // for ordinary string printing (%#v before %+v, and so on). + + case 'v': + if s.Flag('#') { + if stringer, ok := err.(fmt.GoStringer); ok { + io.WriteString(&p.buf, stringer.GoString()) + goto exit + } + // proceed as if it were %v + } else if s.Flag('+') { + p.printDetail = true + sep = "\n - " + } + case 's': + case 'q', 'x', 'X': + // Use an intermediate buffer in the rare cases that precision, + // truncation, or one of the alternative verbs (q, x, and X) are + // specified. + direct = false + + default: + p.buf.WriteString("%!") + p.buf.WriteRune(verb) + p.buf.WriteByte('(') + switch { + case err != nil: + p.buf.WriteString(reflect.TypeOf(f).String()) + default: + p.buf.WriteString("") + } + p.buf.WriteByte(')') + io.Copy(s, &p.buf) + return + } + +loop: + for { + switch v := err.(type) { + case Formatter: + err = v.FormatError((*printer)(p)) + case fmt.Formatter: + v.Format(p, 'v') + break loop + default: + io.WriteString(&p.buf, v.Error()) + break loop + } + if err == nil { + break + } + if p.needColon || !p.printDetail { + p.buf.WriteByte(':') + p.needColon = false + } + p.buf.WriteString(sep) + p.inDetail = false + p.needNewline = false + } + +exit: + width, okW := s.Width() + prec, okP := s.Precision() + + if !direct || (okW && width > 0) || okP { + // Construct format string from State s. + format := []byte{'%'} + if s.Flag('-') { + format = append(format, '-') + } + if s.Flag('+') { + format = append(format, '+') + } + if s.Flag(' ') { + format = append(format, ' ') + } + if okW { + format = strconv.AppendInt(format, int64(width), 10) + } + if okP { + format = append(format, '.') + format = strconv.AppendInt(format, int64(prec), 10) + } + format = append(format, string(verb)...) + fmt.Fprintf(s, string(format), p.buf.String()) + } else { + io.Copy(s, &p.buf) + } +} + +var detailSep = []byte("\n ") + +// state tracks error printing state. It implements fmt.State. +type state struct { + fmt.State + buf bytes.Buffer + + printDetail bool + inDetail bool + needColon bool + needNewline bool +} + +func (s *state) Write(b []byte) (n int, err error) { + if s.printDetail { + if len(b) == 0 { + return 0, nil + } + if s.inDetail && s.needColon { + s.needNewline = true + if b[0] == '\n' { + b = b[1:] + } + } + k := 0 + for i, c := range b { + if s.needNewline { + if s.inDetail && s.needColon { + s.buf.WriteByte(':') + s.needColon = false + } + s.buf.Write(detailSep) + s.needNewline = false + } + if c == '\n' { + s.buf.Write(b[k:i]) + k = i + 1 + s.needNewline = true + } + } + s.buf.Write(b[k:]) + if !s.inDetail { + s.needColon = true + } + } else if !s.inDetail { + s.buf.Write(b) + } + return len(b), nil +} + +// printer wraps a state to implement an xerrors.Printer. +type printer state + +func (s *printer) Print(args ...interface{}) { + if !s.inDetail || s.printDetail { + fmt.Fprint((*state)(s), args...) + } +} + +func (s *printer) Printf(format string, args ...interface{}) { + if !s.inDetail || s.printDetail { + fmt.Fprintf((*state)(s), format, args...) + } +} + +func (s *printer) Detail() bool { + s.inDetail = true + return s.printDetail +} diff --git a/vendor/golang.org/x/xerrors/codereview.cfg b/vendor/golang.org/x/xerrors/codereview.cfg new file mode 100644 index 000000000..3f8b14b64 --- /dev/null +++ b/vendor/golang.org/x/xerrors/codereview.cfg @@ -0,0 +1 @@ +issuerepo: golang/go diff --git a/vendor/golang.org/x/xerrors/doc.go b/vendor/golang.org/x/xerrors/doc.go new file mode 100644 index 000000000..eef99d9d5 --- /dev/null +++ b/vendor/golang.org/x/xerrors/doc.go @@ -0,0 +1,22 @@ +// Copyright 2019 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package xerrors implements functions to manipulate errors. +// +// This package is based on the Go 2 proposal for error values: +// https://golang.org/design/29934-error-values +// +// These functions were incorporated into the standard library's errors package +// in Go 1.13: +// - Is +// - As +// - Unwrap +// +// Also, Errorf's %w verb was incorporated into fmt.Errorf. +// +// Use this package to get equivalent behavior in all supported Go versions. +// +// No other features of this package were included in Go 1.13, and at present +// there are no plans to include any of them. +package xerrors // import "golang.org/x/xerrors" diff --git a/vendor/golang.org/x/xerrors/errors.go b/vendor/golang.org/x/xerrors/errors.go new file mode 100644 index 000000000..e88d3772d --- /dev/null +++ b/vendor/golang.org/x/xerrors/errors.go @@ -0,0 +1,33 @@ +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xerrors + +import "fmt" + +// errorString is a trivial implementation of error. +type errorString struct { + s string + frame Frame +} + +// New returns an error that formats as the given text. +// +// The returned error contains a Frame set to the caller's location and +// implements Formatter to show this information when printed with details. +func New(text string) error { + return &errorString{text, Caller(1)} +} + +func (e *errorString) Error() string { + return e.s +} + +func (e *errorString) Format(s fmt.State, v rune) { FormatError(e, s, v) } + +func (e *errorString) FormatError(p Printer) (next error) { + p.Print(e.s) + e.frame.Format(p) + return nil +} diff --git a/vendor/golang.org/x/xerrors/fmt.go b/vendor/golang.org/x/xerrors/fmt.go new file mode 100644 index 000000000..829862ddf --- /dev/null +++ b/vendor/golang.org/x/xerrors/fmt.go @@ -0,0 +1,187 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xerrors + +import ( + "fmt" + "strings" + "unicode" + "unicode/utf8" + + "golang.org/x/xerrors/internal" +) + +const percentBangString = "%!" + +// Errorf formats according to a format specifier and returns the string as a +// value that satisfies error. +// +// The returned error includes the file and line number of the caller when +// formatted with additional detail enabled. If the last argument is an error +// the returned error's Format method will return it if the format string ends +// with ": %s", ": %v", or ": %w". If the last argument is an error and the +// format string ends with ": %w", the returned error implements an Unwrap +// method returning it. +// +// If the format specifier includes a %w verb with an error operand in a +// position other than at the end, the returned error will still implement an +// Unwrap method returning the operand, but the error's Format method will not +// return the wrapped error. +// +// It is invalid to include more than one %w verb or to supply it with an +// operand that does not implement the error interface. The %w verb is otherwise +// a synonym for %v. +func Errorf(format string, a ...interface{}) error { + format = formatPlusW(format) + // Support a ": %[wsv]" suffix, which works well with xerrors.Formatter. + wrap := strings.HasSuffix(format, ": %w") + idx, format2, ok := parsePercentW(format) + percentWElsewhere := !wrap && idx >= 0 + if !percentWElsewhere && (wrap || strings.HasSuffix(format, ": %s") || strings.HasSuffix(format, ": %v")) { + err := errorAt(a, len(a)-1) + if err == nil { + return &noWrapError{fmt.Sprintf(format, a...), nil, Caller(1)} + } + // TODO: this is not entirely correct. The error value could be + // printed elsewhere in format if it mixes numbered with unnumbered + // substitutions. With relatively small changes to doPrintf we can + // have it optionally ignore extra arguments and pass the argument + // list in its entirety. + msg := fmt.Sprintf(format[:len(format)-len(": %s")], a[:len(a)-1]...) + frame := Frame{} + if internal.EnableTrace { + frame = Caller(1) + } + if wrap { + return &wrapError{msg, err, frame} + } + return &noWrapError{msg, err, frame} + } + // Support %w anywhere. + // TODO: don't repeat the wrapped error's message when %w occurs in the middle. + msg := fmt.Sprintf(format2, a...) + if idx < 0 { + return &noWrapError{msg, nil, Caller(1)} + } + err := errorAt(a, idx) + if !ok || err == nil { + // Too many %ws or argument of %w is not an error. Approximate the Go + // 1.13 fmt.Errorf message. + return &noWrapError{fmt.Sprintf("%sw(%s)", percentBangString, msg), nil, Caller(1)} + } + frame := Frame{} + if internal.EnableTrace { + frame = Caller(1) + } + return &wrapError{msg, err, frame} +} + +func errorAt(args []interface{}, i int) error { + if i < 0 || i >= len(args) { + return nil + } + err, ok := args[i].(error) + if !ok { + return nil + } + return err +} + +// formatPlusW is used to avoid the vet check that will barf at %w. +func formatPlusW(s string) string { + return s +} + +// Return the index of the only %w in format, or -1 if none. +// Also return a rewritten format string with %w replaced by %v, and +// false if there is more than one %w. +// TODO: handle "%[N]w". +func parsePercentW(format string) (idx int, newFormat string, ok bool) { + // Loosely copied from golang.org/x/tools/go/analysis/passes/printf/printf.go. + idx = -1 + ok = true + n := 0 + sz := 0 + var isW bool + for i := 0; i < len(format); i += sz { + if format[i] != '%' { + sz = 1 + continue + } + // "%%" is not a format directive. + if i+1 < len(format) && format[i+1] == '%' { + sz = 2 + continue + } + sz, isW = parsePrintfVerb(format[i:]) + if isW { + if idx >= 0 { + ok = false + } else { + idx = n + } + // "Replace" the last character, the 'w', with a 'v'. + p := i + sz - 1 + format = format[:p] + "v" + format[p+1:] + } + n++ + } + return idx, format, ok +} + +// Parse the printf verb starting with a % at s[0]. +// Return how many bytes it occupies and whether the verb is 'w'. +func parsePrintfVerb(s string) (int, bool) { + // Assume only that the directive is a sequence of non-letters followed by a single letter. + sz := 0 + var r rune + for i := 1; i < len(s); i += sz { + r, sz = utf8.DecodeRuneInString(s[i:]) + if unicode.IsLetter(r) { + return i + sz, r == 'w' + } + } + return len(s), false +} + +type noWrapError struct { + msg string + err error + frame Frame +} + +func (e *noWrapError) Error() string { + return fmt.Sprint(e) +} + +func (e *noWrapError) Format(s fmt.State, v rune) { FormatError(e, s, v) } + +func (e *noWrapError) FormatError(p Printer) (next error) { + p.Print(e.msg) + e.frame.Format(p) + return e.err +} + +type wrapError struct { + msg string + err error + frame Frame +} + +func (e *wrapError) Error() string { + return fmt.Sprint(e) +} + +func (e *wrapError) Format(s fmt.State, v rune) { FormatError(e, s, v) } + +func (e *wrapError) FormatError(p Printer) (next error) { + p.Print(e.msg) + e.frame.Format(p) + return e.err +} + +func (e *wrapError) Unwrap() error { + return e.err +} diff --git a/vendor/golang.org/x/xerrors/format.go b/vendor/golang.org/x/xerrors/format.go new file mode 100644 index 000000000..1bc9c26b9 --- /dev/null +++ b/vendor/golang.org/x/xerrors/format.go @@ -0,0 +1,34 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xerrors + +// A Formatter formats error messages. +type Formatter interface { + error + + // FormatError prints the receiver's first error and returns the next error in + // the error chain, if any. + FormatError(p Printer) (next error) +} + +// A Printer formats error messages. +// +// The most common implementation of Printer is the one provided by package fmt +// during Printf (as of Go 1.13). Localization packages such as golang.org/x/text/message +// typically provide their own implementations. +type Printer interface { + // Print appends args to the message output. + Print(args ...interface{}) + + // Printf writes a formatted string. + Printf(format string, args ...interface{}) + + // Detail reports whether error detail is requested. + // After the first call to Detail, all text written to the Printer + // is formatted as additional detail, or ignored when + // detail has not been requested. + // If Detail returns false, the caller can avoid printing the detail at all. + Detail() bool +} diff --git a/vendor/golang.org/x/xerrors/frame.go b/vendor/golang.org/x/xerrors/frame.go new file mode 100644 index 000000000..0de628ec5 --- /dev/null +++ b/vendor/golang.org/x/xerrors/frame.go @@ -0,0 +1,56 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xerrors + +import ( + "runtime" +) + +// A Frame contains part of a call stack. +type Frame struct { + // Make room for three PCs: the one we were asked for, what it called, + // and possibly a PC for skipPleaseUseCallersFrames. See: + // https://go.googlesource.com/go/+/032678e0fb/src/runtime/extern.go#169 + frames [3]uintptr +} + +// Caller returns a Frame that describes a frame on the caller's stack. +// The argument skip is the number of frames to skip over. +// Caller(0) returns the frame for the caller of Caller. +func Caller(skip int) Frame { + var s Frame + runtime.Callers(skip+1, s.frames[:]) + return s +} + +// location reports the file, line, and function of a frame. +// +// The returned function may be "" even if file and line are not. +func (f Frame) location() (function, file string, line int) { + frames := runtime.CallersFrames(f.frames[:]) + if _, ok := frames.Next(); !ok { + return "", "", 0 + } + fr, ok := frames.Next() + if !ok { + return "", "", 0 + } + return fr.Function, fr.File, fr.Line +} + +// Format prints the stack as error detail. +// It should be called from an error's Format implementation +// after printing any other error detail. +func (f Frame) Format(p Printer) { + if p.Detail() { + function, file, line := f.location() + if function != "" { + p.Printf("%s\n ", function) + } + if file != "" { + p.Printf("%s:%d\n", file, line) + } + } +} diff --git a/vendor/golang.org/x/xerrors/go.mod b/vendor/golang.org/x/xerrors/go.mod new file mode 100644 index 000000000..870d4f612 --- /dev/null +++ b/vendor/golang.org/x/xerrors/go.mod @@ -0,0 +1,3 @@ +module golang.org/x/xerrors + +go 1.11 diff --git a/vendor/golang.org/x/xerrors/internal/internal.go b/vendor/golang.org/x/xerrors/internal/internal.go new file mode 100644 index 000000000..89f4eca5d --- /dev/null +++ b/vendor/golang.org/x/xerrors/internal/internal.go @@ -0,0 +1,8 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package internal + +// EnableTrace indicates whether stack information should be recorded in errors. +var EnableTrace = true diff --git a/vendor/golang.org/x/xerrors/wrap.go b/vendor/golang.org/x/xerrors/wrap.go new file mode 100644 index 000000000..9a3b51037 --- /dev/null +++ b/vendor/golang.org/x/xerrors/wrap.go @@ -0,0 +1,106 @@ +// Copyright 2018 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package xerrors + +import ( + "reflect" +) + +// A Wrapper provides context around another error. +type Wrapper interface { + // Unwrap returns the next error in the error chain. + // If there is no next error, Unwrap returns nil. + Unwrap() error +} + +// Opaque returns an error with the same error formatting as err +// but that does not match err and cannot be unwrapped. +func Opaque(err error) error { + return noWrapper{err} +} + +type noWrapper struct { + error +} + +func (e noWrapper) FormatError(p Printer) (next error) { + if f, ok := e.error.(Formatter); ok { + return f.FormatError(p) + } + p.Print(e.error) + return nil +} + +// Unwrap returns the result of calling the Unwrap method on err, if err implements +// Unwrap. Otherwise, Unwrap returns nil. +func Unwrap(err error) error { + u, ok := err.(Wrapper) + if !ok { + return nil + } + return u.Unwrap() +} + +// Is reports whether any error in err's chain matches target. +// +// An error is considered to match a target if it is equal to that target or if +// it implements a method Is(error) bool such that Is(target) returns true. +func Is(err, target error) bool { + if target == nil { + return err == target + } + + isComparable := reflect.TypeOf(target).Comparable() + for { + if isComparable && err == target { + return true + } + if x, ok := err.(interface{ Is(error) bool }); ok && x.Is(target) { + return true + } + // TODO: consider supporing target.Is(err). This would allow + // user-definable predicates, but also may allow for coping with sloppy + // APIs, thereby making it easier to get away with them. + if err = Unwrap(err); err == nil { + return false + } + } +} + +// As finds the first error in err's chain that matches the type to which target +// points, and if so, sets the target to its value and returns true. An error +// matches a type if it is assignable to the target type, or if it has a method +// As(interface{}) bool such that As(target) returns true. As will panic if target +// is not a non-nil pointer to a type which implements error or is of interface type. +// +// The As method should set the target to its value and return true if err +// matches the type to which target points. +func As(err error, target interface{}) bool { + if target == nil { + panic("errors: target cannot be nil") + } + val := reflect.ValueOf(target) + typ := val.Type() + if typ.Kind() != reflect.Ptr || val.IsNil() { + panic("errors: target must be a non-nil pointer") + } + if e := typ.Elem(); e.Kind() != reflect.Interface && !e.Implements(errorType) { + panic("errors: *target must be interface or implement error") + } + targetType := typ.Elem() + for err != nil { + if reflect.TypeOf(err).AssignableTo(targetType) { + val.Elem().Set(reflect.ValueOf(err)) + return true + } + if x, ok := err.(interface{ As(interface{}) bool }); ok && x.As(target) { + return true + } + err = Unwrap(err) + } + return false +} + +var errorType = reflect.TypeOf((*error)(nil)).Elem() diff --git a/vendor/gopkg.in/ini.v1/.gitignore b/vendor/gopkg.in/ini.v1/.gitignore new file mode 100644 index 000000000..12411127b --- /dev/null +++ b/vendor/gopkg.in/ini.v1/.gitignore @@ -0,0 +1,6 @@ +testdata/conf_out.ini +ini.sublime-project +ini.sublime-workspace +testdata/conf_reflect.ini +.idea +/.vscode diff --git a/vendor/gopkg.in/ini.v1/.travis.yml b/vendor/gopkg.in/ini.v1/.travis.yml new file mode 100644 index 000000000..149b7249f --- /dev/null +++ b/vendor/gopkg.in/ini.v1/.travis.yml @@ -0,0 +1,20 @@ +sudo: false +language: go +go: + - 1.6.x + - 1.7.x + - 1.8.x + - 1.9.x + - 1.10.x + - 1.11.x + - 1.12.x + - 1.13.x + +install: skip +script: + - go get golang.org/x/tools/cmd/cover + - go get github.com/smartystreets/goconvey + - mkdir -p $HOME/gopath/src/gopkg.in + - ln -s $HOME/gopath/src/github.com/go-ini/ini $HOME/gopath/src/gopkg.in/ini.v1 + - cd $HOME/gopath/src/gopkg.in/ini.v1 + - go test -v -cover -race diff --git a/vendor/gopkg.in/ini.v1/LICENSE b/vendor/gopkg.in/ini.v1/LICENSE new file mode 100644 index 000000000..d361bbcdf --- /dev/null +++ b/vendor/gopkg.in/ini.v1/LICENSE @@ -0,0 +1,191 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + +"License" shall mean the terms and conditions for use, reproduction, and +distribution as defined by Sections 1 through 9 of this document. + +"Licensor" shall mean the copyright owner or entity authorized by the copyright +owner that is granting the License. + +"Legal Entity" shall mean the union of the acting entity and all other entities +that control, are controlled by, or are under common control with that entity. +For the purposes of this definition, "control" means (i) the power, direct or +indirect, to cause the direction or management of such entity, whether by +contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the +outstanding shares, or (iii) beneficial ownership of such entity. + +"You" (or "Your") shall mean an individual or Legal Entity exercising +permissions granted by this License. + +"Source" form shall mean the preferred form for making modifications, including +but not limited to software source code, documentation source, and configuration +files. + +"Object" form shall mean any form resulting from mechanical transformation or +translation of a Source form, including but not limited to compiled object code, +generated documentation, and conversions to other media types. + +"Work" shall mean the work of authorship, whether in Source or Object form, made +available under the License, as indicated by a copyright notice that is included +in or attached to the work (an example is provided in the Appendix below). + +"Derivative Works" shall mean any work, whether in Source or Object form, that +is based on (or derived from) the Work and for which the editorial revisions, +annotations, elaborations, or other modifications represent, as a whole, an +original work of authorship. For the purposes of this License, Derivative Works +shall not include works that remain separable from, or merely link (or bind by +name) to the interfaces of, the Work and Derivative Works thereof. + +"Contribution" shall mean any work of authorship, including the original version +of the Work and any modifications or additions to that Work or Derivative Works +thereof, that is intentionally submitted to Licensor for inclusion in the Work +by the copyright owner or by an individual or Legal Entity authorized to submit +on behalf of the copyright owner. For the purposes of this definition, +"submitted" means any form of electronic, verbal, or written communication sent +to the Licensor or its representatives, including but not limited to +communication on electronic mailing lists, source code control systems, and +issue tracking systems that are managed by, or on behalf of, the Licensor for +the purpose of discussing and improving the Work, but excluding communication +that is conspicuously marked or otherwise designated in writing by the copyright +owner as "Not a Contribution." + +"Contributor" shall mean Licensor and any individual or Legal Entity on behalf +of whom a Contribution has been received by Licensor and subsequently +incorporated within the Work. + +2. Grant of Copyright License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable copyright license to reproduce, prepare Derivative Works of, +publicly display, publicly perform, sublicense, and distribute the Work and such +Derivative Works in Source or Object form. + +3. Grant of Patent License. + +Subject to the terms and conditions of this License, each Contributor hereby +grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, +irrevocable (except as stated in this section) patent license to make, have +made, use, offer to sell, sell, import, and otherwise transfer the Work, where +such license applies only to those patent claims licensable by such Contributor +that are necessarily infringed by their Contribution(s) alone or by combination +of their Contribution(s) with the Work to which such Contribution(s) was +submitted. If You institute patent litigation against any entity (including a +cross-claim or counterclaim in a lawsuit) alleging that the Work or a +Contribution incorporated within the Work constitutes direct or contributory +patent infringement, then any patent licenses granted to You under this License +for that Work shall terminate as of the date such litigation is filed. + +4. Redistribution. + +You may reproduce and distribute copies of the Work or Derivative Works thereof +in any medium, with or without modifications, and in Source or Object form, +provided that You meet the following conditions: + +You must give any other recipients of the Work or Derivative Works a copy of +this License; and +You must cause any modified files to carry prominent notices stating that You +changed the files; and +You must retain, in the Source form of any Derivative Works that You distribute, +all copyright, patent, trademark, and attribution notices from the Source form +of the Work, excluding those notices that do not pertain to any part of the +Derivative Works; and +If the Work includes a "NOTICE" text file as part of its distribution, then any +Derivative Works that You distribute must include a readable copy of the +attribution notices contained within such NOTICE file, excluding those notices +that do not pertain to any part of the Derivative Works, in at least one of the +following places: within a NOTICE text file distributed as part of the +Derivative Works; within the Source form or documentation, if provided along +with the Derivative Works; or, within a display generated by the Derivative +Works, if and wherever such third-party notices normally appear. The contents of +the NOTICE file are for informational purposes only and do not modify the +License. You may add Your own attribution notices within Derivative Works that +You distribute, alongside or as an addendum to the NOTICE text from the Work, +provided that such additional attribution notices cannot be construed as +modifying the License. +You may add Your own copyright statement to Your modifications and may provide +additional or different license terms and conditions for use, reproduction, or +distribution of Your modifications, or for any such Derivative Works as a whole, +provided Your use, reproduction, and distribution of the Work otherwise complies +with the conditions stated in this License. + +5. Submission of Contributions. + +Unless You explicitly state otherwise, any Contribution intentionally submitted +for inclusion in the Work by You to the Licensor shall be under the terms and +conditions of this License, without any additional terms or conditions. +Notwithstanding the above, nothing herein shall supersede or modify the terms of +any separate license agreement you may have executed with Licensor regarding +such Contributions. + +6. Trademarks. + +This License does not grant permission to use the trade names, trademarks, +service marks, or product names of the Licensor, except as required for +reasonable and customary use in describing the origin of the Work and +reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. + +Unless required by applicable law or agreed to in writing, Licensor provides the +Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, +including, without limitation, any warranties or conditions of TITLE, +NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are +solely responsible for determining the appropriateness of using or +redistributing the Work and assume any risks associated with Your exercise of +permissions under this License. + +8. Limitation of Liability. + +In no event and under no legal theory, whether in tort (including negligence), +contract, or otherwise, unless required by applicable law (such as deliberate +and grossly negligent acts) or agreed to in writing, shall any Contributor be +liable to You for damages, including any direct, indirect, special, incidental, +or consequential damages of any character arising as a result of this License or +out of the use or inability to use the Work (including but not limited to +damages for loss of goodwill, work stoppage, computer failure or malfunction, or +any and all other commercial damages or losses), even if such Contributor has +been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. + +While redistributing the Work or Derivative Works thereof, You may choose to +offer, and charge a fee for, acceptance of support, warranty, indemnity, or +other liability obligations and/or rights consistent with this License. However, +in accepting such obligations, You may act only on Your own behalf and on Your +sole responsibility, not on behalf of any other Contributor, and only if You +agree to indemnify, defend, and hold each Contributor harmless for any liability +incurred by, or claims asserted against, such Contributor by reason of your +accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS + +APPENDIX: How to apply the Apache License to your work + +To apply the Apache License to your work, attach the following boilerplate +notice, with the fields enclosed by brackets "[]" replaced with your own +identifying information. (Don't include the brackets!) The text should be +enclosed in the appropriate comment syntax for the file format. We also +recommend that a file or class name and description of purpose be included on +the same "printed page" as the copyright notice for easier identification within +third-party archives. + + Copyright 2014 Unknwon + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/gopkg.in/ini.v1/Makefile b/vendor/gopkg.in/ini.v1/Makefile new file mode 100644 index 000000000..af27ff076 --- /dev/null +++ b/vendor/gopkg.in/ini.v1/Makefile @@ -0,0 +1,15 @@ +.PHONY: build test bench vet coverage + +build: vet bench + +test: + go test -v -cover -race + +bench: + go test -v -cover -race -test.bench=. -test.benchmem + +vet: + go vet + +coverage: + go test -coverprofile=c.out && go tool cover -html=c.out && rm c.out diff --git a/vendor/gopkg.in/ini.v1/README.md b/vendor/gopkg.in/ini.v1/README.md new file mode 100644 index 000000000..3d6d3cfc0 --- /dev/null +++ b/vendor/gopkg.in/ini.v1/README.md @@ -0,0 +1,39 @@ +# INI + +[![Build Status](https://img.shields.io/travis/go-ini/ini/master.svg?style=for-the-badge&logo=travis)](https://travis-ci.org/go-ini/ini) [![Sourcegraph](https://img.shields.io/badge/view%20on-Sourcegraph-brightgreen.svg?style=for-the-badge&logo=sourcegraph)](https://sourcegraph.com/github.com/go-ini/ini) + +![](https://avatars0.githubusercontent.com/u/10216035?v=3&s=200) + +Package ini provides INI file read and write functionality in Go. + +## Features + +- Load from multiple data sources(`[]byte`, file and `io.ReadCloser`) with overwrites. +- Read with recursion values. +- Read with parent-child sections. +- Read with auto-increment key names. +- Read with multiple-line values. +- Read with tons of helper methods. +- Read and convert values to Go types. +- Read and **WRITE** comments of sections and keys. +- Manipulate sections, keys and comments with ease. +- Keep sections and keys in order as you parse and save. + +## Installation + +The minimum requirement of Go is **1.6**. + +```sh +$ go get gopkg.in/ini.v1 +``` + +Please add `-u` flag to update in the future. + +## Getting Help + +- [Getting Started](https://ini.unknwon.io/docs/intro/getting_started) +- [API Documentation](https://gowalker.org/gopkg.in/ini.v1) + +## License + +This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text. diff --git a/vendor/gopkg.in/ini.v1/data_source.go b/vendor/gopkg.in/ini.v1/data_source.go new file mode 100644 index 000000000..dc0277ec6 --- /dev/null +++ b/vendor/gopkg.in/ini.v1/data_source.go @@ -0,0 +1,74 @@ +// Copyright 2019 Unknwon +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package ini + +import ( + "bytes" + "fmt" + "io" + "io/ioutil" + "os" +) + +var ( + _ dataSource = (*sourceFile)(nil) + _ dataSource = (*sourceData)(nil) + _ dataSource = (*sourceReadCloser)(nil) +) + +// dataSource is an interface that returns object which can be read and closed. +type dataSource interface { + ReadCloser() (io.ReadCloser, error) +} + +// sourceFile represents an object that contains content on the local file system. +type sourceFile struct { + name string +} + +func (s sourceFile) ReadCloser() (_ io.ReadCloser, err error) { + return os.Open(s.name) +} + +// sourceData represents an object that contains content in memory. +type sourceData struct { + data []byte +} + +func (s *sourceData) ReadCloser() (io.ReadCloser, error) { + return ioutil.NopCloser(bytes.NewReader(s.data)), nil +} + +// sourceReadCloser represents an input stream with Close method. +type sourceReadCloser struct { + reader io.ReadCloser +} + +func (s *sourceReadCloser) ReadCloser() (io.ReadCloser, error) { + return s.reader, nil +} + +func parseDataSource(source interface{}) (dataSource, error) { + switch s := source.(type) { + case string: + return sourceFile{s}, nil + case []byte: + return &sourceData{s}, nil + case io.ReadCloser: + return &sourceReadCloser{s}, nil + default: + return nil, fmt.Errorf("error parsing data source: unknown type %q", s) + } +} diff --git a/vendor/gopkg.in/ini.v1/deprecated.go b/vendor/gopkg.in/ini.v1/deprecated.go new file mode 100644 index 000000000..e8bda06e6 --- /dev/null +++ b/vendor/gopkg.in/ini.v1/deprecated.go @@ -0,0 +1,25 @@ +// Copyright 2019 Unknwon +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package ini + +const ( + // Deprecated: Use "DefaultSection" instead. + DEFAULT_SECTION = DefaultSection +) + +var ( + // Deprecated: AllCapsUnderscore converts to format ALL_CAPS_UNDERSCORE. + AllCapsUnderscore = SnackCase +) diff --git a/vendor/gopkg.in/ini.v1/error.go b/vendor/gopkg.in/ini.v1/error.go new file mode 100644 index 000000000..d88347c54 --- /dev/null +++ b/vendor/gopkg.in/ini.v1/error.go @@ -0,0 +1,34 @@ +// Copyright 2016 Unknwon +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package ini + +import ( + "fmt" +) + +// ErrDelimiterNotFound indicates the error type of no delimiter is found which there should be one. +type ErrDelimiterNotFound struct { + Line string +} + +// IsErrDelimiterNotFound returns true if the given error is an instance of ErrDelimiterNotFound. +func IsErrDelimiterNotFound(err error) bool { + _, ok := err.(ErrDelimiterNotFound) + return ok +} + +func (err ErrDelimiterNotFound) Error() string { + return fmt.Sprintf("key-value delimiter not found: %s", err.Line) +} diff --git a/vendor/gopkg.in/ini.v1/file.go b/vendor/gopkg.in/ini.v1/file.go new file mode 100644 index 000000000..017b77c8b --- /dev/null +++ b/vendor/gopkg.in/ini.v1/file.go @@ -0,0 +1,418 @@ +// Copyright 2017 Unknwon +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package ini + +import ( + "bytes" + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "strings" + "sync" +) + +// File represents a combination of a or more INI file(s) in memory. +type File struct { + options LoadOptions + dataSources []dataSource + + // Should make things safe, but sometimes doesn't matter. + BlockMode bool + lock sync.RWMutex + + // To keep data in order. + sectionList []string + // Actual data is stored here. + sections map[string]*Section + + NameMapper + ValueMapper +} + +// newFile initializes File object with given data sources. +func newFile(dataSources []dataSource, opts LoadOptions) *File { + if len(opts.KeyValueDelimiters) == 0 { + opts.KeyValueDelimiters = "=:" + } + return &File{ + BlockMode: true, + dataSources: dataSources, + sections: make(map[string]*Section), + sectionList: make([]string, 0, 10), + options: opts, + } +} + +// Empty returns an empty file object. +func Empty() *File { + // Ignore error here, we sure our data is good. + f, _ := Load([]byte("")) + return f +} + +// NewSection creates a new section. +func (f *File) NewSection(name string) (*Section, error) { + if len(name) == 0 { + return nil, errors.New("error creating new section: empty section name") + } else if f.options.Insensitive && name != DefaultSection { + name = strings.ToLower(name) + } + + if f.BlockMode { + f.lock.Lock() + defer f.lock.Unlock() + } + + if inSlice(name, f.sectionList) { + return f.sections[name], nil + } + + f.sectionList = append(f.sectionList, name) + f.sections[name] = newSection(f, name) + return f.sections[name], nil +} + +// NewRawSection creates a new section with an unparseable body. +func (f *File) NewRawSection(name, body string) (*Section, error) { + section, err := f.NewSection(name) + if err != nil { + return nil, err + } + + section.isRawSection = true + section.rawBody = body + return section, nil +} + +// NewSections creates a list of sections. +func (f *File) NewSections(names ...string) (err error) { + for _, name := range names { + if _, err = f.NewSection(name); err != nil { + return err + } + } + return nil +} + +// GetSection returns section by given name. +func (f *File) GetSection(name string) (*Section, error) { + if len(name) == 0 { + name = DefaultSection + } + if f.options.Insensitive { + name = strings.ToLower(name) + } + + if f.BlockMode { + f.lock.RLock() + defer f.lock.RUnlock() + } + + sec := f.sections[name] + if sec == nil { + return nil, fmt.Errorf("section '%s' does not exist", name) + } + return sec, nil +} + +// Section assumes named section exists and returns a zero-value when not. +func (f *File) Section(name string) *Section { + sec, err := f.GetSection(name) + if err != nil { + // Note: It's OK here because the only possible error is empty section name, + // but if it's empty, this piece of code won't be executed. + sec, _ = f.NewSection(name) + return sec + } + return sec +} + +// Sections returns a list of Section stored in the current instance. +func (f *File) Sections() []*Section { + if f.BlockMode { + f.lock.RLock() + defer f.lock.RUnlock() + } + + sections := make([]*Section, len(f.sectionList)) + for i, name := range f.sectionList { + sections[i] = f.sections[name] + } + return sections +} + +// ChildSections returns a list of child sections of given section name. +func (f *File) ChildSections(name string) []*Section { + return f.Section(name).ChildSections() +} + +// SectionStrings returns list of section names. +func (f *File) SectionStrings() []string { + list := make([]string, len(f.sectionList)) + copy(list, f.sectionList) + return list +} + +// DeleteSection deletes a section. +func (f *File) DeleteSection(name string) { + if f.BlockMode { + f.lock.Lock() + defer f.lock.Unlock() + } + + if len(name) == 0 { + name = DefaultSection + } + + for i, s := range f.sectionList { + if s == name { + f.sectionList = append(f.sectionList[:i], f.sectionList[i+1:]...) + delete(f.sections, name) + return + } + } +} + +func (f *File) reload(s dataSource) error { + r, err := s.ReadCloser() + if err != nil { + return err + } + defer r.Close() + + return f.parse(r) +} + +// Reload reloads and parses all data sources. +func (f *File) Reload() (err error) { + for _, s := range f.dataSources { + if err = f.reload(s); err != nil { + // In loose mode, we create an empty default section for nonexistent files. + if os.IsNotExist(err) && f.options.Loose { + f.parse(bytes.NewBuffer(nil)) + continue + } + return err + } + } + return nil +} + +// Append appends one or more data sources and reloads automatically. +func (f *File) Append(source interface{}, others ...interface{}) error { + ds, err := parseDataSource(source) + if err != nil { + return err + } + f.dataSources = append(f.dataSources, ds) + for _, s := range others { + ds, err = parseDataSource(s) + if err != nil { + return err + } + f.dataSources = append(f.dataSources, ds) + } + return f.Reload() +} + +func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) { + equalSign := DefaultFormatLeft + "=" + DefaultFormatRight + + if PrettyFormat || PrettyEqual { + equalSign = " = " + } + + // Use buffer to make sure target is safe until finish encoding. + buf := bytes.NewBuffer(nil) + for i, sname := range f.sectionList { + sec := f.Section(sname) + if len(sec.Comment) > 0 { + // Support multiline comments + lines := strings.Split(sec.Comment, LineBreak) + for i := range lines { + if lines[i][0] != '#' && lines[i][0] != ';' { + lines[i] = "; " + lines[i] + } else { + lines[i] = lines[i][:1] + " " + strings.TrimSpace(lines[i][1:]) + } + + if _, err := buf.WriteString(lines[i] + LineBreak); err != nil { + return nil, err + } + } + } + + if i > 0 || DefaultHeader { + if _, err := buf.WriteString("[" + sname + "]" + LineBreak); err != nil { + return nil, err + } + } else { + // Write nothing if default section is empty + if len(sec.keyList) == 0 { + continue + } + } + + if sec.isRawSection { + if _, err := buf.WriteString(sec.rawBody); err != nil { + return nil, err + } + + if PrettySection { + // Put a line between sections + if _, err := buf.WriteString(LineBreak); err != nil { + return nil, err + } + } + continue + } + + // Count and generate alignment length and buffer spaces using the + // longest key. Keys may be modifed if they contain certain characters so + // we need to take that into account in our calculation. + alignLength := 0 + if PrettyFormat { + for _, kname := range sec.keyList { + keyLength := len(kname) + // First case will surround key by ` and second by """ + if strings.Contains(kname, "\"") || strings.ContainsAny(kname, f.options.KeyValueDelimiters) { + keyLength += 2 + } else if strings.Contains(kname, "`") { + keyLength += 6 + } + + if keyLength > alignLength { + alignLength = keyLength + } + } + } + alignSpaces := bytes.Repeat([]byte(" "), alignLength) + + KeyList: + for _, kname := range sec.keyList { + key := sec.Key(kname) + if len(key.Comment) > 0 { + if len(indent) > 0 && sname != DefaultSection { + buf.WriteString(indent) + } + + // Support multiline comments + lines := strings.Split(key.Comment, LineBreak) + for i := range lines { + if lines[i][0] != '#' && lines[i][0] != ';' { + lines[i] = "; " + strings.TrimSpace(lines[i]) + } else { + lines[i] = lines[i][:1] + " " + strings.TrimSpace(lines[i][1:]) + } + + if _, err := buf.WriteString(lines[i] + LineBreak); err != nil { + return nil, err + } + } + } + + if len(indent) > 0 && sname != DefaultSection { + buf.WriteString(indent) + } + + switch { + case key.isAutoIncrement: + kname = "-" + case strings.Contains(kname, "\"") || strings.ContainsAny(kname, f.options.KeyValueDelimiters): + kname = "`" + kname + "`" + case strings.Contains(kname, "`"): + kname = `"""` + kname + `"""` + } + + for _, val := range key.ValueWithShadows() { + if _, err := buf.WriteString(kname); err != nil { + return nil, err + } + + if key.isBooleanType { + if kname != sec.keyList[len(sec.keyList)-1] { + buf.WriteString(LineBreak) + } + continue KeyList + } + + // Write out alignment spaces before "=" sign + if PrettyFormat { + buf.Write(alignSpaces[:alignLength-len(kname)]) + } + + // In case key value contains "\n", "`", "\"", "#" or ";" + if strings.ContainsAny(val, "\n`") { + val = `"""` + val + `"""` + } else if !f.options.IgnoreInlineComment && strings.ContainsAny(val, "#;") { + val = "`" + val + "`" + } + if _, err := buf.WriteString(equalSign + val + LineBreak); err != nil { + return nil, err + } + } + + for _, val := range key.nestedValues { + if _, err := buf.WriteString(indent + " " + val + LineBreak); err != nil { + return nil, err + } + } + } + + if PrettySection { + // Put a line between sections + if _, err := buf.WriteString(LineBreak); err != nil { + return nil, err + } + } + } + + return buf, nil +} + +// WriteToIndent writes content into io.Writer with given indention. +// If PrettyFormat has been set to be true, +// it will align "=" sign with spaces under each section. +func (f *File) WriteToIndent(w io.Writer, indent string) (int64, error) { + buf, err := f.writeToBuffer(indent) + if err != nil { + return 0, err + } + return buf.WriteTo(w) +} + +// WriteTo writes file content into io.Writer. +func (f *File) WriteTo(w io.Writer) (int64, error) { + return f.WriteToIndent(w, "") +} + +// SaveToIndent writes content to file system with given value indention. +func (f *File) SaveToIndent(filename, indent string) error { + // Note: Because we are truncating with os.Create, + // so it's safer to save to a temporary file location and rename afte done. + buf, err := f.writeToBuffer(indent) + if err != nil { + return err + } + + return ioutil.WriteFile(filename, buf.Bytes(), 0666) +} + +// SaveTo writes content to file system. +func (f *File) SaveTo(filename string) error { + return f.SaveToIndent(filename, "") +} diff --git a/vendor/gopkg.in/ini.v1/helper.go b/vendor/gopkg.in/ini.v1/helper.go new file mode 100644 index 000000000..f9d80a682 --- /dev/null +++ b/vendor/gopkg.in/ini.v1/helper.go @@ -0,0 +1,24 @@ +// Copyright 2019 Unknwon +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package ini + +func inSlice(str string, s []string) bool { + for _, v := range s { + if str == v { + return true + } + } + return false +} diff --git a/vendor/gopkg.in/ini.v1/ini.go b/vendor/gopkg.in/ini.v1/ini.go new file mode 100644 index 000000000..945fc00c0 --- /dev/null +++ b/vendor/gopkg.in/ini.v1/ini.go @@ -0,0 +1,166 @@ +// +build go1.6 + +// Copyright 2014 Unknwon +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +// Package ini provides INI file read and write functionality in Go. +package ini + +import ( + "regexp" + "runtime" +) + +const ( + // DefaultSection is the name of default section. You can use this constant or the string literal. + // In most of cases, an empty string is all you need to access the section. + DefaultSection = "DEFAULT" + + // Maximum allowed depth when recursively substituing variable names. + depthValues = 99 + version = "1.51.0" +) + +// Version returns current package version literal. +func Version() string { + return version +} + +var ( + // LineBreak is the delimiter to determine or compose a new line. + // This variable will be changed to "\r\n" automatically on Windows at package init time. + LineBreak = "\n" + + // Variable regexp pattern: %(variable)s + varPattern = regexp.MustCompile(`%\(([^)]+)\)s`) + + // DefaultHeader explicitly writes default section header. + DefaultHeader = false + + // PrettySection indicates whether to put a line between sections. + PrettySection = true + // PrettyFormat indicates whether to align "=" sign with spaces to produce pretty output + // or reduce all possible spaces for compact format. + PrettyFormat = true + // PrettyEqual places spaces around "=" sign even when PrettyFormat is false. + PrettyEqual = false + // DefaultFormatLeft places custom spaces on the left when PrettyFormat and PrettyEqual are both disabled. + DefaultFormatLeft = "" + // DefaultFormatRight places custom spaces on the right when PrettyFormat and PrettyEqual are both disabled. + DefaultFormatRight = "" +) + +func init() { + if runtime.GOOS == "windows" { + LineBreak = "\r\n" + } +} + +// LoadOptions contains all customized options used for load data source(s). +type LoadOptions struct { + // Loose indicates whether the parser should ignore nonexistent files or return error. + Loose bool + // Insensitive indicates whether the parser forces all section and key names to lowercase. + Insensitive bool + // IgnoreContinuation indicates whether to ignore continuation lines while parsing. + IgnoreContinuation bool + // IgnoreInlineComment indicates whether to ignore comments at the end of value and treat it as part of value. + IgnoreInlineComment bool + // SkipUnrecognizableLines indicates whether to skip unrecognizable lines that do not conform to key/value pairs. + SkipUnrecognizableLines bool + // AllowBooleanKeys indicates whether to allow boolean type keys or treat as value is missing. + // This type of keys are mostly used in my.cnf. + AllowBooleanKeys bool + // AllowShadows indicates whether to keep track of keys with same name under same section. + AllowShadows bool + // AllowNestedValues indicates whether to allow AWS-like nested values. + // Docs: http://docs.aws.amazon.com/cli/latest/topic/config-vars.html#nested-values + AllowNestedValues bool + // AllowPythonMultilineValues indicates whether to allow Python-like multi-line values. + // Docs: https://docs.python.org/3/library/configparser.html#supported-ini-file-structure + // Relevant quote: Values can also span multiple lines, as long as they are indented deeper + // than the first line of the value. + AllowPythonMultilineValues bool + // SpaceBeforeInlineComment indicates whether to allow comment symbols (\# and \;) inside value. + // Docs: https://docs.python.org/2/library/configparser.html + // Quote: Comments may appear on their own in an otherwise empty line, or may be entered in lines holding values or section names. + // In the latter case, they need to be preceded by a whitespace character to be recognized as a comment. + SpaceBeforeInlineComment bool + // UnescapeValueDoubleQuotes indicates whether to unescape double quotes inside value to regular format + // when value is surrounded by double quotes, e.g. key="a \"value\"" => key=a "value" + UnescapeValueDoubleQuotes bool + // UnescapeValueCommentSymbols indicates to unescape comment symbols (\# and \;) inside value to regular format + // when value is NOT surrounded by any quotes. + // Note: UNSTABLE, behavior might change to only unescape inside double quotes but may noy necessary at all. + UnescapeValueCommentSymbols bool + // UnparseableSections stores a list of blocks that are allowed with raw content which do not otherwise + // conform to key/value pairs. Specify the names of those blocks here. + UnparseableSections []string + // KeyValueDelimiters is the sequence of delimiters that are used to separate key and value. By default, it is "=:". + KeyValueDelimiters string + // PreserveSurroundedQuote indicates whether to preserve surrounded quote (single and double quotes). + PreserveSurroundedQuote bool + // DebugFunc is called to collect debug information (currently only useful to debug parsing Python-style multiline values). + DebugFunc DebugFunc + // ReaderBufferSize is the buffer size of the reader in bytes. + ReaderBufferSize int +} + +// DebugFunc is the type of function called to log parse events. +type DebugFunc func(message string) + +// LoadSources allows caller to apply customized options for loading from data source(s). +func LoadSources(opts LoadOptions, source interface{}, others ...interface{}) (_ *File, err error) { + sources := make([]dataSource, len(others)+1) + sources[0], err = parseDataSource(source) + if err != nil { + return nil, err + } + for i := range others { + sources[i+1], err = parseDataSource(others[i]) + if err != nil { + return nil, err + } + } + f := newFile(sources, opts) + if err = f.Reload(); err != nil { + return nil, err + } + return f, nil +} + +// Load loads and parses from INI data sources. +// Arguments can be mixed of file name with string type, or raw data in []byte. +// It will return error if list contains nonexistent files. +func Load(source interface{}, others ...interface{}) (*File, error) { + return LoadSources(LoadOptions{}, source, others...) +} + +// LooseLoad has exactly same functionality as Load function +// except it ignores nonexistent files instead of returning error. +func LooseLoad(source interface{}, others ...interface{}) (*File, error) { + return LoadSources(LoadOptions{Loose: true}, source, others...) +} + +// InsensitiveLoad has exactly same functionality as Load function +// except it forces all section and key names to be lowercased. +func InsensitiveLoad(source interface{}, others ...interface{}) (*File, error) { + return LoadSources(LoadOptions{Insensitive: true}, source, others...) +} + +// ShadowLoad has exactly same functionality as Load function +// except it allows have shadow keys. +func ShadowLoad(source interface{}, others ...interface{}) (*File, error) { + return LoadSources(LoadOptions{AllowShadows: true}, source, others...) +} diff --git a/vendor/gopkg.in/ini.v1/key.go b/vendor/gopkg.in/ini.v1/key.go new file mode 100644 index 000000000..3c197410f --- /dev/null +++ b/vendor/gopkg.in/ini.v1/key.go @@ -0,0 +1,801 @@ +// Copyright 2014 Unknwon +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package ini + +import ( + "bytes" + "errors" + "fmt" + "strconv" + "strings" + "time" +) + +// Key represents a key under a section. +type Key struct { + s *Section + Comment string + name string + value string + isAutoIncrement bool + isBooleanType bool + + isShadow bool + shadows []*Key + + nestedValues []string +} + +// newKey simply return a key object with given values. +func newKey(s *Section, name, val string) *Key { + return &Key{ + s: s, + name: name, + value: val, + } +} + +func (k *Key) addShadow(val string) error { + if k.isShadow { + return errors.New("cannot add shadow to another shadow key") + } else if k.isAutoIncrement || k.isBooleanType { + return errors.New("cannot add shadow to auto-increment or boolean key") + } + + // Deduplicate shadows based on their values. + if k.value == val { + return nil + } + for i := range k.shadows { + if k.shadows[i].value == val { + return nil + } + } + + shadow := newKey(k.s, k.name, val) + shadow.isShadow = true + k.shadows = append(k.shadows, shadow) + return nil +} + +// AddShadow adds a new shadow key to itself. +func (k *Key) AddShadow(val string) error { + if !k.s.f.options.AllowShadows { + return errors.New("shadow key is not allowed") + } + return k.addShadow(val) +} + +func (k *Key) addNestedValue(val string) error { + if k.isAutoIncrement || k.isBooleanType { + return errors.New("cannot add nested value to auto-increment or boolean key") + } + + k.nestedValues = append(k.nestedValues, val) + return nil +} + +// AddNestedValue adds a nested value to the key. +func (k *Key) AddNestedValue(val string) error { + if !k.s.f.options.AllowNestedValues { + return errors.New("nested value is not allowed") + } + return k.addNestedValue(val) +} + +// ValueMapper represents a mapping function for values, e.g. os.ExpandEnv +type ValueMapper func(string) string + +// Name returns name of key. +func (k *Key) Name() string { + return k.name +} + +// Value returns raw value of key for performance purpose. +func (k *Key) Value() string { + return k.value +} + +// ValueWithShadows returns raw values of key and its shadows if any. +func (k *Key) ValueWithShadows() []string { + if len(k.shadows) == 0 { + return []string{k.value} + } + vals := make([]string, len(k.shadows)+1) + vals[0] = k.value + for i := range k.shadows { + vals[i+1] = k.shadows[i].value + } + return vals +} + +// NestedValues returns nested values stored in the key. +// It is possible returned value is nil if no nested values stored in the key. +func (k *Key) NestedValues() []string { + return k.nestedValues +} + +// transformValue takes a raw value and transforms to its final string. +func (k *Key) transformValue(val string) string { + if k.s.f.ValueMapper != nil { + val = k.s.f.ValueMapper(val) + } + + // Fail-fast if no indicate char found for recursive value + if !strings.Contains(val, "%") { + return val + } + for i := 0; i < depthValues; i++ { + vr := varPattern.FindString(val) + if len(vr) == 0 { + break + } + + // Take off leading '%(' and trailing ')s'. + noption := vr[2 : len(vr)-2] + + // Search in the same section. + // If not found or found the key itself, then search again in default section. + nk, err := k.s.GetKey(noption) + if err != nil || k == nk { + nk, _ = k.s.f.Section("").GetKey(noption) + if nk == nil { + // Stop when no results found in the default section, + // and returns the value as-is. + break + } + } + + // Substitute by new value and take off leading '%(' and trailing ')s'. + val = strings.Replace(val, vr, nk.value, -1) + } + return val +} + +// String returns string representation of value. +func (k *Key) String() string { + return k.transformValue(k.value) +} + +// Validate accepts a validate function which can +// return modifed result as key value. +func (k *Key) Validate(fn func(string) string) string { + return fn(k.String()) +} + +// parseBool returns the boolean value represented by the string. +// +// It accepts 1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On, +// 0, f, F, FALSE, false, False, NO, no, No, n, OFF, off, Off. +// Any other value returns an error. +func parseBool(str string) (value bool, err error) { + switch str { + case "1", "t", "T", "true", "TRUE", "True", "YES", "yes", "Yes", "y", "ON", "on", "On": + return true, nil + case "0", "f", "F", "false", "FALSE", "False", "NO", "no", "No", "n", "OFF", "off", "Off": + return false, nil + } + return false, fmt.Errorf("parsing \"%s\": invalid syntax", str) +} + +// Bool returns bool type value. +func (k *Key) Bool() (bool, error) { + return parseBool(k.String()) +} + +// Float64 returns float64 type value. +func (k *Key) Float64() (float64, error) { + return strconv.ParseFloat(k.String(), 64) +} + +// Int returns int type value. +func (k *Key) Int() (int, error) { + v, err := strconv.ParseInt(k.String(), 0, 64) + return int(v), err +} + +// Int64 returns int64 type value. +func (k *Key) Int64() (int64, error) { + return strconv.ParseInt(k.String(), 0, 64) +} + +// Uint returns uint type valued. +func (k *Key) Uint() (uint, error) { + u, e := strconv.ParseUint(k.String(), 0, 64) + return uint(u), e +} + +// Uint64 returns uint64 type value. +func (k *Key) Uint64() (uint64, error) { + return strconv.ParseUint(k.String(), 0, 64) +} + +// Duration returns time.Duration type value. +func (k *Key) Duration() (time.Duration, error) { + return time.ParseDuration(k.String()) +} + +// TimeFormat parses with given format and returns time.Time type value. +func (k *Key) TimeFormat(format string) (time.Time, error) { + return time.Parse(format, k.String()) +} + +// Time parses with RFC3339 format and returns time.Time type value. +func (k *Key) Time() (time.Time, error) { + return k.TimeFormat(time.RFC3339) +} + +// MustString returns default value if key value is empty. +func (k *Key) MustString(defaultVal string) string { + val := k.String() + if len(val) == 0 { + k.value = defaultVal + return defaultVal + } + return val +} + +// MustBool always returns value without error, +// it returns false if error occurs. +func (k *Key) MustBool(defaultVal ...bool) bool { + val, err := k.Bool() + if len(defaultVal) > 0 && err != nil { + k.value = strconv.FormatBool(defaultVal[0]) + return defaultVal[0] + } + return val +} + +// MustFloat64 always returns value without error, +// it returns 0.0 if error occurs. +func (k *Key) MustFloat64(defaultVal ...float64) float64 { + val, err := k.Float64() + if len(defaultVal) > 0 && err != nil { + k.value = strconv.FormatFloat(defaultVal[0], 'f', -1, 64) + return defaultVal[0] + } + return val +} + +// MustInt always returns value without error, +// it returns 0 if error occurs. +func (k *Key) MustInt(defaultVal ...int) int { + val, err := k.Int() + if len(defaultVal) > 0 && err != nil { + k.value = strconv.FormatInt(int64(defaultVal[0]), 10) + return defaultVal[0] + } + return val +} + +// MustInt64 always returns value without error, +// it returns 0 if error occurs. +func (k *Key) MustInt64(defaultVal ...int64) int64 { + val, err := k.Int64() + if len(defaultVal) > 0 && err != nil { + k.value = strconv.FormatInt(defaultVal[0], 10) + return defaultVal[0] + } + return val +} + +// MustUint always returns value without error, +// it returns 0 if error occurs. +func (k *Key) MustUint(defaultVal ...uint) uint { + val, err := k.Uint() + if len(defaultVal) > 0 && err != nil { + k.value = strconv.FormatUint(uint64(defaultVal[0]), 10) + return defaultVal[0] + } + return val +} + +// MustUint64 always returns value without error, +// it returns 0 if error occurs. +func (k *Key) MustUint64(defaultVal ...uint64) uint64 { + val, err := k.Uint64() + if len(defaultVal) > 0 && err != nil { + k.value = strconv.FormatUint(defaultVal[0], 10) + return defaultVal[0] + } + return val +} + +// MustDuration always returns value without error, +// it returns zero value if error occurs. +func (k *Key) MustDuration(defaultVal ...time.Duration) time.Duration { + val, err := k.Duration() + if len(defaultVal) > 0 && err != nil { + k.value = defaultVal[0].String() + return defaultVal[0] + } + return val +} + +// MustTimeFormat always parses with given format and returns value without error, +// it returns zero value if error occurs. +func (k *Key) MustTimeFormat(format string, defaultVal ...time.Time) time.Time { + val, err := k.TimeFormat(format) + if len(defaultVal) > 0 && err != nil { + k.value = defaultVal[0].Format(format) + return defaultVal[0] + } + return val +} + +// MustTime always parses with RFC3339 format and returns value without error, +// it returns zero value if error occurs. +func (k *Key) MustTime(defaultVal ...time.Time) time.Time { + return k.MustTimeFormat(time.RFC3339, defaultVal...) +} + +// In always returns value without error, +// it returns default value if error occurs or doesn't fit into candidates. +func (k *Key) In(defaultVal string, candidates []string) string { + val := k.String() + for _, cand := range candidates { + if val == cand { + return val + } + } + return defaultVal +} + +// InFloat64 always returns value without error, +// it returns default value if error occurs or doesn't fit into candidates. +func (k *Key) InFloat64(defaultVal float64, candidates []float64) float64 { + val := k.MustFloat64() + for _, cand := range candidates { + if val == cand { + return val + } + } + return defaultVal +} + +// InInt always returns value without error, +// it returns default value if error occurs or doesn't fit into candidates. +func (k *Key) InInt(defaultVal int, candidates []int) int { + val := k.MustInt() + for _, cand := range candidates { + if val == cand { + return val + } + } + return defaultVal +} + +// InInt64 always returns value without error, +// it returns default value if error occurs or doesn't fit into candidates. +func (k *Key) InInt64(defaultVal int64, candidates []int64) int64 { + val := k.MustInt64() + for _, cand := range candidates { + if val == cand { + return val + } + } + return defaultVal +} + +// InUint always returns value without error, +// it returns default value if error occurs or doesn't fit into candidates. +func (k *Key) InUint(defaultVal uint, candidates []uint) uint { + val := k.MustUint() + for _, cand := range candidates { + if val == cand { + return val + } + } + return defaultVal +} + +// InUint64 always returns value without error, +// it returns default value if error occurs or doesn't fit into candidates. +func (k *Key) InUint64(defaultVal uint64, candidates []uint64) uint64 { + val := k.MustUint64() + for _, cand := range candidates { + if val == cand { + return val + } + } + return defaultVal +} + +// InTimeFormat always parses with given format and returns value without error, +// it returns default value if error occurs or doesn't fit into candidates. +func (k *Key) InTimeFormat(format string, defaultVal time.Time, candidates []time.Time) time.Time { + val := k.MustTimeFormat(format) + for _, cand := range candidates { + if val == cand { + return val + } + } + return defaultVal +} + +// InTime always parses with RFC3339 format and returns value without error, +// it returns default value if error occurs or doesn't fit into candidates. +func (k *Key) InTime(defaultVal time.Time, candidates []time.Time) time.Time { + return k.InTimeFormat(time.RFC3339, defaultVal, candidates) +} + +// RangeFloat64 checks if value is in given range inclusively, +// and returns default value if it's not. +func (k *Key) RangeFloat64(defaultVal, min, max float64) float64 { + val := k.MustFloat64() + if val < min || val > max { + return defaultVal + } + return val +} + +// RangeInt checks if value is in given range inclusively, +// and returns default value if it's not. +func (k *Key) RangeInt(defaultVal, min, max int) int { + val := k.MustInt() + if val < min || val > max { + return defaultVal + } + return val +} + +// RangeInt64 checks if value is in given range inclusively, +// and returns default value if it's not. +func (k *Key) RangeInt64(defaultVal, min, max int64) int64 { + val := k.MustInt64() + if val < min || val > max { + return defaultVal + } + return val +} + +// RangeTimeFormat checks if value with given format is in given range inclusively, +// and returns default value if it's not. +func (k *Key) RangeTimeFormat(format string, defaultVal, min, max time.Time) time.Time { + val := k.MustTimeFormat(format) + if val.Unix() < min.Unix() || val.Unix() > max.Unix() { + return defaultVal + } + return val +} + +// RangeTime checks if value with RFC3339 format is in given range inclusively, +// and returns default value if it's not. +func (k *Key) RangeTime(defaultVal, min, max time.Time) time.Time { + return k.RangeTimeFormat(time.RFC3339, defaultVal, min, max) +} + +// Strings returns list of string divided by given delimiter. +func (k *Key) Strings(delim string) []string { + str := k.String() + if len(str) == 0 { + return []string{} + } + + runes := []rune(str) + vals := make([]string, 0, 2) + var buf bytes.Buffer + escape := false + idx := 0 + for { + if escape { + escape = false + if runes[idx] != '\\' && !strings.HasPrefix(string(runes[idx:]), delim) { + buf.WriteRune('\\') + } + buf.WriteRune(runes[idx]) + } else { + if runes[idx] == '\\' { + escape = true + } else if strings.HasPrefix(string(runes[idx:]), delim) { + idx += len(delim) - 1 + vals = append(vals, strings.TrimSpace(buf.String())) + buf.Reset() + } else { + buf.WriteRune(runes[idx]) + } + } + idx++ + if idx == len(runes) { + break + } + } + + if buf.Len() > 0 { + vals = append(vals, strings.TrimSpace(buf.String())) + } + + return vals +} + +// StringsWithShadows returns list of string divided by given delimiter. +// Shadows will also be appended if any. +func (k *Key) StringsWithShadows(delim string) []string { + vals := k.ValueWithShadows() + results := make([]string, 0, len(vals)*2) + for i := range vals { + if len(vals) == 0 { + continue + } + + results = append(results, strings.Split(vals[i], delim)...) + } + + for i := range results { + results[i] = k.transformValue(strings.TrimSpace(results[i])) + } + return results +} + +// Float64s returns list of float64 divided by given delimiter. Any invalid input will be treated as zero value. +func (k *Key) Float64s(delim string) []float64 { + vals, _ := k.parseFloat64s(k.Strings(delim), true, false) + return vals +} + +// Ints returns list of int divided by given delimiter. Any invalid input will be treated as zero value. +func (k *Key) Ints(delim string) []int { + vals, _ := k.parseInts(k.Strings(delim), true, false) + return vals +} + +// Int64s returns list of int64 divided by given delimiter. Any invalid input will be treated as zero value. +func (k *Key) Int64s(delim string) []int64 { + vals, _ := k.parseInt64s(k.Strings(delim), true, false) + return vals +} + +// Uints returns list of uint divided by given delimiter. Any invalid input will be treated as zero value. +func (k *Key) Uints(delim string) []uint { + vals, _ := k.parseUints(k.Strings(delim), true, false) + return vals +} + +// Uint64s returns list of uint64 divided by given delimiter. Any invalid input will be treated as zero value. +func (k *Key) Uint64s(delim string) []uint64 { + vals, _ := k.parseUint64s(k.Strings(delim), true, false) + return vals +} + +// Bools returns list of bool divided by given delimiter. Any invalid input will be treated as zero value. +func (k *Key) Bools(delim string) []bool { + vals, _ := k.parseBools(k.Strings(delim), true, false) + return vals +} + +// TimesFormat parses with given format and returns list of time.Time divided by given delimiter. +// Any invalid input will be treated as zero value (0001-01-01 00:00:00 +0000 UTC). +func (k *Key) TimesFormat(format, delim string) []time.Time { + vals, _ := k.parseTimesFormat(format, k.Strings(delim), true, false) + return vals +} + +// Times parses with RFC3339 format and returns list of time.Time divided by given delimiter. +// Any invalid input will be treated as zero value (0001-01-01 00:00:00 +0000 UTC). +func (k *Key) Times(delim string) []time.Time { + return k.TimesFormat(time.RFC3339, delim) +} + +// ValidFloat64s returns list of float64 divided by given delimiter. If some value is not float, then +// it will not be included to result list. +func (k *Key) ValidFloat64s(delim string) []float64 { + vals, _ := k.parseFloat64s(k.Strings(delim), false, false) + return vals +} + +// ValidInts returns list of int divided by given delimiter. If some value is not integer, then it will +// not be included to result list. +func (k *Key) ValidInts(delim string) []int { + vals, _ := k.parseInts(k.Strings(delim), false, false) + return vals +} + +// ValidInt64s returns list of int64 divided by given delimiter. If some value is not 64-bit integer, +// then it will not be included to result list. +func (k *Key) ValidInt64s(delim string) []int64 { + vals, _ := k.parseInt64s(k.Strings(delim), false, false) + return vals +} + +// ValidUints returns list of uint divided by given delimiter. If some value is not unsigned integer, +// then it will not be included to result list. +func (k *Key) ValidUints(delim string) []uint { + vals, _ := k.parseUints(k.Strings(delim), false, false) + return vals +} + +// ValidUint64s returns list of uint64 divided by given delimiter. If some value is not 64-bit unsigned +// integer, then it will not be included to result list. +func (k *Key) ValidUint64s(delim string) []uint64 { + vals, _ := k.parseUint64s(k.Strings(delim), false, false) + return vals +} + +// ValidBools returns list of bool divided by given delimiter. If some value is not 64-bit unsigned +// integer, then it will not be included to result list. +func (k *Key) ValidBools(delim string) []bool { + vals, _ := k.parseBools(k.Strings(delim), false, false) + return vals +} + +// ValidTimesFormat parses with given format and returns list of time.Time divided by given delimiter. +func (k *Key) ValidTimesFormat(format, delim string) []time.Time { + vals, _ := k.parseTimesFormat(format, k.Strings(delim), false, false) + return vals +} + +// ValidTimes parses with RFC3339 format and returns list of time.Time divided by given delimiter. +func (k *Key) ValidTimes(delim string) []time.Time { + return k.ValidTimesFormat(time.RFC3339, delim) +} + +// StrictFloat64s returns list of float64 divided by given delimiter or error on first invalid input. +func (k *Key) StrictFloat64s(delim string) ([]float64, error) { + return k.parseFloat64s(k.Strings(delim), false, true) +} + +// StrictInts returns list of int divided by given delimiter or error on first invalid input. +func (k *Key) StrictInts(delim string) ([]int, error) { + return k.parseInts(k.Strings(delim), false, true) +} + +// StrictInt64s returns list of int64 divided by given delimiter or error on first invalid input. +func (k *Key) StrictInt64s(delim string) ([]int64, error) { + return k.parseInt64s(k.Strings(delim), false, true) +} + +// StrictUints returns list of uint divided by given delimiter or error on first invalid input. +func (k *Key) StrictUints(delim string) ([]uint, error) { + return k.parseUints(k.Strings(delim), false, true) +} + +// StrictUint64s returns list of uint64 divided by given delimiter or error on first invalid input. +func (k *Key) StrictUint64s(delim string) ([]uint64, error) { + return k.parseUint64s(k.Strings(delim), false, true) +} + +// StrictBools returns list of bool divided by given delimiter or error on first invalid input. +func (k *Key) StrictBools(delim string) ([]bool, error) { + return k.parseBools(k.Strings(delim), false, true) +} + +// StrictTimesFormat parses with given format and returns list of time.Time divided by given delimiter +// or error on first invalid input. +func (k *Key) StrictTimesFormat(format, delim string) ([]time.Time, error) { + return k.parseTimesFormat(format, k.Strings(delim), false, true) +} + +// StrictTimes parses with RFC3339 format and returns list of time.Time divided by given delimiter +// or error on first invalid input. +func (k *Key) StrictTimes(delim string) ([]time.Time, error) { + return k.StrictTimesFormat(time.RFC3339, delim) +} + +// parseBools transforms strings to bools. +func (k *Key) parseBools(strs []string, addInvalid, returnOnInvalid bool) ([]bool, error) { + vals := make([]bool, 0, len(strs)) + for _, str := range strs { + val, err := parseBool(str) + if err != nil && returnOnInvalid { + return nil, err + } + if err == nil || addInvalid { + vals = append(vals, val) + } + } + return vals, nil +} + +// parseFloat64s transforms strings to float64s. +func (k *Key) parseFloat64s(strs []string, addInvalid, returnOnInvalid bool) ([]float64, error) { + vals := make([]float64, 0, len(strs)) + for _, str := range strs { + val, err := strconv.ParseFloat(str, 64) + if err != nil && returnOnInvalid { + return nil, err + } + if err == nil || addInvalid { + vals = append(vals, val) + } + } + return vals, nil +} + +// parseInts transforms strings to ints. +func (k *Key) parseInts(strs []string, addInvalid, returnOnInvalid bool) ([]int, error) { + vals := make([]int, 0, len(strs)) + for _, str := range strs { + valInt64, err := strconv.ParseInt(str, 0, 64) + val := int(valInt64) + if err != nil && returnOnInvalid { + return nil, err + } + if err == nil || addInvalid { + vals = append(vals, val) + } + } + return vals, nil +} + +// parseInt64s transforms strings to int64s. +func (k *Key) parseInt64s(strs []string, addInvalid, returnOnInvalid bool) ([]int64, error) { + vals := make([]int64, 0, len(strs)) + for _, str := range strs { + val, err := strconv.ParseInt(str, 0, 64) + if err != nil && returnOnInvalid { + return nil, err + } + if err == nil || addInvalid { + vals = append(vals, val) + } + } + return vals, nil +} + +// parseUints transforms strings to uints. +func (k *Key) parseUints(strs []string, addInvalid, returnOnInvalid bool) ([]uint, error) { + vals := make([]uint, 0, len(strs)) + for _, str := range strs { + val, err := strconv.ParseUint(str, 0, 0) + if err != nil && returnOnInvalid { + return nil, err + } + if err == nil || addInvalid { + vals = append(vals, uint(val)) + } + } + return vals, nil +} + +// parseUint64s transforms strings to uint64s. +func (k *Key) parseUint64s(strs []string, addInvalid, returnOnInvalid bool) ([]uint64, error) { + vals := make([]uint64, 0, len(strs)) + for _, str := range strs { + val, err := strconv.ParseUint(str, 0, 64) + if err != nil && returnOnInvalid { + return nil, err + } + if err == nil || addInvalid { + vals = append(vals, val) + } + } + return vals, nil +} + +// parseTimesFormat transforms strings to times in given format. +func (k *Key) parseTimesFormat(format string, strs []string, addInvalid, returnOnInvalid bool) ([]time.Time, error) { + vals := make([]time.Time, 0, len(strs)) + for _, str := range strs { + val, err := time.Parse(format, str) + if err != nil && returnOnInvalid { + return nil, err + } + if err == nil || addInvalid { + vals = append(vals, val) + } + } + return vals, nil +} + +// SetValue changes key value. +func (k *Key) SetValue(v string) { + if k.s.f.BlockMode { + k.s.f.lock.Lock() + defer k.s.f.lock.Unlock() + } + + k.value = v + k.s.keysHash[k.name] = v +} diff --git a/vendor/gopkg.in/ini.v1/parser.go b/vendor/gopkg.in/ini.v1/parser.go new file mode 100644 index 000000000..53ab45c46 --- /dev/null +++ b/vendor/gopkg.in/ini.v1/parser.go @@ -0,0 +1,526 @@ +// Copyright 2015 Unknwon +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package ini + +import ( + "bufio" + "bytes" + "fmt" + "io" + "regexp" + "strconv" + "strings" + "unicode" +) + +const minReaderBufferSize = 4096 + +var pythonMultiline = regexp.MustCompile(`^([\t\f ]+)(.*)`) + +type parserOptions struct { + IgnoreContinuation bool + IgnoreInlineComment bool + AllowPythonMultilineValues bool + SpaceBeforeInlineComment bool + UnescapeValueDoubleQuotes bool + UnescapeValueCommentSymbols bool + PreserveSurroundedQuote bool + DebugFunc DebugFunc + ReaderBufferSize int +} + +type parser struct { + buf *bufio.Reader + options parserOptions + + isEOF bool + count int + comment *bytes.Buffer +} + +func (p *parser) debug(format string, args ...interface{}) { + if p.options.DebugFunc != nil { + p.options.DebugFunc(fmt.Sprintf(format, args...)) + } +} + +func newParser(r io.Reader, opts parserOptions) *parser { + size := opts.ReaderBufferSize + if size < minReaderBufferSize { + size = minReaderBufferSize + } + + return &parser{ + buf: bufio.NewReaderSize(r, size), + options: opts, + count: 1, + comment: &bytes.Buffer{}, + } +} + +// BOM handles header of UTF-8, UTF-16 LE and UTF-16 BE's BOM format. +// http://en.wikipedia.org/wiki/Byte_order_mark#Representations_of_byte_order_marks_by_encoding +func (p *parser) BOM() error { + mask, err := p.buf.Peek(2) + if err != nil && err != io.EOF { + return err + } else if len(mask) < 2 { + return nil + } + + switch { + case mask[0] == 254 && mask[1] == 255: + fallthrough + case mask[0] == 255 && mask[1] == 254: + p.buf.Read(mask) + case mask[0] == 239 && mask[1] == 187: + mask, err := p.buf.Peek(3) + if err != nil && err != io.EOF { + return err + } else if len(mask) < 3 { + return nil + } + if mask[2] == 191 { + p.buf.Read(mask) + } + } + return nil +} + +func (p *parser) readUntil(delim byte) ([]byte, error) { + data, err := p.buf.ReadBytes(delim) + if err != nil { + if err == io.EOF { + p.isEOF = true + } else { + return nil, err + } + } + return data, nil +} + +func cleanComment(in []byte) ([]byte, bool) { + i := bytes.IndexAny(in, "#;") + if i == -1 { + return nil, false + } + return in[i:], true +} + +func readKeyName(delimiters string, in []byte) (string, int, error) { + line := string(in) + + // Check if key name surrounded by quotes. + var keyQuote string + if line[0] == '"' { + if len(line) > 6 && string(line[0:3]) == `"""` { + keyQuote = `"""` + } else { + keyQuote = `"` + } + } else if line[0] == '`' { + keyQuote = "`" + } + + // Get out key name + endIdx := -1 + if len(keyQuote) > 0 { + startIdx := len(keyQuote) + // FIXME: fail case -> """"""name"""=value + pos := strings.Index(line[startIdx:], keyQuote) + if pos == -1 { + return "", -1, fmt.Errorf("missing closing key quote: %s", line) + } + pos += startIdx + + // Find key-value delimiter + i := strings.IndexAny(line[pos+startIdx:], delimiters) + if i < 0 { + return "", -1, ErrDelimiterNotFound{line} + } + endIdx = pos + i + return strings.TrimSpace(line[startIdx:pos]), endIdx + startIdx + 1, nil + } + + endIdx = strings.IndexAny(line, delimiters) + if endIdx < 0 { + return "", -1, ErrDelimiterNotFound{line} + } + return strings.TrimSpace(line[0:endIdx]), endIdx + 1, nil +} + +func (p *parser) readMultilines(line, val, valQuote string) (string, error) { + for { + data, err := p.readUntil('\n') + if err != nil { + return "", err + } + next := string(data) + + pos := strings.LastIndex(next, valQuote) + if pos > -1 { + val += next[:pos] + + comment, has := cleanComment([]byte(next[pos:])) + if has { + p.comment.Write(bytes.TrimSpace(comment)) + } + break + } + val += next + if p.isEOF { + return "", fmt.Errorf("missing closing key quote from '%s' to '%s'", line, next) + } + } + return val, nil +} + +func (p *parser) readContinuationLines(val string) (string, error) { + for { + data, err := p.readUntil('\n') + if err != nil { + return "", err + } + next := strings.TrimSpace(string(data)) + + if len(next) == 0 { + break + } + val += next + if val[len(val)-1] != '\\' { + break + } + val = val[:len(val)-1] + } + return val, nil +} + +// hasSurroundedQuote check if and only if the first and last characters +// are quotes \" or \'. +// It returns false if any other parts also contain same kind of quotes. +func hasSurroundedQuote(in string, quote byte) bool { + return len(in) >= 2 && in[0] == quote && in[len(in)-1] == quote && + strings.IndexByte(in[1:], quote) == len(in)-2 +} + +func (p *parser) readValue(in []byte, bufferSize int) (string, error) { + + line := strings.TrimLeftFunc(string(in), unicode.IsSpace) + if len(line) == 0 { + if p.options.AllowPythonMultilineValues && len(in) > 0 && in[len(in)-1] == '\n' { + return p.readPythonMultilines(line, bufferSize) + } + return "", nil + } + + var valQuote string + if len(line) > 3 && string(line[0:3]) == `"""` { + valQuote = `"""` + } else if line[0] == '`' { + valQuote = "`" + } else if p.options.UnescapeValueDoubleQuotes && line[0] == '"' { + valQuote = `"` + } + + if len(valQuote) > 0 { + startIdx := len(valQuote) + pos := strings.LastIndex(line[startIdx:], valQuote) + // Check for multi-line value + if pos == -1 { + return p.readMultilines(line, line[startIdx:], valQuote) + } + + if p.options.UnescapeValueDoubleQuotes && valQuote == `"` { + return strings.Replace(line[startIdx:pos+startIdx], `\"`, `"`, -1), nil + } + return line[startIdx : pos+startIdx], nil + } + + lastChar := line[len(line)-1] + // Won't be able to reach here if value only contains whitespace + line = strings.TrimSpace(line) + trimmedLastChar := line[len(line)-1] + + // Check continuation lines when desired + if !p.options.IgnoreContinuation && trimmedLastChar == '\\' { + return p.readContinuationLines(line[:len(line)-1]) + } + + // Check if ignore inline comment + if !p.options.IgnoreInlineComment { + var i int + if p.options.SpaceBeforeInlineComment { + i = strings.Index(line, " #") + if i == -1 { + i = strings.Index(line, " ;") + } + + } else { + i = strings.IndexAny(line, "#;") + } + + if i > -1 { + p.comment.WriteString(line[i:]) + line = strings.TrimSpace(line[:i]) + } + + } + + // Trim single and double quotes + if (hasSurroundedQuote(line, '\'') || + hasSurroundedQuote(line, '"')) && !p.options.PreserveSurroundedQuote { + line = line[1 : len(line)-1] + } else if len(valQuote) == 0 && p.options.UnescapeValueCommentSymbols { + if strings.Contains(line, `\;`) { + line = strings.Replace(line, `\;`, ";", -1) + } + if strings.Contains(line, `\#`) { + line = strings.Replace(line, `\#`, "#", -1) + } + } else if p.options.AllowPythonMultilineValues && lastChar == '\n' { + return p.readPythonMultilines(line, bufferSize) + } + + return line, nil +} + +func (p *parser) readPythonMultilines(line string, bufferSize int) (string, error) { + parserBufferPeekResult, _ := p.buf.Peek(bufferSize) + peekBuffer := bytes.NewBuffer(parserBufferPeekResult) + + indentSize := 0 + for { + peekData, peekErr := peekBuffer.ReadBytes('\n') + if peekErr != nil { + if peekErr == io.EOF { + p.debug("readPythonMultilines: io.EOF, peekData: %q, line: %q", string(peekData), line) + return line, nil + } + + p.debug("readPythonMultilines: failed to peek with error: %v", peekErr) + return "", peekErr + } + + p.debug("readPythonMultilines: parsing %q", string(peekData)) + + peekMatches := pythonMultiline.FindStringSubmatch(string(peekData)) + p.debug("readPythonMultilines: matched %d parts", len(peekMatches)) + for n, v := range peekMatches { + p.debug(" %d: %q", n, v) + } + + // Return if not a Python multiline value. + if len(peekMatches) != 3 { + p.debug("readPythonMultilines: end of value, got: %q", line) + return line, nil + } + + // Determine indent size and line prefix. + currentIndentSize := len(peekMatches[1]) + if indentSize < 1 { + indentSize = currentIndentSize + p.debug("readPythonMultilines: indent size is %d", indentSize) + } + + // Make sure each line is indented at least as far as first line. + if currentIndentSize < indentSize { + p.debug("readPythonMultilines: end of value, current indent: %d, expected indent: %d, line: %q", currentIndentSize, indentSize, line) + return line, nil + } + + // Advance the parser reader (buffer) in-sync with the peek buffer. + _, err := p.buf.Discard(len(peekData)) + if err != nil { + p.debug("readPythonMultilines: failed to skip to the end, returning error") + return "", err + } + + // Handle indented empty line. + line += "\n" + peekMatches[1][indentSize:] + peekMatches[2] + } +} + +// parse parses data through an io.Reader. +func (f *File) parse(reader io.Reader) (err error) { + p := newParser(reader, parserOptions{ + IgnoreContinuation: f.options.IgnoreContinuation, + IgnoreInlineComment: f.options.IgnoreInlineComment, + AllowPythonMultilineValues: f.options.AllowPythonMultilineValues, + SpaceBeforeInlineComment: f.options.SpaceBeforeInlineComment, + UnescapeValueDoubleQuotes: f.options.UnescapeValueDoubleQuotes, + UnescapeValueCommentSymbols: f.options.UnescapeValueCommentSymbols, + PreserveSurroundedQuote: f.options.PreserveSurroundedQuote, + DebugFunc: f.options.DebugFunc, + ReaderBufferSize: f.options.ReaderBufferSize, + }) + if err = p.BOM(); err != nil { + return fmt.Errorf("BOM: %v", err) + } + + // Ignore error because default section name is never empty string. + name := DefaultSection + if f.options.Insensitive { + name = strings.ToLower(DefaultSection) + } + section, _ := f.NewSection(name) + + // This "last" is not strictly equivalent to "previous one" if current key is not the first nested key + var isLastValueEmpty bool + var lastRegularKey *Key + + var line []byte + var inUnparseableSection bool + + // NOTE: Iterate and increase `currentPeekSize` until + // the size of the parser buffer is found. + // TODO(unknwon): When Golang 1.10 is the lowest version supported, replace with `parserBufferSize := p.buf.Size()`. + parserBufferSize := 0 + // NOTE: Peek 4kb at a time. + currentPeekSize := minReaderBufferSize + + if f.options.AllowPythonMultilineValues { + for { + peekBytes, _ := p.buf.Peek(currentPeekSize) + peekBytesLength := len(peekBytes) + + if parserBufferSize >= peekBytesLength { + break + } + + currentPeekSize *= 2 + parserBufferSize = peekBytesLength + } + } + + for !p.isEOF { + line, err = p.readUntil('\n') + if err != nil { + return err + } + + if f.options.AllowNestedValues && + isLastValueEmpty && len(line) > 0 { + if line[0] == ' ' || line[0] == '\t' { + lastRegularKey.addNestedValue(string(bytes.TrimSpace(line))) + continue + } + } + + line = bytes.TrimLeftFunc(line, unicode.IsSpace) + if len(line) == 0 { + continue + } + + // Comments + if line[0] == '#' || line[0] == ';' { + // Note: we do not care ending line break, + // it is needed for adding second line, + // so just clean it once at the end when set to value. + p.comment.Write(line) + continue + } + + // Section + if line[0] == '[' { + // Read to the next ']' (TODO: support quoted strings) + closeIdx := bytes.LastIndexByte(line, ']') + if closeIdx == -1 { + return fmt.Errorf("unclosed section: %s", line) + } + + name := string(line[1:closeIdx]) + section, err = f.NewSection(name) + if err != nil { + return err + } + + comment, has := cleanComment(line[closeIdx+1:]) + if has { + p.comment.Write(comment) + } + + section.Comment = strings.TrimSpace(p.comment.String()) + + // Reset aotu-counter and comments + p.comment.Reset() + p.count = 1 + + inUnparseableSection = false + for i := range f.options.UnparseableSections { + if f.options.UnparseableSections[i] == name || + (f.options.Insensitive && strings.ToLower(f.options.UnparseableSections[i]) == strings.ToLower(name)) { + inUnparseableSection = true + continue + } + } + continue + } + + if inUnparseableSection { + section.isRawSection = true + section.rawBody += string(line) + continue + } + + kname, offset, err := readKeyName(f.options.KeyValueDelimiters, line) + if err != nil { + // Treat as boolean key when desired, and whole line is key name. + if IsErrDelimiterNotFound(err) { + switch { + case f.options.AllowBooleanKeys: + kname, err := p.readValue(line, parserBufferSize) + if err != nil { + return err + } + key, err := section.NewBooleanKey(kname) + if err != nil { + return err + } + key.Comment = strings.TrimSpace(p.comment.String()) + p.comment.Reset() + continue + + case f.options.SkipUnrecognizableLines: + continue + } + } + return err + } + + // Auto increment. + isAutoIncr := false + if kname == "-" { + isAutoIncr = true + kname = "#" + strconv.Itoa(p.count) + p.count++ + } + + value, err := p.readValue(line[offset:], parserBufferSize) + if err != nil { + return err + } + isLastValueEmpty = len(value) == 0 + + key, err := section.NewKey(kname, value) + if err != nil { + return err + } + key.isAutoIncrement = isAutoIncr + key.Comment = strings.TrimSpace(p.comment.String()) + p.comment.Reset() + lastRegularKey = key + } + return nil +} diff --git a/vendor/gopkg.in/ini.v1/section.go b/vendor/gopkg.in/ini.v1/section.go new file mode 100644 index 000000000..0bd3e1301 --- /dev/null +++ b/vendor/gopkg.in/ini.v1/section.go @@ -0,0 +1,256 @@ +// Copyright 2014 Unknwon +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package ini + +import ( + "errors" + "fmt" + "strings" +) + +// Section represents a config section. +type Section struct { + f *File + Comment string + name string + keys map[string]*Key + keyList []string + keysHash map[string]string + + isRawSection bool + rawBody string +} + +func newSection(f *File, name string) *Section { + return &Section{ + f: f, + name: name, + keys: make(map[string]*Key), + keyList: make([]string, 0, 10), + keysHash: make(map[string]string), + } +} + +// Name returns name of Section. +func (s *Section) Name() string { + return s.name +} + +// Body returns rawBody of Section if the section was marked as unparseable. +// It still follows the other rules of the INI format surrounding leading/trailing whitespace. +func (s *Section) Body() string { + return strings.TrimSpace(s.rawBody) +} + +// SetBody updates body content only if section is raw. +func (s *Section) SetBody(body string) { + if !s.isRawSection { + return + } + s.rawBody = body +} + +// NewKey creates a new key to given section. +func (s *Section) NewKey(name, val string) (*Key, error) { + if len(name) == 0 { + return nil, errors.New("error creating new key: empty key name") + } else if s.f.options.Insensitive { + name = strings.ToLower(name) + } + + if s.f.BlockMode { + s.f.lock.Lock() + defer s.f.lock.Unlock() + } + + if inSlice(name, s.keyList) { + if s.f.options.AllowShadows { + if err := s.keys[name].addShadow(val); err != nil { + return nil, err + } + } else { + s.keys[name].value = val + s.keysHash[name] = val + } + return s.keys[name], nil + } + + s.keyList = append(s.keyList, name) + s.keys[name] = newKey(s, name, val) + s.keysHash[name] = val + return s.keys[name], nil +} + +// NewBooleanKey creates a new boolean type key to given section. +func (s *Section) NewBooleanKey(name string) (*Key, error) { + key, err := s.NewKey(name, "true") + if err != nil { + return nil, err + } + + key.isBooleanType = true + return key, nil +} + +// GetKey returns key in section by given name. +func (s *Section) GetKey(name string) (*Key, error) { + if s.f.BlockMode { + s.f.lock.RLock() + } + if s.f.options.Insensitive { + name = strings.ToLower(name) + } + key := s.keys[name] + if s.f.BlockMode { + s.f.lock.RUnlock() + } + + if key == nil { + // Check if it is a child-section. + sname := s.name + for { + if i := strings.LastIndex(sname, "."); i > -1 { + sname = sname[:i] + sec, err := s.f.GetSection(sname) + if err != nil { + continue + } + return sec.GetKey(name) + } + break + } + return nil, fmt.Errorf("error when getting key of section '%s': key '%s' not exists", s.name, name) + } + return key, nil +} + +// HasKey returns true if section contains a key with given name. +func (s *Section) HasKey(name string) bool { + key, _ := s.GetKey(name) + return key != nil +} + +// Deprecated: Use "HasKey" instead. +func (s *Section) Haskey(name string) bool { + return s.HasKey(name) +} + +// HasValue returns true if section contains given raw value. +func (s *Section) HasValue(value string) bool { + if s.f.BlockMode { + s.f.lock.RLock() + defer s.f.lock.RUnlock() + } + + for _, k := range s.keys { + if value == k.value { + return true + } + } + return false +} + +// Key assumes named Key exists in section and returns a zero-value when not. +func (s *Section) Key(name string) *Key { + key, err := s.GetKey(name) + if err != nil { + // It's OK here because the only possible error is empty key name, + // but if it's empty, this piece of code won't be executed. + key, _ = s.NewKey(name, "") + return key + } + return key +} + +// Keys returns list of keys of section. +func (s *Section) Keys() []*Key { + keys := make([]*Key, len(s.keyList)) + for i := range s.keyList { + keys[i] = s.Key(s.keyList[i]) + } + return keys +} + +// ParentKeys returns list of keys of parent section. +func (s *Section) ParentKeys() []*Key { + var parentKeys []*Key + sname := s.name + for { + if i := strings.LastIndex(sname, "."); i > -1 { + sname = sname[:i] + sec, err := s.f.GetSection(sname) + if err != nil { + continue + } + parentKeys = append(parentKeys, sec.Keys()...) + } else { + break + } + + } + return parentKeys +} + +// KeyStrings returns list of key names of section. +func (s *Section) KeyStrings() []string { + list := make([]string, len(s.keyList)) + copy(list, s.keyList) + return list +} + +// KeysHash returns keys hash consisting of names and values. +func (s *Section) KeysHash() map[string]string { + if s.f.BlockMode { + s.f.lock.RLock() + defer s.f.lock.RUnlock() + } + + hash := map[string]string{} + for key, value := range s.keysHash { + hash[key] = value + } + return hash +} + +// DeleteKey deletes a key from section. +func (s *Section) DeleteKey(name string) { + if s.f.BlockMode { + s.f.lock.Lock() + defer s.f.lock.Unlock() + } + + for i, k := range s.keyList { + if k == name { + s.keyList = append(s.keyList[:i], s.keyList[i+1:]...) + delete(s.keys, name) + delete(s.keysHash, name) + return + } + } +} + +// ChildSections returns a list of child sections of current section. +// For example, "[parent.child1]" and "[parent.child12]" are child sections +// of section "[parent]". +func (s *Section) ChildSections() []*Section { + prefix := s.name + "." + children := make([]*Section, 0, 3) + for _, name := range s.f.sectionList { + if strings.HasPrefix(name, prefix) { + children = append(children, s.f.sections[name]) + } + } + return children +} diff --git a/vendor/gopkg.in/ini.v1/struct.go b/vendor/gopkg.in/ini.v1/struct.go new file mode 100644 index 000000000..6bc70e4d4 --- /dev/null +++ b/vendor/gopkg.in/ini.v1/struct.go @@ -0,0 +1,603 @@ +// Copyright 2014 Unknwon +// +// Licensed under the Apache License, Version 2.0 (the "License"): you may +// not use this file except in compliance with the License. You may obtain +// a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations +// under the License. + +package ini + +import ( + "bytes" + "errors" + "fmt" + "reflect" + "strings" + "time" + "unicode" +) + +// NameMapper represents a ini tag name mapper. +type NameMapper func(string) string + +// Built-in name getters. +var ( + // SnackCase converts to format SNACK_CASE. + SnackCase NameMapper = func(raw string) string { + newstr := make([]rune, 0, len(raw)) + for i, chr := range raw { + if isUpper := 'A' <= chr && chr <= 'Z'; isUpper { + if i > 0 { + newstr = append(newstr, '_') + } + } + newstr = append(newstr, unicode.ToUpper(chr)) + } + return string(newstr) + } + // TitleUnderscore converts to format title_underscore. + TitleUnderscore NameMapper = func(raw string) string { + newstr := make([]rune, 0, len(raw)) + for i, chr := range raw { + if isUpper := 'A' <= chr && chr <= 'Z'; isUpper { + if i > 0 { + newstr = append(newstr, '_') + } + chr -= 'A' - 'a' + } + newstr = append(newstr, chr) + } + return string(newstr) + } +) + +func (s *Section) parseFieldName(raw, actual string) string { + if len(actual) > 0 { + return actual + } + if s.f.NameMapper != nil { + return s.f.NameMapper(raw) + } + return raw +} + +func parseDelim(actual string) string { + if len(actual) > 0 { + return actual + } + return "," +} + +var reflectTime = reflect.TypeOf(time.Now()).Kind() + +// setSliceWithProperType sets proper values to slice based on its type. +func setSliceWithProperType(key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) error { + var strs []string + if allowShadow { + strs = key.StringsWithShadows(delim) + } else { + strs = key.Strings(delim) + } + + numVals := len(strs) + if numVals == 0 { + return nil + } + + var vals interface{} + var err error + + sliceOf := field.Type().Elem().Kind() + switch sliceOf { + case reflect.String: + vals = strs + case reflect.Int: + vals, err = key.parseInts(strs, true, false) + case reflect.Int64: + vals, err = key.parseInt64s(strs, true, false) + case reflect.Uint: + vals, err = key.parseUints(strs, true, false) + case reflect.Uint64: + vals, err = key.parseUint64s(strs, true, false) + case reflect.Float64: + vals, err = key.parseFloat64s(strs, true, false) + case reflect.Bool: + vals, err = key.parseBools(strs, true, false) + case reflectTime: + vals, err = key.parseTimesFormat(time.RFC3339, strs, true, false) + default: + return fmt.Errorf("unsupported type '[]%s'", sliceOf) + } + if err != nil && isStrict { + return err + } + + slice := reflect.MakeSlice(field.Type(), numVals, numVals) + for i := 0; i < numVals; i++ { + switch sliceOf { + case reflect.String: + slice.Index(i).Set(reflect.ValueOf(vals.([]string)[i])) + case reflect.Int: + slice.Index(i).Set(reflect.ValueOf(vals.([]int)[i])) + case reflect.Int64: + slice.Index(i).Set(reflect.ValueOf(vals.([]int64)[i])) + case reflect.Uint: + slice.Index(i).Set(reflect.ValueOf(vals.([]uint)[i])) + case reflect.Uint64: + slice.Index(i).Set(reflect.ValueOf(vals.([]uint64)[i])) + case reflect.Float64: + slice.Index(i).Set(reflect.ValueOf(vals.([]float64)[i])) + case reflect.Bool: + slice.Index(i).Set(reflect.ValueOf(vals.([]bool)[i])) + case reflectTime: + slice.Index(i).Set(reflect.ValueOf(vals.([]time.Time)[i])) + } + } + field.Set(slice) + return nil +} + +func wrapStrictError(err error, isStrict bool) error { + if isStrict { + return err + } + return nil +} + +// setWithProperType sets proper value to field based on its type, +// but it does not return error for failing parsing, +// because we want to use default value that is already assigned to struct. +func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) error { + vt := t + isPtr := t.Kind() == reflect.Ptr + if isPtr { + vt = t.Elem() + } + switch vt.Kind() { + case reflect.String: + stringVal := key.String() + if isPtr { + field.Set(reflect.ValueOf(&stringVal)) + } else if len(stringVal) > 0 { + field.SetString(key.String()) + } + case reflect.Bool: + boolVal, err := key.Bool() + if err != nil { + return wrapStrictError(err, isStrict) + } + if isPtr { + field.Set(reflect.ValueOf(&boolVal)) + } else { + field.SetBool(boolVal) + } + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + // ParseDuration will not return err for `0`, so check the type name + if vt.Name() == "Duration" { + durationVal, err := key.Duration() + if err != nil { + return wrapStrictError(err, isStrict) + } + if isPtr { + field.Set(reflect.ValueOf(&durationVal)) + } else if int64(durationVal) > 0 { + field.Set(reflect.ValueOf(durationVal)) + } + return nil + } + + intVal, err := key.Int64() + if err != nil { + return wrapStrictError(err, isStrict) + } + if isPtr { + pv := reflect.New(t.Elem()) + pv.Elem().SetInt(intVal) + field.Set(pv) + } else { + field.SetInt(intVal) + } + // byte is an alias for uint8, so supporting uint8 breaks support for byte + case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64: + durationVal, err := key.Duration() + // Skip zero value + if err == nil && uint64(durationVal) > 0 { + if isPtr { + field.Set(reflect.ValueOf(&durationVal)) + } else { + field.Set(reflect.ValueOf(durationVal)) + } + return nil + } + + uintVal, err := key.Uint64() + if err != nil { + return wrapStrictError(err, isStrict) + } + if isPtr { + pv := reflect.New(t.Elem()) + pv.Elem().SetUint(uintVal) + field.Set(pv) + } else { + field.SetUint(uintVal) + } + + case reflect.Float32, reflect.Float64: + floatVal, err := key.Float64() + if err != nil { + return wrapStrictError(err, isStrict) + } + if isPtr { + pv := reflect.New(t.Elem()) + pv.Elem().SetFloat(floatVal) + field.Set(pv) + } else { + field.SetFloat(floatVal) + } + case reflectTime: + timeVal, err := key.Time() + if err != nil { + return wrapStrictError(err, isStrict) + } + if isPtr { + field.Set(reflect.ValueOf(&timeVal)) + } else { + field.Set(reflect.ValueOf(timeVal)) + } + case reflect.Slice: + return setSliceWithProperType(key, field, delim, allowShadow, isStrict) + default: + return fmt.Errorf("unsupported type '%s'", t) + } + return nil +} + +func parseTagOptions(tag string) (rawName string, omitEmpty bool, allowShadow bool) { + opts := strings.SplitN(tag, ",", 3) + rawName = opts[0] + if len(opts) > 1 { + omitEmpty = opts[1] == "omitempty" + } + if len(opts) > 2 { + allowShadow = opts[2] == "allowshadow" + } + return rawName, omitEmpty, allowShadow +} + +func (s *Section) mapTo(val reflect.Value, isStrict bool) error { + if val.Kind() == reflect.Ptr { + val = val.Elem() + } + typ := val.Type() + + for i := 0; i < typ.NumField(); i++ { + field := val.Field(i) + tpField := typ.Field(i) + + tag := tpField.Tag.Get("ini") + if tag == "-" { + continue + } + + rawName, _, allowShadow := parseTagOptions(tag) + fieldName := s.parseFieldName(tpField.Name, rawName) + if len(fieldName) == 0 || !field.CanSet() { + continue + } + + isStruct := tpField.Type.Kind() == reflect.Struct + isStructPtr := tpField.Type.Kind() == reflect.Ptr && tpField.Type.Elem().Kind() == reflect.Struct + isAnonymous := tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous + if isAnonymous { + field.Set(reflect.New(tpField.Type.Elem())) + } + + if isAnonymous || isStruct || isStructPtr { + if sec, err := s.f.GetSection(fieldName); err == nil { + // Only set the field to non-nil struct value if we have + // a section for it. Otherwise, we end up with a non-nil + // struct ptr even though there is no data. + if isStructPtr && field.IsNil() { + field.Set(reflect.New(tpField.Type.Elem())) + } + if err = sec.mapTo(field, isStrict); err != nil { + return fmt.Errorf("error mapping field(%s): %v", fieldName, err) + } + continue + } + } + if key, err := s.GetKey(fieldName); err == nil { + delim := parseDelim(tpField.Tag.Get("delim")) + if err = setWithProperType(tpField.Type, key, field, delim, allowShadow, isStrict); err != nil { + return fmt.Errorf("error mapping field(%s): %v", fieldName, err) + } + } + } + return nil +} + +// MapTo maps section to given struct. +func (s *Section) MapTo(v interface{}) error { + typ := reflect.TypeOf(v) + val := reflect.ValueOf(v) + if typ.Kind() == reflect.Ptr { + typ = typ.Elem() + val = val.Elem() + } else { + return errors.New("cannot map to non-pointer struct") + } + + return s.mapTo(val, false) +} + +// StrictMapTo maps section to given struct in strict mode, +// which returns all possible error including value parsing error. +func (s *Section) StrictMapTo(v interface{}) error { + typ := reflect.TypeOf(v) + val := reflect.ValueOf(v) + if typ.Kind() == reflect.Ptr { + typ = typ.Elem() + val = val.Elem() + } else { + return errors.New("cannot map to non-pointer struct") + } + + return s.mapTo(val, true) +} + +// MapTo maps file to given struct. +func (f *File) MapTo(v interface{}) error { + return f.Section("").MapTo(v) +} + +// StrictMapTo maps file to given struct in strict mode, +// which returns all possible error including value parsing error. +func (f *File) StrictMapTo(v interface{}) error { + return f.Section("").StrictMapTo(v) +} + +// MapToWithMapper maps data sources to given struct with name mapper. +func MapToWithMapper(v interface{}, mapper NameMapper, source interface{}, others ...interface{}) error { + cfg, err := Load(source, others...) + if err != nil { + return err + } + cfg.NameMapper = mapper + return cfg.MapTo(v) +} + +// StrictMapToWithMapper maps data sources to given struct with name mapper in strict mode, +// which returns all possible error including value parsing error. +func StrictMapToWithMapper(v interface{}, mapper NameMapper, source interface{}, others ...interface{}) error { + cfg, err := Load(source, others...) + if err != nil { + return err + } + cfg.NameMapper = mapper + return cfg.StrictMapTo(v) +} + +// MapTo maps data sources to given struct. +func MapTo(v, source interface{}, others ...interface{}) error { + return MapToWithMapper(v, nil, source, others...) +} + +// StrictMapTo maps data sources to given struct in strict mode, +// which returns all possible error including value parsing error. +func StrictMapTo(v, source interface{}, others ...interface{}) error { + return StrictMapToWithMapper(v, nil, source, others...) +} + +// reflectSliceWithProperType does the opposite thing as setSliceWithProperType. +func reflectSliceWithProperType(key *Key, field reflect.Value, delim string, allowShadow bool) error { + slice := field.Slice(0, field.Len()) + if field.Len() == 0 { + return nil + } + sliceOf := field.Type().Elem().Kind() + + if allowShadow { + var keyWithShadows *Key + for i := 0; i < field.Len(); i++ { + var val string + switch sliceOf { + case reflect.String: + val = slice.Index(i).String() + case reflect.Int, reflect.Int64: + val = fmt.Sprint(slice.Index(i).Int()) + case reflect.Uint, reflect.Uint64: + val = fmt.Sprint(slice.Index(i).Uint()) + case reflect.Float64: + val = fmt.Sprint(slice.Index(i).Float()) + case reflect.Bool: + val = fmt.Sprint(slice.Index(i).Bool()) + case reflectTime: + val = slice.Index(i).Interface().(time.Time).Format(time.RFC3339) + default: + return fmt.Errorf("unsupported type '[]%s'", sliceOf) + } + + if i == 0 { + keyWithShadows = newKey(key.s, key.name, val) + } else { + keyWithShadows.AddShadow(val) + } + } + key = keyWithShadows + return nil + } + + var buf bytes.Buffer + for i := 0; i < field.Len(); i++ { + switch sliceOf { + case reflect.String: + buf.WriteString(slice.Index(i).String()) + case reflect.Int, reflect.Int64: + buf.WriteString(fmt.Sprint(slice.Index(i).Int())) + case reflect.Uint, reflect.Uint64: + buf.WriteString(fmt.Sprint(slice.Index(i).Uint())) + case reflect.Float64: + buf.WriteString(fmt.Sprint(slice.Index(i).Float())) + case reflect.Bool: + buf.WriteString(fmt.Sprint(slice.Index(i).Bool())) + case reflectTime: + buf.WriteString(slice.Index(i).Interface().(time.Time).Format(time.RFC3339)) + default: + return fmt.Errorf("unsupported type '[]%s'", sliceOf) + } + buf.WriteString(delim) + } + key.SetValue(buf.String()[:buf.Len()-len(delim)]) + return nil +} + +// reflectWithProperType does the opposite thing as setWithProperType. +func reflectWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string, allowShadow bool) error { + switch t.Kind() { + case reflect.String: + key.SetValue(field.String()) + case reflect.Bool: + key.SetValue(fmt.Sprint(field.Bool())) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + key.SetValue(fmt.Sprint(field.Int())) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + key.SetValue(fmt.Sprint(field.Uint())) + case reflect.Float32, reflect.Float64: + key.SetValue(fmt.Sprint(field.Float())) + case reflectTime: + key.SetValue(fmt.Sprint(field.Interface().(time.Time).Format(time.RFC3339))) + case reflect.Slice: + return reflectSliceWithProperType(key, field, delim, allowShadow) + case reflect.Ptr: + if !field.IsNil() { + return reflectWithProperType(t.Elem(), key, field.Elem(), delim, allowShadow) + } + default: + return fmt.Errorf("unsupported type '%s'", t) + } + return nil +} + +// CR: copied from encoding/json/encode.go with modifications of time.Time support. +// TODO: add more test coverage. +func isEmptyValue(v reflect.Value) bool { + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + case reflectTime: + t, ok := v.Interface().(time.Time) + return ok && t.IsZero() + } + return false +} + +func (s *Section) reflectFrom(val reflect.Value) error { + if val.Kind() == reflect.Ptr { + val = val.Elem() + } + typ := val.Type() + + for i := 0; i < typ.NumField(); i++ { + field := val.Field(i) + tpField := typ.Field(i) + + tag := tpField.Tag.Get("ini") + if tag == "-" { + continue + } + + rawName, omitEmpty, allowShadow := parseTagOptions(tag) + if omitEmpty && isEmptyValue(field) { + continue + } + + fieldName := s.parseFieldName(tpField.Name, rawName) + if len(fieldName) == 0 || !field.CanSet() { + continue + } + + if (tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous) || + (tpField.Type.Kind() == reflect.Struct && tpField.Type.Name() != "Time") { + // Note: The only error here is section doesn't exist. + sec, err := s.f.GetSection(fieldName) + if err != nil { + // Note: fieldName can never be empty here, ignore error. + sec, _ = s.f.NewSection(fieldName) + } + + // Add comment from comment tag + if len(sec.Comment) == 0 { + sec.Comment = tpField.Tag.Get("comment") + } + + if err = sec.reflectFrom(field); err != nil { + return fmt.Errorf("error reflecting field (%s): %v", fieldName, err) + } + continue + } + + // Note: Same reason as secion. + key, err := s.GetKey(fieldName) + if err != nil { + key, _ = s.NewKey(fieldName, "") + } + + // Add comment from comment tag + if len(key.Comment) == 0 { + key.Comment = tpField.Tag.Get("comment") + } + + if err = reflectWithProperType(tpField.Type, key, field, parseDelim(tpField.Tag.Get("delim")), allowShadow); err != nil { + return fmt.Errorf("error reflecting field (%s): %v", fieldName, err) + } + + } + return nil +} + +// ReflectFrom reflects secion from given struct. +func (s *Section) ReflectFrom(v interface{}) error { + typ := reflect.TypeOf(v) + val := reflect.ValueOf(v) + if typ.Kind() == reflect.Ptr { + typ = typ.Elem() + val = val.Elem() + } else { + return errors.New("cannot reflect from non-pointer struct") + } + + return s.reflectFrom(val) +} + +// ReflectFrom reflects file from given struct. +func (f *File) ReflectFrom(v interface{}) error { + return f.Section("").ReflectFrom(v) +} + +// ReflectFromWithMapper reflects data sources from given struct with name mapper. +func ReflectFromWithMapper(cfg *File, v interface{}, mapper NameMapper) error { + cfg.NameMapper = mapper + return cfg.ReflectFrom(v) +} + +// ReflectFrom reflects data sources from given struct. +func ReflectFrom(cfg *File, v interface{}) error { + return ReflectFromWithMapper(cfg, v, nil) +} diff --git a/vendor/gopkg.in/yaml.v2/.travis.yml b/vendor/gopkg.in/yaml.v2/.travis.yml index 9f556934d..055480b9e 100644 --- a/vendor/gopkg.in/yaml.v2/.travis.yml +++ b/vendor/gopkg.in/yaml.v2/.travis.yml @@ -1,12 +1,16 @@ language: go go: - - 1.4 - - 1.5 - - 1.6 - - 1.7 - - 1.8 - - 1.9 - - tip + - "1.4.x" + - "1.5.x" + - "1.6.x" + - "1.7.x" + - "1.8.x" + - "1.9.x" + - "1.10.x" + - "1.11.x" + - "1.12.x" + - "1.13.x" + - "tip" go_import_path: gopkg.in/yaml.v2 diff --git a/vendor/gopkg.in/yaml.v2/apic.go b/vendor/gopkg.in/yaml.v2/apic.go index 1f7e87e67..d2c2308f1 100644 --- a/vendor/gopkg.in/yaml.v2/apic.go +++ b/vendor/gopkg.in/yaml.v2/apic.go @@ -86,6 +86,7 @@ func yaml_emitter_initialize(emitter *yaml_emitter_t) { raw_buffer: make([]byte, 0, output_raw_buffer_size), states: make([]yaml_emitter_state_t, 0, initial_stack_size), events: make([]yaml_event_t, 0, initial_queue_size), + best_width: -1, } } diff --git a/vendor/gopkg.in/yaml.v2/decode.go b/vendor/gopkg.in/yaml.v2/decode.go index e4e56e28e..129bc2a97 100644 --- a/vendor/gopkg.in/yaml.v2/decode.go +++ b/vendor/gopkg.in/yaml.v2/decode.go @@ -229,6 +229,10 @@ type decoder struct { mapType reflect.Type terrors []string strict bool + + decodeCount int + aliasCount int + aliasDepth int } var ( @@ -314,7 +318,43 @@ func (d *decoder) prepare(n *node, out reflect.Value) (newout reflect.Value, unm return out, false, false } +const ( + // 400,000 decode operations is ~500kb of dense object declarations, or + // ~5kb of dense object declarations with 10000% alias expansion + alias_ratio_range_low = 400000 + + // 4,000,000 decode operations is ~5MB of dense object declarations, or + // ~4.5MB of dense object declarations with 10% alias expansion + alias_ratio_range_high = 4000000 + + // alias_ratio_range is the range over which we scale allowed alias ratios + alias_ratio_range = float64(alias_ratio_range_high - alias_ratio_range_low) +) + +func allowedAliasRatio(decodeCount int) float64 { + switch { + case decodeCount <= alias_ratio_range_low: + // allow 99% to come from alias expansion for small-to-medium documents + return 0.99 + case decodeCount >= alias_ratio_range_high: + // allow 10% to come from alias expansion for very large documents + return 0.10 + default: + // scale smoothly from 99% down to 10% over the range. + // this maps to 396,000 - 400,000 allowed alias-driven decodes over the range. + // 400,000 decode operations is ~100MB of allocations in worst-case scenarios (single-item maps). + return 0.99 - 0.89*(float64(decodeCount-alias_ratio_range_low)/alias_ratio_range) + } +} + func (d *decoder) unmarshal(n *node, out reflect.Value) (good bool) { + d.decodeCount++ + if d.aliasDepth > 0 { + d.aliasCount++ + } + if d.aliasCount > 100 && d.decodeCount > 1000 && float64(d.aliasCount)/float64(d.decodeCount) > allowedAliasRatio(d.decodeCount) { + failf("document contains excessive aliasing") + } switch n.kind { case documentNode: return d.document(n, out) @@ -353,7 +393,9 @@ func (d *decoder) alias(n *node, out reflect.Value) (good bool) { failf("anchor '%s' value contains itself", n.value) } d.aliases[n] = true + d.aliasDepth++ good = d.unmarshal(n.alias, out) + d.aliasDepth-- delete(d.aliases, n) return good } @@ -746,8 +788,7 @@ func (d *decoder) merge(n *node, out reflect.Value) { case mappingNode: d.unmarshal(n, out) case aliasNode: - an, ok := d.doc.anchors[n.value] - if ok && an.kind != mappingNode { + if n.alias != nil && n.alias.kind != mappingNode { failWantMap() } d.unmarshal(n, out) @@ -756,8 +797,7 @@ func (d *decoder) merge(n *node, out reflect.Value) { for i := len(n.children) - 1; i >= 0; i-- { ni := n.children[i] if ni.kind == aliasNode { - an, ok := d.doc.anchors[ni.value] - if ok && an.kind != mappingNode { + if ni.alias != nil && ni.alias.kind != mappingNode { failWantMap() } } else if ni.kind != mappingNode { diff --git a/vendor/gopkg.in/yaml.v2/resolve.go b/vendor/gopkg.in/yaml.v2/resolve.go index 6c151db6f..4120e0c91 100644 --- a/vendor/gopkg.in/yaml.v2/resolve.go +++ b/vendor/gopkg.in/yaml.v2/resolve.go @@ -81,7 +81,7 @@ func resolvableTag(tag string) bool { return false } -var yamlStyleFloat = regexp.MustCompile(`^[-+]?[0-9]*\.?[0-9]+([eE][-+][0-9]+)?$`) +var yamlStyleFloat = regexp.MustCompile(`^[-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][-+]?[0-9]+)?$`) func resolve(tag string, in string) (rtag string, out interface{}) { if !resolvableTag(tag) { diff --git a/vendor/gopkg.in/yaml.v2/scannerc.go b/vendor/gopkg.in/yaml.v2/scannerc.go index 077fd1dd2..0b9bb6030 100644 --- a/vendor/gopkg.in/yaml.v2/scannerc.go +++ b/vendor/gopkg.in/yaml.v2/scannerc.go @@ -626,30 +626,17 @@ func trace(args ...interface{}) func() { func yaml_parser_fetch_more_tokens(parser *yaml_parser_t) bool { // While we need more tokens to fetch, do it. for { - // Check if we really need to fetch more tokens. - need_more_tokens := false - - if parser.tokens_head == len(parser.tokens) { - // Queue is empty. - need_more_tokens = true - } else { - // Check if any potential simple key may occupy the head position. - if !yaml_parser_stale_simple_keys(parser) { + if parser.tokens_head != len(parser.tokens) { + // If queue is non-empty, check if any potential simple key may + // occupy the head position. + head_tok_idx, ok := parser.simple_keys_by_tok[parser.tokens_parsed] + if !ok { + break + } else if valid, ok := yaml_simple_key_is_valid(parser, &parser.simple_keys[head_tok_idx]); !ok { return false + } else if !valid { + break } - - for i := range parser.simple_keys { - simple_key := &parser.simple_keys[i] - if simple_key.possible && simple_key.token_number == parser.tokens_parsed { - need_more_tokens = true - break - } - } - } - - // We are finished. - if !need_more_tokens { - break } // Fetch the next token. if !yaml_parser_fetch_next_token(parser) { @@ -678,11 +665,6 @@ func yaml_parser_fetch_next_token(parser *yaml_parser_t) bool { return false } - // Remove obsolete potential simple keys. - if !yaml_parser_stale_simple_keys(parser) { - return false - } - // Check the indentation level against the current column. if !yaml_parser_unroll_indent(parser, parser.mark.column) { return false @@ -837,29 +819,30 @@ func yaml_parser_fetch_next_token(parser *yaml_parser_t) bool { "found character that cannot start any token") } -// Check the list of potential simple keys and remove the positions that -// cannot contain simple keys anymore. -func yaml_parser_stale_simple_keys(parser *yaml_parser_t) bool { - // Check for a potential simple key for each flow level. - for i := range parser.simple_keys { - simple_key := &parser.simple_keys[i] - - // The specification requires that a simple key - // - // - is limited to a single line, - // - is shorter than 1024 characters. - if simple_key.possible && (simple_key.mark.line < parser.mark.line || simple_key.mark.index+1024 < parser.mark.index) { - - // Check if the potential simple key to be removed is required. - if simple_key.required { - return yaml_parser_set_scanner_error(parser, - "while scanning a simple key", simple_key.mark, - "could not find expected ':'") - } - simple_key.possible = false - } +func yaml_simple_key_is_valid(parser *yaml_parser_t, simple_key *yaml_simple_key_t) (valid, ok bool) { + if !simple_key.possible { + return false, true } - return true + + // The 1.2 specification says: + // + // "If the ? indicator is omitted, parsing needs to see past the + // implicit key to recognize it as such. To limit the amount of + // lookahead required, the “:” indicator must appear at most 1024 + // Unicode characters beyond the start of the key. In addition, the key + // is restricted to a single line." + // + if simple_key.mark.line < parser.mark.line || simple_key.mark.index+1024 < parser.mark.index { + // Check if the potential simple key to be removed is required. + if simple_key.required { + return false, yaml_parser_set_scanner_error(parser, + "while scanning a simple key", simple_key.mark, + "could not find expected ':'") + } + simple_key.possible = false + return false, true + } + return true, true } // Check if a simple key may start at the current position and add it if @@ -879,13 +862,14 @@ func yaml_parser_save_simple_key(parser *yaml_parser_t) bool { possible: true, required: required, token_number: parser.tokens_parsed + (len(parser.tokens) - parser.tokens_head), + mark: parser.mark, } - simple_key.mark = parser.mark if !yaml_parser_remove_simple_key(parser) { return false } parser.simple_keys[len(parser.simple_keys)-1] = simple_key + parser.simple_keys_by_tok[simple_key.token_number] = len(parser.simple_keys) - 1 } return true } @@ -900,19 +884,33 @@ func yaml_parser_remove_simple_key(parser *yaml_parser_t) bool { "while scanning a simple key", parser.simple_keys[i].mark, "could not find expected ':'") } + // Remove the key from the stack. + parser.simple_keys[i].possible = false + delete(parser.simple_keys_by_tok, parser.simple_keys[i].token_number) } - // Remove the key from the stack. - parser.simple_keys[i].possible = false return true } +// max_flow_level limits the flow_level +const max_flow_level = 10000 + // Increase the flow level and resize the simple key list if needed. func yaml_parser_increase_flow_level(parser *yaml_parser_t) bool { // Reset the simple key on the next level. - parser.simple_keys = append(parser.simple_keys, yaml_simple_key_t{}) + parser.simple_keys = append(parser.simple_keys, yaml_simple_key_t{ + possible: false, + required: false, + token_number: parser.tokens_parsed + (len(parser.tokens) - parser.tokens_head), + mark: parser.mark, + }) // Increase the flow level. parser.flow_level++ + if parser.flow_level > max_flow_level { + return yaml_parser_set_scanner_error(parser, + "while increasing flow level", parser.simple_keys[len(parser.simple_keys)-1].mark, + fmt.Sprintf("exceeded max depth of %d", max_flow_level)) + } return true } @@ -920,11 +918,16 @@ func yaml_parser_increase_flow_level(parser *yaml_parser_t) bool { func yaml_parser_decrease_flow_level(parser *yaml_parser_t) bool { if parser.flow_level > 0 { parser.flow_level-- - parser.simple_keys = parser.simple_keys[:len(parser.simple_keys)-1] + last := len(parser.simple_keys) - 1 + delete(parser.simple_keys_by_tok, parser.simple_keys[last].token_number) + parser.simple_keys = parser.simple_keys[:last] } return true } +// max_indents limits the indents stack size +const max_indents = 10000 + // Push the current indentation level to the stack and set the new level // the current column is greater than the indentation level. In this case, // append or insert the specified token into the token queue. @@ -939,6 +942,11 @@ func yaml_parser_roll_indent(parser *yaml_parser_t, column, number int, typ yaml // indentation level. parser.indents = append(parser.indents, parser.indent) parser.indent = column + if len(parser.indents) > max_indents { + return yaml_parser_set_scanner_error(parser, + "while increasing indent level", parser.simple_keys[len(parser.simple_keys)-1].mark, + fmt.Sprintf("exceeded max depth of %d", max_indents)) + } // Create a token and insert it into the queue. token := yaml_token_t{ @@ -989,6 +997,8 @@ func yaml_parser_fetch_stream_start(parser *yaml_parser_t) bool { // Initialize the simple key stack. parser.simple_keys = append(parser.simple_keys, yaml_simple_key_t{}) + parser.simple_keys_by_tok = make(map[int]int) + // A simple key is allowed at the beginning of the stream. parser.simple_key_allowed = true @@ -1270,7 +1280,11 @@ func yaml_parser_fetch_value(parser *yaml_parser_t) bool { simple_key := &parser.simple_keys[len(parser.simple_keys)-1] // Have we found a simple key? - if simple_key.possible { + if valid, ok := yaml_simple_key_is_valid(parser, simple_key); !ok { + return false + + } else if valid { + // Create the KEY token and insert it into the queue. token := yaml_token_t{ typ: yaml_KEY_TOKEN, @@ -1288,6 +1302,7 @@ func yaml_parser_fetch_value(parser *yaml_parser_t) bool { // Remove the simple key. simple_key.possible = false + delete(parser.simple_keys_by_tok, simple_key.token_number) // A simple key cannot follow another simple key. parser.simple_key_allowed = false diff --git a/vendor/gopkg.in/yaml.v2/yaml.go b/vendor/gopkg.in/yaml.v2/yaml.go index de85aa4cd..89650e293 100644 --- a/vendor/gopkg.in/yaml.v2/yaml.go +++ b/vendor/gopkg.in/yaml.v2/yaml.go @@ -89,7 +89,7 @@ func UnmarshalStrict(in []byte, out interface{}) (err error) { return unmarshal(in, out, true) } -// A Decorder reads and decodes YAML values from an input stream. +// A Decoder reads and decodes YAML values from an input stream. type Decoder struct { strict bool parser *parser diff --git a/vendor/gopkg.in/yaml.v2/yamlh.go b/vendor/gopkg.in/yaml.v2/yamlh.go index e25cee563..f6a9c8e34 100644 --- a/vendor/gopkg.in/yaml.v2/yamlh.go +++ b/vendor/gopkg.in/yaml.v2/yamlh.go @@ -579,6 +579,7 @@ type yaml_parser_t struct { simple_key_allowed bool // May a simple key occur at the current position? simple_keys []yaml_simple_key_t // The stack of simple keys. + simple_keys_by_tok map[int]int // possible simple_key indexes indexed by token_number // Parser stuff diff --git a/vendor/modules.txt b/vendor/modules.txt index f452415b5..ea595a4df 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,4 +1,4 @@ -# github.com/99designs/gqlgen v0.9.0 +# github.com/99designs/gqlgen v0.12.2 github.com/99designs/gqlgen github.com/99designs/gqlgen/api github.com/99designs/gqlgen/cmd @@ -7,17 +7,34 @@ github.com/99designs/gqlgen/codegen/config github.com/99designs/gqlgen/codegen/templates github.com/99designs/gqlgen/complexity github.com/99designs/gqlgen/graphql +github.com/99designs/gqlgen/graphql/errcode +github.com/99designs/gqlgen/graphql/executor +github.com/99designs/gqlgen/graphql/handler +github.com/99designs/gqlgen/graphql/handler/extension +github.com/99designs/gqlgen/graphql/handler/lru +github.com/99designs/gqlgen/graphql/handler/transport github.com/99designs/gqlgen/graphql/introspection +github.com/99designs/gqlgen/graphql/playground github.com/99designs/gqlgen/handler github.com/99designs/gqlgen/internal/code github.com/99designs/gqlgen/internal/imports +github.com/99designs/gqlgen/internal/rewrite github.com/99designs/gqlgen/plugin +github.com/99designs/gqlgen/plugin/federation github.com/99designs/gqlgen/plugin/modelgen github.com/99designs/gqlgen/plugin/resolvergen github.com/99designs/gqlgen/plugin/servergen # github.com/BurntSushi/toml v0.3.1 github.com/BurntSushi/toml -# github.com/agnivade/levenshtein v1.0.1 +# github.com/Yamashou/gqlgenc v0.0.0-20200902035953-4dbef3551953 +github.com/Yamashou/gqlgenc +github.com/Yamashou/gqlgenc/client +github.com/Yamashou/gqlgenc/clientgen +github.com/Yamashou/gqlgenc/config +github.com/Yamashou/gqlgenc/generator +github.com/Yamashou/gqlgenc/graphqljson +github.com/Yamashou/gqlgenc/introspection +# github.com/agnivade/levenshtein v1.1.0 github.com/agnivade/levenshtein # github.com/antchfx/htmlquery v1.2.3 github.com/antchfx/htmlquery @@ -75,6 +92,8 @@ github.com/chromedp/cdproto/webauthn github.com/chromedp/chromedp github.com/chromedp/chromedp/device github.com/chromedp/chromedp/kb +# github.com/cpuguy83/go-md2man/v2 v2.0.0 +github.com/cpuguy83/go-md2man/v2/md2man # github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew/spew # github.com/disintegration/imaging v1.6.0 @@ -139,7 +158,7 @@ github.com/golang/groupcache/lru github.com/gorilla/securecookie # github.com/gorilla/sessions v1.2.0 github.com/gorilla/sessions -# github.com/gorilla/websocket v1.4.0 +# github.com/gorilla/websocket v1.4.2 github.com/gorilla/websocket # github.com/h2non/filetype v1.0.8 github.com/h2non/filetype @@ -181,7 +200,7 @@ github.com/karrick/godirwalk github.com/knq/sysutil # github.com/konsorten/go-windows-terminal-sequences v1.0.2 github.com/konsorten/go-windows-terminal-sequences -# github.com/magiconair/properties v1.8.0 +# github.com/magiconair/properties v1.8.1 github.com/magiconair/properties # github.com/mailru/easyjson v0.7.1 github.com/mailru/easyjson @@ -192,32 +211,46 @@ github.com/mailru/easyjson/jwriter github.com/markbates/oncer # github.com/markbates/safe v1.0.1 github.com/markbates/safe +# github.com/matryer/moq v0.0.0-20200106131100-75d0ddfc0007 +github.com/matryer/moq +github.com/matryer/moq/pkg/moq # github.com/mattn/go-sqlite3 v1.13.0 github.com/mattn/go-sqlite3 +# github.com/mitchellh/go-homedir v1.1.0 +github.com/mitchellh/go-homedir # github.com/mitchellh/mapstructure v1.1.2 github.com/mitchellh/mapstructure -# github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 +# github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd github.com/modern-go/concurrent -# github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 +# github.com/modern-go/reflect2 v1.0.1 github.com/modern-go/reflect2 # github.com/natefinch/pie v0.0.0-20170715172608-9a0d72014007 github.com/natefinch/pie # github.com/pelletier/go-toml v1.2.0 github.com/pelletier/go-toml -# github.com/pkg/errors v0.8.1 +# github.com/pkg/errors v0.9.1 github.com/pkg/errors # github.com/pmezard/go-difflib v1.0.0 github.com/pmezard/go-difflib/difflib -# github.com/rogpeppe/go-internal v1.2.2 +# github.com/rogpeppe/go-internal v1.3.0 github.com/rogpeppe/go-internal/modfile github.com/rogpeppe/go-internal/module github.com/rogpeppe/go-internal/semver # github.com/rs/cors v1.6.0 github.com/rs/cors +# github.com/rs/zerolog v1.18.0 +github.com/rs/zerolog +github.com/rs/zerolog/internal/cbor +github.com/rs/zerolog/internal/json +github.com/rs/zerolog/log +# github.com/russross/blackfriday/v2 v2.0.1 +github.com/russross/blackfriday/v2 # github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f github.com/shurcooL/graphql github.com/shurcooL/graphql/ident github.com/shurcooL/graphql/internal/jsonutil +# github.com/shurcooL/sanitized_anchor_name v1.0.0 +github.com/shurcooL/sanitized_anchor_name # github.com/sirupsen/logrus v1.4.2 github.com/sirupsen/logrus # github.com/spf13/afero v1.2.0 @@ -225,48 +258,64 @@ github.com/spf13/afero github.com/spf13/afero/mem # github.com/spf13/cast v1.3.0 github.com/spf13/cast -# github.com/spf13/cobra v0.0.3 +# github.com/spf13/cobra v1.0.0 github.com/spf13/cobra # github.com/spf13/jwalterweatherman v1.0.0 github.com/spf13/jwalterweatherman # github.com/spf13/pflag v1.0.3 github.com/spf13/pflag -# github.com/spf13/viper v1.4.0 +# github.com/spf13/viper v1.7.0 github.com/spf13/viper +# github.com/stretchr/objx v0.1.1 +github.com/stretchr/objx # github.com/stretchr/testify v1.5.1 github.com/stretchr/testify/assert +github.com/stretchr/testify/mock +# github.com/subosito/gotenv v1.2.0 +github.com/subosito/gotenv # github.com/tidwall/gjson v1.6.0 github.com/tidwall/gjson # github.com/tidwall/match v1.0.1 github.com/tidwall/match # github.com/tidwall/pretty v1.0.0 github.com/tidwall/pretty -# github.com/urfave/cli v1.20.0 -github.com/urfave/cli +# github.com/urfave/cli/v2 v2.1.1 +github.com/urfave/cli/v2 # github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e github.com/vektah/dataloaden github.com/vektah/dataloaden/pkg/generator -# github.com/vektah/gqlparser v1.1.2 -github.com/vektah/gqlparser -github.com/vektah/gqlparser/ast -github.com/vektah/gqlparser/gqlerror -github.com/vektah/gqlparser/lexer -github.com/vektah/gqlparser/parser -github.com/vektah/gqlparser/validator -github.com/vektah/gqlparser/validator/rules -# golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4 +# github.com/vektah/gqlparser/v2 v2.0.1 +github.com/vektah/gqlparser/v2 +github.com/vektah/gqlparser/v2/ast +github.com/vektah/gqlparser/v2/formatter +github.com/vektah/gqlparser/v2/gqlerror +github.com/vektah/gqlparser/v2/lexer +github.com/vektah/gqlparser/v2/parser +github.com/vektah/gqlparser/v2/validator +github.com/vektah/gqlparser/v2/validator/rules +# github.com/vektra/mockery/v2 v2.2.1 +github.com/vektra/mockery/v2 +github.com/vektra/mockery/v2/cmd +github.com/vektra/mockery/v2/pkg +github.com/vektra/mockery/v2/pkg/config +github.com/vektra/mockery/v2/pkg/logging +# golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 golang.org/x/crypto/bcrypt golang.org/x/crypto/blowfish golang.org/x/crypto/ssh/terminal -# golang.org/x/image v0.0.0-20190118043309-183bebdce1b2 +# golang.org/x/image v0.0.0-20190802002840-cff245a6509b golang.org/x/image/bmp +golang.org/x/image/ccitt golang.org/x/image/riff golang.org/x/image/tiff golang.org/x/image/tiff/lzw golang.org/x/image/vp8 golang.org/x/image/vp8l golang.org/x/image/webp -# golang.org/x/net v0.0.0-20200602114024-627f9648deb9 +# golang.org/x/mod v0.3.0 +golang.org/x/mod/module +golang.org/x/mod/semver +# golang.org/x/net v0.0.0-20200822124328-c89045814202 golang.org/x/net/context/ctxhttp golang.org/x/net/html golang.org/x/net/html/atom @@ -294,16 +343,27 @@ golang.org/x/text/language golang.org/x/text/runes golang.org/x/text/transform golang.org/x/text/unicode/norm -# golang.org/x/tools v0.0.0-20190515012406-7d7faa4812bd +# golang.org/x/tools v0.0.0-20200915031644-64986481280e golang.org/x/tools/go/ast/astutil golang.org/x/tools/go/gcexportdata golang.org/x/tools/go/internal/gcimporter golang.org/x/tools/go/internal/packagesdriver golang.org/x/tools/go/packages golang.org/x/tools/imports +golang.org/x/tools/internal/event +golang.org/x/tools/internal/event/core +golang.org/x/tools/internal/event/keys +golang.org/x/tools/internal/event/label golang.org/x/tools/internal/fastwalk +golang.org/x/tools/internal/gocommand golang.org/x/tools/internal/gopathwalk -golang.org/x/tools/internal/module -golang.org/x/tools/internal/semver -# gopkg.in/yaml.v2 v2.2.2 +golang.org/x/tools/internal/imports +golang.org/x/tools/internal/packagesinternal +golang.org/x/tools/internal/typesinternal +# golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 +golang.org/x/xerrors +golang.org/x/xerrors/internal +# gopkg.in/ini.v1 v1.51.0 +gopkg.in/ini.v1 +# gopkg.in/yaml.v2 v2.3.0 gopkg.in/yaml.v2