Move to Go modules (#10)
This commit is contained in:
parent
2159aadfcb
commit
6207229e9b
|
@ -1,16 +1,16 @@
|
||||||
FROM golang:1.13.1-alpine AS build
|
FROM golang:1.13.1-alpine AS build
|
||||||
|
|
||||||
WORKDIR /go/src/go.dev.pztrn.name/fastpastebin
|
WORKDIR /fastpastebin
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
WORKDIR /go/src/go.dev.pztrn.name/fastpastebin/cmd/fastpastebin
|
WORKDIR /fastpastebin/cmd/fastpastebin
|
||||||
|
|
||||||
RUN go build
|
RUN GOFLAGS="-mod=vendor" go build
|
||||||
|
|
||||||
FROM alpine:3.10
|
FROM alpine:3.10
|
||||||
LABEL maintainer "Stanislav N. <pztrn@pztrn.name>"
|
LABEL maintainer "Stanislav N. <pztrn@pztrn.name>"
|
||||||
|
|
||||||
COPY --from=build /go/src/go.dev.pztrn.name/fastpastebin/cmd/fastpastebin/fastpastebin /app/fastpastebin
|
COPY --from=build /fastpastebin/cmd/fastpastebin/fastpastebin /app/fastpastebin
|
||||||
COPY docker/fastpastebin.docker.yaml /app/fastpastebin.yaml
|
COPY docker/fastpastebin.docker.yaml /app/fastpastebin.yaml
|
||||||
|
|
||||||
EXPOSE 25544
|
EXPOSE 25544
|
||||||
|
|
|
@ -1,257 +0,0 @@
|
||||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
|
||||||
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:aa94227e54ee105fcd03f25c85d3e5bbe13d2f2d5eb02379b5b4db5320dfb371"
|
|
||||||
name = "github.com/alecthomas/chroma"
|
|
||||||
packages = [
|
|
||||||
".",
|
|
||||||
"formatters",
|
|
||||||
"formatters/html",
|
|
||||||
"lexers",
|
|
||||||
"lexers/a",
|
|
||||||
"lexers/b",
|
|
||||||
"lexers/c",
|
|
||||||
"lexers/circular",
|
|
||||||
"lexers/d",
|
|
||||||
"lexers/e",
|
|
||||||
"lexers/f",
|
|
||||||
"lexers/g",
|
|
||||||
"lexers/h",
|
|
||||||
"lexers/i",
|
|
||||||
"lexers/internal",
|
|
||||||
"lexers/j",
|
|
||||||
"lexers/k",
|
|
||||||
"lexers/l",
|
|
||||||
"lexers/m",
|
|
||||||
"lexers/n",
|
|
||||||
"lexers/o",
|
|
||||||
"lexers/p",
|
|
||||||
"lexers/q",
|
|
||||||
"lexers/r",
|
|
||||||
"lexers/s",
|
|
||||||
"lexers/t",
|
|
||||||
"lexers/v",
|
|
||||||
"lexers/w",
|
|
||||||
"lexers/x",
|
|
||||||
"lexers/y",
|
|
||||||
"styles",
|
|
||||||
]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "881a441774f9d707d3b7852025b7f2149a556182"
|
|
||||||
version = "v0.6.2"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
digest = "1:710109527a119c813d4fac773712dd92f449ed7c2f7b81f49822f94d290c8f72"
|
|
||||||
name = "github.com/danwakefield/fnmatch"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "cbb64ac3d964b81592e64f957ad53df015803288"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
digest = "1:3e38fc80f8fb9482345b1a596b21dfa2f86d696061cbd76a6620a5658e038132"
|
|
||||||
name = "github.com/dchest/captcha"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "6a29415a8364ec2971fdc62d9e415ed53fc20410"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:76dc72490af7174349349838f2fe118996381b31ea83243812a97e5a0fd5ed55"
|
|
||||||
name = "github.com/dgrijalva/jwt-go"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "06ea1031745cb8b3dab3f6a236daf2b0aa468b7e"
|
|
||||||
version = "v3.2.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:72dc2b6056e7097f829260e4a2ff08d32fec6017df1982a66e110ab4128486f8"
|
|
||||||
name = "github.com/dlclark/regexp2"
|
|
||||||
packages = [
|
|
||||||
".",
|
|
||||||
"syntax",
|
|
||||||
]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "487489b64fb796de2e55f4e8a4ad1e145f80e957"
|
|
||||||
version = "v1.1.6"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:850c49ca338a10fec2cb9e78f793043ed23965489d09e30bcc19fe29719da313"
|
|
||||||
name = "github.com/go-sql-driver/mysql"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "a0583e0143b1624142adab07e0e97fe106d99561"
|
|
||||||
version = "v1.3"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
digest = "1:7654989089e5bd5b6734ec3be8b695e87d3f1f8d95620b343fd7d3995a5b60d7"
|
|
||||||
name = "github.com/jmoiron/sqlx"
|
|
||||||
packages = [
|
|
||||||
".",
|
|
||||||
"reflectx",
|
|
||||||
]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "2aeb6a910c2b94f2d5eb53d9895d80e27264ec41"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:87f801436ec4799e0e043ab348ff9f3e95b1b3761138158f0950ac67695cc272"
|
|
||||||
name = "github.com/labstack/echo"
|
|
||||||
packages = [
|
|
||||||
".",
|
|
||||||
"middleware",
|
|
||||||
]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "6d227dfea4d2e52cb76856120b3c17f758139b4e"
|
|
||||||
version = "3.3.5"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:89d3b2cc2e6542b111a2f35d76eb35d1bed03f65455ebdb66a2340653da4f8e1"
|
|
||||||
name = "github.com/labstack/gommon"
|
|
||||||
packages = [
|
|
||||||
"bytes",
|
|
||||||
"color",
|
|
||||||
"log",
|
|
||||||
"random",
|
|
||||||
]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "588f4e8bddc6cb45c27b448e925c7fd6a5545434"
|
|
||||||
version = "0.2.5"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:8ef506fc2bb9ced9b151dafa592d4046063d744c646c1bbe801982ce87e4bc24"
|
|
||||||
name = "github.com/lib/pq"
|
|
||||||
packages = [
|
|
||||||
".",
|
|
||||||
"oid",
|
|
||||||
]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "4ded0e9383f75c197b3a2aaa6d590ac52df6fd79"
|
|
||||||
version = "v1.0.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:c658e84ad3916da105a761660dcaeb01e63416c8ec7bc62256a9b411a05fcd67"
|
|
||||||
name = "github.com/mattn/go-colorable"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072"
|
|
||||||
version = "v0.0.9"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:d4d17353dbd05cb52a2a52b7fe1771883b682806f68db442b436294926bbfafb"
|
|
||||||
name = "github.com/mattn/go-isatty"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
|
|
||||||
version = "v0.0.3"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:7a1c93f3d66dacf8e483fa48f117559c35e9b7209cffc3bbe3002f8b1d8f6810"
|
|
||||||
name = "github.com/pressly/goose"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "056a4d47dcc4d67fa3947a4f13945a5c690e568b"
|
|
||||||
version = "v2.1.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:9be615b2a72fc4a99e623c6776cce3afe3451741c34ea1805ea4a9b58604b9df"
|
|
||||||
name = "github.com/rs/zerolog"
|
|
||||||
packages = [
|
|
||||||
".",
|
|
||||||
"internal/cbor",
|
|
||||||
"internal/json",
|
|
||||||
]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "6d6350a51143b5c0d0a6a3b736ee2b41315f7269"
|
|
||||||
version = "v1.12.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
digest = "1:c468422f334a6b46a19448ad59aaffdfc0a36b08fdcc1c749a0b29b6453d7e59"
|
|
||||||
name = "github.com/valyala/bytebufferpool"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "e746df99fe4a3986f4d4f79e13c1e0117ce9c2f7"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
digest = "1:268b8bce0064e8c057d7b913605459f9a26dcab864c0886a56d196540fbf003f"
|
|
||||||
name = "github.com/valyala/fasttemplate"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "dcecefd839c4193db0d35b88ec65b4c12d360ab0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
digest = "1:517b2d010ccd8f6780dacab5ed4d3f9cc8d07e740ecf13da00cd8924d5ca530a"
|
|
||||||
name = "gitlab.com/pztrn/flagger"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "d429d7149cc92b7eaa7c98ab3d80b6d5c2e44046"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
digest = "1:99d34d34b70959a2a8c4a6112dd795c26ab3d546db932a613d1aa13311d83b40"
|
|
||||||
name = "golang.org/x/crypto"
|
|
||||||
packages = [
|
|
||||||
"acme",
|
|
||||||
"acme/autocert",
|
|
||||||
"pbkdf2",
|
|
||||||
"scrypt",
|
|
||||||
]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "b49d69b5da943f7ef3c9cf91c8777c1f78a0cc3c"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
digest = "1:a76a2a8110f745ec7ae232fbf6a8d3d3504cbc08d147bfb33c07de8274c5860a"
|
|
||||||
name = "golang.org/x/net"
|
|
||||||
packages = [
|
|
||||||
"context",
|
|
||||||
"webdav",
|
|
||||||
"webdav/internal/xml",
|
|
||||||
]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "5f9ae10d9af5b1c89ae6904293b14b064d4ada23"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
digest = "1:4057bcfb3790228dfabfe43f71a1e8c56df28698d20f5302f810e6194fde14ab"
|
|
||||||
name = "golang.org/x/sys"
|
|
||||||
packages = ["unix"]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "cbbc999da32df943dac6cd71eb3ee39e1d7838b9"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
digest = "1:342378ac4dcb378a5448dd723f0784ae519383532f5e70ade24132c4c8693202"
|
|
||||||
name = "gopkg.in/yaml.v2"
|
|
||||||
packages = ["."]
|
|
||||||
pruneopts = "UT"
|
|
||||||
revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
|
|
||||||
version = "v2.2.1"
|
|
||||||
|
|
||||||
[solve-meta]
|
|
||||||
analyzer-name = "dep"
|
|
||||||
analyzer-version = 1
|
|
||||||
input-imports = [
|
|
||||||
"github.com/alecthomas/chroma",
|
|
||||||
"github.com/alecthomas/chroma/formatters",
|
|
||||||
"github.com/alecthomas/chroma/formatters/html",
|
|
||||||
"github.com/alecthomas/chroma/lexers",
|
|
||||||
"github.com/alecthomas/chroma/styles",
|
|
||||||
"github.com/dchest/captcha",
|
|
||||||
"github.com/go-sql-driver/mysql",
|
|
||||||
"github.com/jmoiron/sqlx",
|
|
||||||
"github.com/labstack/echo",
|
|
||||||
"github.com/labstack/echo/middleware",
|
|
||||||
"github.com/lib/pq",
|
|
||||||
"github.com/pressly/goose",
|
|
||||||
"github.com/rs/zerolog",
|
|
||||||
"gitlab.com/pztrn/flagger",
|
|
||||||
"golang.org/x/crypto/scrypt",
|
|
||||||
"golang.org/x/net/webdav",
|
|
||||||
"gopkg.in/yaml.v2",
|
|
||||||
]
|
|
||||||
solver-name = "gps-cdcl"
|
|
||||||
solver-version = 1
|
|
34
Gopkg.toml
34
Gopkg.toml
|
@ -1,34 +0,0 @@
|
||||||
# Gopkg.toml example
|
|
||||||
#
|
|
||||||
# Refer to https://golang.github.io/dep/docs/Gopkg.toml.html
|
|
||||||
# for detailed Gopkg.toml documentation.
|
|
||||||
#
|
|
||||||
# required = ["github.com/user/thing/cmd/thing"]
|
|
||||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
|
||||||
#
|
|
||||||
# [[constraint]]
|
|
||||||
# name = "github.com/user/project"
|
|
||||||
# version = "1.0.0"
|
|
||||||
#
|
|
||||||
# [[constraint]]
|
|
||||||
# name = "github.com/user/project2"
|
|
||||||
# branch = "dev"
|
|
||||||
# source = "github.com/myfork/project2"
|
|
||||||
#
|
|
||||||
# [[override]]
|
|
||||||
# name = "github.com/x/y"
|
|
||||||
# version = "2.4.0"
|
|
||||||
#
|
|
||||||
# [prune]
|
|
||||||
# non-go = false
|
|
||||||
# go-tests = true
|
|
||||||
# unused-packages = true
|
|
||||||
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/rs/zerolog"
|
|
||||||
version = "1.6.0"
|
|
||||||
|
|
||||||
[prune]
|
|
||||||
go-tests = true
|
|
||||||
unused-packages = true
|
|
|
@ -17,7 +17,7 @@ for os in ${OS_LIST[@]}; do
|
||||||
goos=$(echo ${os} | awk -F"/" '{ print $1 }')
|
goos=$(echo ${os} | awk -F"/" '{ print $1 }')
|
||||||
goarch=$(echo ${os} | awk -F"/" '{ print $2 }')
|
goarch=$(echo ${os} | awk -F"/" '{ print $2 }')
|
||||||
echo "Building for ${goos} ${goarch}..."
|
echo "Building for ${goos} ${goarch}..."
|
||||||
GOOS=${goos} GOARCH=${goarch} go build -o ./dist/${os}/fastpastebin ./cmd/fastpastebin/
|
GOOS=${goos} GOARCH=${goarch} GOFLAGS="-mod=vendor" go build -o ./dist/${os}/fastpastebin ./cmd/fastpastebin/
|
||||||
cp ./examples/fastpastebin.yaml.dist ./dist/${os}/fastpastebin.yaml
|
cp ./examples/fastpastebin.yaml.dist ./dist/${os}/fastpastebin.yaml
|
||||||
cd ./dist/${os}/
|
cd ./dist/${os}/
|
||||||
tar -czf fastpastebin-${VERSION}-${goos}-${goarch}.tar.gz fastpastebin fastpastebin.yaml
|
tar -czf fastpastebin-${VERSION}-${goos}-${goarch}.tar.gz fastpastebin fastpastebin.yaml
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
module go.dev.pztrn.name/fastpastebin
|
||||||
|
|
||||||
|
go 1.13
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/alecthomas/chroma v0.6.7
|
||||||
|
github.com/dchest/captcha v0.0.0-20170622155422-6a29415a8364
|
||||||
|
github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
|
||||||
|
github.com/go-sql-driver/mysql v1.4.1
|
||||||
|
github.com/jmoiron/sqlx v1.2.0
|
||||||
|
github.com/labstack/echo v3.3.10+incompatible
|
||||||
|
github.com/labstack/gommon v0.3.0 // indirect
|
||||||
|
github.com/lib/pq v1.2.0
|
||||||
|
github.com/pressly/goose v2.6.0+incompatible
|
||||||
|
github.com/rs/zerolog v1.15.0
|
||||||
|
gitlab.com/pztrn/flagger v0.0.0-20190122123836-d429d7149cc9
|
||||||
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550
|
||||||
|
golang.org/x/net v0.0.0-20191011234655-491137f69257
|
||||||
|
google.golang.org/appengine v1.6.5 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.2.4
|
||||||
|
)
|
|
@ -0,0 +1,112 @@
|
||||||
|
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
|
||||||
|
github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=
|
||||||
|
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||||
|
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U=
|
||||||
|
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
|
||||||
|
github.com/alecthomas/chroma v0.6.7 h1:1hKci+AyKOxJrugR9veaocu9DQGR2/GecI72BpaO0Rg=
|
||||||
|
github.com/alecthomas/chroma v0.6.7/go.mod h1:zVlgtbRS7BJDrDY9SB238RmpoCBCYFlLmcfZ3durxTk=
|
||||||
|
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo=
|
||||||
|
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
|
||||||
|
github.com/alecthomas/kong v0.1.17-0.20190424132513-439c674f7ae0/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI=
|
||||||
|
github.com/alecthomas/kong v0.2.1-0.20190708041108-0548c6b1afae/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI=
|
||||||
|
github.com/alecthomas/kong-hcl v0.1.8-0.20190615233001-b21fea9723c8/go.mod h1:MRgZdU3vrFd05IQ89AxUZ0aYdF39BYoNFa324SodPCA=
|
||||||
|
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY=
|
||||||
|
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
|
||||||
|
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||||
|
github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E=
|
||||||
|
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ=
|
||||||
|
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk=
|
||||||
|
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/dchest/captcha v0.0.0-20170622155422-6a29415a8364 h1:U+BMqUt8LFgyrF0/NKgPZdr1sGZ3j6uBECpOGcISpFI=
|
||||||
|
github.com/dchest/captcha v0.0.0-20170622155422-6a29415a8364/go.mod h1:QGrK8vMWWHQYQ3QU9bw9Y9OPNfxccGzfb41qjvVeXtY=
|
||||||
|
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/dlclark/regexp2 v1.1.6 h1:CqB4MjHw0MFCDj+PHHjiESmHX+N7t0tJzKvC6M97BRg=
|
||||||
|
github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||||
|
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
|
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
|
||||||
|
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||||
|
github.com/gorilla/csrf v1.6.0/go.mod h1:7tSf8kmjNYr7IWDCYhd3U8Ck34iQ/Yw5CJu7bAkHEGI=
|
||||||
|
github.com/gorilla/handlers v1.4.1/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
||||||
|
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||||
|
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||||
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
|
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
|
github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA=
|
||||||
|
github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks=
|
||||||
|
github.com/labstack/echo v3.3.10+incompatible h1:pGRcYk231ExFAyoAjAfD85kQzRJCRI8bbnE7CX5OEgg=
|
||||||
|
github.com/labstack/echo v3.3.10+incompatible/go.mod h1:0INS7j/VjnFxD4E2wkz67b8cVwCLbBmJyDaka6Cmk1s=
|
||||||
|
github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0=
|
||||||
|
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
|
||||||
|
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0=
|
||||||
|
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||||
|
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
|
||||||
|
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
|
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
|
||||||
|
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.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg=
|
||||||
|
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
|
||||||
|
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/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
|
github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E=
|
||||||
|
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/pressly/goose v2.6.0+incompatible h1:3f8zIQ8rfgP9tyI0Hmcs2YNAqUCL1c+diLe3iU8Qd/k=
|
||||||
|
github.com/pressly/goose v2.6.0+incompatible/go.mod h1:m+QHWCqxR3k8D9l7qfzuC/djtlfzxr34mozWDYEu1z8=
|
||||||
|
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
|
||||||
|
github.com/rs/zerolog v1.15.0 h1:uPRuwkWF4J6fGsJ2R0Gn2jB1EQiav9k3S6CSdygQJXY=
|
||||||
|
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
|
||||||
|
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
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 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
|
github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8=
|
||||||
|
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||||
|
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
|
||||||
|
gitlab.com/pztrn/flagger v0.0.0-20190122123836-d429d7149cc9 h1:qLlIZybSEr3MSCaE+cJZpT6O/1q3IsBPV7GcZJc05vs=
|
||||||
|
gitlab.com/pztrn/flagger v0.0.0-20190122123836-d429d7149cc9/go.mod h1:2cxOO1JFwNeKmT/icFNkUOPzQnG+qeMXURKeVFsuQA8=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
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/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-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
|
golang.org/x/net v0.0.0-20191011234655-491137f69257 h1:ry8e2D+cwaV6hk7lb3aRTjjZo24shrbK0e11QEOkTIg=
|
||||||
|
golang.org/x/net v0.0.0-20191011234655-491137f69257/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-20181128092732-4ed8d59d0b35/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-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-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
|
||||||
|
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/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.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||||
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||||
|
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
|
||||||
|
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
|
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/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=
|
|
@ -16,3 +16,4 @@
|
||||||
|
|
||||||
_models/
|
_models/
|
||||||
|
|
||||||
|
_examples/
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
run:
|
||||||
|
tests: true
|
||||||
|
skip-dirs:
|
||||||
|
- _examples
|
||||||
|
|
||||||
|
output:
|
||||||
|
print-issued-lines: false
|
||||||
|
|
||||||
|
linters:
|
||||||
|
enable-all: true
|
||||||
|
disable:
|
||||||
|
- maligned
|
||||||
|
- megacheck
|
||||||
|
- lll
|
||||||
|
- gocyclo
|
||||||
|
- dupl
|
||||||
|
- gochecknoglobals
|
||||||
|
|
||||||
|
linters-settings:
|
||||||
|
govet:
|
||||||
|
check-shadowing: true
|
||||||
|
gocyclo:
|
||||||
|
min-complexity: 10
|
||||||
|
dupl:
|
||||||
|
threshold: 100
|
||||||
|
goconst:
|
||||||
|
min-len: 8
|
||||||
|
min-occurrences: 3
|
||||||
|
|
||||||
|
issues:
|
||||||
|
max-per-linter: 0
|
||||||
|
max-same: 0
|
||||||
|
exclude-use-default: false
|
||||||
|
exclude:
|
||||||
|
# Captured by errcheck.
|
||||||
|
- '^(G104|G204):'
|
||||||
|
# Very commonly not checked.
|
||||||
|
- 'Error return value of .(.*\.Help|.*\.MarkFlagRequired|(os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*printf?|os\.(Un)?Setenv). is not checked'
|
||||||
|
- 'exported method (.*\.MarshalJSON|.*\.UnmarshalJSON|.*\.EntityURN|.*\.GoString|.*\.Pos) should have comment or be unexported'
|
||||||
|
- 'composite literal uses unkeyed fields'
|
||||||
|
- 'declaration of "err" shadows declaration'
|
||||||
|
- 'should not use dot imports'
|
||||||
|
- 'Potential file inclusion via variable'
|
||||||
|
- 'should have comment or be unexported'
|
||||||
|
- 'comment on exported var .* should be of the form'
|
|
@ -1,5 +1,10 @@
|
||||||
sudo: false
|
sudo: false
|
||||||
language: go
|
language: go
|
||||||
|
script:
|
||||||
|
- go test -v ./...
|
||||||
|
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s v1.15.0
|
||||||
|
- ./bin/golangci-lint run
|
||||||
|
- git clean -fdx .
|
||||||
after_success:
|
after_success:
|
||||||
go get github.com/goreleaser/goreleaser && goreleaser
|
curl -sL https://git.io/goreleaser | bash && goreleaser
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
# Chroma - A general purpose syntax highlighter in pure Go [![](https://godoc.org/github.com/alecthomas/chroma?status.svg)](http://godoc.org/github.com/alecthomas/chroma) [![Build Status](https://travis-ci.org/alecthomas/chroma.png)](https://travis-ci.org/alecthomas/chroma) [![Gitter chat](https://badges.gitter.im/alecthomas.png)](https://gitter.im/alecthomas/Lobby)
|
# Chroma — A general purpose syntax highlighter in pure Go [![Golang Documentation](https://godoc.org/github.com/alecthomas/chroma?status.svg)](https://godoc.org/github.com/alecthomas/chroma) [![Build Status](https://travis-ci.org/alecthomas/chroma.svg)](https://travis-ci.org/alecthomas/chroma) [![Gitter chat](https://badges.gitter.im/alecthomas.svg)](https://gitter.im/alecthomas/Lobby)
|
||||||
|
|
||||||
> **NOTE:** As Chroma has just been released, its API is still in flux. That said, the high-level interface should not change significantly.
|
> **NOTE:** As Chroma has just been released, its API is still in flux. That said, the high-level interface should not change significantly.
|
||||||
|
|
||||||
|
@ -8,57 +8,68 @@ highlighted HTML, ANSI-coloured text, etc.
|
||||||
Chroma is based heavily on [Pygments](http://pygments.org/), and includes
|
Chroma is based heavily on [Pygments](http://pygments.org/), and includes
|
||||||
translators for Pygments lexers and styles.
|
translators for Pygments lexers and styles.
|
||||||
|
|
||||||
|
<a id="markdown-table-of-contents" name="table-of-contents"></a>
|
||||||
## Table of Contents
|
## Table of Contents
|
||||||
|
|
||||||
<!-- MarkdownTOC -->
|
<!-- TOC -->
|
||||||
|
|
||||||
1. [Supported languages](#supported-languages)
|
1. [Table of Contents](#table-of-contents)
|
||||||
1. [Using the library](#using-the-library)
|
2. [Supported languages](#supported-languages)
|
||||||
|
3. [Try it](#try-it)
|
||||||
|
4. [Using the library](#using-the-library)
|
||||||
1. [Quick start](#quick-start)
|
1. [Quick start](#quick-start)
|
||||||
1. [Identifying the language](#identifying-the-language)
|
2. [Identifying the language](#identifying-the-language)
|
||||||
1. [Formatting the output](#formatting-the-output)
|
3. [Formatting the output](#formatting-the-output)
|
||||||
1. [The HTML formatter](#the-html-formatter)
|
4. [The HTML formatter](#the-html-formatter)
|
||||||
1. [More detail](#more-detail)
|
5. [More detail](#more-detail)
|
||||||
1. [Lexers](#lexers)
|
1. [Lexers](#lexers)
|
||||||
1. [Formatters](#formatters)
|
2. [Formatters](#formatters)
|
||||||
1. [Styles](#styles)
|
3. [Styles](#styles)
|
||||||
1. [Command-line interface](#command-line-interface)
|
6. [Command-line interface](#command-line-interface)
|
||||||
1. [What's missing compared to Pygments?](#whats-missing-compared-to-pygments)
|
7. [What's missing compared to Pygments?](#whats-missing-compared-to-pygments)
|
||||||
|
|
||||||
<!-- /MarkdownTOC -->
|
<!-- /TOC -->
|
||||||
|
|
||||||
|
<a id="markdown-supported-languages" name="supported-languages"></a>
|
||||||
## Supported languages
|
## Supported languages
|
||||||
|
|
||||||
Prefix | Language
|
Prefix | Language
|
||||||
:----: | --------
|
:----: | --------
|
||||||
A | ABNF, ActionScript, ActionScript 3, Ada, Angular2, ANTLR, ApacheConf, APL, AppleScript, Awk
|
A | ABAP, ABNF, ActionScript, ActionScript 3, Ada, Angular2, ANTLR, ApacheConf, APL, AppleScript, Arduino, Awk
|
||||||
B | Ballerina, Base Makefile, Bash, Batchfile, BlitzBasic, BNF, Brainfuck
|
B | Ballerina, Base Makefile, Bash, Batchfile, BlitzBasic, BNF, Brainfuck
|
||||||
C | C, C#, C++, Cassandra CQL, CFEngine3, cfstatement/ColdFusion, CMake, COBOL, CSS, Cap'n Proto, Ceylon, ChaiScript, Cheetah, Clojure, CoffeeScript, Common Lisp, Coq, Crystal, Cython
|
C | C, C#, C++, Cap'n Proto, Cassandra CQL, Ceylon, CFEngine3, cfstatement, ChaiScript, Cheetah, Clojure, CMake, COBOL, CoffeeScript, Common Lisp, Coq, Crystal, CSS, Cython
|
||||||
D | Dart, Diff, Django/Jinja, Docker, DTD
|
D | D, Dart, Diff, Django/Jinja, Docker, DTD
|
||||||
E | EBNF, Elixir, Elm, EmacsLisp, Erlang
|
E | EBNF, Elixir, Elm, EmacsLisp, Erlang
|
||||||
F | Factor, Fish, Forth, Fortran, FSharp
|
F | Factor, Fish, Forth, Fortran, FSharp
|
||||||
G | GAS, GDScript, GLSL, Genshi, Genshi HTML, Genshi Text, Gnuplot, Go, Go HTML Template, Go Text Template, Groovy
|
G | GAS, GDScript, Genshi, Genshi HTML, Genshi Text, GLSL, Gnuplot, Go, Go HTML Template, Go Text Template, GraphQL, Groovy
|
||||||
H | Handlebars, Haskell, Haxe, Hexdump, HTML, HTTP, Hy
|
H | Handlebars, Haskell, Haxe, HCL, Hexdump, HTML, HTTP, Hy
|
||||||
I | Idris, INI, Io
|
I | Idris, INI, Io
|
||||||
J | Java, JavaScript, JSON, Jsx, Julia, Jungle
|
J | Java, JavaScript, JSON, Julia, Jungle
|
||||||
K | Kotlin
|
K | Kotlin
|
||||||
L | Lighttpd configuration file, LLVM, Lua
|
L | Lighttpd configuration file, LLVM, Lua
|
||||||
M | Mako, Markdown, Mason, Mathematica, MiniZinc, Modula-2, MonkeyC, MorrowindScript, Myghty, MySQL
|
M | Mako, markdown, Mason, Mathematica, Matlab, MiniZinc, Modula-2, MonkeyC, MorrowindScript, Myghty, MySQL
|
||||||
N | NASM, Newspeak, Nginx configuration file, Nim, Nix
|
N | NASM, Newspeak, Nginx configuration file, Nim, Nix
|
||||||
O | Objective-C, OCaml, Octave, OpenSCAD, Org Mode
|
O | Objective-C, OCaml, Octave, OpenSCAD, Org Mode
|
||||||
P | PacmanConf, Perl, PHP, Pig, PkgConfig, Plaintext, PL/pgSQL, PostgreSQL SQL dialect, PostScript, POVRay, PowerShell, Prolog, Protocol Buffer, Puppet, Python, Python 3
|
P | PacmanConf, Perl, PHP, Pig, PkgConfig, PL/pgSQL, plaintext, PostgreSQL SQL dialect, PostScript, POVRay, PowerShell, Prolog, Protocol Buffer, Puppet, Python, Python 3
|
||||||
Q | QBasic
|
Q | QBasic
|
||||||
R | R, Racket, Ragel, reg, reStructuredText, Rexx, Ruby, Rust
|
R | R, Racket, Ragel, react, reg, reStructuredText, Rexx, Ruby, Rust
|
||||||
S | Sass, Scala, Scheme, Scilab, SCSS, Smalltalk, Smarty, Snobol, Solidity, SPARQL, SQL, SquidConf, Swift, systemd, Systemverilog
|
S | Sass, Scala, Scheme, Scilab, SCSS, Smalltalk, Smarty, Snobol, Solidity, SPARQL, SQL, SquidConf, Swift, SYSTEMD, systemverilog
|
||||||
T | TASM, Tcl, Tcsh, Termcap, Terminfo, Terraform, TeX, Thrift, TOML, TradingView, Transact-SQL, Turtle, Twig, TypeScript, TypoScript, TypoScriptCssData, TypoScriptHtmlData
|
T | TASM, Tcl, Tcsh, Termcap, Terminfo, Terraform, TeX, Thrift, TOML, TradingView, Transact-SQL, Turing, Turtle, Twig, TypeScript, TypoScript, TypoScriptCssData, TypoScriptHtmlData
|
||||||
V | verilog, VHDL, VimL
|
V | VB.net, verilog, VHDL, VimL, vue
|
||||||
W | WDTE
|
W | WDTE
|
||||||
X | XML, Xorg
|
X | XML, Xorg
|
||||||
Y | YAML
|
Y | YAML
|
||||||
|
|
||||||
|
|
||||||
_I will attempt to keep this section up to date, but an authoritative list can be
|
_I will attempt to keep this section up to date, but an authoritative list can be
|
||||||
displayed with `chroma --list`._
|
displayed with `chroma --list`._
|
||||||
|
|
||||||
|
<a id="markdown-try-it" name="try-it"></a>
|
||||||
|
## Try it
|
||||||
|
|
||||||
|
Try out various languages and styles on the [Chroma Playground](https://swapoff.org/chroma/playground/).
|
||||||
|
|
||||||
|
<a id="markdown-using-the-library" name="using-the-library"></a>
|
||||||
## Using the library
|
## Using the library
|
||||||
|
|
||||||
Chroma, like Pygments, has the concepts of
|
Chroma, like Pygments, has the concepts of
|
||||||
|
@ -79,6 +90,7 @@ In all cases, if a lexer, formatter or style can not be determined, `nil` will
|
||||||
be returned. In this situation you may want to default to the `Fallback`
|
be returned. In this situation you may want to default to the `Fallback`
|
||||||
value in each respective package, which provides sane defaults.
|
value in each respective package, which provides sane defaults.
|
||||||
|
|
||||||
|
<a id="markdown-quick-start" name="quick-start"></a>
|
||||||
### Quick start
|
### Quick start
|
||||||
|
|
||||||
A convenience function exists that can be used to simply format some source
|
A convenience function exists that can be used to simply format some source
|
||||||
|
@ -88,6 +100,7 @@ text, without any effort:
|
||||||
err := quick.Highlight(os.Stdout, someSourceCode, "go", "html", "monokai")
|
err := quick.Highlight(os.Stdout, someSourceCode, "go", "html", "monokai")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
<a id="markdown-identifying-the-language" name="identifying-the-language"></a>
|
||||||
### Identifying the language
|
### Identifying the language
|
||||||
|
|
||||||
To highlight code, you'll first have to identify what language the code is
|
To highlight code, you'll first have to identify what language the code is
|
||||||
|
@ -127,6 +140,7 @@ token types into a single token:
|
||||||
lexer = chroma.Coalesce(lexer)
|
lexer = chroma.Coalesce(lexer)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
<a id="markdown-formatting-the-output" name="formatting-the-output"></a>
|
||||||
### Formatting the output
|
### Formatting the output
|
||||||
|
|
||||||
Once a language is identified you will need to pick a formatter and a style (theme).
|
Once a language is identified you will need to pick a formatter and a style (theme).
|
||||||
|
@ -155,6 +169,7 @@ And finally, format the tokens from the iterator:
|
||||||
err := formatter.Format(w, style, iterator)
|
err := formatter.Format(w, style, iterator)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
<a id="markdown-the-html-formatter" name="the-html-formatter"></a>
|
||||||
### The HTML formatter
|
### The HTML formatter
|
||||||
|
|
||||||
By default the `html` registered formatter generates standalone HTML with
|
By default the `html` registered formatter generates standalone HTML with
|
||||||
|
@ -178,8 +193,10 @@ formatter := html.New(html.WithClasses())
|
||||||
err := formatter.WriteCSS(w, style)
|
err := formatter.WriteCSS(w, style)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
<a id="markdown-more-detail" name="more-detail"></a>
|
||||||
## More detail
|
## More detail
|
||||||
|
|
||||||
|
<a id="markdown-lexers" name="lexers"></a>
|
||||||
### Lexers
|
### Lexers
|
||||||
|
|
||||||
See the [Pygments documentation](http://pygments.org/docs/lexerdevelopment/)
|
See the [Pygments documentation](http://pygments.org/docs/lexerdevelopment/)
|
||||||
|
@ -190,7 +207,7 @@ In many cases lexers can be automatically converted directly from Pygments by
|
||||||
using the included Python 3 script `pygments2chroma.py`. I use something like
|
using the included Python 3 script `pygments2chroma.py`. I use something like
|
||||||
the following:
|
the following:
|
||||||
|
|
||||||
```
|
```sh
|
||||||
python3 ~/Projects/chroma/_tools/pygments2chroma.py \
|
python3 ~/Projects/chroma/_tools/pygments2chroma.py \
|
||||||
pygments.lexers.jvm.KotlinLexer \
|
pygments.lexers.jvm.KotlinLexer \
|
||||||
> ~/Projects/chroma/lexers/kotlin.go \
|
> ~/Projects/chroma/lexers/kotlin.go \
|
||||||
|
@ -200,6 +217,7 @@ python3 ~/Projects/chroma/_tools/pygments2chroma.py \
|
||||||
See notes in [pygments-lexers.go](https://github.com/alecthomas/chroma/blob/master/pygments-lexers.txt)
|
See notes in [pygments-lexers.go](https://github.com/alecthomas/chroma/blob/master/pygments-lexers.txt)
|
||||||
for a list of lexers, and notes on some of the issues importing them.
|
for a list of lexers, and notes on some of the issues importing them.
|
||||||
|
|
||||||
|
<a id="markdown-formatters" name="formatters"></a>
|
||||||
### Formatters
|
### Formatters
|
||||||
|
|
||||||
Chroma supports HTML output, as well as terminal output in 8 colour, 256 colour, and true-colour.
|
Chroma supports HTML output, as well as terminal output in 8 colour, 256 colour, and true-colour.
|
||||||
|
@ -207,22 +225,35 @@ Chroma supports HTML output, as well as terminal output in 8 colour, 256 colour,
|
||||||
A `noop` formatter is included that outputs the token text only, and a `tokens`
|
A `noop` formatter is included that outputs the token text only, and a `tokens`
|
||||||
formatter outputs raw tokens. The latter is useful for debugging lexers.
|
formatter outputs raw tokens. The latter is useful for debugging lexers.
|
||||||
|
|
||||||
|
<a id="markdown-styles" name="styles"></a>
|
||||||
### Styles
|
### Styles
|
||||||
|
|
||||||
Chroma styles use the [same syntax](http://pygments.org/docs/styles/) as Pygments.
|
Chroma styles use the [same syntax](http://pygments.org/docs/styles/) as Pygments.
|
||||||
|
|
||||||
All Pygments styles have been converted to Chroma using the `_tools/style.py` script.
|
All Pygments styles have been converted to Chroma using the `_tools/style.py` script.
|
||||||
|
|
||||||
|
When you work with one of [Chroma's styles](https://github.com/alecthomas/chroma/tree/master/styles), know that the `chroma.Background` token type provides the default style for tokens. It does so by defining a foreground color and background color.
|
||||||
|
|
||||||
|
For example, this gives each token name not defined in the style a default color of `#f8f8f8` and uses `#000000` for the highlighted code block's background:
|
||||||
|
|
||||||
|
~~~go
|
||||||
|
chroma.Background: "#f8f8f2 bg:#000000",
|
||||||
|
~~~
|
||||||
|
|
||||||
|
Also, token types in a style file are hierarchical. For instance, when `CommentSpecial` is not defined, Chroma uses the token style from `Comment`. So when several comment tokens use the same color, you'll only need to define `Comment` and override the one that has a different color.
|
||||||
|
|
||||||
For a quick overview of the available styles and how they look, check out the [Chroma Style Gallery](https://xyproto.github.io/splash/docs/).
|
For a quick overview of the available styles and how they look, check out the [Chroma Style Gallery](https://xyproto.github.io/splash/docs/).
|
||||||
|
|
||||||
|
<a id="markdown-command-line-interface" name="command-line-interface"></a>
|
||||||
## Command-line interface
|
## Command-line interface
|
||||||
|
|
||||||
A command-line interface to Chroma is included. It can be installed with:
|
A command-line interface to Chroma is included. It can be installed with:
|
||||||
|
|
||||||
```
|
```sh
|
||||||
go get -u github.com/alecthomas/chroma/cmd/chroma
|
go get -u github.com/alecthomas/chroma/cmd/chroma
|
||||||
```
|
```
|
||||||
|
|
||||||
|
<a id="markdown-whats-missing-compared-to-pygments" name="whats-missing-compared-to-pygments"></a>
|
||||||
## What's missing compared to Pygments?
|
## What's missing compared to Pygments?
|
||||||
|
|
||||||
- Quite a few lexers, for various reasons (pull-requests welcome):
|
- Quite a few lexers, for various reasons (pull-requests welcome):
|
||||||
|
|
|
@ -92,6 +92,14 @@ func (c Colour) Brighten(factor float64) Colour {
|
||||||
return NewColour(uint8(r), uint8(g), uint8(b))
|
return NewColour(uint8(r), uint8(g), uint8(b))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BrightenOrDarken brightens a colour if it is < 0.5 brighteness or darkens if > 0.5 brightness.
|
||||||
|
func (c Colour) BrightenOrDarken(factor float64) Colour {
|
||||||
|
if c.Brightness() < 0.5 {
|
||||||
|
return c.Brighten(factor)
|
||||||
|
}
|
||||||
|
return c.Brighten(-factor)
|
||||||
|
}
|
||||||
|
|
||||||
// Brightness of the colour (roughly) in the range 0.0 to 1.0
|
// Brightness of the colour (roughly) in the range 0.0 to 1.0
|
||||||
func (c Colour) Brightness() float64 {
|
func (c Colour) Brightness() float64 {
|
||||||
return (float64(c.Red()) + float64(c.Green()) + float64(c.Blue())) / 255.0 / 3.0
|
return (float64(c.Red()) + float64(c.Green()) + float64(c.Blue())) / 255.0 / 3.0
|
||||||
|
@ -119,6 +127,7 @@ func MustParseColour(colour string) Colour {
|
||||||
return parsed
|
return parsed
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsSet returns true if the colour is set.
|
||||||
func (c Colour) IsSet() bool { return c != 0 }
|
func (c Colour) IsSet() bool { return c != 0 }
|
||||||
|
|
||||||
func (c Colour) String() string { return fmt.Sprintf("#%06x", int(c-1)) }
|
func (c Colour) String() string { return fmt.Sprintf("#%06x", int(c-1)) }
|
||||||
|
|
|
@ -17,7 +17,7 @@ type Formatter interface {
|
||||||
// Guards against iterator panics.
|
// Guards against iterator panics.
|
||||||
type FormatterFunc func(w io.Writer, style *Style, iterator Iterator) error
|
type FormatterFunc func(w io.Writer, style *Style, iterator Iterator) error
|
||||||
|
|
||||||
func (f FormatterFunc) Format(w io.Writer, s *Style, it Iterator) (err error) {
|
func (f FormatterFunc) Format(w io.Writer, s *Style, it Iterator) (err error) { // nolint
|
||||||
defer func() {
|
defer func() {
|
||||||
if perr := recover(); perr != nil {
|
if perr := recover(); perr != nil {
|
||||||
err = perr.(error)
|
err = perr.(error)
|
||||||
|
|
|
@ -19,7 +19,7 @@ var (
|
||||||
return nil
|
return nil
|
||||||
}))
|
}))
|
||||||
// Default HTML formatter outputs self-contained HTML.
|
// Default HTML formatter outputs self-contained HTML.
|
||||||
htmlFull = Register("html", html.New(html.Standalone(), html.WithClasses()))
|
htmlFull = Register("html", html.New(html.Standalone(), html.WithClasses())) // nolint
|
||||||
)
|
)
|
||||||
|
|
||||||
// Fallback formatter.
|
// Fallback formatter.
|
||||||
|
|
|
@ -99,41 +99,10 @@ func (f *Formatter) Format(w io.Writer, style *chroma.Style, iterator chroma.Ite
|
||||||
return f.writeHTML(w, style, iterator.Tokens())
|
return f.writeHTML(w, style, iterator.Tokens())
|
||||||
}
|
}
|
||||||
|
|
||||||
func brightenOrDarken(colour chroma.Colour, factor float64) chroma.Colour {
|
|
||||||
if colour.Brightness() < 0.5 {
|
|
||||||
return colour.Brighten(factor)
|
|
||||||
}
|
|
||||||
return colour.Brighten(-factor)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure that style entries exist for highlighting, etc.
|
|
||||||
func (f *Formatter) restyle(style *chroma.Style) (*chroma.Style, error) {
|
|
||||||
builder := style.Builder()
|
|
||||||
bg := builder.Get(chroma.Background)
|
|
||||||
// If we don't have a line highlight colour, make one that is 10% brighter/darker than the background.
|
|
||||||
if !style.Has(chroma.LineHighlight) {
|
|
||||||
highlight := chroma.StyleEntry{Background: bg.Background}
|
|
||||||
highlight.Background = brightenOrDarken(highlight.Background, 0.1)
|
|
||||||
builder.AddEntry(chroma.LineHighlight, highlight)
|
|
||||||
}
|
|
||||||
// If we don't have line numbers, use the text colour but 20% brighter/darker
|
|
||||||
if !style.Has(chroma.LineNumbers) {
|
|
||||||
text := chroma.StyleEntry{Colour: bg.Colour}
|
|
||||||
text.Colour = brightenOrDarken(text.Colour, 0.5)
|
|
||||||
builder.AddEntry(chroma.LineNumbers, text)
|
|
||||||
builder.AddEntry(chroma.LineNumbersTable, text)
|
|
||||||
}
|
|
||||||
return builder.Build()
|
|
||||||
}
|
|
||||||
|
|
||||||
// We deliberately don't use html/template here because it is two orders of magnitude slower (benchmarked).
|
// We deliberately don't use html/template here because it is two orders of magnitude slower (benchmarked).
|
||||||
//
|
//
|
||||||
// OTOH we need to be super careful about correct escaping...
|
// OTOH we need to be super careful about correct escaping...
|
||||||
func (f *Formatter) writeHTML(w io.Writer, style *chroma.Style, tokens []chroma.Token) (err error) { // nolint: gocyclo
|
func (f *Formatter) writeHTML(w io.Writer, style *chroma.Style, tokens []chroma.Token) (err error) { // nolint: gocyclo
|
||||||
style, err = f.restyle(style)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
css := f.styleToCSS(style)
|
css := f.styleToCSS(style)
|
||||||
if !f.Classes {
|
if !f.Classes {
|
||||||
for t, style := range css {
|
for t, style := range css {
|
||||||
|
@ -144,7 +113,10 @@ func (f *Formatter) writeHTML(w io.Writer, style *chroma.Style, tokens []chroma.
|
||||||
fmt.Fprint(w, "<html>\n")
|
fmt.Fprint(w, "<html>\n")
|
||||||
if f.Classes {
|
if f.Classes {
|
||||||
fmt.Fprint(w, "<style type=\"text/css\">\n")
|
fmt.Fprint(w, "<style type=\"text/css\">\n")
|
||||||
f.WriteCSS(w, style)
|
err = f.WriteCSS(w, style)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
fmt.Fprintf(w, "body { %s; }\n", css[chroma.Background])
|
fmt.Fprintf(w, "body { %s; }\n", css[chroma.Background])
|
||||||
fmt.Fprint(w, "</style>")
|
fmt.Fprint(w, "</style>")
|
||||||
}
|
}
|
||||||
|
@ -185,7 +157,7 @@ func (f *Formatter) writeHTML(w io.Writer, style *chroma.Style, tokens []chroma.
|
||||||
fmt.Fprint(w, "</pre>")
|
fmt.Fprint(w, "</pre>")
|
||||||
}
|
}
|
||||||
fmt.Fprint(w, "</td>\n")
|
fmt.Fprint(w, "</td>\n")
|
||||||
fmt.Fprintf(w, "<td%s>\n", f.styleAttr(css, chroma.LineTableTD))
|
fmt.Fprintf(w, "<td%s>\n", f.styleAttr(css, chroma.LineTableTD, "width:100%"))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !f.preventSurroundingPre {
|
if !f.preventSurroundingPre {
|
||||||
|
@ -268,13 +240,13 @@ func (f *Formatter) class(t chroma.TokenType) string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Formatter) styleAttr(styles map[chroma.TokenType]string, tt chroma.TokenType) string {
|
func (f *Formatter) styleAttr(styles map[chroma.TokenType]string, tt chroma.TokenType, extraCSS ...string) string {
|
||||||
if f.Classes {
|
if f.Classes {
|
||||||
cls := f.class(tt)
|
cls := f.class(tt)
|
||||||
if cls == "" {
|
if cls == "" {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
return string(fmt.Sprintf(` class="%s"`, cls))
|
return fmt.Sprintf(` class="%s"`, cls)
|
||||||
}
|
}
|
||||||
if _, ok := styles[tt]; !ok {
|
if _, ok := styles[tt]; !ok {
|
||||||
tt = tt.SubCategory()
|
tt = tt.SubCategory()
|
||||||
|
@ -285,7 +257,9 @@ func (f *Formatter) styleAttr(styles map[chroma.TokenType]string, tt chroma.Toke
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return fmt.Sprintf(` style="%s"`, styles[tt])
|
css := []string{styles[tt]}
|
||||||
|
css = append(css, extraCSS...)
|
||||||
|
return fmt.Sprintf(` style="%s"`, strings.Join(css, ";"))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Formatter) tabWidthStyle() string {
|
func (f *Formatter) tabWidthStyle() string {
|
||||||
|
|
|
@ -174,6 +174,9 @@ func entryToEscapeSequence(table *ttyTable, entry chroma.StyleEntry) string {
|
||||||
if entry.Underline == chroma.Yes {
|
if entry.Underline == chroma.Yes {
|
||||||
out += "\033[4m"
|
out += "\033[4m"
|
||||||
}
|
}
|
||||||
|
if entry.Italic == chroma.Yes {
|
||||||
|
out += "\033[3m"
|
||||||
|
}
|
||||||
if entry.Colour.IsSet() {
|
if entry.Colour.IsSet() {
|
||||||
out += table.foreground[findClosest(table, entry.Colour)]
|
out += table.foreground[findClosest(table, entry.Colour)]
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,9 @@ func trueColourFormatter(w io.Writer, style *chroma.Style, it chroma.Iterator) e
|
||||||
if entry.Underline == chroma.Yes {
|
if entry.Underline == chroma.Yes {
|
||||||
out += "\033[4m"
|
out += "\033[4m"
|
||||||
}
|
}
|
||||||
|
if entry.Italic == chroma.Yes {
|
||||||
|
out += "\033[3m"
|
||||||
|
}
|
||||||
if entry.Colour.IsSet() {
|
if entry.Colour.IsSet() {
|
||||||
out += fmt.Sprintf("\033[38;2;%d;%d;%dm", entry.Colour.Red(), entry.Colour.Green(), entry.Colour.Blue())
|
out += fmt.Sprintf("\033[38;2;%d;%d;%dm", entry.Colour.Red(), entry.Colour.Green(), entry.Colour.Blue())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,22 @@
|
||||||
module github.com/alecthomas/chroma
|
module github.com/alecthomas/chroma
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/GeertJohan/go.rice v1.0.0
|
||||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38
|
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38
|
||||||
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 // indirect
|
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 // indirect
|
||||||
github.com/alecthomas/kong v0.1.15
|
github.com/alecthomas/kong v0.2.1-0.20190708041108-0548c6b1afae
|
||||||
|
github.com/alecthomas/kong-hcl v0.1.8-0.20190615233001-b21fea9723c8
|
||||||
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 // indirect
|
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 // indirect
|
||||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964
|
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964
|
||||||
github.com/dlclark/regexp2 v1.1.6
|
github.com/dlclark/regexp2 v1.1.6
|
||||||
|
github.com/gorilla/csrf v1.6.0
|
||||||
|
github.com/gorilla/handlers v1.4.1
|
||||||
|
github.com/gorilla/mux v1.7.3
|
||||||
github.com/mattn/go-colorable v0.0.9
|
github.com/mattn/go-colorable v0.0.9
|
||||||
github.com/mattn/go-isatty v0.0.4
|
github.com/mattn/go-isatty v0.0.4
|
||||||
github.com/sergi/go-diff v1.0.0 // indirect
|
github.com/sergi/go-diff v1.0.0 // indirect
|
||||||
|
github.com/stretchr/testify v1.3.0
|
||||||
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35 // indirect
|
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
replace github.com/GeertJohan/go.rice => github.com/alecthomas/go.rice v1.0.1-0.20190719113735-961b99d742e7
|
||||||
|
|
|
@ -1,26 +1,58 @@
|
||||||
|
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
|
||||||
|
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
|
||||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U=
|
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38 h1:smF2tmSOzy2Mm+0dGI2AIUHY+w0BUc+4tn40djz7+6U=
|
||||||
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
|
github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI=
|
||||||
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo=
|
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721 h1:JHZL0hZKJ1VENNfmXvHbgYlbUOvpzYzvy2aZU5gXVeo=
|
||||||
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
|
github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0=
|
||||||
github.com/alecthomas/kong v0.1.15 h1:IWBg+KrLvoHBicD50OzMI8fKjrtAa1okMR9g38HVM/s=
|
github.com/alecthomas/go.rice v1.0.1-0.20190719113735-961b99d742e7 h1:0cMlP9evwgTzs5JUe2C/3rLttYjmRm0kbz9fdGBIV1E=
|
||||||
github.com/alecthomas/kong v0.1.15/go.mod h1:0m2VYms8rH0qbCqVB2gvGHk74bqLIq0HXjCs5bNbNQU=
|
github.com/alecthomas/go.rice v1.0.1-0.20190719113735-961b99d742e7/go.mod h1:af5vUNlDNkCjOZeSGFgIJxDje9qdjsO6hshx0gTmZt4=
|
||||||
|
github.com/alecthomas/kong v0.1.17-0.20190424132513-439c674f7ae0/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI=
|
||||||
|
github.com/alecthomas/kong v0.2.1-0.20190708041108-0548c6b1afae h1:C4Q9m+oXOxcSWwYk9XzzafY2xAVAaeubZbUHJkw3PlY=
|
||||||
|
github.com/alecthomas/kong v0.2.1-0.20190708041108-0548c6b1afae/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI=
|
||||||
|
github.com/alecthomas/kong-hcl v0.1.8-0.20190615233001-b21fea9723c8 h1:atLL+K8Hg0e8863K2X+k7qu+xz3M2a/mWFIACAPf55M=
|
||||||
|
github.com/alecthomas/kong-hcl v0.1.8-0.20190615233001-b21fea9723c8/go.mod h1:MRgZdU3vrFd05IQ89AxUZ0aYdF39BYoNFa324SodPCA=
|
||||||
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY=
|
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897 h1:p9Sln00KOTlrYkxI1zYWl1QLnEqAqEARBEYa8FQnQcY=
|
||||||
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
|
github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ=
|
||||||
|
github.com/daaku/go.zipexe v1.0.0 h1:VSOgZtH418pH9L16hC/JrgSNJbbAL26pj7lmD1+CGdY=
|
||||||
|
github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E=
|
||||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ=
|
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ=
|
||||||
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk=
|
github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dlclark/regexp2 v1.1.6 h1:CqB4MjHw0MFCDj+PHHjiESmHX+N7t0tJzKvC6M97BRg=
|
github.com/dlclark/regexp2 v1.1.6 h1:CqB4MjHw0MFCDj+PHHjiESmHX+N7t0tJzKvC6M97BRg=
|
||||||
github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
|
||||||
|
github.com/gorilla/csrf v1.6.0 h1:60oN1cFdncCE8tjwQ3QEkFND5k37lQPcRjnlvm7CIJ0=
|
||||||
|
github.com/gorilla/csrf v1.6.0/go.mod h1:7tSf8kmjNYr7IWDCYhd3U8Ck34iQ/Yw5CJu7bAkHEGI=
|
||||||
|
github.com/gorilla/handlers v1.4.1 h1:BHvcRGJe/TrL+OqFxoKQGddTgeibiOjaBssV5a/N9sw=
|
||||||
|
github.com/gorilla/handlers v1.4.1/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ=
|
||||||
|
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
|
||||||
|
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
|
||||||
|
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
||||||
|
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||||
|
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/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
|
||||||
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
|
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
|
||||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||||
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
|
github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs=
|
||||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
|
github.com/nkovacs/streamquote v1.0.0/go.mod h1:BN+NaZ2CmdKqUuTUXUEm9j95B2TRbpOWpxbJYzzgUsc=
|
||||||
|
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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
|
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.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
|
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||||
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
|
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
|
||||||
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35 h1:YAFjXN64LMvktoUZH9zgY4lGc/msGN7HQfoSuKCgaDU=
|
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35 h1:YAFjXN64LMvktoUZH9zgY4lGc/msGN7HQfoSuKCgaDU=
|
||||||
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
|
|
@ -44,8 +44,9 @@ func Literator(tokens ...Token) Iterator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SplitTokensIntoLines splits tokens containing newlines in two.
|
||||||
func SplitTokensIntoLines(tokens []Token) (out [][]Token) {
|
func SplitTokensIntoLines(tokens []Token) (out [][]Token) {
|
||||||
var line []Token
|
var line []Token // nolint: prealloc
|
||||||
for _, token := range tokens {
|
for _, token := range tokens {
|
||||||
for strings.Contains(token.Value, "\n") {
|
for strings.Contains(token.Value, "\n") {
|
||||||
parts := strings.SplitAfterN(token.Value, "\n", 2)
|
parts := strings.SplitAfterN(token.Value, "\n", 2)
|
||||||
|
|
|
@ -66,12 +66,15 @@ type Token struct {
|
||||||
func (t *Token) String() string { return t.Value }
|
func (t *Token) String() string { return t.Value }
|
||||||
func (t *Token) GoString() string { return fmt.Sprintf("&Token{%s, %q}", t.Type, t.Value) }
|
func (t *Token) GoString() string { return fmt.Sprintf("&Token{%s, %q}", t.Type, t.Value) }
|
||||||
|
|
||||||
|
// Clone returns a clone of the Token.
|
||||||
func (t *Token) Clone() Token {
|
func (t *Token) Clone() Token {
|
||||||
return *t
|
return *t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EOF is returned by lexers at the end of input.
|
||||||
var EOF Token
|
var EOF Token
|
||||||
|
|
||||||
|
// TokeniseOptions contains options for tokenisers.
|
||||||
type TokeniseOptions struct {
|
type TokeniseOptions struct {
|
||||||
// State to start tokenisation in. Defaults to "root".
|
// State to start tokenisation in. Defaults to "root".
|
||||||
State string
|
State string
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
# Lexer tests
|
||||||
|
|
||||||
|
The tests in this directory feed a known input `testdata/<name>.actual` into the parser for `<name>` and check
|
||||||
|
that its output matches `<name>.exported`.
|
||||||
|
|
||||||
|
## Running the tests
|
||||||
|
|
||||||
|
Run the tests as normal:
|
||||||
|
```go
|
||||||
|
go test ./lexers
|
||||||
|
```
|
||||||
|
|
||||||
|
## Updating the existing tests
|
||||||
|
|
||||||
|
You can regenerate all the test outputs
|
||||||
|
|
||||||
|
```go
|
||||||
|
RECORD=true go test ./lexers
|
||||||
|
```
|
|
@ -0,0 +1,56 @@
|
||||||
|
package a
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/alecthomas/chroma" // nolint
|
||||||
|
"github.com/alecthomas/chroma/lexers/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ABAP lexer.
|
||||||
|
var Abap = internal.Register(MustNewLexer(
|
||||||
|
&Config{
|
||||||
|
Name: "ABAP",
|
||||||
|
Aliases: []string{"abap"},
|
||||||
|
Filenames: []string{"*.abap", "*.ABAP"},
|
||||||
|
MimeTypes: []string{"text/x-abap"},
|
||||||
|
CaseInsensitive: true,
|
||||||
|
},
|
||||||
|
Rules{
|
||||||
|
"common": {
|
||||||
|
{`\s+`, Text, nil},
|
||||||
|
{`^\*.*$`, CommentSingle, nil},
|
||||||
|
{`\".*?\n`, CommentSingle, nil},
|
||||||
|
{`##\w+`, CommentSpecial, nil},
|
||||||
|
},
|
||||||
|
"variable-names": {
|
||||||
|
{`<\S+>`, NameVariable, nil},
|
||||||
|
{`\w[\w~]*(?:(\[\])|->\*)?`, NameVariable, nil},
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
Include("common"),
|
||||||
|
{`CALL\s+(?:BADI|CUSTOMER-FUNCTION|FUNCTION)`, Keyword, nil},
|
||||||
|
{`(CALL\s+(?:DIALOG|SCREEN|SUBSCREEN|SELECTION-SCREEN|TRANSACTION|TRANSFORMATION))\b`, Keyword, nil},
|
||||||
|
{`(FORM|PERFORM)(\s+)(\w+)`, ByGroups(Keyword, Text, NameFunction), nil},
|
||||||
|
{`(PERFORM)(\s+)(\()(\w+)(\))`, ByGroups(Keyword, Text, Punctuation, NameVariable, Punctuation), nil},
|
||||||
|
{`(MODULE)(\s+)(\S+)(\s+)(INPUT|OUTPUT)`, ByGroups(Keyword, Text, NameFunction, Text, Keyword), nil},
|
||||||
|
{`(METHOD)(\s+)([\w~]+)`, ByGroups(Keyword, Text, NameFunction), nil},
|
||||||
|
{`(\s+)([\w\-]+)([=\-]>)([\w\-~]+)`, ByGroups(Text, NameVariable, Operator, NameFunction), nil},
|
||||||
|
{`(?<=(=|-)>)([\w\-~]+)(?=\()`, NameFunction, nil},
|
||||||
|
{`(TEXT)(-)(\d{3})`, ByGroups(Keyword, Punctuation, LiteralNumberInteger), nil},
|
||||||
|
{`(TEXT)(-)(\w{3})`, ByGroups(Keyword, Punctuation, NameVariable), nil},
|
||||||
|
{`(ADD-CORRESPONDING|AUTHORITY-CHECK|CLASS-DATA|CLASS-EVENTS|CLASS-METHODS|CLASS-POOL|DELETE-ADJACENT|DIVIDE-CORRESPONDING|EDITOR-CALL|ENHANCEMENT-POINT|ENHANCEMENT-SECTION|EXIT-COMMAND|FIELD-GROUPS|FIELD-SYMBOLS|FUNCTION-POOL|INTERFACE-POOL|INVERTED-DATE|LOAD-OF-PROGRAM|LOG-POINT|MESSAGE-ID|MOVE-CORRESPONDING|MULTIPLY-CORRESPONDING|NEW-LINE|NEW-PAGE|NEW-SECTION|NO-EXTENSION|OUTPUT-LENGTH|PRINT-CONTROL|SELECT-OPTIONS|START-OF-SELECTION|SUBTRACT-CORRESPONDING|SYNTAX-CHECK|SYSTEM-EXCEPTIONS|TYPE-POOL|TYPE-POOLS|NO-DISPLAY)\b`, Keyword, nil},
|
||||||
|
{`(?<![-\>])(CREATE\s+(PUBLIC|PRIVATE|DATA|OBJECT)|(PUBLIC|PRIVATE|PROTECTED)\s+SECTION|(TYPE|LIKE)\s+((LINE\s+OF|REF\s+TO|(SORTED|STANDARD|HASHED)\s+TABLE\s+OF))?|FROM\s+(DATABASE|MEMORY)|CALL\s+METHOD|(GROUP|ORDER) BY|HAVING|SEPARATED BY|GET\s+(BADI|BIT|CURSOR|DATASET|LOCALE|PARAMETER|PF-STATUS|(PROPERTY|REFERENCE)\s+OF|RUN\s+TIME|TIME\s+(STAMP)?)?|SET\s+(BIT|BLANK\s+LINES|COUNTRY|CURSOR|DATASET|EXTENDED\s+CHECK|HANDLER|HOLD\s+DATA|LANGUAGE|LEFT\s+SCROLL-BOUNDARY|LOCALE|MARGIN|PARAMETER|PF-STATUS|PROPERTY\s+OF|RUN\s+TIME\s+(ANALYZER|CLOCK\s+RESOLUTION)|SCREEN|TITLEBAR|UPADTE\s+TASK\s+LOCAL|USER-COMMAND)|CONVERT\s+((INVERTED-)?DATE|TIME|TIME\s+STAMP|TEXT)|(CLOSE|OPEN)\s+(DATASET|CURSOR)|(TO|FROM)\s+(DATA BUFFER|INTERNAL TABLE|MEMORY ID|DATABASE|SHARED\s+(MEMORY|BUFFER))|DESCRIBE\s+(DISTANCE\s+BETWEEN|FIELD|LIST|TABLE)|FREE\s(MEMORY|OBJECT)?|PROCESS\s+(BEFORE\s+OUTPUT|AFTER\s+INPUT|ON\s+(VALUE-REQUEST|HELP-REQUEST))|AT\s+(LINE-SELECTION|USER-COMMAND|END\s+OF|NEW)|AT\s+SELECTION-SCREEN(\s+(ON(\s+(BLOCK|(HELP|VALUE)-REQUEST\s+FOR|END\s+OF|RADIOBUTTON\s+GROUP))?|OUTPUT))?|SELECTION-SCREEN:?\s+((BEGIN|END)\s+OF\s+((TABBED\s+)?BLOCK|LINE|SCREEN)|COMMENT|FUNCTION\s+KEY|INCLUDE\s+BLOCKS|POSITION|PUSHBUTTON|SKIP|ULINE)|LEAVE\s+(LIST-PROCESSING|PROGRAM|SCREEN|TO LIST-PROCESSING|TO TRANSACTION)(ENDING|STARTING)\s+AT|FORMAT\s+(COLOR|INTENSIFIED|INVERSE|HOTSPOT|INPUT|FRAMES|RESET)|AS\s+(CHECKBOX|SUBSCREEN|WINDOW)|WITH\s+(((NON-)?UNIQUE)?\s+KEY|FRAME)|(BEGIN|END)\s+OF|DELETE(\s+ADJACENT\s+DUPLICATES\sFROM)?|COMPARING(\s+ALL\s+FIELDS)?|(INSERT|APPEND)(\s+INITIAL\s+LINE\s+(IN)?TO|\s+LINES\s+OF)?|IN\s+((BYTE|CHARACTER)\s+MODE|PROGRAM)|END-OF-(DEFINITION|PAGE|SELECTION)|WITH\s+FRAME(\s+TITLE)|(REPLACE|FIND)\s+((FIRST|ALL)\s+OCCURRENCES?\s+OF\s+)?(SUBSTRING|REGEX)?|MATCH\s+(LENGTH|COUNT|LINE|OFFSET)|(RESPECTING|IGNORING)\s+CASE|IN\s+UPDATE\s+TASK|(SOURCE|RESULT)\s+(XML)?|REFERENCE\s+INTO|AND\s+(MARK|RETURN)|CLIENT\s+SPECIFIED|CORRESPONDING\s+FIELDS\s+OF|IF\s+FOUND|FOR\s+EVENT|INHERITING\s+FROM|LEAVE\s+TO\s+SCREEN|LOOP\s+AT\s+(SCREEN)?|LOWER\s+CASE|MATCHCODE\s+OBJECT|MODIF\s+ID|MODIFY\s+SCREEN|NESTING\s+LEVEL|NO\s+INTERVALS|OF\s+STRUCTURE|RADIOBUTTON\s+GROUP|RANGE\s+OF|REF\s+TO|SUPPRESS DIALOG|TABLE\s+OF|UPPER\s+CASE|TRANSPORTING\s+NO\s+FIELDS|VALUE\s+CHECK|VISIBLE\s+LENGTH|HEADER\s+LINE|COMMON\s+PART)\b`, Keyword, nil},
|
||||||
|
{`(^|(?<=(\s|\.)))(ABBREVIATED|ABSTRACT|ADD|ALIASES|ALIGN|ALPHA|ASSERT|AS|ASSIGN(ING)?|AT(\s+FIRST)?|BACK|BLOCK|BREAK-POINT|CASE|CATCH|CHANGING|CHECK|CLASS|CLEAR|COLLECT|COLOR|COMMIT|CREATE|COMMUNICATION|COMPONENTS?|COMPUTE|CONCATENATE|CONDENSE|CONSTANTS|CONTEXTS|CONTINUE|CONTROLS|COUNTRY|CURRENCY|DATA|DATE|DECIMALS|DEFAULT|DEFINE|DEFINITION|DEFERRED|DEMAND|DETAIL|DIRECTORY|DIVIDE|DO|DUMMY|ELSE(IF)?|ENDAT|ENDCASE|ENDCATCH|ENDCLASS|ENDDO|ENDFORM|ENDFUNCTION|ENDIF|ENDINTERFACE|ENDLOOP|ENDMETHOD|ENDMODULE|ENDSELECT|ENDTRY|ENDWHILE|ENHANCEMENT|EVENTS|EXACT|EXCEPTIONS?|EXIT|EXPONENT|EXPORT|EXPORTING|EXTRACT|FETCH|FIELDS?|FOR|FORM|FORMAT|FREE|FROM|FUNCTION|HIDE|ID|IF|IMPORT|IMPLEMENTATION|IMPORTING|IN|INCLUDE|INCLUDING|INDEX|INFOTYPES|INITIALIZATION|INTERFACE|INTERFACES|INTO|LANGUAGE|LEAVE|LENGTH|LINES|LOAD|LOCAL|JOIN|KEY|NEXT|MAXIMUM|MESSAGE|METHOD[S]?|MINIMUM|MODULE|MODIFIER|MODIFY|MOVE|MULTIPLY|NODES|NUMBER|OBLIGATORY|OBJECT|OF|OFF|ON|OTHERS|OVERLAY|PACK|PAD|PARAMETERS|PERCENTAGE|POSITION|PROGRAM|PROVIDE|PUBLIC|PUT|PF\d\d|RAISE|RAISING|RANGES?|READ|RECEIVE|REDEFINITION|REFRESH|REJECT|REPORT|RESERVE|RESUME|RETRY|RETURN|RETURNING|RIGHT|ROLLBACK|REPLACE|SCROLL|SEARCH|SELECT|SHIFT|SIGN|SINGLE|SIZE|SKIP|SORT|SPLIT|STATICS|STOP|STYLE|SUBMATCHES|SUBMIT|SUBTRACT|SUM(?!\()|SUMMARY|SUMMING|SUPPLY|TABLE|TABLES|TIMESTAMP|TIMES?|TIMEZONE|TITLE|\??TO|TOP-OF-PAGE|TRANSFER|TRANSLATE|TRY|TYPES|ULINE|UNDER|UNPACK|UPDATE|USING|VALUE|VALUES|VIA|VARYING|VARY|WAIT|WHEN|WHERE|WIDTH|WHILE|WITH|WINDOW|WRITE|XSD|ZERO)\b`, Keyword, nil},
|
||||||
|
{`(abs|acos|asin|atan|boolc|boolx|bit_set|char_off|charlen|ceil|cmax|cmin|condense|contains|contains_any_of|contains_any_not_of|concat_lines_of|cos|cosh|count|count_any_of|count_any_not_of|dbmaxlen|distance|escape|exp|find|find_end|find_any_of|find_any_not_of|floor|frac|from_mixed|insert|lines|log|log10|match|matches|nmax|nmin|numofchar|repeat|replace|rescale|reverse|round|segment|shift_left|shift_right|sign|sin|sinh|sqrt|strlen|substring|substring_after|substring_from|substring_before|substring_to|tan|tanh|to_upper|to_lower|to_mixed|translate|trunc|xstrlen)(\()\b`, ByGroups(NameBuiltin, Punctuation), nil},
|
||||||
|
{`&[0-9]`, Name, nil},
|
||||||
|
{`[0-9]+`, LiteralNumberInteger, nil},
|
||||||
|
{`(?<=(\s|.))(AND|OR|EQ|NE|GT|LT|GE|LE|CO|CN|CA|NA|CS|NOT|NS|CP|NP|BYTE-CO|BYTE-CN|BYTE-CA|BYTE-NA|BYTE-CS|BYTE-NS|IS\s+(NOT\s+)?(INITIAL|ASSIGNED|REQUESTED|BOUND))\b`, OperatorWord, nil},
|
||||||
|
Include("variable-names"),
|
||||||
|
{`[?*<>=\-+&]`, Operator, nil},
|
||||||
|
{`'(''|[^'])*'`, LiteralStringSingle, nil},
|
||||||
|
{"`([^`])*`", LiteralStringSingle, nil},
|
||||||
|
{`([|}])([^{}|]*?)([|{])`, ByGroups(Punctuation, LiteralStringSingle, Punctuation), nil},
|
||||||
|
{`[/;:()\[\],.]`, Punctuation, nil},
|
||||||
|
{`(!)(\w+)`, ByGroups(Operator, Name), nil},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
))
|
|
@ -0,0 +1,110 @@
|
||||||
|
package a
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/alecthomas/chroma" // nolint
|
||||||
|
"github.com/alecthomas/chroma/lexers/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Arduino lexer.
|
||||||
|
var Arduino = internal.Register(MustNewLexer(
|
||||||
|
&Config{
|
||||||
|
Name: "Arduino",
|
||||||
|
Aliases: []string{"arduino"},
|
||||||
|
Filenames: []string{"*.ino"},
|
||||||
|
MimeTypes: []string{"text/x-arduino"},
|
||||||
|
EnsureNL: true,
|
||||||
|
},
|
||||||
|
Rules{
|
||||||
|
"statements": {
|
||||||
|
{Words(``, `\b`, `catch`, `const_cast`, `delete`, `dynamic_cast`, `explicit`, `export`, `friend`, `mutable`, `namespace`, `new`, `operator`, `private`, `protected`, `public`, `reinterpret_cast`, `restrict`, `static_cast`, `template`, `this`, `throw`, `throws`, `try`, `typeid`, `typename`, `using`, `virtual`, `constexpr`, `nullptr`, `decltype`, `thread_local`, `alignas`, `alignof`, `static_assert`, `noexcept`, `override`, `final`), Keyword, nil},
|
||||||
|
{`char(16_t|32_t)\b`, KeywordType, nil},
|
||||||
|
{`(class)\b`, ByGroups(Keyword, Text), Push("classname")},
|
||||||
|
{`(R)(")([^\\()\s]{,16})(\()((?:.|\n)*?)(\)\3)(")`, ByGroups(LiteralStringAffix, LiteralString, LiteralStringDelimiter, LiteralStringDelimiter, LiteralString, LiteralStringDelimiter, LiteralString), nil},
|
||||||
|
{`(u8|u|U)(")`, ByGroups(LiteralStringAffix, LiteralString), Push("string")},
|
||||||
|
{`(L?)(")`, ByGroups(LiteralStringAffix, LiteralString), Push("string")},
|
||||||
|
{`(L?)(')(\\.|\\[0-7]{1,3}|\\x[a-fA-F0-9]{1,2}|[^\\\'\n])(')`, ByGroups(LiteralStringAffix, LiteralStringChar, LiteralStringChar, LiteralStringChar), nil},
|
||||||
|
{`(\d+\.\d*|\.\d+|\d+)[eE][+-]?\d+[LlUu]*`, LiteralNumberFloat, nil},
|
||||||
|
{`(\d+\.\d*|\.\d+|\d+[fF])[fF]?`, LiteralNumberFloat, nil},
|
||||||
|
{`0x[0-9a-fA-F]+[LlUu]*`, LiteralNumberHex, nil},
|
||||||
|
{`0[0-7]+[LlUu]*`, LiteralNumberOct, nil},
|
||||||
|
{`\d+[LlUu]*`, LiteralNumberInteger, nil},
|
||||||
|
{`\*/`, Error, nil},
|
||||||
|
{`[~!%^&*+=|?:<>/-]`, Operator, nil},
|
||||||
|
{`[()\[\],.]`, Punctuation, nil},
|
||||||
|
{Words(``, `\b`, `asm`, `auto`, `break`, `case`, `const`, `continue`, `default`, `do`, `else`, `enum`, `extern`, `for`, `goto`, `if`, `register`, `restricted`, `return`, `sizeof`, `static`, `struct`, `switch`, `typedef`, `union`, `volatile`, `while`), Keyword, nil},
|
||||||
|
{`(_Bool|_Complex|_Imaginary|array|atomic_bool|atomic_char|atomic_int|atomic_llong|atomic_long|atomic_schar|atomic_short|atomic_uchar|atomic_uint|atomic_ullong|atomic_ulong|atomic_ushort|auto|bool|boolean|BooleanVariables|Byte|byte|Char|char|char16_t|char32_t|class|complex|Const|const|const_cast|delete|double|dynamic_cast|enum|explicit|extern|Float|float|friend|inline|Int|int|int16_t|int32_t|int64_t|int8_t|Long|long|new|NULL|null|operator|private|PROGMEM|protected|public|register|reinterpret_cast|short|signed|sizeof|Static|static|static_cast|String|struct|typedef|uint16_t|uint32_t|uint64_t|uint8_t|union|unsigned|virtual|Void|void|Volatile|volatile|word)\b`, KeywordType, nil},
|
||||||
|
// Start of: Arduino-specific syntax
|
||||||
|
{`(and|final|If|Loop|loop|not|or|override|setup|Setup|throw|try|xor)\b`, Keyword, nil}, // Addition to keywords already defined by C++
|
||||||
|
{`(ANALOG_MESSAGE|BIN|CHANGE|DEC|DEFAULT|DIGITAL_MESSAGE|EXTERNAL|FALLING|FIRMATA_STRING|HALF_PI|HEX|HIGH|INPUT|INPUT_PULLUP|INTERNAL|INTERNAL1V1|INTERNAL1V1|INTERNAL2V56|INTERNAL2V56|LED_BUILTIN|LED_BUILTIN_RX|LED_BUILTIN_TX|LOW|LSBFIRST|MSBFIRST|OCT|OUTPUT|PI|REPORT_ANALOG|REPORT_DIGITAL|RISING|SET_PIN_MODE|SYSEX_START|SYSTEM_RESET|TWO_PI)\b`, KeywordConstant, nil},
|
||||||
|
{`(boolean|const|byte|word|string|String|array)\b`, NameVariable, nil},
|
||||||
|
{`(Keyboard|KeyboardController|MouseController|SoftwareSerial|EthernetServer|EthernetClient|LiquidCrystal|RobotControl|GSMVoiceCall|EthernetUDP|EsploraTFT|HttpClient|RobotMotor|WiFiClient|GSMScanner|FileSystem|Scheduler|GSMServer|YunClient|YunServer|IPAddress|GSMClient|GSMModem|Keyboard|Ethernet|Console|GSMBand|Esplora|Stepper|Process|WiFiUDP|GSM_SMS|Mailbox|USBHost|Firmata|PImage|Client|Server|GSMPIN|FileIO|Bridge|Serial|EEPROM|Stream|Mouse|Audio|Servo|File|Task|GPRS|WiFi|Wire|TFT|GSM|SPI|SD)\b`, NameClass, nil},
|
||||||
|
{`(abs|Abs|accept|ACos|acos|acosf|addParameter|analogRead|AnalogRead|analogReadResolution|AnalogReadResolution|analogReference|AnalogReference|analogWrite|AnalogWrite|analogWriteResolution|AnalogWriteResolution|answerCall|asin|ASin|asinf|atan|ATan|atan2|ATan2|atan2f|atanf|attach|attached|attachGPRS|attachInterrupt|AttachInterrupt|autoscroll|available|availableForWrite|background|beep|begin|beginPacket|beginSD|beginSMS|beginSpeaker|beginTFT|beginTransmission|beginWrite|bit|Bit|BitClear|bitClear|bitRead|BitRead|bitSet|BitSet|BitWrite|bitWrite|blink|blinkVersion|BSSID|buffer|byte|cbrt|cbrtf|Ceil|ceil|ceilf|changePIN|char|charAt|checkPIN|checkPUK|checkReg|circle|cityNameRead|cityNameWrite|clear|clearScreen|click|close|compareTo|compassRead|concat|config|connect|connected|constrain|Constrain|copysign|copysignf|cos|Cos|cosf|cosh|coshf|countryNameRead|countryNameWrite|createChar|cursor|debugPrint|degrees|Delay|delay|DelayMicroseconds|delayMicroseconds|detach|DetachInterrupt|detachInterrupt|DigitalPinToInterrupt|digitalPinToInterrupt|DigitalRead|digitalRead|DigitalWrite|digitalWrite|disconnect|display|displayLogos|drawBMP|drawCompass|encryptionType|end|endPacket|endSMS|endsWith|endTransmission|endWrite|equals|equalsIgnoreCase|exists|exitValue|Exp|exp|expf|fabs|fabsf|fdim|fdimf|fill|find|findUntil|float|floor|Floor|floorf|flush|fma|fmaf|fmax|fmaxf|fmin|fminf|fmod|fmodf|gatewayIP|get|getAsynchronously|getBand|getButton|getBytes|getCurrentCarrier|getIMEI|getKey|getModifiers|getOemKey|getPINUsed|getResult|getSignalStrength|getSocket|getVoiceCallStatus|getXChange|getYChange|hangCall|height|highByte|HighByte|home|hypot|hypotf|image|indexOf|int|interrupts|IPAddress|IRread|isActionDone|isAlpha|isAlphaNumeric|isAscii|isControl|isDigit|isDirectory|isfinite|isGraph|isHexadecimalDigit|isinf|isListening|isLowerCase|isnan|isPIN|isPressed|isPrintable|isPunct|isSpace|isUpperCase|isValid|isWhitespace|keyboardRead|keyPressed|keyReleased|knobRead|lastIndexOf|ldexp|ldexpf|leftToRight|length|line|lineFollowConfig|listen|listenOnLocalhost|loadImage|localIP|log|Log|log10|log10f|logf|long|lowByte|LowByte|lrint|lrintf|lround|lroundf|macAddress|maintain|map|Map|Max|max|messageAvailable|Micros|micros|millis|Millis|Min|min|mkdir|motorsStop|motorsWrite|mouseDragged|mouseMoved|mousePressed|mouseReleased|move|noAutoscroll|noBlink|noBuffer|noCursor|noDisplay|noFill|noInterrupts|NoInterrupts|noListenOnLocalhost|noStroke|noTone|NoTone|onReceive|onRequest|open|openNextFile|overflow|parseCommand|parseFloat|parseInt|parsePacket|pauseMode|peek|PinMode|pinMode|playFile|playMelody|point|pointTo|position|Pow|pow|powf|prepare|press|print|printFirmwareVersion|println|printVersion|process|processInput|PulseIn|pulseIn|pulseInLong|PulseInLong|put|radians|random|Random|randomSeed|RandomSeed|read|readAccelerometer|readBlue|readButton|readBytes|readBytesUntil|readGreen|readJoystickButton|readJoystickSwitch|readJoystickX|readJoystickY|readLightSensor|readMessage|readMicrophone|readNetworks|readRed|readSlider|readString|readStringUntil|readTemperature|ready|rect|release|releaseAll|remoteIP|remoteNumber|remotePort|remove|replace|requestFrom|retrieveCallingNumber|rewindDirectory|rightToLeft|rmdir|robotNameRead|robotNameWrite|round|roundf|RSSI|run|runAsynchronously|running|runShellCommand|runShellCommandAsynchronously|scanNetworks|scrollDisplayLeft|scrollDisplayRight|seek|sendAnalog|sendDigitalPortPair|sendDigitalPorts|sendString|sendSysex|Serial_Available|Serial_Begin|Serial_End|Serial_Flush|Serial_Peek|Serial_Print|Serial_Println|Serial_Read|serialEvent|setBand|setBitOrder|setCharAt|setClockDivider|setCursor|setDataMode|setDNS|setFirmwareVersion|setMode|setPINUsed|setSpeed|setTextSize|setTimeout|ShiftIn|shiftIn|ShiftOut|shiftOut|shutdown|signbit|sin|Sin|sinf|sinh|sinhf|size|sizeof|Sq|sq|Sqrt|sqrt|sqrtf|SSID|startLoop|startsWith|step|stop|stroke|subnetMask|substring|switchPIN|tan|Tan|tanf|tanh|tanhf|tempoWrite|text|toCharArray|toInt|toLowerCase|tone|Tone|toUpperCase|transfer|trim|trunc|truncf|tuneWrite|turn|updateIR|userNameRead|userNameWrite|voiceCall|waitContinue|width|WiFiServer|word|write|writeBlue|writeGreen|writeJSON|writeMessage|writeMicroseconds|writeRed|writeRGB|yield|Yield)\b`, NameFunction, nil},
|
||||||
|
// End of: Arduino-specific syntax
|
||||||
|
{Words(``, `\b`, `inline`, `_inline`, `__inline`, `naked`, `restrict`, `thread`, `typename`), KeywordReserved, nil},
|
||||||
|
{`(__m(128i|128d|128|64))\b`, KeywordReserved, nil},
|
||||||
|
{Words(`__`, `\b`, `asm`, `int8`, `based`, `except`, `int16`, `stdcall`, `cdecl`, `fastcall`, `int32`, `declspec`, `finally`, `int64`, `try`, `leave`, `wchar_t`, `w64`, `unaligned`, `raise`, `noop`, `identifier`, `forceinline`, `assume`), KeywordReserved, nil},
|
||||||
|
{`(true|false|NULL)\b`, NameBuiltin, nil},
|
||||||
|
{`([a-zA-Z_]\w*)(\s*)(:)(?!:)`, ByGroups(NameLabel, Text, Punctuation), nil},
|
||||||
|
{`[a-zA-Z_]\w*`, Name, nil},
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
Include("whitespace"),
|
||||||
|
{`((?:[\w*\s])+?(?:\s|[*]))([a-zA-Z_]\w*)(\s*\([^;]*?\))([^;{]*)(\{)`, ByGroups(UsingSelf("root"), NameFunction, UsingSelf("root"), UsingSelf("root"), Punctuation), Push("function")},
|
||||||
|
{`((?:[\w*\s])+?(?:\s|[*]))([a-zA-Z_]\w*)(\s*\([^;]*?\))([^;]*)(;)`, ByGroups(UsingSelf("root"), NameFunction, UsingSelf("root"), UsingSelf("root"), Punctuation), nil},
|
||||||
|
Default(Push("statement")),
|
||||||
|
{Words(`__`, `\b`, `virtual_inheritance`, `uuidof`, `super`, `single_inheritance`, `multiple_inheritance`, `interface`, `event`), KeywordReserved, nil},
|
||||||
|
{`__(offload|blockingoffload|outer)\b`, KeywordPseudo, nil},
|
||||||
|
},
|
||||||
|
"classname": {
|
||||||
|
{`[a-zA-Z_]\w*`, NameClass, Pop(1)},
|
||||||
|
{`\s*(?=>)`, Text, Pop(1)},
|
||||||
|
},
|
||||||
|
"whitespace": {
|
||||||
|
{`^#if\s+0`, CommentPreproc, Push("if0")},
|
||||||
|
{`^#`, CommentPreproc, Push("macro")},
|
||||||
|
{`^(\s*(?:/[*].*?[*]/\s*)?)(#if\s+0)`, ByGroups(UsingSelf("root"), CommentPreproc), Push("if0")},
|
||||||
|
{`^(\s*(?:/[*].*?[*]/\s*)?)(#)`, ByGroups(UsingSelf("root"), CommentPreproc), Push("macro")},
|
||||||
|
{`\n`, Text, nil},
|
||||||
|
{`\s+`, Text, nil},
|
||||||
|
{`\\\n`, Text, nil},
|
||||||
|
{`//(\n|[\w\W]*?[^\\]\n)`, CommentSingle, nil},
|
||||||
|
{`/(\\\n)?[*][\w\W]*?[*](\\\n)?/`, CommentMultiline, nil},
|
||||||
|
{`/(\\\n)?[*][\w\W]*`, CommentMultiline, nil},
|
||||||
|
},
|
||||||
|
"statement": {
|
||||||
|
Include("whitespace"),
|
||||||
|
Include("statements"),
|
||||||
|
{`[{}]`, Punctuation, nil},
|
||||||
|
{`;`, Punctuation, Pop(1)},
|
||||||
|
},
|
||||||
|
"function": {
|
||||||
|
Include("whitespace"),
|
||||||
|
Include("statements"),
|
||||||
|
{`;`, Punctuation, nil},
|
||||||
|
{`\{`, Punctuation, Push()},
|
||||||
|
{`\}`, Punctuation, Pop(1)},
|
||||||
|
},
|
||||||
|
"string": {
|
||||||
|
{`"`, LiteralString, Pop(1)},
|
||||||
|
{`\\([\\abfnrtv"\']|x[a-fA-F0-9]{2,4}|u[a-fA-F0-9]{4}|U[a-fA-F0-9]{8}|[0-7]{1,3})`, LiteralStringEscape, nil},
|
||||||
|
{`[^\\"\n]+`, LiteralString, nil},
|
||||||
|
{`\\\n`, LiteralString, nil},
|
||||||
|
{`\\`, LiteralString, nil},
|
||||||
|
},
|
||||||
|
"macro": {
|
||||||
|
{`(include)(\s*(?:/[*].*?[*]/\s*)?)([^\n]+)`, ByGroups(CommentPreproc, Text, CommentPreprocFile), nil},
|
||||||
|
{`[^/\n]+`, CommentPreproc, nil},
|
||||||
|
{`/[*](.|\n)*?[*]/`, CommentMultiline, nil},
|
||||||
|
{`//.*?\n`, CommentSingle, Pop(1)},
|
||||||
|
{`/`, CommentPreproc, nil},
|
||||||
|
{`(?<=\\)\n`, CommentPreproc, nil},
|
||||||
|
{`\n`, CommentPreproc, Pop(1)},
|
||||||
|
},
|
||||||
|
"if0": {
|
||||||
|
{`^\s*#if.*?(?<!\\)\n`, CommentPreproc, Push()},
|
||||||
|
{`^\s*#el(?:se|if).*\n`, CommentPreproc, Pop(1)},
|
||||||
|
{`^\s*#endif.*?(?<!\\)\n`, CommentPreproc, Pop(1)},
|
||||||
|
{`.*?\n`, Comment, nil},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
))
|
|
@ -5,8 +5,232 @@ import (
|
||||||
"github.com/alecthomas/chroma/lexers/internal"
|
"github.com/alecthomas/chroma/lexers/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
clBuiltinFunctions = []string{
|
||||||
|
"<", "<=", "=", ">", ">=", "-", "/", "/=", "*", "+", "1-", "1+",
|
||||||
|
"abort", "abs", "acons", "acos", "acosh", "add-method", "adjoin",
|
||||||
|
"adjustable-array-p", "adjust-array", "allocate-instance",
|
||||||
|
"alpha-char-p", "alphanumericp", "append", "apply", "apropos",
|
||||||
|
"apropos-list", "aref", "arithmetic-error-operands",
|
||||||
|
"arithmetic-error-operation", "array-dimension", "array-dimensions",
|
||||||
|
"array-displacement", "array-element-type", "array-has-fill-pointer-p",
|
||||||
|
"array-in-bounds-p", "arrayp", "array-rank", "array-row-major-index",
|
||||||
|
"array-total-size", "ash", "asin", "asinh", "assoc", "assoc-if",
|
||||||
|
"assoc-if-not", "atan", "atanh", "atom", "bit", "bit-and", "bit-andc1",
|
||||||
|
"bit-andc2", "bit-eqv", "bit-ior", "bit-nand", "bit-nor", "bit-not",
|
||||||
|
"bit-orc1", "bit-orc2", "bit-vector-p", "bit-xor", "boole",
|
||||||
|
"both-case-p", "boundp", "break", "broadcast-stream-streams",
|
||||||
|
"butlast", "byte", "byte-position", "byte-size", "caaaar", "caaadr",
|
||||||
|
"caaar", "caadar", "caaddr", "caadr", "caar", "cadaar", "cadadr",
|
||||||
|
"cadar", "caddar", "cadddr", "caddr", "cadr", "call-next-method", "car",
|
||||||
|
"cdaaar", "cdaadr", "cdaar", "cdadar", "cdaddr", "cdadr", "cdar",
|
||||||
|
"cddaar", "cddadr", "cddar", "cdddar", "cddddr", "cdddr", "cddr", "cdr",
|
||||||
|
"ceiling", "cell-error-name", "cerror", "change-class", "char", "char<",
|
||||||
|
"char<=", "char=", "char>", "char>=", "char/=", "character",
|
||||||
|
"characterp", "char-code", "char-downcase", "char-equal",
|
||||||
|
"char-greaterp", "char-int", "char-lessp", "char-name",
|
||||||
|
"char-not-equal", "char-not-greaterp", "char-not-lessp", "char-upcase",
|
||||||
|
"cis", "class-name", "class-of", "clear-input", "clear-output",
|
||||||
|
"close", "clrhash", "code-char", "coerce", "compile",
|
||||||
|
"compiled-function-p", "compile-file", "compile-file-pathname",
|
||||||
|
"compiler-macro-function", "complement", "complex", "complexp",
|
||||||
|
"compute-applicable-methods", "compute-restarts", "concatenate",
|
||||||
|
"concatenated-stream-streams", "conjugate", "cons", "consp",
|
||||||
|
"constantly", "constantp", "continue", "copy-alist", "copy-list",
|
||||||
|
"copy-pprint-dispatch", "copy-readtable", "copy-seq", "copy-structure",
|
||||||
|
"copy-symbol", "copy-tree", "cos", "cosh", "count", "count-if",
|
||||||
|
"count-if-not", "decode-float", "decode-universal-time", "delete",
|
||||||
|
"delete-duplicates", "delete-file", "delete-if", "delete-if-not",
|
||||||
|
"delete-package", "denominator", "deposit-field", "describe",
|
||||||
|
"describe-object", "digit-char", "digit-char-p", "directory",
|
||||||
|
"directory-namestring", "disassemble", "documentation", "dpb",
|
||||||
|
"dribble", "echo-stream-input-stream", "echo-stream-output-stream",
|
||||||
|
"ed", "eighth", "elt", "encode-universal-time", "endp",
|
||||||
|
"enough-namestring", "ensure-directories-exist",
|
||||||
|
"ensure-generic-function", "eq", "eql", "equal", "equalp", "error",
|
||||||
|
"eval", "evenp", "every", "exp", "export", "expt", "fboundp",
|
||||||
|
"fceiling", "fdefinition", "ffloor", "fifth", "file-author",
|
||||||
|
"file-error-pathname", "file-length", "file-namestring",
|
||||||
|
"file-position", "file-string-length", "file-write-date",
|
||||||
|
"fill", "fill-pointer", "find", "find-all-symbols", "find-class",
|
||||||
|
"find-if", "find-if-not", "find-method", "find-package", "find-restart",
|
||||||
|
"find-symbol", "finish-output", "first", "float", "float-digits",
|
||||||
|
"floatp", "float-precision", "float-radix", "float-sign", "floor",
|
||||||
|
"fmakunbound", "force-output", "format", "fourth", "fresh-line",
|
||||||
|
"fround", "ftruncate", "funcall", "function-keywords",
|
||||||
|
"function-lambda-expression", "functionp", "gcd", "gensym", "gentemp",
|
||||||
|
"get", "get-decoded-time", "get-dispatch-macro-character", "getf",
|
||||||
|
"gethash", "get-internal-real-time", "get-internal-run-time",
|
||||||
|
"get-macro-character", "get-output-stream-string", "get-properties",
|
||||||
|
"get-setf-expansion", "get-universal-time", "graphic-char-p",
|
||||||
|
"hash-table-count", "hash-table-p", "hash-table-rehash-size",
|
||||||
|
"hash-table-rehash-threshold", "hash-table-size", "hash-table-test",
|
||||||
|
"host-namestring", "identity", "imagpart", "import",
|
||||||
|
"initialize-instance", "input-stream-p", "inspect",
|
||||||
|
"integer-decode-float", "integer-length", "integerp",
|
||||||
|
"interactive-stream-p", "intern", "intersection",
|
||||||
|
"invalid-method-error", "invoke-debugger", "invoke-restart",
|
||||||
|
"invoke-restart-interactively", "isqrt", "keywordp", "last", "lcm",
|
||||||
|
"ldb", "ldb-test", "ldiff", "length", "lisp-implementation-type",
|
||||||
|
"lisp-implementation-version", "list", "list*", "list-all-packages",
|
||||||
|
"listen", "list-length", "listp", "load",
|
||||||
|
"load-logical-pathname-translations", "log", "logand", "logandc1",
|
||||||
|
"logandc2", "logbitp", "logcount", "logeqv", "logical-pathname",
|
||||||
|
"logical-pathname-translations", "logior", "lognand", "lognor",
|
||||||
|
"lognot", "logorc1", "logorc2", "logtest", "logxor", "long-site-name",
|
||||||
|
"lower-case-p", "machine-instance", "machine-type", "machine-version",
|
||||||
|
"macroexpand", "macroexpand-1", "macro-function", "make-array",
|
||||||
|
"make-broadcast-stream", "make-concatenated-stream", "make-condition",
|
||||||
|
"make-dispatch-macro-character", "make-echo-stream", "make-hash-table",
|
||||||
|
"make-instance", "make-instances-obsolete", "make-list",
|
||||||
|
"make-load-form", "make-load-form-saving-slots", "make-package",
|
||||||
|
"make-pathname", "make-random-state", "make-sequence", "make-string",
|
||||||
|
"make-string-input-stream", "make-string-output-stream", "make-symbol",
|
||||||
|
"make-synonym-stream", "make-two-way-stream", "makunbound", "map",
|
||||||
|
"mapc", "mapcan", "mapcar", "mapcon", "maphash", "map-into", "mapl",
|
||||||
|
"maplist", "mask-field", "max", "member", "member-if", "member-if-not",
|
||||||
|
"merge", "merge-pathnames", "method-combination-error",
|
||||||
|
"method-qualifiers", "min", "minusp", "mismatch", "mod",
|
||||||
|
"muffle-warning", "name-char", "namestring", "nbutlast", "nconc",
|
||||||
|
"next-method-p", "nintersection", "ninth", "no-applicable-method",
|
||||||
|
"no-next-method", "not", "notany", "notevery", "nreconc", "nreverse",
|
||||||
|
"nset-difference", "nset-exclusive-or", "nstring-capitalize",
|
||||||
|
"nstring-downcase", "nstring-upcase", "nsublis", "nsubst", "nsubst-if",
|
||||||
|
"nsubst-if-not", "nsubstitute", "nsubstitute-if", "nsubstitute-if-not",
|
||||||
|
"nth", "nthcdr", "null", "numberp", "numerator", "nunion", "oddp",
|
||||||
|
"open", "open-stream-p", "output-stream-p", "package-error-package",
|
||||||
|
"package-name", "package-nicknames", "packagep",
|
||||||
|
"package-shadowing-symbols", "package-used-by-list", "package-use-list",
|
||||||
|
"pairlis", "parse-integer", "parse-namestring", "pathname",
|
||||||
|
"pathname-device", "pathname-directory", "pathname-host",
|
||||||
|
"pathname-match-p", "pathname-name", "pathnamep", "pathname-type",
|
||||||
|
"pathname-version", "peek-char", "phase", "plusp", "position",
|
||||||
|
"position-if", "position-if-not", "pprint", "pprint-dispatch",
|
||||||
|
"pprint-fill", "pprint-indent", "pprint-linear", "pprint-newline",
|
||||||
|
"pprint-tab", "pprint-tabular", "prin1", "prin1-to-string", "princ",
|
||||||
|
"princ-to-string", "print", "print-object", "probe-file", "proclaim",
|
||||||
|
"provide", "random", "random-state-p", "rassoc", "rassoc-if",
|
||||||
|
"rassoc-if-not", "rational", "rationalize", "rationalp", "read",
|
||||||
|
"read-byte", "read-char", "read-char-no-hang", "read-delimited-list",
|
||||||
|
"read-from-string", "read-line", "read-preserving-whitespace",
|
||||||
|
"read-sequence", "readtable-case", "readtablep", "realp", "realpart",
|
||||||
|
"reduce", "reinitialize-instance", "rem", "remhash", "remove",
|
||||||
|
"remove-duplicates", "remove-if", "remove-if-not", "remove-method",
|
||||||
|
"remprop", "rename-file", "rename-package", "replace", "require",
|
||||||
|
"rest", "restart-name", "revappend", "reverse", "room", "round",
|
||||||
|
"row-major-aref", "rplaca", "rplacd", "sbit", "scale-float", "schar",
|
||||||
|
"search", "second", "set", "set-difference",
|
||||||
|
"set-dispatch-macro-character", "set-exclusive-or",
|
||||||
|
"set-macro-character", "set-pprint-dispatch", "set-syntax-from-char",
|
||||||
|
"seventh", "shadow", "shadowing-import", "shared-initialize",
|
||||||
|
"short-site-name", "signal", "signum", "simple-bit-vector-p",
|
||||||
|
"simple-condition-format-arguments", "simple-condition-format-control",
|
||||||
|
"simple-string-p", "simple-vector-p", "sin", "sinh", "sixth", "sleep",
|
||||||
|
"slot-boundp", "slot-exists-p", "slot-makunbound", "slot-missing",
|
||||||
|
"slot-unbound", "slot-value", "software-type", "software-version",
|
||||||
|
"some", "sort", "special-operator-p", "sqrt", "stable-sort",
|
||||||
|
"standard-char-p", "store-value", "stream-element-type",
|
||||||
|
"stream-error-stream", "stream-external-format", "streamp", "string",
|
||||||
|
"string<", "string<=", "string=", "string>", "string>=", "string/=",
|
||||||
|
"string-capitalize", "string-downcase", "string-equal",
|
||||||
|
"string-greaterp", "string-left-trim", "string-lessp",
|
||||||
|
"string-not-equal", "string-not-greaterp", "string-not-lessp",
|
||||||
|
"stringp", "string-right-trim", "string-trim", "string-upcase",
|
||||||
|
"sublis", "subseq", "subsetp", "subst", "subst-if", "subst-if-not",
|
||||||
|
"substitute", "substitute-if", "substitute-if-not", "subtypep", "svref",
|
||||||
|
"sxhash", "symbol-function", "symbol-name", "symbolp", "symbol-package",
|
||||||
|
"symbol-plist", "symbol-value", "synonym-stream-symbol", "syntax:",
|
||||||
|
"tailp", "tan", "tanh", "tenth", "terpri", "third",
|
||||||
|
"translate-logical-pathname", "translate-pathname", "tree-equal",
|
||||||
|
"truename", "truncate", "two-way-stream-input-stream",
|
||||||
|
"two-way-stream-output-stream", "type-error-datum",
|
||||||
|
"type-error-expected-type", "type-of", "typep", "unbound-slot-instance",
|
||||||
|
"unexport", "unintern", "union", "unread-char", "unuse-package",
|
||||||
|
"update-instance-for-different-class",
|
||||||
|
"update-instance-for-redefined-class", "upgraded-array-element-type",
|
||||||
|
"upgraded-complex-part-type", "upper-case-p", "use-package",
|
||||||
|
"user-homedir-pathname", "use-value", "values", "values-list", "vector",
|
||||||
|
"vectorp", "vector-pop", "vector-push", "vector-push-extend", "warn",
|
||||||
|
"wild-pathname-p", "write", "write-byte", "write-char", "write-line",
|
||||||
|
"write-sequence", "write-string", "write-to-string", "yes-or-no-p",
|
||||||
|
"y-or-n-p", "zerop",
|
||||||
|
}
|
||||||
|
|
||||||
|
clSpecialForms = []string{
|
||||||
|
"block", "catch", "declare", "eval-when", "flet", "function", "go", "if",
|
||||||
|
"labels", "lambda", "let", "let*", "load-time-value", "locally", "macrolet",
|
||||||
|
"multiple-value-call", "multiple-value-prog1", "progn", "progv", "quote",
|
||||||
|
"return-from", "setq", "symbol-macrolet", "tagbody", "the", "throw",
|
||||||
|
"unwind-protect",
|
||||||
|
}
|
||||||
|
|
||||||
|
clMacros = []string{
|
||||||
|
"and", "assert", "call-method", "case", "ccase", "check-type", "cond",
|
||||||
|
"ctypecase", "decf", "declaim", "defclass", "defconstant", "defgeneric",
|
||||||
|
"define-compiler-macro", "define-condition", "define-method-combination",
|
||||||
|
"define-modify-macro", "define-setf-expander", "define-symbol-macro",
|
||||||
|
"defmacro", "defmethod", "defpackage", "defparameter", "defsetf",
|
||||||
|
"defstruct", "deftype", "defun", "defvar", "destructuring-bind", "do",
|
||||||
|
"do*", "do-all-symbols", "do-external-symbols", "dolist", "do-symbols",
|
||||||
|
"dotimes", "ecase", "etypecase", "formatter", "handler-bind",
|
||||||
|
"handler-case", "ignore-errors", "incf", "in-package", "lambda", "loop",
|
||||||
|
"loop-finish", "make-method", "multiple-value-bind", "multiple-value-list",
|
||||||
|
"multiple-value-setq", "nth-value", "or", "pop",
|
||||||
|
"pprint-exit-if-list-exhausted", "pprint-logical-block", "pprint-pop",
|
||||||
|
"print-unreadable-object", "prog", "prog*", "prog1", "prog2", "psetf",
|
||||||
|
"psetq", "push", "pushnew", "remf", "restart-bind", "restart-case",
|
||||||
|
"return", "rotatef", "setf", "shiftf", "step", "time", "trace", "typecase",
|
||||||
|
"unless", "untrace", "when", "with-accessors", "with-compilation-unit",
|
||||||
|
"with-condition-restarts", "with-hash-table-iterator",
|
||||||
|
"with-input-from-string", "with-open-file", "with-open-stream",
|
||||||
|
"with-output-to-string", "with-package-iterator", "with-simple-restart",
|
||||||
|
"with-slots", "with-standard-io-syntax",
|
||||||
|
}
|
||||||
|
|
||||||
|
clLambdaListKeywords = []string{
|
||||||
|
"&allow-other-keys", "&aux", "&body", "&environment", "&key", "&optional",
|
||||||
|
"&rest", "&whole",
|
||||||
|
}
|
||||||
|
|
||||||
|
clDeclarations = []string{
|
||||||
|
"dynamic-extent", "ignore", "optimize", "ftype", "inline", "special",
|
||||||
|
"ignorable", "notinline", "type",
|
||||||
|
}
|
||||||
|
|
||||||
|
clBuiltinTypes = []string{
|
||||||
|
"atom", "boolean", "base-char", "base-string", "bignum", "bit",
|
||||||
|
"compiled-function", "extended-char", "fixnum", "keyword", "nil",
|
||||||
|
"signed-byte", "short-float", "single-float", "double-float", "long-float",
|
||||||
|
"simple-array", "simple-base-string", "simple-bit-vector", "simple-string",
|
||||||
|
"simple-vector", "standard-char", "unsigned-byte",
|
||||||
|
|
||||||
|
// Condition Types
|
||||||
|
"arithmetic-error", "cell-error", "condition", "control-error",
|
||||||
|
"division-by-zero", "end-of-file", "error", "file-error",
|
||||||
|
"floating-point-inexact", "floating-point-overflow",
|
||||||
|
"floating-point-underflow", "floating-point-invalid-operation",
|
||||||
|
"parse-error", "package-error", "print-not-readable", "program-error",
|
||||||
|
"reader-error", "serious-condition", "simple-condition", "simple-error",
|
||||||
|
"simple-type-error", "simple-warning", "stream-error", "storage-condition",
|
||||||
|
"style-warning", "type-error", "unbound-variable", "unbound-slot",
|
||||||
|
"undefined-function", "warning",
|
||||||
|
}
|
||||||
|
|
||||||
|
clBuiltinClasses = []string{
|
||||||
|
"array", "broadcast-stream", "bit-vector", "built-in-class", "character",
|
||||||
|
"class", "complex", "concatenated-stream", "cons", "echo-stream",
|
||||||
|
"file-stream", "float", "function", "generic-function", "hash-table",
|
||||||
|
"integer", "list", "logical-pathname", "method-combination", "method",
|
||||||
|
"null", "number", "package", "pathname", "ratio", "rational", "readtable",
|
||||||
|
"real", "random-state", "restart", "sequence", "standard-class",
|
||||||
|
"standard-generic-function", "standard-method", "standard-object",
|
||||||
|
"string-stream", "stream", "string", "structure-class", "structure-object",
|
||||||
|
"symbol", "synonym-stream", "t", "two-way-stream", "vector",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
// Common Lisp lexer.
|
// Common Lisp lexer.
|
||||||
var CommonLisp = internal.Register(MustNewLexer(
|
var CommonLisp = internal.Register(TypeRemappingLexer(MustNewLexer(
|
||||||
&Config{
|
&Config{
|
||||||
Name: "Common Lisp",
|
Name: "Common Lisp",
|
||||||
Aliases: []string{"common-lisp", "cl", "lisp"},
|
Aliases: []string{"common-lisp", "cl", "lisp"},
|
||||||
|
@ -71,4 +295,12 @@ var CommonLisp = internal.Register(MustNewLexer(
|
||||||
{`\)`, Punctuation, Pop(1)},
|
{`\)`, Punctuation, Pop(1)},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
))
|
), TypeMapping{
|
||||||
|
{NameVariable, NameFunction, clBuiltinFunctions},
|
||||||
|
{NameVariable, Keyword, clSpecialForms},
|
||||||
|
{NameVariable, NameBuiltin, clMacros},
|
||||||
|
{NameVariable, Keyword, clLambdaListKeywords},
|
||||||
|
{NameVariable, Keyword, clDeclarations},
|
||||||
|
{NameVariable, KeywordType, clBuiltinTypes},
|
||||||
|
{NameVariable, NameClass, clBuiltinClasses},
|
||||||
|
}))
|
||||||
|
|
|
@ -16,26 +16,27 @@ var CPP = internal.Register(MustNewLexer(
|
||||||
},
|
},
|
||||||
Rules{
|
Rules{
|
||||||
"statements": {
|
"statements": {
|
||||||
{Words(``, `\b`, `catch`, `const_cast`, `delete`, `dynamic_cast`, `explicit`, `export`, `friend`, `mutable`, `namespace`, `new`, `operator`, `private`, `protected`, `public`, `reinterpret_cast`, `restrict`, `static_cast`, `template`, `this`, `throw`, `throws`, `try`, `typeid`, `typename`, `using`, `virtual`, `constexpr`, `nullptr`, `decltype`, `thread_local`, `alignas`, `alignof`, `static_assert`, `noexcept`, `override`, `final`), Keyword, nil},
|
{Words(``, `\b`, `catch`, `const_cast`, `delete`, `dynamic_cast`, `explicit`, `export`, `friend`, `mutable`, `namespace`, `new`, `operator`, `private`, `protected`, `public`, `reinterpret_cast`, `restrict`, `static_cast`, `template`, `this`, `throw`, `throws`, `try`, `typeid`, `typename`, `using`, `virtual`, `constexpr`, `nullptr`, `decltype`, `thread_local`, `alignas`, `alignof`, `static_assert`, `noexcept`, `override`, `final`, `concept`, `requires`, `consteval`, `co_await`, `co_return`, `co_yield`), Keyword, nil},
|
||||||
{`char(16_t|32_t)\b`, KeywordType, nil},
|
{`(enum)\b(\s+)(class)\b(\s*)`, ByGroups(Keyword, Text, Keyword, Text), Push("classname")},
|
||||||
{`(class)\b`, ByGroups(Keyword, Text), Push("classname")},
|
{`(class|struct|enum|union)\b(\s*)`, ByGroups(Keyword, Text), Push("classname")},
|
||||||
|
{`\[\[.+\]\]`, NameAttribute, nil},
|
||||||
{`(R)(")([^\\()\s]{,16})(\()((?:.|\n)*?)(\)\3)(")`, ByGroups(LiteralStringAffix, LiteralString, LiteralStringDelimiter, LiteralStringDelimiter, LiteralString, LiteralStringDelimiter, LiteralString), nil},
|
{`(R)(")([^\\()\s]{,16})(\()((?:.|\n)*?)(\)\3)(")`, ByGroups(LiteralStringAffix, LiteralString, LiteralStringDelimiter, LiteralStringDelimiter, LiteralString, LiteralStringDelimiter, LiteralString), nil},
|
||||||
{`(u8|u|U)(")`, ByGroups(LiteralStringAffix, LiteralString), Push("string")},
|
{`(u8|u|U)(")`, ByGroups(LiteralStringAffix, LiteralString), Push("string")},
|
||||||
{`(L?)(")`, ByGroups(LiteralStringAffix, LiteralString), Push("string")},
|
{`(L?)(")`, ByGroups(LiteralStringAffix, LiteralString), Push("string")},
|
||||||
{`(L?)(')(\\.|\\[0-7]{1,3}|\\x[a-fA-F0-9]{1,2}|[^\\\'\n])(')`, ByGroups(LiteralStringAffix, LiteralStringChar, LiteralStringChar, LiteralStringChar), nil},
|
{`(L?)(')(\\.|\\[0-7]{1,3}|\\x[a-fA-F0-9]{1,2}|[^\\\'\n])(')`, ByGroups(LiteralStringAffix, LiteralStringChar, LiteralStringChar, LiteralStringChar), nil},
|
||||||
{`(\d+\.\d*|\.\d+|\d+)[eE][+-]?\d+[LlUu]*`, LiteralNumberFloat, nil},
|
{`(\.([0-9](?:'?[0-9]+)*)([eE][+-]?([0-9]('?[0-9]+)*))?|([0-9]('?[0-9]+)*)(([eE][+-]?([0-9]('?[0-9]+)*))|\.([0-9]('?[0-9]+)*)?([eE][+-]?([0-9]('?[0-9]+)*))?)|0[xX](\.([0-9A-Fa-f]('?[0-9A-Fa-f]+)*)([pP][-+]?([0-9]('?[0-9]+)*))?|([0-9A-Fa-f]('?[0-9A-Fa-f]+)*)(([pP][-+]?([0-9]('?[0-9]+)*))|\.([0-9A-Fa-f]('?[0-9A-Fa-f]+)*)?([pP][-+]?([0-9]('?[0-9]+)*))?)))[fFLlUu]*`, LiteralNumberFloat, nil},
|
||||||
{`(\d+\.\d*|\.\d+|\d+[fF])[fF]?`, LiteralNumberFloat, nil},
|
{`0[xX]([0-9A-Fa-f]('?[0-9A-Fa-f]+)*)[LlUu]*`, LiteralNumberHex, nil},
|
||||||
{`0x[0-9a-fA-F]+[LlUu]*`, LiteralNumberHex, nil},
|
{`0('?[0-7]+)+[LlUu]*`, LiteralNumberOct, nil},
|
||||||
{`0[0-7]+[LlUu]*`, LiteralNumberOct, nil},
|
{`0[Bb][01]('?[01]+)*[LlUu]*`, LiteralNumberBin, nil},
|
||||||
{`\d+[LlUu]*`, LiteralNumberInteger, nil},
|
{`[0-9]('?[0-9]+)*[LlUu]*`, LiteralNumberInteger, nil},
|
||||||
{`\*/`, Error, nil},
|
{`\*/`, Error, nil},
|
||||||
{`[~!%^&*+=|?:<>/-]`, Operator, nil},
|
{`[~!%^&*+=|?:<>/-]`, Operator, nil},
|
||||||
{`[()\[\],.]`, Punctuation, nil},
|
{`[()\[\],.]`, Punctuation, nil},
|
||||||
{Words(``, `\b`, `asm`, `auto`, `break`, `case`, `const`, `continue`, `default`, `do`, `else`, `enum`, `extern`, `for`, `goto`, `if`, `register`, `restricted`, `return`, `sizeof`, `static`, `struct`, `switch`, `typedef`, `union`, `volatile`, `while`), Keyword, nil},
|
{Words(``, `\b`, `asm`, `auto`, `break`, `case`, `const`, `continue`, `default`, `do`, `else`, `enum`, `extern`, `for`, `goto`, `if`, `register`, `restricted`, `return`, `sizeof`, `static`, `struct`, `switch`, `typedef`, `union`, `volatile`, `while`), Keyword, nil},
|
||||||
{`(bool|int|long|float|short|double|char|unsigned|signed|void)\b`, KeywordType, nil},
|
{`(bool|int|long|float|short|double|char((8|16|32)_t)?|wchar_t|unsigned|signed|void|u?int(_fast|_least|)(8|16|32|64)_t)\b`, KeywordType, nil},
|
||||||
{Words(``, `\b`, `inline`, `_inline`, `__inline`, `naked`, `restrict`, `thread`, `typename`), KeywordReserved, nil},
|
{Words(``, `\b`, `inline`, `_inline`, `__inline`, `naked`, `restrict`, `thread`, `typename`), KeywordReserved, nil},
|
||||||
{`(__m(128i|128d|128|64))\b`, KeywordReserved, nil},
|
{`(__m(128i|128d|128|64))\b`, KeywordReserved, nil},
|
||||||
{Words(`__`, `\b`, `asm`, `int8`, `based`, `except`, `int16`, `stdcall`, `cdecl`, `fastcall`, `int32`, `declspec`, `finally`, `int64`, `try`, `leave`, `wchar_t`, `w64`, `unaligned`, `raise`, `noop`, `identifier`, `forceinline`, `assume`), KeywordReserved, nil},
|
{Words(`__`, `\b`, `asm`, `int8`, `based`, `except`, `int16`, `stdcall`, `cdecl`, `fastcall`, `int32`, `declspec`, `finally`, `int64`, `try`, `leave`, `w64`, `unaligned`, `raise`, `noop`, `identifier`, `forceinline`, `assume`), KeywordReserved, nil},
|
||||||
{`(true|false|NULL)\b`, NameBuiltin, nil},
|
{`(true|false|NULL)\b`, NameBuiltin, nil},
|
||||||
{`([a-zA-Z_]\w*)(\s*)(:)(?!:)`, ByGroups(NameLabel, Text, Punctuation), nil},
|
{`([a-zA-Z_]\w*)(\s*)(:)(?!:)`, ByGroups(NameLabel, Text, Punctuation), nil},
|
||||||
{`[a-zA-Z_]\w*`, Name, nil},
|
{`[a-zA-Z_]\w*`, Name, nil},
|
||||||
|
@ -49,8 +50,9 @@ var CPP = internal.Register(MustNewLexer(
|
||||||
{`__(offload|blockingoffload|outer)\b`, KeywordPseudo, nil},
|
{`__(offload|blockingoffload|outer)\b`, KeywordPseudo, nil},
|
||||||
},
|
},
|
||||||
"classname": {
|
"classname": {
|
||||||
|
{`(\[\[.+\]\])(\s*)`, ByGroups(NameAttribute, Text), nil},
|
||||||
{`[a-zA-Z_]\w*`, NameClass, Pop(1)},
|
{`[a-zA-Z_]\w*`, NameClass, Pop(1)},
|
||||||
{`\s*(?=>)`, Text, Pop(1)},
|
{`\s*(?=[>{])`, Text, Pop(1)},
|
||||||
},
|
},
|
||||||
"whitespace": {
|
"whitespace": {
|
||||||
{`^#if\s+0`, CommentPreproc, Push("if0")},
|
{`^#if\s+0`, CommentPreproc, Push("if0")},
|
||||||
|
@ -67,8 +69,8 @@ var CPP = internal.Register(MustNewLexer(
|
||||||
"statement": {
|
"statement": {
|
||||||
Include("whitespace"),
|
Include("whitespace"),
|
||||||
Include("statements"),
|
Include("statements"),
|
||||||
{`[{}]`, Punctuation, nil},
|
{`[{]`, Punctuation, Push("root")},
|
||||||
{`;`, Punctuation, Pop(1)},
|
{`[;}]`, Punctuation, Pop(1)},
|
||||||
},
|
},
|
||||||
"function": {
|
"function": {
|
||||||
Include("whitespace"),
|
Include("whitespace"),
|
||||||
|
|
|
@ -13,6 +13,7 @@ var CSharp = internal.Register(MustNewLexer(
|
||||||
Filenames: []string{"*.cs"},
|
Filenames: []string{"*.cs"},
|
||||||
MimeTypes: []string{"text/x-csharp"},
|
MimeTypes: []string{"text/x-csharp"},
|
||||||
DotAll: true,
|
DotAll: true,
|
||||||
|
EnsureNL: true,
|
||||||
},
|
},
|
||||||
Rules{
|
Rules{
|
||||||
"root": {
|
"root": {
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
package d
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/alecthomas/chroma" // nolint
|
||||||
|
"github.com/alecthomas/chroma/lexers/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// D lexer. https://dlang.org/spec/lex.html
|
||||||
|
var D = internal.Register(MustNewLexer(
|
||||||
|
&Config{
|
||||||
|
Name: "D",
|
||||||
|
Aliases: []string{"d"},
|
||||||
|
Filenames: []string{"*.d", "*.di"},
|
||||||
|
MimeTypes: []string{"text/x-d"},
|
||||||
|
EnsureNL: true,
|
||||||
|
},
|
||||||
|
Rules{
|
||||||
|
"root": {
|
||||||
|
{`[^\S\n]+`, Text, nil},
|
||||||
|
|
||||||
|
// https://dlang.org/spec/lex.html#comment
|
||||||
|
{`//.*?\n`, CommentSingle, nil},
|
||||||
|
{`/\*.*?\*/`, CommentMultiline, nil},
|
||||||
|
{`/\+.*?\+/`, CommentMultiline, nil},
|
||||||
|
|
||||||
|
// https://dlang.org/spec/lex.html#keywords
|
||||||
|
{`(asm|assert|body|break|case|cast|catch|continue|default|debug|delete|deprecated|do|else|finally|for|foreach|foreach_reverse|goto|if|in|invariant|is|macro|mixin|new|out|pragma|return|super|switch|this|throw|try|version|while|with)\b`, Keyword, nil},
|
||||||
|
{`__(FILE|FILE_FULL_PATH|MODULE|LINE|FUNCTION|PRETTY_FUNCTION|DATE|EOF|TIME|TIMESTAMP|VENDOR|VERSION)__\b`, NameBuiltin, nil},
|
||||||
|
{`__(traits|vector|parameters)\b`, NameBuiltin, nil},
|
||||||
|
{`((?:(?:[^\W\d]|\$)[\w.\[\]$<>]*\s+)+?)((?:[^\W\d]|\$)[\w$]*)(\s*)(\()`, ByGroups(UsingSelf("root"), NameFunction, Text, Operator), nil},
|
||||||
|
|
||||||
|
// https://dlang.org/spec/attribute.html#uda
|
||||||
|
{`@[\w.]*`, NameDecorator, nil},
|
||||||
|
{`(abstract|auto|alias|align|const|delegate|enum|export|final|function|inout|lazy|nothrow|override|package|private|protected|public|pure|static|synchronized|template|volatile|__gshared)\b`, KeywordDeclaration, nil},
|
||||||
|
|
||||||
|
// https://dlang.org/spec/type.html#basic-data-types
|
||||||
|
{`(void|bool|byte|ubyte|short|ushort|int|uint|long|ulong|cent|ucent|float|double|real|ifloat|idouble|ireal|cfloat|cdouble|creal|char|wchar|dchar|string|wstring|dstring)\b`, KeywordType, nil},
|
||||||
|
{`(module)(\s+)`, ByGroups(KeywordNamespace, Text), Push("import")},
|
||||||
|
{`(true|false|null)\b`, KeywordConstant, nil},
|
||||||
|
{`(class|interface|struct|template|union)(\s+)`, ByGroups(KeywordDeclaration, Text), Push("class")},
|
||||||
|
{`(import)(\s+)`, ByGroups(KeywordNamespace, Text), Push("import")},
|
||||||
|
|
||||||
|
// https://dlang.org/spec/lex.html#string_literals
|
||||||
|
// TODO support delimited strings
|
||||||
|
{`[qr]?"(\\\\|\\"|[^"])*"[cwd]?`, LiteralString, nil},
|
||||||
|
{"(`)([^`]*)(`)[cwd]?", LiteralString, nil},
|
||||||
|
{`'\\.'|'[^\\]'|'\\u[0-9a-fA-F]{4}'`, LiteralStringChar, nil},
|
||||||
|
{`(\.)((?:[^\W\d]|\$)[\w$]*)`, ByGroups(Operator, NameAttribute), nil},
|
||||||
|
{`^\s*([^\W\d]|\$)[\w$]*:`, NameLabel, nil},
|
||||||
|
|
||||||
|
// https://dlang.org/spec/lex.html#floatliteral
|
||||||
|
{`([0-9][0-9_]*\.([0-9][0-9_]*)?|\.[0-9][0-9_]*)([eE][+\-]?[0-9][0-9_]*)?[fFL]?i?|[0-9][eE][+\-]?[0-9][0-9_]*[fFL]?|[0-9]([eE][+\-]?[0-9][0-9_]*)?[fFL]|0[xX]([0-9a-fA-F][0-9a-fA-F_]*\.?|([0-9a-fA-F][0-9a-fA-F_]*)?\.[0-9a-fA-F][0-9a-fA-F_]*)[pP][+\-]?[0-9][0-9_]*[fFL]?`, LiteralNumberFloat, nil},
|
||||||
|
// https://dlang.org/spec/lex.html#integerliteral
|
||||||
|
{`0[xX][0-9a-fA-F][0-9a-fA-F_]*[lL]?`, LiteralNumberHex, nil},
|
||||||
|
{`0[bB][01][01_]*[lL]?`, LiteralNumberBin, nil},
|
||||||
|
{`0[0-7_]+[lL]?`, LiteralNumberOct, nil},
|
||||||
|
{`0|[1-9][0-9_]*[lL]?`, LiteralNumberInteger, nil},
|
||||||
|
{`([~^*!%&\[\](){}<>|+=:;,./?-]|q{)`, Operator, nil},
|
||||||
|
{`([^\W\d]|\$)[\w$]*`, Name, nil},
|
||||||
|
{`\n`, Text, nil},
|
||||||
|
},
|
||||||
|
"class": {
|
||||||
|
{`([^\W\d]|\$)[\w$]*`, NameClass, Pop(1)},
|
||||||
|
},
|
||||||
|
"import": {
|
||||||
|
{`[\w.]+\*?`, NameNamespace, Pop(1)},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
))
|
|
@ -2,8 +2,9 @@ package d
|
||||||
|
|
||||||
import (
|
import (
|
||||||
. "github.com/alecthomas/chroma" // nolint
|
. "github.com/alecthomas/chroma" // nolint
|
||||||
. "github.com/alecthomas/chroma/lexers/b"
|
"github.com/alecthomas/chroma/lexers/b"
|
||||||
"github.com/alecthomas/chroma/lexers/internal"
|
"github.com/alecthomas/chroma/lexers/internal"
|
||||||
|
"github.com/alecthomas/chroma/lexers/j"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Docker lexer.
|
// Docker lexer.
|
||||||
|
@ -17,11 +18,14 @@ var Docker = internal.Register(MustNewLexer(
|
||||||
},
|
},
|
||||||
Rules{
|
Rules{
|
||||||
"root": {
|
"root": {
|
||||||
{`^(ONBUILD)(\s+)((?:FROM|MAINTAINER|CMD|EXPOSE|ENV|ADD|ENTRYPOINT|VOLUME|WORKDIR))\b`, ByGroups(NameKeyword, TextWhitespace, Keyword), nil},
|
|
||||||
{`^((?:FROM|MAINTAINER|CMD|EXPOSE|ENV|ADD|ENTRYPOINT|VOLUME|WORKDIR))\b(.*)`, ByGroups(Keyword, LiteralString), nil},
|
|
||||||
{`#.*`, Comment, nil},
|
{`#.*`, Comment, nil},
|
||||||
{`RUN`, Keyword, nil},
|
{`(ONBUILD)((?:\s*\\?\s*))`, ByGroups(Keyword, Using(b.Bash)), nil},
|
||||||
{`(.*\\\n)*.+`, Using(Bash), nil},
|
{`(HEALTHCHECK)(((?:\s*\\?\s*)--\w+=\w+(?:\s*\\?\s*))*)`, ByGroups(Keyword, Using(b.Bash)), nil},
|
||||||
|
{`(VOLUME|ENTRYPOINT|CMD|SHELL)((?:\s*\\?\s*))(\[.*?\])`, ByGroups(Keyword, Using(b.Bash), Using(j.JSON)), nil},
|
||||||
|
{`(LABEL|ENV|ARG)(((?:\s*\\?\s*)\w+=\w+(?:\s*\\?\s*))*)`, ByGroups(Keyword, Using(b.Bash)), nil},
|
||||||
|
{`((?:FROM|MAINTAINER|EXPOSE|WORKDIR|USER|STOPSIGNAL)|VOLUME)\b(.*)`, ByGroups(Keyword, LiteralString), nil},
|
||||||
|
{`((?:RUN|CMD|ENTRYPOINT|ENV|ARG|LABEL|ADD|COPY))`, Keyword, nil},
|
||||||
|
{`(.*\\\n)*.+`, Using(b.Bash), nil},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
|
|
|
@ -10,7 +10,7 @@ var Forth = internal.Register(MustNewLexer(
|
||||||
&Config{
|
&Config{
|
||||||
Name: "Forth",
|
Name: "Forth",
|
||||||
Aliases: []string{"forth"},
|
Aliases: []string{"forth"},
|
||||||
Filenames: []string{"*.frt", "*.fs"},
|
Filenames: []string{"*.frt", "*.fth", "*.fs"},
|
||||||
MimeTypes: []string{"application/x-forth"},
|
MimeTypes: []string{"application/x-forth"},
|
||||||
CaseInsensitive: true,
|
CaseInsensitive: true,
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
package g
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/alecthomas/chroma" // nolint
|
||||||
|
"github.com/alecthomas/chroma/lexers/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Go lexer.
|
||||||
|
var Graphql = internal.Register(MustNewLexer(
|
||||||
|
&Config{
|
||||||
|
Name: "GraphQL",
|
||||||
|
Aliases: []string{"graphql", "graphqls", "gql"},
|
||||||
|
Filenames: []string{"*.graphql", "*.graphqls"},
|
||||||
|
},
|
||||||
|
Rules{
|
||||||
|
"root": {
|
||||||
|
{`(query|mutation|subscription|fragment|scalar|implements|interface|union|enum|input|type)`, KeywordDeclaration, Push("type")},
|
||||||
|
{`(on|extend|schema|directive|\.\.\.)`, KeywordDeclaration, nil},
|
||||||
|
{`(QUERY|MUTATION|SUBSCRIPTION|FIELD|FRAGMENT_DEFINITION|FRAGMENT_SPREAD|INLINE_FRAGMENT|SCHEMA|SCALAR|OBJECT|FIELD_DEFINITION|ARGUMENT_DEFINITION|INTERFACE|UNION|ENUM|ENUM_VALUE|INPUT_OBJECT|INPUT_FIELD_DEFINITION)\b`, KeywordConstant, nil},
|
||||||
|
{`[^\W\d]\w*`, NameProperty, nil},
|
||||||
|
{`\@\w+`, NameDecorator, nil},
|
||||||
|
{`:`, Punctuation, Push("type")},
|
||||||
|
{`[\(\)\{\}\[\],!\|=]`, Punctuation, nil},
|
||||||
|
{`\$\w+`, NameVariable, nil},
|
||||||
|
{`\d+i`, LiteralNumber, nil},
|
||||||
|
{`\d+\.\d*([Ee][-+]\d+)?i`, LiteralNumber, nil},
|
||||||
|
{`\.\d+([Ee][-+]\d+)?i`, LiteralNumber, nil},
|
||||||
|
{`\d+[Ee][-+]\d+i`, LiteralNumber, nil},
|
||||||
|
{`\d+(\.\d+[eE][+\-]?\d+|\.\d*|[eE][+\-]?\d+)`, LiteralNumberFloat, nil},
|
||||||
|
{`\.\d+([eE][+\-]?\d+)?`, LiteralNumberFloat, nil},
|
||||||
|
{`(0|[1-9][0-9]*)`, LiteralNumberInteger, nil},
|
||||||
|
{`"""[\x00-\x7F]*?"""`, LiteralString, nil},
|
||||||
|
{`"(\\["\\abfnrtv]|\\x[0-9a-fA-F]{2}|\\[0-7]{1,3}|\\u[0-9a-fA-F]{4}|\\U[0-9a-fA-F]{8}|[^\\])"`, LiteralStringChar, nil},
|
||||||
|
{`"(\\\\|\\"|[^"])*"`, LiteralString, nil},
|
||||||
|
{`"(true|false|null)*"`, Literal, nil},
|
||||||
|
{`[\r\n\s]+`, Whitespace, nil},
|
||||||
|
{`#[^\r\n]*`, Comment, nil},
|
||||||
|
},
|
||||||
|
// Treats the next word as a class, default rules it would be a property
|
||||||
|
"type": {
|
||||||
|
{`[^\W\d]\w*`, NameClass, Pop(1)},
|
||||||
|
Include("root"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
))
|
|
@ -619,13 +619,14 @@ func haxePreProcMutator(state *LexerState) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
proc := state.Groups[2]
|
proc := state.Groups[2]
|
||||||
if proc == "if" {
|
switch proc {
|
||||||
|
case "if":
|
||||||
stack = append(stack, state.Stack)
|
stack = append(stack, state.Stack)
|
||||||
} else if proc == "else" || proc == "elseif" {
|
case "else", "elseif":
|
||||||
if len(stack) > 0 {
|
if len(stack) > 0 {
|
||||||
state.Stack = stack[len(stack)-1]
|
state.Stack = stack[len(stack)-1]
|
||||||
}
|
}
|
||||||
} else if proc == "end" {
|
case "end":
|
||||||
stack = stack[:len(stack)-1]
|
stack = stack[:len(stack)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
package h
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/alecthomas/chroma" // nolint
|
||||||
|
"github.com/alecthomas/chroma/lexers/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HCL lexer.
|
||||||
|
var HCL = internal.Register(MustNewLexer(
|
||||||
|
&Config{
|
||||||
|
Name: "HCL",
|
||||||
|
Aliases: []string{"hcl"},
|
||||||
|
Filenames: []string{"*.hcl"},
|
||||||
|
MimeTypes: []string{"application/x-hcl"},
|
||||||
|
},
|
||||||
|
Rules{
|
||||||
|
"root": {
|
||||||
|
Include("string"),
|
||||||
|
Include("punctuation"),
|
||||||
|
Include("curly"),
|
||||||
|
Include("basic"),
|
||||||
|
Include("whitespace"),
|
||||||
|
{`[0-9]+`, LiteralNumber, nil},
|
||||||
|
},
|
||||||
|
"basic": {
|
||||||
|
{Words(`\b`, `\b`, `true`, `false`), KeywordType, nil},
|
||||||
|
{`\s*/\*`, CommentMultiline, Push("comment")},
|
||||||
|
{`\s*#.*\n`, CommentSingle, nil},
|
||||||
|
{`(.*?)(\s*)(=)`, ByGroups(Name, Text, Operator), nil},
|
||||||
|
{`\d+`, Number, nil},
|
||||||
|
{`\b\w+\b`, Keyword, nil},
|
||||||
|
{`\$\{`, LiteralStringInterpol, Push("var_builtin")},
|
||||||
|
},
|
||||||
|
"function": {
|
||||||
|
{`(\s+)(".*")(\s+)`, ByGroups(Text, LiteralString, Text), nil},
|
||||||
|
Include("punctuation"),
|
||||||
|
Include("curly"),
|
||||||
|
},
|
||||||
|
"var_builtin": {
|
||||||
|
{`\$\{`, LiteralStringInterpol, Push()},
|
||||||
|
{Words(`\b`, `\b`, `concat`, `file`, `join`, `lookup`, `element`), NameBuiltin, nil},
|
||||||
|
Include("string"),
|
||||||
|
Include("punctuation"),
|
||||||
|
{`\s+`, Text, nil},
|
||||||
|
{`\}`, LiteralStringInterpol, Pop(1)},
|
||||||
|
},
|
||||||
|
"string": {
|
||||||
|
{`(".*")`, ByGroups(LiteralStringDouble), nil},
|
||||||
|
},
|
||||||
|
"punctuation": {
|
||||||
|
{`[\[\](),.]`, Punctuation, nil},
|
||||||
|
},
|
||||||
|
"curly": {
|
||||||
|
{`\{`, TextPunctuation, nil},
|
||||||
|
{`\}`, TextPunctuation, nil},
|
||||||
|
},
|
||||||
|
"comment": {
|
||||||
|
{`[^*/]`, CommentMultiline, nil},
|
||||||
|
{`/\*`, CommentMultiline, Push()},
|
||||||
|
{`\*/`, CommentMultiline, Pop(1)},
|
||||||
|
{`[*/]`, CommentMultiline, nil},
|
||||||
|
},
|
||||||
|
"whitespace": {
|
||||||
|
{`\n`, Text, nil},
|
||||||
|
{`\s+`, Text, nil},
|
||||||
|
{`\\\n`, Text, nil},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
))
|
|
@ -77,56 +77,55 @@ func (d *httpBodyContentTyper) Tokenise(options *TokeniseOptions, text string) (
|
||||||
}
|
}
|
||||||
|
|
||||||
return func() Token {
|
return func() Token {
|
||||||
for token := it(); token != EOF; token = it() {
|
token := it()
|
||||||
switch {
|
|
||||||
case token.Type == Name && strings.ToLower(token.Value) == "content-type":
|
|
||||||
{
|
|
||||||
isContentType = true
|
|
||||||
}
|
|
||||||
case token.Type == Literal && isContentType:
|
|
||||||
{
|
|
||||||
isContentType = false
|
|
||||||
contentType = strings.TrimSpace(token.Value)
|
|
||||||
pos := strings.Index(contentType, ";")
|
|
||||||
if pos > 0 {
|
|
||||||
contentType = strings.TrimSpace(contentType[:pos])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case token.Type == Generic && contentType != "":
|
|
||||||
{
|
|
||||||
lexer := internal.MatchMimeType(contentType)
|
|
||||||
|
|
||||||
// application/calendar+xml can be treated as application/xml
|
|
||||||
// if there's not a better match.
|
|
||||||
if lexer == nil && strings.Contains(contentType, "+") {
|
|
||||||
slashPos := strings.Index(contentType, "/")
|
|
||||||
plusPos := strings.LastIndex(contentType, "+")
|
|
||||||
contentType = contentType[:slashPos+1] + contentType[plusPos+1:]
|
|
||||||
lexer = internal.MatchMimeType(contentType)
|
|
||||||
}
|
|
||||||
|
|
||||||
if lexer == nil {
|
|
||||||
token.Type = Text
|
|
||||||
} else {
|
|
||||||
subIterator, err = lexer.Tokenise(nil, token.Value)
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return EOF
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if token == EOF {
|
||||||
|
if subIterator != nil {
|
||||||
|
return subIterator()
|
||||||
}
|
}
|
||||||
|
return EOF
|
||||||
return token
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if subIterator != nil {
|
switch {
|
||||||
for token := subIterator(); token != EOF; token = subIterator() {
|
case token.Type == Name && strings.ToLower(token.Value) == "content-type":
|
||||||
return token
|
{
|
||||||
|
isContentType = true
|
||||||
}
|
}
|
||||||
|
case token.Type == Literal && isContentType:
|
||||||
|
{
|
||||||
|
isContentType = false
|
||||||
|
contentType = strings.TrimSpace(token.Value)
|
||||||
|
pos := strings.Index(contentType, ";")
|
||||||
|
if pos > 0 {
|
||||||
|
contentType = strings.TrimSpace(contentType[:pos])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case token.Type == Generic && contentType != "":
|
||||||
|
{
|
||||||
|
lexer := internal.MatchMimeType(contentType)
|
||||||
|
|
||||||
|
// application/calendar+xml can be treated as application/xml
|
||||||
|
// if there's not a better match.
|
||||||
|
if lexer == nil && strings.Contains(contentType, "+") {
|
||||||
|
slashPos := strings.Index(contentType, "/")
|
||||||
|
plusPos := strings.LastIndex(contentType, "+")
|
||||||
|
contentType = contentType[:slashPos+1] + contentType[plusPos+1:]
|
||||||
|
lexer = internal.MatchMimeType(contentType)
|
||||||
|
}
|
||||||
|
|
||||||
|
if lexer == nil {
|
||||||
|
token.Type = Text
|
||||||
|
} else {
|
||||||
|
subIterator, err = lexer.Tokenise(nil, token.Value)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return EOF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return EOF
|
return token
|
||||||
|
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -20,7 +20,7 @@ var Java = internal.Register(MustNewLexer(
|
||||||
{`//.*?\n`, CommentSingle, nil},
|
{`//.*?\n`, CommentSingle, nil},
|
||||||
{`/\*.*?\*/`, CommentMultiline, nil},
|
{`/\*.*?\*/`, CommentMultiline, nil},
|
||||||
{`(assert|break|case|catch|continue|default|do|else|finally|for|if|goto|instanceof|new|return|switch|this|throw|try|while)\b`, Keyword, nil},
|
{`(assert|break|case|catch|continue|default|do|else|finally|for|if|goto|instanceof|new|return|switch|this|throw|try|while)\b`, Keyword, nil},
|
||||||
{`((?:(?:[^\W\d]|\$)[\w.\[\]$<>]*\s+)+?)((?:[^\W\d]|\$)[\w$]*)(\s*)(\()`, ByGroups(UsingSelf("root"), NameFunction, Text, Operator), nil},
|
{`((?:(?:[^\W\d]|\$)[\w.\[\]$<>]*\s+)+?)((?:[^\W\d]|\$)[\w$]*)(\s*)`, ByGroups(UsingSelf("root"), NameFunction, Text, Operator), nil},
|
||||||
{`@[^\W\d][\w.]*`, NameDecorator, nil},
|
{`@[^\W\d][\w.]*`, NameDecorator, nil},
|
||||||
{`(abstract|const|enum|extends|final|implements|native|private|protected|public|static|strictfp|super|synchronized|throws|transient|volatile)\b`, KeywordDeclaration, nil},
|
{`(abstract|const|enum|extends|final|implements|native|private|protected|public|static|strictfp|super|synchronized|throws|transient|volatile)\b`, KeywordDeclaration, nil},
|
||||||
{`(boolean|byte|char|double|float|int|long|short|void)\b`, KeywordType, nil},
|
{`(boolean|byte|char|double|float|int|long|short|void)\b`, KeywordType, nil},
|
||||||
|
@ -30,7 +30,7 @@ var Java = internal.Register(MustNewLexer(
|
||||||
{`(import(?:\s+static)?)(\s+)`, ByGroups(KeywordNamespace, Text), Push("import")},
|
{`(import(?:\s+static)?)(\s+)`, ByGroups(KeywordNamespace, Text), Push("import")},
|
||||||
{`"(\\\\|\\"|[^"])*"`, LiteralString, nil},
|
{`"(\\\\|\\"|[^"])*"`, LiteralString, nil},
|
||||||
{`'\\.'|'[^\\]'|'\\u[0-9a-fA-F]{4}'`, LiteralStringChar, nil},
|
{`'\\.'|'[^\\]'|'\\u[0-9a-fA-F]{4}'`, LiteralStringChar, nil},
|
||||||
{`(\.)((?:[^\W\d]|\$)[\w$]*)`, ByGroups(Operator, NameAttribute), nil},
|
{`(\.)((?:[^\W\d]|\$)[\w$]*)`, ByGroups(Punctuation, NameAttribute), nil},
|
||||||
{`^\s*([^\W\d]|\$)[\w$]*:`, NameLabel, nil},
|
{`^\s*([^\W\d]|\$)[\w$]*:`, NameLabel, nil},
|
||||||
{`([^\W\d]|\$)[\w$]*`, Name, nil},
|
{`([^\W\d]|\$)[\w$]*`, Name, nil},
|
||||||
{`([0-9][0-9_]*\.([0-9][0-9_]*)?|\.[0-9][0-9_]*)([eE][+\-]?[0-9][0-9_]*)?[fFdD]?|[0-9][eE][+\-]?[0-9][0-9_]*[fFdD]?|[0-9]([eE][+\-]?[0-9][0-9_]*)?[fFdD]|0[xX]([0-9a-fA-F][0-9a-fA-F_]*\.?|([0-9a-fA-F][0-9a-fA-F_]*)?\.[0-9a-fA-F][0-9a-fA-F_]*)[pP][+\-]?[0-9][0-9_]*[fFdD]?`, LiteralNumberFloat, nil},
|
{`([0-9][0-9_]*\.([0-9][0-9_]*)?|\.[0-9][0-9_]*)([eE][+\-]?[0-9][0-9_]*)?[fFdD]?|[0-9][eE][+\-]?[0-9][0-9_]*[fFdD]?|[0-9]([eE][+\-]?[0-9][0-9_]*)?[fFdD]|0[xX]([0-9a-fA-F][0-9a-fA-F_]*\.?|([0-9a-fA-F][0-9a-fA-F_]*)?\.[0-9a-fA-F][0-9a-fA-F_]*)[pP][+\-]?[0-9][0-9_]*[fFdD]?`, LiteralNumberFloat, nil},
|
||||||
|
@ -38,7 +38,8 @@ var Java = internal.Register(MustNewLexer(
|
||||||
{`0[bB][01][01_]*[lL]?`, LiteralNumberBin, nil},
|
{`0[bB][01][01_]*[lL]?`, LiteralNumberBin, nil},
|
||||||
{`0[0-7_]+[lL]?`, LiteralNumberOct, nil},
|
{`0[0-7_]+[lL]?`, LiteralNumberOct, nil},
|
||||||
{`0|[1-9][0-9_]*[lL]?`, LiteralNumberInteger, nil},
|
{`0|[1-9][0-9_]*[lL]?`, LiteralNumberInteger, nil},
|
||||||
{`[~^*!%&\[\](){}<>|+=:;,./?-]`, Operator, nil},
|
{`[~^*!%&<>|+=:/?-]`, Operator, nil},
|
||||||
|
{`[\[\](){};,.]`, Punctuation, nil},
|
||||||
{`\n`, Text, nil},
|
{`\n`, Text, nil},
|
||||||
},
|
},
|
||||||
"class": {
|
"class": {
|
||||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -21,12 +21,13 @@ var Julia = internal.Register(MustNewLexer(
|
||||||
{`#.*$`, Comment, nil},
|
{`#.*$`, Comment, nil},
|
||||||
{`[\[\]{}(),;]`, Punctuation, nil},
|
{`[\[\]{}(),;]`, Punctuation, nil},
|
||||||
{`in\b`, KeywordPseudo, nil},
|
{`in\b`, KeywordPseudo, nil},
|
||||||
|
{`isa\b`, KeywordPseudo, nil},
|
||||||
{`(true|false)\b`, KeywordConstant, nil},
|
{`(true|false)\b`, KeywordConstant, nil},
|
||||||
{`(local|global|const)\b`, KeywordDeclaration, nil},
|
{`(local|global|const)\b`, KeywordDeclaration, nil},
|
||||||
{Words(``, `\b`, `function`, `type`, `typealias`, `abstract`, `immutable`, `baremodule`, `begin`, `bitstype`, `break`, `catch`, `ccall`, `continue`, `do`, `else`, `elseif`, `end`, `export`, `finally`, `for`, `if`, `import`, `importall`, `let`, `macro`, `module`, `quote`, `return`, `try`, `using`, `while`), Keyword, nil},
|
{Words(``, `\b`, `function`, `abstract type`, `primitive type`, `baremodule`, `begin`, `bitstype`, `break`, `catch`, `ccall`, `continue`, `do`, `else`, `elseif`, `end`, `export`, `finally`, `for`, `if`, `import`, `let`, `macro`, `module`, `mutable`, `quote`, `return`, `struct`, `try`, `using`, `while`), Keyword, nil},
|
||||||
{Words(``, `\b`, `ANY`, `ASCIIString`, `AbstractArray`, `AbstractChannel`, `AbstractFloat`, `AbstractMatrix`, `AbstractRNG`, `AbstractSparseArray`, `AbstractSparseMatrix`, `AbstractSparseVector`, `AbstractString`, `AbstractVecOrMat`, `AbstractVector`, `Any`, `ArgumentError`, `Array`, `AssertionError`, `Associative`, `Base64DecodePipe`, `Base64EncodePipe`, `Bidiagonal`, `BigFloat`, `BigInt`, `BitArray`, `BitMatrix`, `BitVector`, `Bool`, `BoundsError`, `Box`, `BufferStream`, `CapturedException`, `CartesianIndex`, `CartesianRange`, `Cchar`, `Cdouble`, `Cfloat`, `Channel`, `Char`, `Cint`, `Cintmax_t`, `Clong`, `Clonglong`, `ClusterManager`, `Cmd`, `Coff_t`, `Colon`, `Complex`, `Complex128`, `Complex32`, `Complex64`, `CompositeException`, `Condition`, `Cptrdiff_t`, `Cshort`, `Csize_t`, `Cssize_t`, `Cstring`, `Cuchar`, `Cuint`, `Cuintmax_t`, `Culong`, `Culonglong`, `Cushort`, `Cwchar_t`, `Cwstring`, `DataType`, `Date`, `DateTime`, `DenseArray`, `DenseMatrix`, `DenseVecOrMat`, `DenseVector`, `Diagonal`, `Dict`, `DimensionMismatch`, `Dims`, `DirectIndexString`, `Display`, `DivideError`, `DomainError`, `EOFError`, `EachLine`, `Enum`, `Enumerate`, `ErrorException`, `Exception`, `Expr`, `Factorization`, `FileMonitor`, `FileOffset`, `Filter`, `Float16`, `Float32`, `Float64`, `FloatRange`, `Function`, `GenSym`, `GlobalRef`, `GotoNode`, `HTML`, `Hermitian`, `IO`, `IOBuffer`, `IOStream`, `IPv4`, `IPv6`, `InexactError`, `InitError`, `Int`, `Int128`, `Int16`, `Int32`, `Int64`, `Int8`, `IntSet`, `Integer`, `InterruptException`, `IntrinsicFunction`, `InvalidStateException`, `Irrational`, `KeyError`, `LabelNode`, `LambdaStaticData`, `LinSpace`, `LineNumberNode`, `LoadError`, `LocalProcess`, `LowerTriangular`, `MIME`, `Matrix`, `MersenneTwister`, `Method`, `MethodError`, `MethodTable`, `Module`, `NTuple`, `NewvarNode`, `NullException`, `Nullable`, `Number`, `ObjectIdDict`, `OrdinalRange`, `OutOfMemoryError`, `OverflowError`, `Pair`, `ParseError`, `PartialQuickSort`, `Pipe`, `PollingFileWatcher`, `ProcessExitedException`, `ProcessGroup`, `Ptr`, `QuoteNode`, `RandomDevice`, `Range`, `Rational`, `RawFD`, `ReadOnlyMemoryError`, `Real`, `ReentrantLock`, `Ref`, `Regex`, `RegexMatch`, `RemoteException`, `RemoteRef`, `RepString`, `RevString`, `RopeString`, `RoundingMode`, `SegmentationFault`, `SerializationState`, `Set`, `SharedArray`, `SharedMatrix`, `SharedVector`, `Signed`, `SimpleVector`, `SparseMatrixCSC`, `StackOverflowError`, `StatStruct`, `StepRange`, `StridedArray`, `StridedMatrix`, `StridedVecOrMat`, `StridedVector`, `SubArray`, `SubString`, `SymTridiagonal`, `Symbol`, `SymbolNode`, `Symmetric`, `SystemError`, `TCPSocket`, `Task`, `Text`, `TextDisplay`, `Timer`, `TopNode`, `Tridiagonal`, `Tuple`, `Type`, `TypeConstructor`, `TypeError`, `TypeName`, `TypeVar`, `UDPSocket`, `UInt`, `UInt128`, `UInt16`, `UInt32`, `UInt64`, `UInt8`, `UTF16String`, `UTF32String`, `UTF8String`, `UndefRefError`, `UndefVarError`, `UnicodeError`, `UniformScaling`, `Union`, `UnitRange`, `Unsigned`, `UpperTriangular`, `Val`, `Vararg`, `VecOrMat`, `Vector`, `VersionNumber`, `Void`, `WString`, `WeakKeyDict`, `WeakRef`, `WorkerConfig`, `Zip`), KeywordType, nil},
|
{Words(``, `\b`, `ASCIIString`, `AbstractArray`, `AbstractChannel`, `AbstractDict`, `AbstractFloat`, `AbstractMatrix`, `AbstractRNG`, `AbstractSparseArray`, `AbstractSparseMatrix`, `AbstractSparseVector`, `AbstractString`, `AbstractVecOrMat`, `AbstractVector`, `Any`, `ArgumentError`, `Array`, `AssertionError`, `Base64DecodePipe`, `Base64EncodePipe`, `Bidiagonal`, `BigFloat`, `BigInt`, `BitArray`, `BitMatrix`, `BitVector`, `Bool`, `BoundsError`, `Box`, `BufferStream`, `CapturedException`, `CartesianIndex`, `CartesianRange`, `Cchar`, `Cdouble`, `Cfloat`, `Channel`, `Char`, `Cint`, `Cintmax_t`, `Clong`, `Clonglong`, `ClusterManager`, `Cmd`, `Coff_t`, `Colon`, `Complex`, `Complex128`, `Complex32`, `Complex64`, `CompositeException`, `Condition`, `Cptrdiff_t`, `Cshort`, `Csize_t`, `Cssize_t`, `Cstring`, `Cuchar`, `Cuint`, `Cuintmax_t`, `Culong`, `Culonglong`, `Cushort`, `Cwchar_t`, `Cwstring`, `DataType`, `Date`, `DateTime`, `DenseArray`, `DenseMatrix`, `DenseVecOrMat`, `DenseVector`, `Diagonal`, `Dict`, `DimensionMismatch`, `Dims`, `DirectIndexString`, `Display`, `DivideError`, `DomainError`, `EOFError`, `EachLine`, `Enum`, `Enumerate`, `ErrorException`, `Exception`, `Expr`, `Factorization`, `FileMonitor`, `FileOffset`, `Filter`, `Float16`, `Float32`, `Float64`, `FloatRange`, `Function`, `GenSym`, `GlobalRef`, `GotoNode`, `HTML`, `Hermitian`, `IO`, `IOBuffer`, `IOStream`, `IPv4`, `IPv6`, `InexactError`, `InitError`, `Int`, `Int128`, `Int16`, `Int32`, `Int64`, `Int8`, `IntSet`, `Integer`, `InterruptException`, `IntrinsicFunction`, `InvalidStateException`, `Irrational`, `KeyError`, `LabelNode`, `LambdaStaticData`, `LinSpace`, `LineNumberNode`, `LoadError`, `LocalProcess`, `LowerTriangular`, `MIME`, `Matrix`, `MersenneTwister`, `Method`, `MethodError`, `MethodTable`, `Module`, `NTuple`, `NewvarNode`, `NullException`, `Nullable`, `Number`, `ObjectIdDict`, `OrdinalRange`, `OutOfMemoryError`, `OverflowError`, `Pair`, `ParseError`, `PartialQuickSort`, `Pipe`, `PollingFileWatcher`, `ProcessExitedException`, `ProcessGroup`, `Ptr`, `QuoteNode`, `RandomDevice`, `Range`, `Rational`, `RawFD`, `ReadOnlyMemoryError`, `Real`, `ReentrantLock`, `Ref`, `Regex`, `RegexMatch`, `RemoteException`, `RemoteRef`, `RepString`, `RevString`, `RopeString`, `RoundingMode`, `SegmentationFault`, `SerializationState`, `Set`, `SharedArray`, `SharedMatrix`, `SharedVector`, `Signed`, `SimpleVector`, `SparseMatrixCSC`, `StackOverflowError`, `StatStruct`, `StepRange`, `StridedArray`, `StridedMatrix`, `StridedVecOrMat`, `StridedVector`, `SubArray`, `SubString`, `SymTridiagonal`, `Symbol`, `SymbolNode`, `Symmetric`, `SystemError`, `TCPSocket`, `Task`, `Text`, `TextDisplay`, `Timer`, `TopNode`, `Tridiagonal`, `Tuple`, `Type`, `TypeConstructor`, `TypeError`, `TypeName`, `TypeVar`, `UDPSocket`, `UInt`, `UInt128`, `UInt16`, `UInt32`, `UInt64`, `UInt8`, `UTF16String`, `UTF32String`, `UTF8String`, `UndefRefError`, `UndefVarError`, `UnicodeError`, `UniformScaling`, `Union`, `UnitRange`, `Unsigned`, `UpperTriangular`, `Val`, `Vararg`, `VecOrMat`, `Vector`, `VersionNumber`, `Void`, `WString`, `WeakKeyDict`, `WeakRef`, `WorkerConfig`, `Zip`), KeywordType, nil},
|
||||||
{Words(``, `\b`, `ARGS`, `CPU_CORES`, `C_NULL`, `DevNull`, `ENDIAN_BOM`, `ENV`, `I`, `Inf`, `Inf16`, `Inf32`, `Inf64`, `InsertionSort`, `JULIA_HOME`, `LOAD_PATH`, `MergeSort`, `NaN`, `NaN16`, `NaN32`, `NaN64`, `OS_NAME`, `QuickSort`, `RoundDown`, `RoundFromZero`, `RoundNearest`, `RoundNearestTiesAway`, `RoundNearestTiesUp`, `RoundToZero`, `RoundUp`, `STDERR`, `STDIN`, `STDOUT`, `VERSION`, `WORD_SIZE`, `catalan`, `e`, `eu`, `eulergamma`, `golden`, `im`, `nothing`, `pi`, `γ`, `π`, `φ`), NameBuiltin, nil},
|
{Words(``, `\b`, `ARGS`, `CPU_CORES`, `C_NULL`, `DevNull`, `ENDIAN_BOM`, `ENV`, `I`, `Inf`, `Inf16`, `Inf32`, `Inf64`, `InsertionSort`, `JULIA_HOME`, `LOAD_PATH`, `MergeSort`, `NaN`, `NaN16`, `NaN32`, `NaN64`, `OS_NAME`, `QuickSort`, `RoundDown`, `RoundFromZero`, `RoundNearest`, `RoundNearestTiesAway`, `RoundNearestTiesUp`, `RoundToZero`, `RoundUp`, `STDERR`, `STDIN`, `STDOUT`, `VERSION`, `WORD_SIZE`, `catalan`, `e`, `eu`, `eulergamma`, `golden`, `im`, `nothing`, `pi`, `γ`, `π`, `φ`), NameBuiltin, nil},
|
||||||
{Words(``, ``, `=`, `:=`, `+=`, `-=`, `*=`, `/=`, `//=`, `.//=`, `.*=`, `./=`, `\=`, `.\=`, `^=`, `.^=`, `÷=`, `.÷=`, `%=`, `.%=`, `|=`, `&=`, `$=`, `=>`, `<<=`, `>>=`, `>>>=`, `~`, `.+=`, `.-=`, `?`, `--`, `-->`, `||`, `&&`, `>`, `<`, `>=`, `≥`, `<=`, `≤`, `==`, `===`, `≡`, `!=`, `≠`, `!==`, `≢`, `.>`, `.<`, `.>=`, `.≥`, `.<=`, `.≤`, `.==`, `.!=`, `.≠`, `.=`, `.!`, `<:`, `>:`, `∈`, `∉`, `∋`, `∌`, `⊆`, `⊈`, `⊂`, `⊄`, `⊊`, `|>`, `<|`, `:`, `+`, `-`, `.+`, `.-`, `|`, `∪`, `$`, `<<`, `>>`, `>>>`, `.<<`, `.>>`, `.>>>`, `*`, `/`, `./`, `÷`, `.÷`, `%`, `⋅`, `.%`, `.*`, `\`, `.\`, `&`, `∩`, `//`, `.//`, `^`, `.^`, `::`, `.`, `+`, `-`, `!`, `~`, `√`, `∛`, `∜`), Operator, nil},
|
{Words(``, ``, `=`, `:=`, `+=`, `-=`, `*=`, `/=`, `//=`, `.//=`, `.*=`, `./=`, `\=`, `.\=`, `^=`, `.^=`, `÷=`, `.÷=`, `%=`, `.%=`, `|=`, `&=`, `$=`, `=>`, `<<=`, `>>=`, `>>>=`, `~`, `.+=`, `.-=`, `?`, `--`, `-->`, `||`, `&&`, `>`, `<`, `>=`, `≥`, `<=`, `≤`, `==`, `===`, `≡`, `!=`, `≠`, `!==`, `≢`, `.>`, `.<`, `.>=`, `.≥`, `.<=`, `.≤`, `.==`, `.!=`, `.≠`, `.=`, `.!`, `<:`, `>:`, `∈`, `∉`, `∋`, `∌`, `⊆`, `⊈`, `⊂`, `⊄`, `⊊`, `|>`, `<|`, `:`, `+`, `-`, `.+`, `.-`, `|`, `∪`, `$`, `<<`, `>>`, `>>>`, `.<<`, `.>>`, `.>>>`, `*`, `/`, `./`, `÷`, `.÷`, `%`, `⋅`, `.%`, `.*`, `\`, `.\`, `&`, `∩`, `//`, `.//`, `^`, `.^`, `::`, `.`, `+`, `-`, `!`, `√`, `∛`, `∜`), Operator, nil},
|
||||||
{`'(\\.|\\[0-7]{1,3}|\\x[a-fA-F0-9]{1,3}|\\u[a-fA-F0-9]{1,4}|\\U[a-fA-F0-9]{1,6}|[^\\\'\n])'`, LiteralStringChar, nil},
|
{`'(\\.|\\[0-7]{1,3}|\\x[a-fA-F0-9]{1,3}|\\u[a-fA-F0-9]{1,4}|\\U[a-fA-F0-9]{1,6}|[^\\\'\n])'`, LiteralStringChar, nil},
|
||||||
{`(?<=[.\w)\]])\'+`, Operator, nil},
|
{`(?<=[.\w)\]])\'+`, Operator, nil},
|
||||||
{`"""`, LiteralString, Push("tqstring")},
|
{`"""`, LiteralString, Push("tqstring")},
|
||||||
|
@ -34,8 +35,8 @@ var Julia = internal.Register(MustNewLexer(
|
||||||
{`r"""`, LiteralStringRegex, Push("tqregex")},
|
{`r"""`, LiteralStringRegex, Push("tqregex")},
|
||||||
{`r"`, LiteralStringRegex, Push("regex")},
|
{`r"`, LiteralStringRegex, Push("regex")},
|
||||||
{"`", LiteralStringBacktick, Push("command")},
|
{"`", LiteralStringBacktick, Push("command")},
|
||||||
{`(?:[a-zA-Z_¡-]|[𐀀-])(?:[a-zA-Z_0-9¡-]|[𐀀-])*!*`, Name, nil},
|
{`((?:[a-zA-Z_¡-]|[𐀀-])(?:[a-zA-Z_0-9¡-]|[𐀀-])*!*)(')?`, ByGroups(Name, Operator), nil},
|
||||||
{`@(?:[a-zA-Z_¡-]|[𐀀-])(?:[a-zA-Z_0-9¡-]|[𐀀-])*!*`, NameDecorator, nil},
|
{`(@(?:[a-zA-Z_¡-]|[𐀀-])(?:[a-zA-Z_0-9¡-]|[𐀀-])*!*)(')?`, ByGroups(NameDecorator, Operator), nil},
|
||||||
{`(\d+(_\d+)+\.\d*|\d*\.\d+(_\d+)+)([eEf][+-]?[0-9]+)?`, LiteralNumberFloat, nil},
|
{`(\d+(_\d+)+\.\d*|\d*\.\d+(_\d+)+)([eEf][+-]?[0-9]+)?`, LiteralNumberFloat, nil},
|
||||||
{`(\d+\.\d*|\d*\.\d+)([eEf][+-]?[0-9]+)?`, LiteralNumberFloat, nil},
|
{`(\d+\.\d*|\d*\.\d+)([eEf][+-]?[0-9]+)?`, LiteralNumberFloat, nil},
|
||||||
{`\d+(_\d+)+[eEf][+-]?[0-9]+`, LiteralNumberFloat, nil},
|
{`\d+(_\d+)+[eEf][+-]?[0-9]+`, LiteralNumberFloat, nil},
|
||||||
|
|
|
@ -30,7 +30,7 @@ var Kotlin = internal.Register(MustNewLexer(
|
||||||
{`"""[^"]*"""`, LiteralString, nil},
|
{`"""[^"]*"""`, LiteralString, nil},
|
||||||
{`"(\\\\|\\"|[^"\n])*["\n]`, LiteralString, nil},
|
{`"(\\\\|\\"|[^"\n])*["\n]`, LiteralString, nil},
|
||||||
{`'\\.'|'[^\\]'`, LiteralStringChar, nil},
|
{`'\\.'|'[^\\]'`, LiteralStringChar, nil},
|
||||||
{`[0-9](\.[0-9]*)?([eE][+-][0-9]+)?[flFL]?|0[xX][0-9a-fA-F]+[Ll]?`, LiteralNumber, nil},
|
{`0[xX][0-9a-fA-F]+[Uu]?[Ll]?|[0-9]+(\.[0-9]*)?([eE][+-][0-9]+)?[fF]?[Uu]?[Ll]?`, LiteralNumber, nil},
|
||||||
{`(companion)(\s+)(object)`, ByGroups(Keyword, Text, Keyword), nil},
|
{`(companion)(\s+)(object)`, ByGroups(Keyword, Text, Keyword), nil},
|
||||||
{`(class|interface|object)(\s+)`, ByGroups(Keyword, Text), Push("class")},
|
{`(class|interface|object)(\s+)`, ByGroups(Keyword, Text), Push("class")},
|
||||||
{`(package|import)(\s+)`, ByGroups(Keyword, Text), Push("package")},
|
{`(package|import)(\s+)`, ByGroups(Keyword, Text), Push("package")},
|
||||||
|
|
|
@ -15,8 +15,8 @@ var Markdown = internal.Register(MustNewLexer(
|
||||||
},
|
},
|
||||||
Rules{
|
Rules{
|
||||||
"root": {
|
"root": {
|
||||||
{`^(#)([^#].+\n)`, ByGroups(GenericHeading, Text), nil},
|
{`^(#[^#].+\n)`, ByGroups(GenericHeading), nil},
|
||||||
{`^(#{2,6})(.+\n)`, ByGroups(GenericSubheading, Text), nil},
|
{`^(#{2,6}.+\n)`, ByGroups(GenericSubheading), nil},
|
||||||
{`^(\s*)([*-] )(\[[ xX]\])( .+\n)`, ByGroups(Text, Keyword, Keyword, UsingSelf("inline")), nil},
|
{`^(\s*)([*-] )(\[[ xX]\])( .+\n)`, ByGroups(Text, Keyword, Keyword, UsingSelf("inline")), nil},
|
||||||
{`^(\s*)([*-])(\s)(.+\n)`, ByGroups(Text, Keyword, Text, UsingSelf("inline")), nil},
|
{`^(\s*)([*-])(\s)(.+\n)`, ByGroups(Text, Keyword, Text, UsingSelf("inline")), nil},
|
||||||
{`^(\s*)([0-9]+\.)( .+\n)`, ByGroups(Text, Keyword, UsingSelf("inline")), nil},
|
{`^(\s*)([0-9]+\.)( .+\n)`, ByGroups(Text, Keyword, UsingSelf("inline")), nil},
|
||||||
|
@ -35,13 +35,13 @@ var Markdown = internal.Register(MustNewLexer(
|
||||||
"inline": {
|
"inline": {
|
||||||
{`\\.`, Text, nil},
|
{`\\.`, Text, nil},
|
||||||
{`(\s)([*_][^*_]+[*_])(\W|\n)`, ByGroups(Text, GenericEmph, Text), nil},
|
{`(\s)([*_][^*_]+[*_])(\W|\n)`, ByGroups(Text, GenericEmph, Text), nil},
|
||||||
{`(\s)((\*\*|__).*\3)((?=\W|\n))`, ByGroups(Text, GenericStrong, None, Text), nil},
|
{`(\s)((\*\*|__).*?)\3((?=\W|\n))`, ByGroups(Text, GenericStrong, GenericStrong, Text), nil},
|
||||||
{`(\s)(~~[^~]+~~)((?=\W|\n))`, ByGroups(Text, GenericDeleted, Text), nil},
|
{`(\s)(~~[^~]+~~)((?=\W|\n))`, ByGroups(Text, GenericDeleted, Text), nil},
|
||||||
{"`[^`]+`", LiteralStringBacktick, nil},
|
{"`[^`]+`", LiteralStringBacktick, nil},
|
||||||
{`[@#][\w/:]+`, NameEntity, nil},
|
{`[@#][\w/:]+`, NameEntity, nil},
|
||||||
{`(!?\[)([^]]+)(\])(\()([^)]+)(\))`, ByGroups(Text, NameTag, Text, Text, NameAttribute, Text), nil},
|
{`(!?\[)([^]]+)(\])(\()([^)]+)(\))`, ByGroups(Text, NameTag, Text, Text, NameAttribute, Text), nil},
|
||||||
{`[^\\\s]+`, Text, nil},
|
{`[^\\\s]+`, Text, nil},
|
||||||
{`.`, Text, nil},
|
{`.|\n`, Text, nil},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
package m
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/alecthomas/chroma" // nolint
|
||||||
|
"github.com/alecthomas/chroma/lexers/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Matlab lexer.
|
||||||
|
var Matlab = internal.Register(MustNewLexer(
|
||||||
|
&Config{
|
||||||
|
Name: "Matlab",
|
||||||
|
Aliases: []string{"matlab"},
|
||||||
|
Filenames: []string{"*.m"},
|
||||||
|
MimeTypes: []string{"text/matlab"},
|
||||||
|
},
|
||||||
|
Rules{
|
||||||
|
"root": {
|
||||||
|
{`\n`, Text, nil},
|
||||||
|
{`^!.*`, LiteralStringOther, nil},
|
||||||
|
{`%\{\s*\n`, CommentMultiline, Push("blockcomment")},
|
||||||
|
{`%.*$`, Comment, nil},
|
||||||
|
{`^\s*function`, Keyword, Push("deffunc")},
|
||||||
|
{Words(``, `\b`, `break`, `case`, `catch`, `classdef`, `continue`, `else`, `elseif`, `end`, `enumerated`, `events`, `for`, `function`, `global`, `if`, `methods`, `otherwise`, `parfor`, `persistent`, `properties`, `return`, `spmd`, `switch`, `try`, `while`), Keyword, nil},
|
||||||
|
{`(sin|sind|sinh|asin|asind|asinh|cos|cosd|cosh|acos|acosd|acosh|tan|tand|tanh|atan|atand|atan2|atanh|sec|secd|sech|asec|asecd|asech|csc|cscd|csch|acsc|acscd|acsch|cot|cotd|coth|acot|acotd|acoth|hypot|exp|expm1|log|log1p|log10|log2|pow2|realpow|reallog|realsqrt|sqrt|nthroot|nextpow2|abs|angle|complex|conj|imag|real|unwrap|isreal|cplxpair|fix|floor|ceil|round|mod|rem|sign|airy|besselj|bessely|besselh|besseli|besselk|beta|betainc|betaln|ellipj|ellipke|erf|erfc|erfcx|erfinv|expint|gamma|gammainc|gammaln|psi|legendre|cross|dot|factor|isprime|primes|gcd|lcm|rat|rats|perms|nchoosek|factorial|cart2sph|cart2pol|pol2cart|sph2cart|hsv2rgb|rgb2hsv|zeros|ones|eye|repmat|rand|randn|linspace|logspace|freqspace|meshgrid|accumarray|size|length|ndims|numel|disp|isempty|isequal|isequalwithequalnans|cat|reshape|diag|blkdiag|tril|triu|fliplr|flipud|flipdim|rot90|find|end|sub2ind|ind2sub|bsxfun|ndgrid|permute|ipermute|shiftdim|circshift|squeeze|isscalar|isvector|ans|eps|realmax|realmin|pi|i|inf|nan|isnan|isinf|isfinite|j|why|compan|gallery|hadamard|hankel|hilb|invhilb|magic|pascal|rosser|toeplitz|vander|wilkinson)\b`, NameBuiltin, nil},
|
||||||
|
{`\.\.\..*$`, Comment, nil},
|
||||||
|
{`-|==|~=|<|>|<=|>=|&&|&|~|\|\|?`, Operator, nil},
|
||||||
|
{`\.\*|\*|\+|\.\^|\.\\|\.\/|\/|\\`, Operator, nil},
|
||||||
|
{`\[|\]|\(|\)|\{|\}|:|@|\.|,`, Punctuation, nil},
|
||||||
|
{`=|:|;`, Punctuation, nil},
|
||||||
|
{`(?<=[\w)\].])\'+`, Operator, nil},
|
||||||
|
{`(\d+\.\d*|\d*\.\d+)([eEf][+-]?[0-9]+)?`, LiteralNumberFloat, nil},
|
||||||
|
{`\d+[eEf][+-]?[0-9]+`, LiteralNumberFloat, nil},
|
||||||
|
{`\d+`, LiteralNumberInteger, nil},
|
||||||
|
{`(?<![\w)\].])\'`, LiteralString, Push("string")},
|
||||||
|
{`[a-zA-Z_]\w*`, Name, nil},
|
||||||
|
{`.`, Text, nil},
|
||||||
|
},
|
||||||
|
"string": {
|
||||||
|
{`[^\']*\'`, LiteralString, Pop(1)},
|
||||||
|
},
|
||||||
|
"blockcomment": {
|
||||||
|
{`^\s*%\}`, CommentMultiline, Pop(1)},
|
||||||
|
{`^.*\n`, CommentMultiline, nil},
|
||||||
|
{`.`, CommentMultiline, nil},
|
||||||
|
},
|
||||||
|
"deffunc": {
|
||||||
|
{`(\s*)(?:(.+)(\s*)(=)(\s*))?(.+)(\()(.*)(\))(\s*)`, ByGroups(TextWhitespace, Text, TextWhitespace, Punctuation, TextWhitespace, NameFunction, Punctuation, Text, Punctuation, TextWhitespace), Pop(1)},
|
||||||
|
{`(\s*)([a-zA-Z_]\w*)`, ByGroups(Text, NameFunction), Pop(1)},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
))
|
|
@ -15,7 +15,6 @@ var Prolog = internal.Register(MustNewLexer(
|
||||||
},
|
},
|
||||||
Rules{
|
Rules{
|
||||||
"root": {
|
"root": {
|
||||||
{`^#.*`, CommentSingle, nil},
|
|
||||||
{`/\*`, CommentMultiline, Push("nested-comment")},
|
{`/\*`, CommentMultiline, Push("nested-comment")},
|
||||||
{`%.*`, CommentSingle, nil},
|
{`%.*`, CommentSingle, nil},
|
||||||
{`0\'.`, LiteralStringChar, nil},
|
{`0\'.`, LiteralStringChar, nil},
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -28,6 +28,8 @@ var Rust = internal.Register(MustNewLexer(
|
||||||
{`/\*\*(\n|[^/*])`, LiteralStringDoc, Push("doccomment")},
|
{`/\*\*(\n|[^/*])`, LiteralStringDoc, Push("doccomment")},
|
||||||
{`/\*!`, LiteralStringDoc, Push("doccomment")},
|
{`/\*!`, LiteralStringDoc, Push("doccomment")},
|
||||||
{`/\*`, CommentMultiline, Push("comment")},
|
{`/\*`, CommentMultiline, Push("comment")},
|
||||||
|
{`r#*"(?:\\.|[^\\\r\n;])*"#*`, LiteralString, nil},
|
||||||
|
{`"(?:\\.|[^\\\r\n"])*"`, LiteralString, nil},
|
||||||
{`\$([a-zA-Z_]\w*|\(,?|\),?|,?)`, CommentPreproc, nil},
|
{`\$([a-zA-Z_]\w*|\(,?|\),?|,?)`, CommentPreproc, nil},
|
||||||
{Words(``, `\b`, `as`, `box`, `const`, `crate`, `else`, `extern`, `for`, `if`, `impl`, `in`, `loop`, `match`, `move`, `mut`, `pub`, `ref`, `return`, `static`, `super`, `trait`, `unsafe`, `use`, `where`, `while`), Keyword, nil},
|
{Words(``, `\b`, `as`, `box`, `const`, `crate`, `else`, `extern`, `for`, `if`, `impl`, `in`, `loop`, `match`, `move`, `mut`, `pub`, `ref`, `return`, `static`, `super`, `trait`, `unsafe`, `use`, `where`, `while`), Keyword, nil},
|
||||||
{Words(``, `\b`, `abstract`, `alignof`, `become`, `do`, `final`, `macro`, `offsetof`, `override`, `priv`, `proc`, `pure`, `sizeof`, `typeof`, `unsized`, `virtual`, `yield`), KeywordReserved, nil},
|
{Words(``, `\b`, `abstract`, `alignof`, `become`, `do`, `final`, `macro`, `offsetof`, `override`, `priv`, `proc`, `pure`, `sizeof`, `typeof`, `unsized`, `virtual`, `yield`), KeywordReserved, nil},
|
||||||
|
@ -51,7 +53,6 @@ var Rust = internal.Register(MustNewLexer(
|
||||||
{`[0-9][0-9_]*(\.[0-9_]+[eE][+\-]?[0-9_]+|\.[0-9_]*(?!\.)|[eE][+\-]?[0-9_]+)`, LiteralNumberFloat, Push("number_lit")},
|
{`[0-9][0-9_]*(\.[0-9_]+[eE][+\-]?[0-9_]+|\.[0-9_]*(?!\.)|[eE][+\-]?[0-9_]+)`, LiteralNumberFloat, Push("number_lit")},
|
||||||
{`[0-9][0-9_]*`, LiteralNumberInteger, Push("number_lit")},
|
{`[0-9][0-9_]*`, LiteralNumberInteger, Push("number_lit")},
|
||||||
{`b"`, LiteralString, Push("bytestring")},
|
{`b"`, LiteralString, Push("bytestring")},
|
||||||
{`"`, LiteralString, Push("string")},
|
|
||||||
{`b?r(#*)".*?"\1`, LiteralString, nil},
|
{`b?r(#*)".*?"\1`, LiteralString, nil},
|
||||||
{`'static`, NameBuiltin, nil},
|
{`'static`, NameBuiltin, nil},
|
||||||
{`'[a-zA-Z_]\w*`, NameAttribute, nil},
|
{`'[a-zA-Z_]\w*`, NameAttribute, nil},
|
||||||
|
|
|
@ -8,11 +8,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
scalaOp = "[-~\\^\\*!%&\\\\<>\\|+=:/?@\xa6-\xa7\xa9\xac\xae\xb0-\xb1\xb6\xd7\xf7\u03f6\u0482\u0606-\u0608\u060e-\u060f\u06e9\u06fd-\u06fe\u07f6\u09fa\u0b70\u0bf3-\u0bf8\u0bfa\u0c7f\u0cf1-\u0cf2\u0d79\u0f01-\u0f03\u0f13-\u0f17\u0f1a-\u0f1f\u0f34\u0f36\u0f38\u0fbe-\u0fc5\u0fc7-\u0fcf\u109e-\u109f\u1360\u1390-\u1399\u1940\u19e0-\u19ff\u1b61-\u1b6a\u1b74-\u1b7c\u2044\u2052\u207a-\u207c\u208a-\u208c\u2100-\u2101\u2103-\u2106\u2108-\u2109\u2114\u2116-\u2118\u211e-\u2123\u2125\u2127\u2129\u212e\u213a-\u213b\u2140-\u2144\u214a-\u214d\u214f\u2190-\u2328\u232b-\u244a\u249c-\u24e9\u2500-\u2767\u2794-\u27c4\u27c7-\u27e5\u27f0-\u2982\u2999-\u29d7\u29dc-\u29fb\u29fe-\u2b54\u2ce5-\u2cea\u2e80-\u2ffb\u3004\u3012-\u3013\u3020\u3036-\u3037\u303e-\u303f\u3190-\u3191\u3196-\u319f\u31c0-\u31e3\u3200-\u321e\u322a-\u3250\u3260-\u327f\u328a-\u32b0\u32c0-\u33ff\u4dc0-\u4dff\ua490-\ua4c6\ua828-\ua82b\ufb29\ufdfd\ufe62\ufe64-\ufe66\uff0b\uff1c-\uff1e\uff5c\uff5e\uffe2\uffe4\uffe8-\uffee\ufffc-\ufffd]+"
|
scalaOp = "[-~\\^\\*!%&\\\\<>\\|+=:/?@\xa6-\xa7\xa9\xac\xae\xb0-\xb1\xb6\xd7\xf7\u03f6\u0482\u0606-\u0608\u060e-\u060f\u06e9\u06fd-\u06fe\u07f6\u09fa\u0b70\u0bf3-\u0bf8\u0bfa\u0c7f\u0cf1-\u0cf2\u0d79\u0f01-\u0f03\u0f13-\u0f17\u0f1a-\u0f1f\u0f34\u0f36\u0f38\u0fbe-\u0fc5\u0fc7-\u0fcf\u109e-\u109f\u1360\u1390-\u1399\u1940\u19e0-\u19ff\u1b61-\u1b6a\u1b74-\u1b7c\u2044\u2052\u207a-\u207c\u208a-\u208c\u2100-\u2101\u2103-\u2106\u2108-\u2109\u2114\u2116-\u2118\u211e-\u2123\u2125\u2127\u2129\u212e\u213a-\u213b\u2140-\u2144\u214a-\u214d\u214f\u2190-\u2328\u232b-\u244a\u249c-\u24e9\u2500-\u2767\u2794-\u27c4\u27c7-\u27e5\u27f0-\u2982\u2999-\u29d7\u29dc-\u29fb\u29fe-\u2b54\u2ce5-\u2cea\u2e80-\u2ffb\u3004\u3012-\u3013\u3020\u3036-\u3037\u303e-\u303f\u3190-\u3191\u3196-\u319f\u31c0-\u31e3\u3200-\u321e\u322a-\u3250\u3260-\u327f\u328a-\u32b0\u32c0-\u33ff\u4dc0-\u4dff\ua490-\ua4c6\ua828-\ua82b\ufb29\ufdfd\ufe62\ufe64-\ufe66\uff0b\uff1c-\uff1e\uff5c\uff5e\uffe2\uffe4\uffe8-\uffee\ufffc-\ufffd]+"
|
||||||
scalaUpper = "[A-Z\\$_\xc0-\xd6\xd8-\xde\u0100\u0102\u0104\u0106\u0108\u010a\u010c\u010e\u0110\u0112\u0114\u0116\u0118\u011a\u011c\u011e\u0120\u0122\u0124\u0126\u0128\u012a\u012c\u012e\u0130\u0132\u0134\u0136\u0139\u013b\u013d\u013f\u0141\u0143\u0145\u0147\u014a\u014c\u014e\u0150\u0152\u0154\u0156\u0158\u015a\u015c\u015e\u0160\u0162\u0164\u0166\u0168\u016a\u016c\u016e\u0170\u0172\u0174\u0176\u0178-\u0179\u017b\u017d\u0181-\u0182\u0184\u0186-\u0187\u0189-\u018b\u018e-\u0191\u0193-\u0194\u0196-\u0198\u019c-\u019d\u019f-\u01a0\u01a2\u01a4\u01a6-\u01a7\u01a9\u01ac\u01ae-\u01af\u01b1-\u01b3\u01b5\u01b7-\u01b8\u01bc\u01c4\u01c7\u01ca\u01cd\u01cf\u01d1\u01d3\u01d5\u01d7\u01d9\u01db\u01de\u01e0\u01e2\u01e4\u01e6\u01e8\u01ea\u01ec\u01ee\u01f1\u01f4\u01f6-\u01f8\u01fa\u01fc\u01fe\u0200\u0202\u0204\u0206\u0208\u020a\u020c\u020e\u0210\u0212\u0214\u0216\u0218\u021a\u021c\u021e\u0220\u0222\u0224\u0226\u0228\u022a\u022c\u022e\u0230\u0232\u023a-\u023b\u023d-\u023e\u0241\u0243-\u0246\u0248\u024a\u024c\u024e\u0370\u0372\u0376\u0386\u0388-\u038f\u0391-\u03ab\u03cf\u03d2-\u03d4\u03d8\u03da\u03dc\u03de\u03e0\u03e2\u03e4\u03e6\u03e8\u03ea\u03ec\u03ee\u03f4\u03f7\u03f9-\u03fa\u03fd-\u042f\u0460\u0462\u0464\u0466\u0468\u046a\u046c\u046e\u0470\u0472\u0474\u0476\u0478\u047a\u047c\u047e\u0480\u048a\u048c\u048e\u0490\u0492\u0494\u0496\u0498\u049a\u049c\u049e\u04a0\u04a2\u04a4\u04a6\u04a8\u04aa\u04ac\u04ae\u04b0\u04b2\u04b4\u04b6\u04b8\u04ba\u04bc\u04be\u04c0-\u04c1\u04c3\u04c5\u04c7\u04c9\u04cb\u04cd\u04d0\u04d2\u04d4\u04d6\u04d8\u04da\u04dc\u04de\u04e0\u04e2\u04e4\u04e6\u04e8\u04ea\u04ec\u04ee\u04f0\u04f2\u04f4\u04f6\u04f8\u04fa\u04fc\u04fe\u0500\u0502\u0504\u0506\u0508\u050a\u050c\u050e\u0510\u0512\u0514\u0516\u0518\u051a\u051c\u051e\u0520\u0522\u0531-\u0556\u10a0-\u10c5\u1e00\u1e02\u1e04\u1e06\u1e08\u1e0a\u1e0c\u1e0e\u1e10\u1e12\u1e14\u1e16\u1e18\u1e1a\u1e1c\u1e1e\u1e20\u1e22\u1e24\u1e26\u1e28\u1e2a\u1e2c\u1e2e\u1e30\u1e32\u1e34\u1e36\u1e38\u1e3a\u1e3c\u1e3e\u1e40\u1e42\u1e44\u1e46\u1e48\u1e4a\u1e4c\u1e4e\u1e50\u1e52\u1e54\u1e56\u1e58\u1e5a\u1e5c\u1e5e\u1e60\u1e62\u1e64\u1e66\u1e68\u1e6a\u1e6c\u1e6e\u1e70\u1e72\u1e74\u1e76\u1e78\u1e7a\u1e7c\u1e7e\u1e80\u1e82\u1e84\u1e86\u1e88\u1e8a\u1e8c\u1e8e\u1e90\u1e92\u1e94\u1e9e\u1ea0\u1ea2\u1ea4\u1ea6\u1ea8\u1eaa\u1eac\u1eae\u1eb0\u1eb2\u1eb4\u1eb6\u1eb8\u1eba\u1ebc\u1ebe\u1ec0\u1ec2\u1ec4\u1ec6\u1ec8\u1eca\u1ecc\u1ece\u1ed0\u1ed2\u1ed4\u1ed6\u1ed8\u1eda\u1edc\u1ede\u1ee0\u1ee2\u1ee4\u1ee6\u1ee8\u1eea\u1eec\u1eee\u1ef0\u1ef2\u1ef4\u1ef6\u1ef8\u1efa\u1efc\u1efe\u1f08-\u1f0f\u1f18-\u1f1d\u1f28-\u1f2f\u1f38-\u1f3f\u1f48-\u1f4d\u1f59-\u1f5f\u1f68-\u1f6f\u1fb8-\u1fbb\u1fc8-\u1fcb\u1fd8-\u1fdb\u1fe8-\u1fec\u1ff8-\u1ffb\u2102\u2107\u210b-\u210d\u2110-\u2112\u2115\u2119-\u211d\u2124\u2126\u2128\u212a-\u212d\u2130-\u2133\u213e-\u213f\u2145\u2183\u2c00-\u2c2e\u2c60\u2c62-\u2c64\u2c67\u2c69\u2c6b\u2c6d-\u2c6f\u2c72\u2c75\u2c80\u2c82\u2c84\u2c86\u2c88\u2c8a\u2c8c\u2c8e\u2c90\u2c92\u2c94\u2c96\u2c98\u2c9a\u2c9c\u2c9e\u2ca0\u2ca2\u2ca4\u2ca6\u2ca8\u2caa\u2cac\u2cae\u2cb0\u2cb2\u2cb4\u2cb6\u2cb8\u2cba\u2cbc\u2cbe\u2cc0\u2cc2\u2cc4\u2cc6\u2cc8\u2cca\u2ccc\u2cce\u2cd0\u2cd2\u2cd4\u2cd6\u2cd8\u2cda\u2cdc\u2cde\u2ce0\u2ce2\ua640\ua642\ua644\ua646\ua648\ua64a\ua64c\ua64e\ua650\ua652\ua654\ua656\ua658\ua65a\ua65c\ua65e\ua662\ua664\ua666\ua668\ua66a\ua66c\ua680\ua682\ua684\ua686\ua688\ua68a\ua68c\ua68e\ua690\ua692\ua694\ua696\ua722\ua724\ua726\ua728\ua72a\ua72c\ua72e\ua732\ua734\ua736\ua738\ua73a\ua73c\ua73e\ua740\ua742\ua744\ua746\ua748\ua74a\ua74c\ua74e\ua750\ua752\ua754\ua756\ua758\ua75a\ua75c\ua75e\ua760\ua762\ua764\ua766\ua768\ua76a\ua76c\ua76e\ua779\ua77b\ua77d-\ua77e\ua780\ua782\ua784\ua786\ua78b\uff21-\uff3a]"
|
scalaUpper = "[A-Z\\$_\xc0-\xd6\xd8-\xde\u0100\u0102\u0104\u0106\u0108\u010a\u010c\u010e\u0110\u0112\u0114\u0116\u0118\u011a\u011c\u011e\u0120\u0122\u0124\u0126\u0128\u012a\u012c\u012e\u0130\u0132\u0134\u0136\u0139\u013b\u013d\u013f\u0141\u0143\u0145\u0147\u014a\u014c\u014e\u0150\u0152\u0154\u0156\u0158\u015a\u015c\u015e\u0160\u0162\u0164\u0166\u0168\u016a\u016c\u016e\u0170\u0172\u0174\u0176\u0178-\u0179\u017b\u017d\u0181-\u0182\u0184\u0186-\u0187\u0189-\u018b\u018e-\u0191\u0193-\u0194\u0196-\u0198\u019c-\u019d\u019f-\u01a0\u01a2\u01a4\u01a6-\u01a7\u01a9\u01ac\u01ae-\u01af\u01b1-\u01b3\u01b5\u01b7-\u01b8\u01bc\u01c4\u01c7\u01ca\u01cd\u01cf\u01d1\u01d3\u01d5\u01d7\u01d9\u01db\u01de\u01e0\u01e2\u01e4\u01e6\u01e8\u01ea\u01ec\u01ee\u01f1\u01f4\u01f6-\u01f8\u01fa\u01fc\u01fe\u0200\u0202\u0204\u0206\u0208\u020a\u020c\u020e\u0210\u0212\u0214\u0216\u0218\u021a\u021c\u021e\u0220\u0222\u0224\u0226\u0228\u022a\u022c\u022e\u0230\u0232\u023a-\u023b\u023d-\u023e\u0241\u0243-\u0246\u0248\u024a\u024c\u024e\u0370\u0372\u0376\u0386\u0388-\u038f\u0391-\u03ab\u03cf\u03d2-\u03d4\u03d8\u03da\u03dc\u03de\u03e0\u03e2\u03e4\u03e6\u03e8\u03ea\u03ec\u03ee\u03f4\u03f7\u03f9-\u03fa\u03fd-\u042f\u0460\u0462\u0464\u0466\u0468\u046a\u046c\u046e\u0470\u0472\u0474\u0476\u0478\u047a\u047c\u047e\u0480\u048a\u048c\u048e\u0490\u0492\u0494\u0496\u0498\u049a\u049c\u049e\u04a0\u04a2\u04a4\u04a6\u04a8\u04aa\u04ac\u04ae\u04b0\u04b2\u04b4\u04b6\u04b8\u04ba\u04bc\u04be\u04c0-\u04c1\u04c3\u04c5\u04c7\u04c9\u04cb\u04cd\u04d0\u04d2\u04d4\u04d6\u04d8\u04da\u04dc\u04de\u04e0\u04e2\u04e4\u04e6\u04e8\u04ea\u04ec\u04ee\u04f0\u04f2\u04f4\u04f6\u04f8\u04fa\u04fc\u04fe\u0500\u0502\u0504\u0506\u0508\u050a\u050c\u050e\u0510\u0512\u0514\u0516\u0518\u051a\u051c\u051e\u0520\u0522\u0531-\u0556\u10a0-\u10c5\u1e00\u1e02\u1e04\u1e06\u1e08\u1e0a\u1e0c\u1e0e\u1e10\u1e12\u1e14\u1e16\u1e18\u1e1a\u1e1c\u1e1e\u1e20\u1e22\u1e24\u1e26\u1e28\u1e2a\u1e2c\u1e2e\u1e30\u1e32\u1e34\u1e36\u1e38\u1e3a\u1e3c\u1e3e\u1e40\u1e42\u1e44\u1e46\u1e48\u1e4a\u1e4c\u1e4e\u1e50\u1e52\u1e54\u1e56\u1e58\u1e5a\u1e5c\u1e5e\u1e60\u1e62\u1e64\u1e66\u1e68\u1e6a\u1e6c\u1e6e\u1e70\u1e72\u1e74\u1e76\u1e78\u1e7a\u1e7c\u1e7e\u1e80\u1e82\u1e84\u1e86\u1e88\u1e8a\u1e8c\u1e8e\u1e90\u1e92\u1e94\u1e9e\u1ea0\u1ea2\u1ea4\u1ea6\u1ea8\u1eaa\u1eac\u1eae\u1eb0\u1eb2\u1eb4\u1eb6\u1eb8\u1eba\u1ebc\u1ebe\u1ec0\u1ec2\u1ec4\u1ec6\u1ec8\u1eca\u1ecc\u1ece\u1ed0\u1ed2\u1ed4\u1ed6\u1ed8\u1eda\u1edc\u1ede\u1ee0\u1ee2\u1ee4\u1ee6\u1ee8\u1eea\u1eec\u1eee\u1ef0\u1ef2\u1ef4\u1ef6\u1ef8\u1efa\u1efc\u1efe\u1f08-\u1f0f\u1f18-\u1f1d\u1f28-\u1f2f\u1f38-\u1f3f\u1f48-\u1f4d\u1f59-\u1f5f\u1f68-\u1f6f\u1fb8-\u1fbb\u1fc8-\u1fcb\u1fd8-\u1fdb\u1fe8-\u1fec\u1ff8-\u1ffb\u2102\u2107\u210b-\u210d\u2110-\u2112\u2115\u2119-\u211d\u2124\u2126\u2128\u212a-\u212d\u2130-\u2133\u213e-\u213f\u2145\u2183\u2c00-\u2c2e\u2c60\u2c62-\u2c64\u2c67\u2c69\u2c6b\u2c6d-\u2c6f\u2c72\u2c75\u2c80\u2c82\u2c84\u2c86\u2c88\u2c8a\u2c8c\u2c8e\u2c90\u2c92\u2c94\u2c96\u2c98\u2c9a\u2c9c\u2c9e\u2ca0\u2ca2\u2ca4\u2ca6\u2ca8\u2caa\u2cac\u2cae\u2cb0\u2cb2\u2cb4\u2cb6\u2cb8\u2cba\u2cbc\u2cbe\u2cc0\u2cc2\u2cc4\u2cc6\u2cc8\u2cca\u2ccc\u2cce\u2cd0\u2cd2\u2cd4\u2cd6\u2cd8\u2cda\u2cdc\u2cde\u2ce0\u2ce2\ua640\ua642\ua644\ua646\ua648\ua64a\ua64c\ua64e\ua650\ua652\ua654\ua656\ua658\ua65a\ua65c\ua65e\ua662\ua664\ua666\ua668\ua66a\ua66c\ua680\ua682\ua684\ua686\ua688\ua68a\ua68c\ua68e\ua690\ua692\ua694\ua696\ua722\ua724\ua726\ua728\ua72a\ua72c\ua72e\ua732\ua734\ua736\ua738\ua73a\ua73c\ua73e\ua740\ua742\ua744\ua746\ua748\ua74a\ua74c\ua74e\ua750\ua752\ua754\ua756\ua758\ua75a\ua75c\ua75e\ua760\ua762\ua764\ua766\ua768\ua76a\ua76c\ua76e\ua779\ua77b\ua77d-\ua77e\ua780\ua782\ua784\ua786\ua78b\uff21-\uff3a]"
|
||||||
scalaLetter = `[a-zA-Z\\$_ªµºÀ-ÖØ-öø-ʯͰ-ͳͶ-ͷͻ-ͽΆΈ-ϵϷ-ҁҊ-Ֆա-ևא-ײء-ؿف-يٮ-ٯٱ-ۓەۮ-ۯۺ-ۼۿܐܒ-ܯݍ-ޥޱߊ-ߪऄ-हऽॐक़-ॡॲ-ॿঅ-হঽৎড়-ৡৰ-ৱਅ-ਹਖ਼-ਫ਼ੲ-ੴઅ-હઽૐ-ૡଅ-ହଽଡ଼-ୡୱஃ-ஹௐఅ-ఽౘ-ౡಅ-ಹಽೞ-ೡഅ-ഽൠ-ൡൺ-ൿඅ-ෆก-ะา-ำเ-ๅກ-ະາ-ຳຽ-ໄໜ-ༀཀ-ཬྈ-ྋက-ဪဿၐ-ၕၚ-ၝၡၥ-ၦၮ-ၰၵ-ႁႎႠ-ჺᄀ-ፚᎀ-ᎏᎠ-ᙬᙯ-ᙶᚁ-ᚚᚠ-ᛪᛮ-ᜑᜠ-ᜱᝀ-ᝑᝠ-ᝰក-ឳៜᠠ-ᡂᡄ-ᢨᢪ-ᤜᥐ-ᦩᧁ-ᧇᨀ-ᨖᬅ-ᬳᭅ-ᭋᮃ-ᮠᮮ-ᮯᰀ-ᰣᱍ-ᱏᱚ-ᱷᴀ-ᴫᵢ-ᵷᵹ-ᶚḀ-ᾼιῂ-ῌῐ-Ίῠ-Ῥῲ-ῼⁱⁿℂℇℊ-ℓℕℙ-ℝℤΩℨK-ℭℯ-ℹℼ-ℿⅅ-ⅉⅎⅠ-ↈⰀ-ⱼⲀ-ⳤⴀ-ⵥⶀ-ⷞ〆-〇〡-〩〸-〺〼ぁ-ゖゟァ-ヺヿ-ㆎㆠ-ㆷㇰ-ㇿ㐀-䶵一-ꀔꀖ-ꒌꔀ-ꘋꘐ-ꘟꘪ-ꙮꚀ-ꚗꜢ-ꝯꝱ-ꞇꞋ-ꠁꠃ-ꠅꠇ-ꠊꠌ-ꠢꡀ-ꡳꢂ-ꢳꤊ-ꤥꤰ-ꥆꨀ-ꨨꩀ-ꩂꩄ-ꩋ가-힣豈-יִײַ-ﬨשׁ-ﴽﵐ-ﷻﹰ-ﻼA-Za-zヲ-ッア-ンᅠ-ᅵ]`
|
scalaLetter = `[a-zA-Z\\$_ªµºÀ-ÖØ-öø-ʯͰ-ͳͶ-ͷͻ-ͽΆΈ-ϵϷ-ҁҊ-Ֆա-ևא-ײء-ؿف-يٮ-ٯٱ-ۓەۮ-ۯۺ-ۼۿܐܒ-ܯݍ-ޥޱߊ-ߪऄ-हऽॐक़-ॡॲ-ॿঅ-হঽৎড়-ৡৰ-ৱਅ-ਹਖ਼-ਫ਼ੲ-ੴઅ-હઽૐ-ૡଅ-ହଽଡ଼-ୡୱஃ-ஹௐఅ-ఽౘ-ౡಅ-ಹಽೞ-ೡഅ-ഽൠ-ൡൺ-ൿඅ-ෆก-ะา-ำเ-ๅກ-ະາ-ຳຽ-ໄໜ-ༀཀ-ཬྈ-ྋက-ဪဿၐ-ၕၚ-ၝၡၥ-ၦၮ-ၰၵ-ႁႎႠ-ჺᄀ-ፚᎀ-ᎏᎠ-ᙬᙯ-ᙶᚁ-ᚚᚠ-ᛪᛮ-ᜑᜠ-ᜱᝀ-ᝑᝠ-ᝰក-ឳៜᠠ-ᡂᡄ-ᢨᢪ-ᤜᥐ-ᦩᧁ-ᧇᨀ-ᨖᬅ-ᬳᭅ-ᭋᮃ-ᮠᮮ-ᮯᰀ-ᰣᱍ-ᱏᱚ-ᱷᴀ-ᴫᵢ-ᵷᵹ-ᶚḀ-ᾼιῂ-ῌῐ-Ίῠ-Ῥῲ-ῼⁱⁿℂℇℊ-ℓℕℙ-ℝℤΩℨK-ℭℯ-ℹℼ-ℿⅅ-ⅉⅎⅠ-ↈⰀ-ⱼⲀ-ⳤⴀ-ⵥⶀ-ⷞ〆-〇〡-〩〸-〺〼ぁ-ゖゟァ-ヺヿ-ㆎㆠ-ㆷㇰ-ㇿ㐀-䶵一-ꀔꀖ-ꒌꔀ-ꘋꘐ-ꘟꘪ-ꙮꚀ-ꚗꜢ-ꝯꝱ-ꞇꞋ-ꠁꠃ-ꠅꠇ-ꠊꠌ-ꠢꡀ-ꡳꢂ-ꢳꤊ-ꤥꤰ-ꥆꨀ-ꨨꩀ-ꩂꩄ-ꩋ가-힣豈-יִײַ-ﬨשׁ-ﴽﵐ-ﷻﹰ-ﻼA-Za-zヲ-ッア-ンᅠ-ᅵ]`
|
||||||
scalaIDRest = fmt.Sprintf(`%s(?:%s|[0-9])*(?:(?<=_)%s)?`, scalaLetter, scalaLetter, scalaOp)
|
scalaIDRest = fmt.Sprintf(`%s(?:%s|[0-9])*(?:(?<=_)%s)?`, scalaLetter, scalaLetter, scalaOp)
|
||||||
scalaLetterLetterDigit = fmt.Sprintf(`%s(?:%s|\d)*`, scalaLetter, scalaLetter)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Scala lexer.
|
// Scala lexer.
|
||||||
|
|
|
@ -80,7 +80,7 @@ var Scss = internal.Register(MustNewLexer(
|
||||||
{`[\w-]+`, NameTag, nil},
|
{`[\w-]+`, NameTag, nil},
|
||||||
{`#\{`, LiteralStringInterpol, Push("interpolation")},
|
{`#\{`, LiteralStringInterpol, Push("interpolation")},
|
||||||
{`&`, Keyword, nil},
|
{`&`, Keyword, nil},
|
||||||
{`[~^*!&\[\]()<>|+=@:;,./?-]`, Operator, nil},
|
{`[~^*!&\[\]()<>|+=@:,./?-]`, Operator, nil},
|
||||||
{`"`, LiteralStringDouble, Push("string-double")},
|
{`"`, LiteralStringDouble, Push("string-double")},
|
||||||
{`'`, LiteralStringSingle, Push("string-single")},
|
{`'`, LiteralStringSingle, Push("string-single")},
|
||||||
{`\n`, Text, nil},
|
{`\n`, Text, nil},
|
||||||
|
@ -92,9 +92,9 @@ var Scss = internal.Register(MustNewLexer(
|
||||||
{`"`, LiteralStringDouble, Pop(1)},
|
{`"`, LiteralStringDouble, Pop(1)},
|
||||||
},
|
},
|
||||||
"string-single": {
|
"string-single": {
|
||||||
{`(\\.|#(?=[^\n{])|[^\n'#])+`, LiteralStringDouble, nil},
|
{`(\\.|#(?=[^\n{])|[^\n'#])+`, LiteralStringSingle, nil},
|
||||||
{`#\{`, LiteralStringInterpol, Push("interpolation")},
|
{`#\{`, LiteralStringInterpol, Push("interpolation")},
|
||||||
{`'`, LiteralStringDouble, Pop(1)},
|
{`'`, LiteralStringSingle, Pop(1)},
|
||||||
},
|
},
|
||||||
"string-url": {
|
"string-url": {
|
||||||
{`(\\#|#(?=[^\n{])|[^\n#)])+`, LiteralStringOther, nil},
|
{`(\\#|#(?=[^\n{])|[^\n#)])+`, LiteralStringOther, nil},
|
||||||
|
|
|
@ -5,8 +5,8 @@ import (
|
||||||
"github.com/alecthomas/chroma/lexers/internal"
|
"github.com/alecthomas/chroma/lexers/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Sql lexer.
|
// SQL lexer.
|
||||||
var Sql = internal.Register(MustNewLexer(
|
var SQL = internal.Register(MustNewLexer(
|
||||||
&Config{
|
&Config{
|
||||||
Name: "SQL",
|
Name: "SQL",
|
||||||
Aliases: []string{"sql"},
|
Aliases: []string{"sql"},
|
||||||
|
|
|
@ -18,6 +18,7 @@ var Systemverilog = internal.Register(MustNewLexer(
|
||||||
"root": {
|
"root": {
|
||||||
{"^\\s*`define", CommentPreproc, Push("macro")},
|
{"^\\s*`define", CommentPreproc, Push("macro")},
|
||||||
{`^(\s*)(package)(\s+)`, ByGroups(Text, KeywordNamespace, Text), nil},
|
{`^(\s*)(package)(\s+)`, ByGroups(Text, KeywordNamespace, Text), nil},
|
||||||
|
{`^(\s*)(import)(\s+)("DPI(?:-C)?")(\s+)`, ByGroups(Text, KeywordNamespace, Text, LiteralString, Text), nil},
|
||||||
{`^(\s*)(import)(\s+)`, ByGroups(Text, KeywordNamespace, Text), Push("import")},
|
{`^(\s*)(import)(\s+)`, ByGroups(Text, KeywordNamespace, Text), Push("import")},
|
||||||
{`\n`, Text, nil},
|
{`\n`, Text, nil},
|
||||||
{`\s+`, Text, nil},
|
{`\s+`, Text, nil},
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"github.com/alecthomas/chroma/lexers/internal"
|
"github.com/alecthomas/chroma/lexers/internal"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TradingView lexer.
|
// TradingView lexer
|
||||||
var TradingView = internal.Register(MustNewLexer(
|
var TradingView = internal.Register(MustNewLexer(
|
||||||
&Config{
|
&Config{
|
||||||
Name: "TradingView",
|
Name: "TradingView",
|
||||||
|
@ -13,6 +13,7 @@ var TradingView = internal.Register(MustNewLexer(
|
||||||
Filenames: []string{"*.tv"},
|
Filenames: []string{"*.tv"},
|
||||||
MimeTypes: []string{"text/x-tradingview"},
|
MimeTypes: []string{"text/x-tradingview"},
|
||||||
DotAll: true,
|
DotAll: true,
|
||||||
|
EnsureNL: true,
|
||||||
},
|
},
|
||||||
Rules{
|
Rules{
|
||||||
"root": {
|
"root": {
|
||||||
|
@ -24,10 +25,13 @@ var TradingView = internal.Register(MustNewLexer(
|
||||||
{`"(\\\\|\\"|[^"\n])*["\n]`, LiteralString, nil},
|
{`"(\\\\|\\"|[^"\n])*["\n]`, LiteralString, nil},
|
||||||
{`'\\.'|'[^\\]'`, LiteralString, nil},
|
{`'\\.'|'[^\\]'`, LiteralString, nil},
|
||||||
{`[0-9](\.[0-9]*)?([eE][+-][0-9]+)?`, LiteralNumber, nil},
|
{`[0-9](\.[0-9]*)?([eE][+-][0-9]+)?`, LiteralNumber, nil},
|
||||||
{`(abs|acos|alertcondition|alma|asin|atan|atr|avg|barcolor|barssince|bgcolor|cci|ceil|change|cog|correlation|cos|crossover|crossunder|cum|dev|ema|exp|falling|fill|fixnan|floor|heikinashi|highest|highestbars|hline|iff|input|kagi|linebreak|linreg|log|log10|lowest|lowestbars|macd|max|min|mom|nz|percentile_linear_interpolation|percentile_nearest_rank|percentrank|pivothigh|pivotlow|plot|plotarrow|plotbar|plotcandle|plotchar|plotshape|pointfigure|pow|renko|rising|rma|roc|round|rsi|sar|security|sign|sin|sma|sqrt|stdev|stoch|study|sum|swma|tan|tostring|tsi|valuewhen|variance|vwma|wma|strategy\.(cancel|cancel_all|close|close_all|entry|exit|order)|strategy\.risk\.(allow_entry_in|max_cons_loss_days|max_drawdown|max_intraday_filled_orders|max_intraday_loss|max_position_size))\b`, NameFunction, nil},
|
{`#[a-fA-F0-9]{8}|#[a-fA-F0-9]{6}|#[a-fA-F0-9]{3}`, LiteralStringOther, nil},
|
||||||
{`\b(cross|dayofmonth|dayofweek|hour|minute|month|na|offset|second|tickerid|time|tr|vwap|weekofyear|year)(\()`, ByGroups(NameFunction, Text), nil}, // functions that can also be variable
|
{`(abs|acos|alertcondition|alma|asin|atan|atr|avg|barcolor|barssince|bgcolor|cci|ceil|change|cog|color\.new|correlation|cos|crossover|crossunder|cum|dev|ema|exp|falling|fill|fixnan|floor|heikinashi|highest|highestbars|hline|iff|kagi|label\.(delete|get_text|get_x|get_y|new|set_color|set_size|set_style|set_text|set_textcolor|set_x|set_xloc|set_xy|set_y|set_yloc)|line\.(new|delete|get_x1|get_x2|get_y1|get_y2|set_color|set_width|set_style|set_extend|set_xy1|set_xy2|set_x1|set_x2|set_y1|set_y2|set_xloc)|linebreak|linreg|log|log10|lowest|lowestbars|macd|max|max_bars_back|min|mom|nz|percentile_(linear_interpolation|nearest_rank)|percentrank|pivothigh|pivotlow|plot|plotarrow|plotbar|plotcandle|plotchar|plotshape|pointfigure|pow|renko|rising|rma|roc|round|rsi|sar|security|sign|sin|sma|sqrt|stdev|stoch|study|sum|swma|tan|timestamp|tostring|tsi|valuewhen|variance|vwma|wma|strategy\.(cancel|cancel_all|close|close_all|entry|exit|order|risk\.(allow_entry_in|max_cons_loss_days|max_drawdown|max_intraday_filled_orders|max_intraday_loss|max_position_size)))\b`, NameFunction, nil},
|
||||||
{`(accdist|aqua|area|areabr|black|blue|bool|circles|close|columns|currency\.(AUD|CAD|CHF|EUR|GBP|HKD|JPY|NOK|NONE|NZD|SEK|SGD|TRY|USD|ZAR)|dashed|dotted|float|friday|fuchsia|gray|green|high|histogram|hl2|hlc3|integer|interval|isdaily|isdwm|isintraday|ismonthly|isweekly|lime|line|linebr|location\.(abovebar|belowbar|bottom|top)|low|maroon|monday|n|navy|ohlc4|olive|open|orange|period|purple|red|resolution|saturday|scale\.(left|none|right)|session|session\.(extended|regular)|silver|size\.(auto|huge|large|normal|small|tiny)|solid|source|string|sunday|symbol|syminfo\.(mintick|pointvalue|prefix|root|session)|teal|thursday|ticker|tuesday|volume|wednesday|white|yellow|strategy\.(cash|position_size|closedtrades|direction\.(all|long|short)|equity|eventrades|fixed|grossloss|grossprofit|initial_capital|long|losstrades|max_contracts_held_all|max_contracts_held_long|max_contracts_held_short|max_drawdown|netprofit|oca\.(cancel|none|reduce)|openprofit|opentrades|percent_of_equity|position_avg_price|position_entry_name|short|wintrades)|shape\.(arrowdown|arrowup|circle|cross|diamond|flag|labeldown|labelup|square|triangledown|triangleup|xcross)|barstate\.is(first|history|last|new|realtime)|barmerge\.(gaps_on|gaps_off|lookahead_on|lookahead_off)|strategy\.commission\.(cash_per_contract|cash_per_order|percent))\b`, NameVariable, nil},
|
{`\b(bool|color|cross|dayofmonth|dayofweek|float|hour|input|int|label|line|minute|month|na|offset|second|strategy|string|tickerid|time|tr|vwap|weekofyear|year)(\()`, ByGroups(NameFunction, Text), nil}, // functions that can also be variable
|
||||||
|
{`(accdist|adjustment\.(dividends|none|splits)|aqua|area|areabr|bar_index|black|blue|bool|circles|close|columns|currency\.(AUD|CAD|CHF|EUR|GBP|HKD|JPY|NOK|NONE|NZD|RUB|SEK|SGD|TRY|USD|ZAR)|color\.(aqua|black|blue|fuchsia|gray|green|lime|maroon|navy|olive|orange|purple|red|silver|teal|white|yellow)|dashed|dotted|dayofweek\.(monday|tuesday|wednesday|thursday|friday|saturday|sunday)|extend\.(both|left|right|none)|float|format\.(inherit|price|volume)|friday|fuchsia|gray|green|high|histogram|hl2|hlc3|hline\.style_(dotted|solid|dashed)|input\.(bool|float|integer|resolution|session|source|string|symbol)|integer|interval|isdaily|isdwm|isintraday|ismonthly|isweekly|label\.style_(arrowdown|arrowup|circle|cross|diamond|flag|labeldown|labelup|none|square|triangledown|triangleup|xcross)|lime|line\.style_(dashed|dotted|solid|arrow_both|arrow_left|arrow_right)|linebr|location\.(abovebar|absolute|belowbar|bottom|top)|low|maroon|monday|n|navy|ohlc4|olive|open|orange|period|plot\.style_(area|areabr|circles|columns|cross|histogram|line|linebr|stepline)|purple|red|resolution|saturday|scale\.(left|none|right)|session|session\.(extended|regular)|silver|size\.(auto|huge|large|normal|small|tiny)|solid|source|stepline|string|sunday|symbol|syminfo\.(mintick|pointvalue|prefix|root|session|ticker|tickerid|timezone)|teal|thursday|ticker|timeframe\.(isdaily|isdwm|isintraday|ismonthly|isweekly|multiplier|period)|timenow|tuesday|volume|wednesday|white|yellow|strategy\.(cash|closedtrades|commission\.(cash_per_contract|cash_per_order|percent)|direction\.(all|long|short)|equity|eventrades|fixed|grossloss|grossprofit|initial_capital|long|losstrades|max_contracts_held_(all|long|short)|max_drawdown|netprofit|oca\.(cancel|none|reduce)|openprofit|opentrades|percent_of_equity|position_avg_price|position_entry_name|position_size|short|wintrades)|shape\.(arrowdown|arrowup|circle|cross|diamond|flag|labeldown|labelup|square|triangledown|triangleup|xcross)|barstate\.is(first|history|last|new|realtime)|barmerge\.(gaps_on|gaps_off|lookahead_on|lookahead_off)|xloc\.bar_(index|time)|yloc\.(abovebar|belowbar|price))\b`, NameVariable, nil},
|
||||||
{`(cross|dayofmonth|dayofweek|hour|minute|month|na|second|tickerid|time|tr|vwap|weekofyear|year)(\b[^\(])`, ByGroups(NameVariable, Text), nil}, // variables that can also be function
|
{`(cross|dayofmonth|dayofweek|hour|minute|month|na|second|tickerid|time|tr|vwap|weekofyear|year)(\b[^\(])`, ByGroups(NameVariable, Text), nil}, // variables that can also be function
|
||||||
|
{`(int|float|bool|color|string|label|line)(\b[^\(=.])`, ByGroups(KeywordType, Text), nil}, // types that can also be a function
|
||||||
|
{`(var)\b`, KeywordType, nil},
|
||||||
{`(true|false)\b`, KeywordConstant, nil},
|
{`(true|false)\b`, KeywordConstant, nil},
|
||||||
{`(and|or|not|if|else|for|to)\b`, OperatorWord, nil},
|
{`(and|or|not|if|else|for|to)\b`, OperatorWord, nil},
|
||||||
{`@?[_a-zA-Z]\w*`, Text, nil},
|
{`@?[_a-zA-Z]\w*`, Text, nil},
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
package t
|
||||||
|
|
||||||
|
import (
|
||||||
|
. "github.com/alecthomas/chroma" // nolint
|
||||||
|
"github.com/alecthomas/chroma/lexers/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Turing lexer.
|
||||||
|
var Turing = internal.Register(MustNewLexer(
|
||||||
|
&Config{
|
||||||
|
Name: "Turing",
|
||||||
|
Aliases: []string{"turing"},
|
||||||
|
Filenames: []string{"*.turing", "*.tu"},
|
||||||
|
MimeTypes: []string{"text/x-turing"},
|
||||||
|
},
|
||||||
|
Rules{
|
||||||
|
"root": {
|
||||||
|
{`\n`, Text, nil},
|
||||||
|
{`\s+`, Text, nil},
|
||||||
|
{`\\\n`, Text, nil},
|
||||||
|
{`%(.*?)\n`, CommentSingle, nil},
|
||||||
|
{`/(\\\n)?[*](.|\n)*?[*](\\\n)?/`, CommentMultiline, nil},
|
||||||
|
{`(var|fcn|function|proc|procedure|process|class|end|record|type|begin|case|loop|for|const|union|monitor|module|handler)\b`, KeywordDeclaration, nil},
|
||||||
|
{`(all|asm|assert|bind|bits|body|break|by|cheat|checked|close|condition|decreasing|def|deferred|else|elsif|exit|export|external|flexible|fork|forward|free|get|if|implement|import|include|inherit|init|invariant|label|new|objectclass|of|opaque|open|packed|pause|pervasive|post|pre|priority|put|quit|read|register|result|seek|self|set|signal|skip|tag|tell|then|timeout|to|unchecked|unqualified|wait|when|write)\b`, Keyword, nil},
|
||||||
|
{`(true|false)\b`, KeywordConstant, nil},
|
||||||
|
{Words(``, `\b`, `addressint`, `array`, `boolean`, `char`, `int`, `int1`, `int2`, `int4`, `int8`, `nat`, `nat1`, `nat2`, `nat4`, `nat8`, `pointer`, `real`, `real4`, `real8`, `string`, `enum`), KeywordType, nil},
|
||||||
|
{`\d+i`, LiteralNumber, nil},
|
||||||
|
{`\d+\.\d*([Ee][-+]\d+)?i`, LiteralNumber, nil},
|
||||||
|
{`\.\d+([Ee][-+]\d+)?i`, LiteralNumber, nil},
|
||||||
|
{`\d+[Ee][-+]\d+i`, LiteralNumber, nil},
|
||||||
|
{`\d+(\.\d+[eE][+\-]?\d+|\.\d*|[eE][+\-]?\d+)`, LiteralNumberFloat, nil},
|
||||||
|
{`\.\d+([eE][+\-]?\d+)?`, LiteralNumberFloat, nil},
|
||||||
|
{`0[0-7]+`, LiteralNumberOct, nil},
|
||||||
|
{`0[xX][0-9a-fA-F]+`, LiteralNumberHex, nil},
|
||||||
|
{`(0|[1-9][0-9]*)`, LiteralNumberInteger, nil},
|
||||||
|
{`(div|mod|rem|\*\*|=|<|>|>=|<=|not=|not|and|or|xor|=>|in|shl|shr|->|~|~=|~in|&|:=|\.\.|[\^+\-*/&#])`, Operator, nil},
|
||||||
|
{`'(\\['"\\abfnrtv]|\\x[0-9a-fA-F]{2}|\\[0-7]{1,3}|\\u[0-9a-fA-F]{4}|\\U[0-9a-fA-F]{8}|[^\\])'`, LiteralStringChar, nil},
|
||||||
|
{`"(\\\\|\\"|[^"])*"`, LiteralString, nil},
|
||||||
|
{`[()\[\]{}.,:]`, Punctuation, nil},
|
||||||
|
{`[^\W\d]\w*`, NameOther, nil},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
))
|
|
@ -13,6 +13,7 @@ var TypeScript = internal.Register(MustNewLexer(
|
||||||
Filenames: []string{"*.ts", "*.tsx"},
|
Filenames: []string{"*.ts", "*.tsx"},
|
||||||
MimeTypes: []string{"text/x-typescript"},
|
MimeTypes: []string{"text/x-typescript"},
|
||||||
DotAll: true,
|
DotAll: true,
|
||||||
|
EnsureNL: true,
|
||||||
},
|
},
|
||||||
Rules{
|
Rules{
|
||||||
"commentsandwhitespace": {
|
"commentsandwhitespace": {
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -21,7 +21,7 @@ type LexerMutator interface {
|
||||||
// A MutatorFunc is a Mutator that mutates the lexer state machine as it is processing.
|
// A MutatorFunc is a Mutator that mutates the lexer state machine as it is processing.
|
||||||
type MutatorFunc func(state *LexerState) error
|
type MutatorFunc func(state *LexerState) error
|
||||||
|
|
||||||
func (m MutatorFunc) Mutate(state *LexerState) error { return m(state) }
|
func (m MutatorFunc) Mutate(state *LexerState) error { return m(state) } // nolint
|
||||||
|
|
||||||
// Mutators applies a set of Mutators in order.
|
// Mutators applies a set of Mutators in order.
|
||||||
func Mutators(modifiers ...Mutator) MutatorFunc {
|
func Mutators(modifiers ...Mutator) MutatorFunc {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
"github.com/dlclark/regexp2"
|
"github.com/dlclark/regexp2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// A Rule is the fundamental matching unit of the Regex lexer state machine.
|
||||||
type Rule struct {
|
type Rule struct {
|
||||||
Pattern string
|
Pattern string
|
||||||
Type Emitter
|
Type Emitter
|
||||||
|
@ -56,26 +57,26 @@ func ByGroups(emitters ...Emitter) Emitter {
|
||||||
//
|
//
|
||||||
// Example:
|
// Example:
|
||||||
//
|
//
|
||||||
// var Markdown = internal.Register(MustNewLexer(
|
// var Markdown = internal.Register(MustNewLexer(
|
||||||
// &Config{
|
// &Config{
|
||||||
// Name: "markdown",
|
// Name: "markdown",
|
||||||
// Aliases: []string{"md", "mkd"},
|
// Aliases: []string{"md", "mkd"},
|
||||||
// Filenames: []string{"*.md", "*.mkd", "*.markdown"},
|
// Filenames: []string{"*.md", "*.mkd", "*.markdown"},
|
||||||
// MimeTypes: []string{"text/x-markdown"},
|
// MimeTypes: []string{"text/x-markdown"},
|
||||||
// },
|
// },
|
||||||
// Rules{
|
// Rules{
|
||||||
// "root": {
|
// "root": {
|
||||||
// {"^(```)(\\w+)(\\n)([\\w\\W]*?)(^```$)",
|
// {"^(```)(\\w+)(\\n)([\\w\\W]*?)(^```$)",
|
||||||
// UsingByGroup(
|
// UsingByGroup(
|
||||||
// internal.Get,
|
// internal.Get,
|
||||||
// 2, 4,
|
// 2, 4,
|
||||||
// String, String, String, Text, String,
|
// String, String, String, Text, String,
|
||||||
// ),
|
// ),
|
||||||
// nil,
|
// nil,
|
||||||
// },
|
// },
|
||||||
// },
|
// },
|
||||||
// },
|
// },
|
||||||
// ))
|
// ))
|
||||||
//
|
//
|
||||||
// See the lexers/m/markdown.go for the complete example.
|
// See the lexers/m/markdown.go for the complete example.
|
||||||
//
|
//
|
||||||
|
@ -155,6 +156,7 @@ func Tokenise(lexer Lexer, options *TokeniseOptions, text string) ([]Token, erro
|
||||||
// Rules maps from state to a sequence of Rules.
|
// Rules maps from state to a sequence of Rules.
|
||||||
type Rules map[string][]Rule
|
type Rules map[string][]Rule
|
||||||
|
|
||||||
|
// Clone returns a clone of the Rules.
|
||||||
func (r Rules) Clone() Rules {
|
func (r Rules) Clone() Rules {
|
||||||
out := map[string][]Rule{}
|
out := map[string][]Rule{}
|
||||||
for key, rules := range r {
|
for key, rules := range r {
|
||||||
|
@ -207,6 +209,7 @@ func NewLexer(config *Config, rules Rules) (*RegexLexer, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Trace enables debug tracing.
|
||||||
func (r *RegexLexer) Trace(trace bool) *RegexLexer {
|
func (r *RegexLexer) Trace(trace bool) *RegexLexer {
|
||||||
r.trace = trace
|
r.trace = trace
|
||||||
return r
|
return r
|
||||||
|
@ -221,8 +224,10 @@ type CompiledRule struct {
|
||||||
flags string
|
flags string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CompiledRules is a map of rule name to sequence of compiled rules in that rule.
|
||||||
type CompiledRules map[string][]*CompiledRule
|
type CompiledRules map[string][]*CompiledRule
|
||||||
|
|
||||||
|
// LexerState contains the state for a single lex.
|
||||||
type LexerState struct {
|
type LexerState struct {
|
||||||
Lexer *RegexLexer
|
Lexer *RegexLexer
|
||||||
Text []rune
|
Text []rune
|
||||||
|
@ -236,16 +241,20 @@ type LexerState struct {
|
||||||
// Custum context for mutators.
|
// Custum context for mutators.
|
||||||
MutatorContext map[interface{}]interface{}
|
MutatorContext map[interface{}]interface{}
|
||||||
iteratorStack []Iterator
|
iteratorStack []Iterator
|
||||||
|
options *TokeniseOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set mutator context.
|
||||||
func (l *LexerState) Set(key interface{}, value interface{}) {
|
func (l *LexerState) Set(key interface{}, value interface{}) {
|
||||||
l.MutatorContext[key] = value
|
l.MutatorContext[key] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get mutator context.
|
||||||
func (l *LexerState) Get(key interface{}) interface{} {
|
func (l *LexerState) Get(key interface{}) interface{} {
|
||||||
return l.MutatorContext[key]
|
return l.MutatorContext[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Iterator returns the next Token from the lexer.
|
||||||
func (l *LexerState) Iterator() Token {
|
func (l *LexerState) Iterator() Token {
|
||||||
for l.Pos < len(l.Text) && len(l.Stack) > 0 {
|
for l.Pos < len(l.Text) && len(l.Stack) > 0 {
|
||||||
// Exhaust the iterator stack, if any.
|
// Exhaust the iterator stack, if any.
|
||||||
|
@ -267,9 +276,19 @@ func (l *LexerState) Iterator() Token {
|
||||||
if !ok {
|
if !ok {
|
||||||
panic("unknown state " + l.State)
|
panic("unknown state " + l.State)
|
||||||
}
|
}
|
||||||
ruleIndex, rule, groups := matchRules(l.Text[l.Pos:], selectedRule)
|
ruleIndex, rule, groups := matchRules(l.Text, l.Pos, selectedRule)
|
||||||
// No match.
|
// No match.
|
||||||
if groups == nil {
|
if groups == nil {
|
||||||
|
// From Pygments :\
|
||||||
|
//
|
||||||
|
// If the RegexLexer encounters a newline that is flagged as an error token, the stack is
|
||||||
|
// emptied and the lexer continues scanning in the 'root' state. This can help producing
|
||||||
|
// error-tolerant highlighting for erroneous input, e.g. when a single-line string is not
|
||||||
|
// closed.
|
||||||
|
if l.Text[l.Pos] == '\n' && l.State != l.options.State {
|
||||||
|
l.Stack = []string{l.options.State}
|
||||||
|
continue
|
||||||
|
}
|
||||||
l.Pos++
|
l.Pos++
|
||||||
return Token{Error, string(l.Text[l.Pos-1 : l.Pos])}
|
return Token{Error, string(l.Text[l.Pos-1 : l.Pos])}
|
||||||
}
|
}
|
||||||
|
@ -306,6 +325,7 @@ func (l *LexerState) Iterator() Token {
|
||||||
return EOF
|
return EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RegexLexer is the default lexer implementation used in Chroma.
|
||||||
type RegexLexer struct {
|
type RegexLexer struct {
|
||||||
config *Config
|
config *Config
|
||||||
analyser func(text string) float32
|
analyser func(text string) float32
|
||||||
|
@ -322,14 +342,14 @@ func (r *RegexLexer) SetAnalyser(analyser func(text string) float32) *RegexLexer
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RegexLexer) AnalyseText(text string) float32 {
|
func (r *RegexLexer) AnalyseText(text string) float32 { // nolint
|
||||||
if r.analyser != nil {
|
if r.analyser != nil {
|
||||||
return r.analyser(text)
|
return r.analyser(text)
|
||||||
}
|
}
|
||||||
return 0.0
|
return 0.0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RegexLexer) Config() *Config {
|
func (r *RegexLexer) Config() *Config { // nolint
|
||||||
return r.config
|
return r.config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -343,7 +363,12 @@ func (r *RegexLexer) maybeCompile() (err error) {
|
||||||
for state, rules := range r.rules {
|
for state, rules := range r.rules {
|
||||||
for i, rule := range rules {
|
for i, rule := range rules {
|
||||||
if rule.Regexp == nil {
|
if rule.Regexp == nil {
|
||||||
rule.Regexp, err = regexp2.Compile("^(?"+rule.flags+")(?:"+rule.Pattern+")", 0)
|
pattern := "(?:" + rule.Pattern + ")"
|
||||||
|
if rule.flags != "" {
|
||||||
|
pattern = "(?" + rule.flags + ")" + pattern
|
||||||
|
}
|
||||||
|
pattern = `\G` + pattern
|
||||||
|
rule.Regexp, err = regexp2.Compile(pattern, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to compile rule %s.%d: %s", state, i, err)
|
return fmt.Errorf("failed to compile rule %s.%d: %s", state, i, err)
|
||||||
}
|
}
|
||||||
|
@ -374,7 +399,7 @@ restart:
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RegexLexer) Tokenise(options *TokeniseOptions, text string) (Iterator, error) {
|
func (r *RegexLexer) Tokenise(options *TokeniseOptions, text string) (Iterator, error) { // nolint
|
||||||
if err := r.maybeCompile(); err != nil {
|
if err := r.maybeCompile(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -385,6 +410,7 @@ func (r *RegexLexer) Tokenise(options *TokeniseOptions, text string) (Iterator,
|
||||||
text += "\n"
|
text += "\n"
|
||||||
}
|
}
|
||||||
state := &LexerState{
|
state := &LexerState{
|
||||||
|
options: options,
|
||||||
Lexer: r,
|
Lexer: r,
|
||||||
Text: []rune(text),
|
Text: []rune(text),
|
||||||
Stack: []string{options.State},
|
Stack: []string{options.State},
|
||||||
|
@ -394,10 +420,10 @@ func (r *RegexLexer) Tokenise(options *TokeniseOptions, text string) (Iterator,
|
||||||
return state.Iterator, nil
|
return state.Iterator, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func matchRules(text []rune, rules []*CompiledRule) (int, *CompiledRule, []string) {
|
func matchRules(text []rune, pos int, rules []*CompiledRule) (int, *CompiledRule, []string) {
|
||||||
for i, rule := range rules {
|
for i, rule := range rules {
|
||||||
match, err := rule.Regexp.FindRunesMatch(text)
|
match, err := rule.Regexp.FindRunesMatchStartingAt(text, pos)
|
||||||
if match != nil && err == nil {
|
if match != nil && err == nil && match.Index == pos {
|
||||||
groups := []string{}
|
groups := []string{}
|
||||||
for _, g := range match.Groups() {
|
for _, g := range match.Groups() {
|
||||||
groups = append(groups, g.String())
|
groups = append(groups, g.String())
|
||||||
|
|
|
@ -36,6 +36,7 @@ func (r *remappingLexer) Tokenise(options *TokeniseOptions, text string) (Iterat
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TypeMapping defines type maps for the TypeRemappingLexer.
|
||||||
type TypeMapping []struct {
|
type TypeMapping []struct {
|
||||||
From, To TokenType
|
From, To TokenType
|
||||||
Words []string
|
Words []string
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
// Trilean value for StyleEntry value inheritance.
|
// Trilean value for StyleEntry value inheritance.
|
||||||
type Trilean uint8
|
type Trilean uint8
|
||||||
|
|
||||||
|
// Trilean states.
|
||||||
const (
|
const (
|
||||||
Pass Trilean = iota
|
Pass Trilean = iota
|
||||||
Yes
|
Yes
|
||||||
|
@ -25,6 +26,7 @@ func (t Trilean) String() string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Prefix returns s with "no" as a prefix if Trilean is no.
|
||||||
func (t Trilean) Prefix(s string) string {
|
func (t Trilean) Prefix(s string) string {
|
||||||
if t == Yes {
|
if t == Yes {
|
||||||
return s
|
return s
|
||||||
|
@ -73,6 +75,7 @@ func (s StyleEntry) String() string {
|
||||||
return strings.Join(out, " ")
|
return strings.Join(out, " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Sub subtracts e from s where elements match.
|
||||||
func (s StyleEntry) Sub(e StyleEntry) StyleEntry {
|
func (s StyleEntry) Sub(e StyleEntry) StyleEntry {
|
||||||
out := StyleEntry{}
|
out := StyleEntry{}
|
||||||
if e.Colour != s.Colour {
|
if e.Colour != s.Colour {
|
||||||
|
@ -247,15 +250,7 @@ func (s *Style) Builder() *StyleBuilder {
|
||||||
//
|
//
|
||||||
// This is distinct from Get() which will merge parent tokens.
|
// This is distinct from Get() which will merge parent tokens.
|
||||||
func (s *Style) Has(ttype TokenType) bool {
|
func (s *Style) Has(ttype TokenType) bool {
|
||||||
return !s.get(ttype).IsZero()
|
return !s.get(ttype).IsZero() || s.synthesisable(ttype)
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Style) get(ttype TokenType) StyleEntry {
|
|
||||||
out := s.entries[ttype]
|
|
||||||
if out.IsZero() && s.parent != nil {
|
|
||||||
return s.parent.get(ttype)
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a style entry. Will try sub-category or category if an exact match is not found, and
|
// Get a style entry. Will try sub-category or category if an exact match is not found, and
|
||||||
|
@ -268,6 +263,38 @@ func (s *Style) Get(ttype TokenType) StyleEntry {
|
||||||
s.get(ttype.SubCategory()))
|
s.get(ttype.SubCategory()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Style) get(ttype TokenType) StyleEntry {
|
||||||
|
out := s.entries[ttype]
|
||||||
|
if out.IsZero() && s.synthesisable(ttype) {
|
||||||
|
out = s.synthesise(ttype)
|
||||||
|
}
|
||||||
|
if out.IsZero() && s.parent != nil {
|
||||||
|
return s.parent.get(ttype)
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Style) synthesise(ttype TokenType) StyleEntry {
|
||||||
|
bg := s.get(Background)
|
||||||
|
text := StyleEntry{Colour: bg.Colour}
|
||||||
|
text.Colour = text.Colour.BrightenOrDarken(0.5)
|
||||||
|
|
||||||
|
switch ttype {
|
||||||
|
// If we don't have a line highlight colour, make one that is 10% brighter/darker than the background.
|
||||||
|
case LineHighlight:
|
||||||
|
return StyleEntry{Background: bg.Background.BrightenOrDarken(0.1)}
|
||||||
|
|
||||||
|
// If we don't have line numbers, use the text colour but 20% brighter/darker
|
||||||
|
case LineNumbers, LineNumbersTable:
|
||||||
|
return text
|
||||||
|
}
|
||||||
|
return StyleEntry{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Style) synthesisable(ttype TokenType) bool {
|
||||||
|
return ttype == LineHighlight || ttype == LineNumbers || ttype == LineNumbersTable
|
||||||
|
}
|
||||||
|
|
||||||
// ParseStyleEntry parses a Pygments style entry.
|
// ParseStyleEntry parses a Pygments style entry.
|
||||||
func ParseStyleEntry(entry string) (StyleEntry, error) { // nolint: gocyclo
|
func ParseStyleEntry(entry string) (StyleEntry, error) { // nolint: gocyclo
|
||||||
out := StyleEntry{}
|
out := StyleEntry{}
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"github.com/alecthomas/chroma"
|
"github.com/alecthomas/chroma"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Algol_Nu style.
|
// AlgolNu style.
|
||||||
var Algol_Nu = Register(chroma.MustNewStyle("algol_nu", chroma.StyleEntries{
|
var AlgolNu = Register(chroma.MustNewStyle("algol_nu", chroma.StyleEntries{
|
||||||
chroma.Comment: "italic #888",
|
chroma.Comment: "italic #888",
|
||||||
chroma.CommentPreproc: "bold noitalic #888",
|
chroma.CommentPreproc: "bold noitalic #888",
|
||||||
chroma.CommentSpecial: "bold noitalic #888",
|
chroma.CommentSpecial: "bold noitalic #888",
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Pygments default theme.
|
// Pygments default theme.
|
||||||
var Pygments = Register(chroma.MustNewStyle("pygments", map[chroma.TokenType]string{
|
var Pygments = Register(chroma.MustNewStyle("pygments", chroma.StyleEntries{
|
||||||
chroma.Whitespace: "#bbbbbb",
|
chroma.Whitespace: "#bbbbbb",
|
||||||
chroma.Comment: "italic #408080",
|
chroma.Comment: "italic #408080",
|
||||||
chroma.CommentPreproc: "noitalic #BC7A00",
|
chroma.CommentPreproc: "noitalic #BC7A00",
|
||||||
|
|
|
@ -6,13 +6,15 @@ import (
|
||||||
|
|
||||||
// Rrt style.
|
// Rrt style.
|
||||||
var Rrt = Register(chroma.MustNewStyle("rrt", chroma.StyleEntries{
|
var Rrt = Register(chroma.MustNewStyle("rrt", chroma.StyleEntries{
|
||||||
chroma.Comment: "#00ff00",
|
chroma.CommentPreproc: "#e5e5e5",
|
||||||
chroma.NameFunction: "#ffff00",
|
chroma.Comment: "#00ff00",
|
||||||
chroma.NameVariable: "#eedd82",
|
chroma.KeywordType: "#ee82ee",
|
||||||
chroma.NameConstant: "#7fffd4",
|
chroma.Keyword: "#ff0000",
|
||||||
chroma.Keyword: "#ff0000",
|
chroma.LiteralNumber: "#ff6600",
|
||||||
chroma.CommentPreproc: "#e5e5e5",
|
chroma.LiteralStringSymbol: "#ff6600",
|
||||||
chroma.LiteralString: "#87ceeb",
|
chroma.LiteralString: "#87ceeb",
|
||||||
chroma.KeywordType: "#ee82ee",
|
chroma.NameFunction: "#ffff00",
|
||||||
chroma.Background: " bg:#000000",
|
chroma.NameConstant: "#7fffd4",
|
||||||
|
chroma.NameVariable: "#eedd82",
|
||||||
|
chroma.Background: "#f8f8f2 bg:#000000",
|
||||||
}))
|
}))
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// SwapOff theme.
|
// SwapOff theme.
|
||||||
var SwapOff = Register(chroma.MustNewStyle("swapoff", map[chroma.TokenType]string{
|
var SwapOff = Register(chroma.MustNewStyle("swapoff", chroma.StyleEntries{
|
||||||
chroma.Background: "#lightgray bg:#black",
|
chroma.Background: "#lightgray bg:#black",
|
||||||
chroma.Number: "bold #ansiyellow",
|
chroma.Number: "bold #ansiyellow",
|
||||||
chroma.Comment: "#ansiteal",
|
chroma.Comment: "#ansiteal",
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
from collections import defaultdict
|
||||||
|
from subprocess import check_output
|
||||||
|
|
||||||
|
lines = check_output(["go", "run", "./cmd/chroma/main.go", "--list"]).decode('utf-8').splitlines()
|
||||||
|
lines = [line.strip() for line in lines if line.startswith(" ") and not line.startswith(" ")]
|
||||||
|
lines = sorted(lines, key=lambda l: l.lower())
|
||||||
|
|
||||||
|
table = defaultdict(list)
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
table[line[0].upper()].append(line)
|
||||||
|
|
||||||
|
for key, value in table.items():
|
||||||
|
print("{} | {}".format(key, ', '.join(value)))
|
|
@ -4,6 +4,107 @@ package chroma
|
||||||
|
|
||||||
import "strconv"
|
import "strconv"
|
||||||
|
|
||||||
|
func _() {
|
||||||
|
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||||
|
// Re-run the stringer command to generate them again.
|
||||||
|
var x [1]struct{}
|
||||||
|
_ = x[Background - -1]
|
||||||
|
_ = x[LineNumbers - -2]
|
||||||
|
_ = x[LineNumbersTable - -3]
|
||||||
|
_ = x[LineHighlight - -4]
|
||||||
|
_ = x[LineTable - -5]
|
||||||
|
_ = x[LineTableTD - -6]
|
||||||
|
_ = x[Error - -7]
|
||||||
|
_ = x[Other - -8]
|
||||||
|
_ = x[None - -9]
|
||||||
|
_ = x[EOFType-0]
|
||||||
|
_ = x[Keyword-1000]
|
||||||
|
_ = x[KeywordConstant-1001]
|
||||||
|
_ = x[KeywordDeclaration-1002]
|
||||||
|
_ = x[KeywordNamespace-1003]
|
||||||
|
_ = x[KeywordPseudo-1004]
|
||||||
|
_ = x[KeywordReserved-1005]
|
||||||
|
_ = x[KeywordType-1006]
|
||||||
|
_ = x[Name-2000]
|
||||||
|
_ = x[NameAttribute-2001]
|
||||||
|
_ = x[NameBuiltin-2002]
|
||||||
|
_ = x[NameBuiltinPseudo-2003]
|
||||||
|
_ = x[NameClass-2004]
|
||||||
|
_ = x[NameConstant-2005]
|
||||||
|
_ = x[NameDecorator-2006]
|
||||||
|
_ = x[NameEntity-2007]
|
||||||
|
_ = x[NameException-2008]
|
||||||
|
_ = x[NameFunction-2009]
|
||||||
|
_ = x[NameFunctionMagic-2010]
|
||||||
|
_ = x[NameKeyword-2011]
|
||||||
|
_ = x[NameLabel-2012]
|
||||||
|
_ = x[NameNamespace-2013]
|
||||||
|
_ = x[NameOperator-2014]
|
||||||
|
_ = x[NameOther-2015]
|
||||||
|
_ = x[NamePseudo-2016]
|
||||||
|
_ = x[NameProperty-2017]
|
||||||
|
_ = x[NameTag-2018]
|
||||||
|
_ = x[NameVariable-2019]
|
||||||
|
_ = x[NameVariableAnonymous-2020]
|
||||||
|
_ = x[NameVariableClass-2021]
|
||||||
|
_ = x[NameVariableGlobal-2022]
|
||||||
|
_ = x[NameVariableInstance-2023]
|
||||||
|
_ = x[NameVariableMagic-2024]
|
||||||
|
_ = x[Literal-3000]
|
||||||
|
_ = x[LiteralDate-3001]
|
||||||
|
_ = x[LiteralOther-3002]
|
||||||
|
_ = x[LiteralString-3100]
|
||||||
|
_ = x[LiteralStringAffix-3101]
|
||||||
|
_ = x[LiteralStringAtom-3102]
|
||||||
|
_ = x[LiteralStringBacktick-3103]
|
||||||
|
_ = x[LiteralStringBoolean-3104]
|
||||||
|
_ = x[LiteralStringChar-3105]
|
||||||
|
_ = x[LiteralStringDelimiter-3106]
|
||||||
|
_ = x[LiteralStringDoc-3107]
|
||||||
|
_ = x[LiteralStringDouble-3108]
|
||||||
|
_ = x[LiteralStringEscape-3109]
|
||||||
|
_ = x[LiteralStringHeredoc-3110]
|
||||||
|
_ = x[LiteralStringInterpol-3111]
|
||||||
|
_ = x[LiteralStringName-3112]
|
||||||
|
_ = x[LiteralStringOther-3113]
|
||||||
|
_ = x[LiteralStringRegex-3114]
|
||||||
|
_ = x[LiteralStringSingle-3115]
|
||||||
|
_ = x[LiteralStringSymbol-3116]
|
||||||
|
_ = x[LiteralNumber-3200]
|
||||||
|
_ = x[LiteralNumberBin-3201]
|
||||||
|
_ = x[LiteralNumberFloat-3202]
|
||||||
|
_ = x[LiteralNumberHex-3203]
|
||||||
|
_ = x[LiteralNumberInteger-3204]
|
||||||
|
_ = x[LiteralNumberIntegerLong-3205]
|
||||||
|
_ = x[LiteralNumberOct-3206]
|
||||||
|
_ = x[Operator-4000]
|
||||||
|
_ = x[OperatorWord-4001]
|
||||||
|
_ = x[Punctuation-5000]
|
||||||
|
_ = x[Comment-6000]
|
||||||
|
_ = x[CommentHashbang-6001]
|
||||||
|
_ = x[CommentMultiline-6002]
|
||||||
|
_ = x[CommentSingle-6003]
|
||||||
|
_ = x[CommentSpecial-6004]
|
||||||
|
_ = x[CommentPreproc-6100]
|
||||||
|
_ = x[CommentPreprocFile-6101]
|
||||||
|
_ = x[Generic-7000]
|
||||||
|
_ = x[GenericDeleted-7001]
|
||||||
|
_ = x[GenericEmph-7002]
|
||||||
|
_ = x[GenericError-7003]
|
||||||
|
_ = x[GenericHeading-7004]
|
||||||
|
_ = x[GenericInserted-7005]
|
||||||
|
_ = x[GenericOutput-7006]
|
||||||
|
_ = x[GenericPrompt-7007]
|
||||||
|
_ = x[GenericStrong-7008]
|
||||||
|
_ = x[GenericSubheading-7009]
|
||||||
|
_ = x[GenericTraceback-7010]
|
||||||
|
_ = x[GenericUnderline-7011]
|
||||||
|
_ = x[Text-8000]
|
||||||
|
_ = x[TextWhitespace-8001]
|
||||||
|
_ = x[TextSymbol-8002]
|
||||||
|
_ = x[TextPunctuation-8003]
|
||||||
|
}
|
||||||
|
|
||||||
const _TokenType_name = "NoneOtherErrorLineTableTDLineTableLineHighlightLineNumbersTableLineNumbersBackgroundEOFTypeKeywordKeywordConstantKeywordDeclarationKeywordNamespaceKeywordPseudoKeywordReservedKeywordTypeNameNameAttributeNameBuiltinNameBuiltinPseudoNameClassNameConstantNameDecoratorNameEntityNameExceptionNameFunctionNameFunctionMagicNameKeywordNameLabelNameNamespaceNameOperatorNameOtherNamePseudoNamePropertyNameTagNameVariableNameVariableAnonymousNameVariableClassNameVariableGlobalNameVariableInstanceNameVariableMagicLiteralLiteralDateLiteralOtherLiteralStringLiteralStringAffixLiteralStringAtomLiteralStringBacktickLiteralStringBooleanLiteralStringCharLiteralStringDelimiterLiteralStringDocLiteralStringDoubleLiteralStringEscapeLiteralStringHeredocLiteralStringInterpolLiteralStringNameLiteralStringOtherLiteralStringRegexLiteralStringSingleLiteralStringSymbolLiteralNumberLiteralNumberBinLiteralNumberFloatLiteralNumberHexLiteralNumberIntegerLiteralNumberIntegerLongLiteralNumberOctOperatorOperatorWordPunctuationCommentCommentHashbangCommentMultilineCommentSingleCommentSpecialCommentPreprocCommentPreprocFileGenericGenericDeletedGenericEmphGenericErrorGenericHeadingGenericInsertedGenericOutputGenericPromptGenericStrongGenericSubheadingGenericTracebackGenericUnderlineTextTextWhitespaceTextSymbolTextPunctuation"
|
const _TokenType_name = "NoneOtherErrorLineTableTDLineTableLineHighlightLineNumbersTableLineNumbersBackgroundEOFTypeKeywordKeywordConstantKeywordDeclarationKeywordNamespaceKeywordPseudoKeywordReservedKeywordTypeNameNameAttributeNameBuiltinNameBuiltinPseudoNameClassNameConstantNameDecoratorNameEntityNameExceptionNameFunctionNameFunctionMagicNameKeywordNameLabelNameNamespaceNameOperatorNameOtherNamePseudoNamePropertyNameTagNameVariableNameVariableAnonymousNameVariableClassNameVariableGlobalNameVariableInstanceNameVariableMagicLiteralLiteralDateLiteralOtherLiteralStringLiteralStringAffixLiteralStringAtomLiteralStringBacktickLiteralStringBooleanLiteralStringCharLiteralStringDelimiterLiteralStringDocLiteralStringDoubleLiteralStringEscapeLiteralStringHeredocLiteralStringInterpolLiteralStringNameLiteralStringOtherLiteralStringRegexLiteralStringSingleLiteralStringSymbolLiteralNumberLiteralNumberBinLiteralNumberFloatLiteralNumberHexLiteralNumberIntegerLiteralNumberIntegerLongLiteralNumberOctOperatorOperatorWordPunctuationCommentCommentHashbangCommentMultilineCommentSingleCommentSpecialCommentPreprocCommentPreprocFileGenericGenericDeletedGenericEmphGenericErrorGenericHeadingGenericInsertedGenericOutputGenericPromptGenericStrongGenericSubheadingGenericTracebackGenericUnderlineTextTextWhitespaceTextSymbolTextPunctuation"
|
||||||
|
|
||||||
var _TokenType_map = map[TokenType]string{
|
var _TokenType_map = map[TokenType]string{
|
||||||
|
|
|
@ -6,3 +6,4 @@
|
||||||
Icon?
|
Icon?
|
||||||
ehthumbs.db
|
ehthumbs.db
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
.idea
|
||||||
|
|
|
@ -1,13 +1,107 @@
|
||||||
sudo: false
|
sudo: false
|
||||||
language: go
|
language: go
|
||||||
go:
|
go:
|
||||||
- 1.2
|
- 1.7.x
|
||||||
- 1.3
|
- 1.8.x
|
||||||
- 1.4
|
- 1.9.x
|
||||||
- 1.5
|
- 1.10.x
|
||||||
- 1.6
|
- master
|
||||||
- 1.7
|
|
||||||
- tip
|
before_install:
|
||||||
|
- go get golang.org/x/tools/cmd/cover
|
||||||
|
- go get github.com/mattn/goveralls
|
||||||
|
|
||||||
before_script:
|
before_script:
|
||||||
|
- echo -e "[server]\ninnodb_log_file_size=256MB\ninnodb_buffer_pool_size=512MB\nmax_allowed_packet=16MB" | sudo tee -a /etc/mysql/my.cnf
|
||||||
|
- sudo service mysql restart
|
||||||
|
- .travis/wait_mysql.sh
|
||||||
- mysql -e 'create database gotest;'
|
- mysql -e 'create database gotest;'
|
||||||
|
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- env: DB=MYSQL8
|
||||||
|
sudo: required
|
||||||
|
dist: trusty
|
||||||
|
go: 1.10.x
|
||||||
|
services:
|
||||||
|
- docker
|
||||||
|
before_install:
|
||||||
|
- go get golang.org/x/tools/cmd/cover
|
||||||
|
- go get github.com/mattn/goveralls
|
||||||
|
- docker pull mysql:8.0
|
||||||
|
- docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret
|
||||||
|
mysql:8.0 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB --local-infile=1
|
||||||
|
- cp .travis/docker.cnf ~/.my.cnf
|
||||||
|
- .travis/wait_mysql.sh
|
||||||
|
before_script:
|
||||||
|
- export MYSQL_TEST_USER=gotest
|
||||||
|
- export MYSQL_TEST_PASS=secret
|
||||||
|
- export MYSQL_TEST_ADDR=127.0.0.1:3307
|
||||||
|
- export MYSQL_TEST_CONCURRENT=1
|
||||||
|
|
||||||
|
- env: DB=MYSQL57
|
||||||
|
sudo: required
|
||||||
|
dist: trusty
|
||||||
|
go: 1.10.x
|
||||||
|
services:
|
||||||
|
- docker
|
||||||
|
before_install:
|
||||||
|
- go get golang.org/x/tools/cmd/cover
|
||||||
|
- go get github.com/mattn/goveralls
|
||||||
|
- docker pull mysql:5.7
|
||||||
|
- docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret
|
||||||
|
mysql:5.7 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB --local-infile=1
|
||||||
|
- cp .travis/docker.cnf ~/.my.cnf
|
||||||
|
- .travis/wait_mysql.sh
|
||||||
|
before_script:
|
||||||
|
- export MYSQL_TEST_USER=gotest
|
||||||
|
- export MYSQL_TEST_PASS=secret
|
||||||
|
- export MYSQL_TEST_ADDR=127.0.0.1:3307
|
||||||
|
- export MYSQL_TEST_CONCURRENT=1
|
||||||
|
|
||||||
|
- env: DB=MARIA55
|
||||||
|
sudo: required
|
||||||
|
dist: trusty
|
||||||
|
go: 1.10.x
|
||||||
|
services:
|
||||||
|
- docker
|
||||||
|
before_install:
|
||||||
|
- go get golang.org/x/tools/cmd/cover
|
||||||
|
- go get github.com/mattn/goveralls
|
||||||
|
- docker pull mariadb:5.5
|
||||||
|
- docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret
|
||||||
|
mariadb:5.5 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB --local-infile=1
|
||||||
|
- cp .travis/docker.cnf ~/.my.cnf
|
||||||
|
- .travis/wait_mysql.sh
|
||||||
|
before_script:
|
||||||
|
- export MYSQL_TEST_USER=gotest
|
||||||
|
- export MYSQL_TEST_PASS=secret
|
||||||
|
- export MYSQL_TEST_ADDR=127.0.0.1:3307
|
||||||
|
- export MYSQL_TEST_CONCURRENT=1
|
||||||
|
|
||||||
|
- env: DB=MARIA10_1
|
||||||
|
sudo: required
|
||||||
|
dist: trusty
|
||||||
|
go: 1.10.x
|
||||||
|
services:
|
||||||
|
- docker
|
||||||
|
before_install:
|
||||||
|
- go get golang.org/x/tools/cmd/cover
|
||||||
|
- go get github.com/mattn/goveralls
|
||||||
|
- docker pull mariadb:10.1
|
||||||
|
- docker run -d -p 127.0.0.1:3307:3306 --name mysqld -e MYSQL_DATABASE=gotest -e MYSQL_USER=gotest -e MYSQL_PASSWORD=secret -e MYSQL_ROOT_PASSWORD=verysecret
|
||||||
|
mariadb:10.1 --innodb_log_file_size=256MB --innodb_buffer_pool_size=512MB --max_allowed_packet=16MB --local-infile=1
|
||||||
|
- cp .travis/docker.cnf ~/.my.cnf
|
||||||
|
- .travis/wait_mysql.sh
|
||||||
|
before_script:
|
||||||
|
- export MYSQL_TEST_USER=gotest
|
||||||
|
- export MYSQL_TEST_PASS=secret
|
||||||
|
- export MYSQL_TEST_ADDR=127.0.0.1:3307
|
||||||
|
- export MYSQL_TEST_CONCURRENT=1
|
||||||
|
|
||||||
|
script:
|
||||||
|
- go test -v -covermode=count -coverprofile=coverage.out
|
||||||
|
- go vet ./...
|
||||||
|
- .travis/gofmt.sh
|
||||||
|
after_script:
|
||||||
|
- $HOME/gopath/bin/goveralls -coverprofile=coverage.out -service=travis-ci
|
||||||
|
|
|
@ -12,35 +12,63 @@
|
||||||
# Individual Persons
|
# Individual Persons
|
||||||
|
|
||||||
Aaron Hopkins <go-sql-driver at die.net>
|
Aaron Hopkins <go-sql-driver at die.net>
|
||||||
|
Achille Roussel <achille.roussel at gmail.com>
|
||||||
|
Alexey Palazhchenko <alexey.palazhchenko at gmail.com>
|
||||||
|
Andrew Reid <andrew.reid at tixtrack.com>
|
||||||
Arne Hormann <arnehormann at gmail.com>
|
Arne Hormann <arnehormann at gmail.com>
|
||||||
|
Asta Xie <xiemengjun at gmail.com>
|
||||||
|
Bulat Gaifullin <gaifullinbf at gmail.com>
|
||||||
Carlos Nieto <jose.carlos at menteslibres.net>
|
Carlos Nieto <jose.carlos at menteslibres.net>
|
||||||
Chris Moos <chris at tech9computers.com>
|
Chris Moos <chris at tech9computers.com>
|
||||||
|
Craig Wilson <craiggwilson at gmail.com>
|
||||||
|
Daniel Montoya <dsmontoyam at gmail.com>
|
||||||
Daniel Nichter <nil at codenode.com>
|
Daniel Nichter <nil at codenode.com>
|
||||||
Daniël van Eeden <git at myname.nl>
|
Daniël van Eeden <git at myname.nl>
|
||||||
|
Dave Protasowski <dprotaso at gmail.com>
|
||||||
DisposaBoy <disposaboy at dby.me>
|
DisposaBoy <disposaboy at dby.me>
|
||||||
|
Egor Smolyakov <egorsmkv at gmail.com>
|
||||||
|
Evan Shaw <evan at vendhq.com>
|
||||||
Frederick Mayle <frederickmayle at gmail.com>
|
Frederick Mayle <frederickmayle at gmail.com>
|
||||||
Gustavo Kristic <gkristic at gmail.com>
|
Gustavo Kristic <gkristic at gmail.com>
|
||||||
|
Hajime Nakagami <nakagami at gmail.com>
|
||||||
Hanno Braun <mail at hannobraun.com>
|
Hanno Braun <mail at hannobraun.com>
|
||||||
Henri Yandell <flamefew at gmail.com>
|
Henri Yandell <flamefew at gmail.com>
|
||||||
Hirotaka Yamamoto <ymmt2005 at gmail.com>
|
Hirotaka Yamamoto <ymmt2005 at gmail.com>
|
||||||
|
ICHINOSE Shogo <shogo82148 at gmail.com>
|
||||||
INADA Naoki <songofacandy at gmail.com>
|
INADA Naoki <songofacandy at gmail.com>
|
||||||
|
Jacek Szwec <szwec.jacek at gmail.com>
|
||||||
James Harr <james.harr at gmail.com>
|
James Harr <james.harr at gmail.com>
|
||||||
|
Jeff Hodges <jeff at somethingsimilar.com>
|
||||||
|
Jeffrey Charles <jeffreycharles at gmail.com>
|
||||||
Jian Zhen <zhenjl at gmail.com>
|
Jian Zhen <zhenjl at gmail.com>
|
||||||
Joshua Prunier <joshua.prunier at gmail.com>
|
Joshua Prunier <joshua.prunier at gmail.com>
|
||||||
Julien Lefevre <julien.lefevr at gmail.com>
|
Julien Lefevre <julien.lefevr at gmail.com>
|
||||||
Julien Schmidt <go-sql-driver at julienschmidt.com>
|
Julien Schmidt <go-sql-driver at julienschmidt.com>
|
||||||
|
Justin Li <jli at j-li.net>
|
||||||
|
Justin Nuß <nuss.justin at gmail.com>
|
||||||
Kamil Dziedzic <kamil at klecza.pl>
|
Kamil Dziedzic <kamil at klecza.pl>
|
||||||
Kevin Malachowski <kevin at chowski.com>
|
Kevin Malachowski <kevin at chowski.com>
|
||||||
|
Kieron Woodhouse <kieron.woodhouse at infosum.com>
|
||||||
Lennart Rudolph <lrudolph at hmc.edu>
|
Lennart Rudolph <lrudolph at hmc.edu>
|
||||||
Leonardo YongUk Kim <dalinaum at gmail.com>
|
Leonardo YongUk Kim <dalinaum at gmail.com>
|
||||||
|
Linh Tran Tuan <linhduonggnu at gmail.com>
|
||||||
|
Lion Yang <lion at aosc.xyz>
|
||||||
Luca Looz <luca.looz92 at gmail.com>
|
Luca Looz <luca.looz92 at gmail.com>
|
||||||
Lucas Liu <extrafliu at gmail.com>
|
Lucas Liu <extrafliu at gmail.com>
|
||||||
Luke Scott <luke at webconnex.com>
|
Luke Scott <luke at webconnex.com>
|
||||||
|
Maciej Zimnoch <maciej.zimnoch at codilime.com>
|
||||||
Michael Woolnough <michael.woolnough at gmail.com>
|
Michael Woolnough <michael.woolnough at gmail.com>
|
||||||
Nicola Peduzzi <thenikso at gmail.com>
|
Nicola Peduzzi <thenikso at gmail.com>
|
||||||
Olivier Mengué <dolmen at cpan.org>
|
Olivier Mengué <dolmen at cpan.org>
|
||||||
|
oscarzhao <oscarzhaosl at gmail.com>
|
||||||
Paul Bonser <misterpib at gmail.com>
|
Paul Bonser <misterpib at gmail.com>
|
||||||
|
Peter Schultz <peter.schultz at classmarkets.com>
|
||||||
|
Rebecca Chin <rchin at pivotal.io>
|
||||||
|
Reed Allman <rdallman10 at gmail.com>
|
||||||
|
Richard Wilkes <wilkes at me.com>
|
||||||
|
Robert Russell <robert at rrbrussell.com>
|
||||||
Runrioter Wung <runrioter at gmail.com>
|
Runrioter Wung <runrioter at gmail.com>
|
||||||
|
Shuode Li <elemount at qq.com>
|
||||||
Soroush Pour <me at soroushjp.com>
|
Soroush Pour <me at soroushjp.com>
|
||||||
Stan Putrya <root.vagner at gmail.com>
|
Stan Putrya <root.vagner at gmail.com>
|
||||||
Stanley Gunawan <gunawan.stanley at gmail.com>
|
Stanley Gunawan <gunawan.stanley at gmail.com>
|
||||||
|
@ -52,5 +80,10 @@ Zhenye Xie <xiezhenye at gmail.com>
|
||||||
# Organizations
|
# Organizations
|
||||||
|
|
||||||
Barracuda Networks, Inc.
|
Barracuda Networks, Inc.
|
||||||
|
Counting Ltd.
|
||||||
Google Inc.
|
Google Inc.
|
||||||
|
InfoSum Ltd.
|
||||||
|
Keybase Inc.
|
||||||
|
Percona LLC
|
||||||
|
Pivotal Inc.
|
||||||
Stripe Inc.
|
Stripe Inc.
|
||||||
|
|
|
@ -1,3 +1,62 @@
|
||||||
|
## Version 1.4.1 (2018-11-14)
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
|
||||||
|
- Fix TIME format for binary columns (#818)
|
||||||
|
- Fix handling of empty auth plugin names (#835)
|
||||||
|
- Fix caching_sha2_password with empty password (#826)
|
||||||
|
- Fix canceled context broke mysqlConn (#862)
|
||||||
|
- Fix OldAuthSwitchRequest support (#870)
|
||||||
|
- Fix Auth Response packet for cleartext password (#887)
|
||||||
|
|
||||||
|
## Version 1.4 (2018-06-03)
|
||||||
|
|
||||||
|
Changes:
|
||||||
|
|
||||||
|
- Documentation fixes (#530, #535, #567)
|
||||||
|
- Refactoring (#575, #579, #580, #581, #603, #615, #704)
|
||||||
|
- Cache column names (#444)
|
||||||
|
- Sort the DSN parameters in DSNs generated from a config (#637)
|
||||||
|
- Allow native password authentication by default (#644)
|
||||||
|
- Use the default port if it is missing in the DSN (#668)
|
||||||
|
- Removed the `strict` mode (#676)
|
||||||
|
- Do not query `max_allowed_packet` by default (#680)
|
||||||
|
- Dropped support Go 1.6 and lower (#696)
|
||||||
|
- Updated `ConvertValue()` to match the database/sql/driver implementation (#760)
|
||||||
|
- Document the usage of `0000-00-00T00:00:00` as the time.Time zero value (#783)
|
||||||
|
- Improved the compatibility of the authentication system (#807)
|
||||||
|
|
||||||
|
New Features:
|
||||||
|
|
||||||
|
- Multi-Results support (#537)
|
||||||
|
- `rejectReadOnly` DSN option (#604)
|
||||||
|
- `context.Context` support (#608, #612, #627, #761)
|
||||||
|
- Transaction isolation level support (#619, #744)
|
||||||
|
- Read-Only transactions support (#618, #634)
|
||||||
|
- `NewConfig` function which initializes a config with default values (#679)
|
||||||
|
- Implemented the `ColumnType` interfaces (#667, #724)
|
||||||
|
- Support for custom string types in `ConvertValue` (#623)
|
||||||
|
- Implemented `NamedValueChecker`, improving support for uint64 with high bit set (#690, #709, #710)
|
||||||
|
- `caching_sha2_password` authentication plugin support (#794, #800, #801, #802)
|
||||||
|
- Implemented `driver.SessionResetter` (#779)
|
||||||
|
- `sha256_password` authentication plugin support (#808)
|
||||||
|
|
||||||
|
Bugfixes:
|
||||||
|
|
||||||
|
- Use the DSN hostname as TLS default ServerName if `tls=true` (#564, #718)
|
||||||
|
- Fixed LOAD LOCAL DATA INFILE for empty files (#590)
|
||||||
|
- Removed columns definition cache since it sometimes cached invalid data (#592)
|
||||||
|
- Don't mutate registered TLS configs (#600)
|
||||||
|
- Make RegisterTLSConfig concurrency-safe (#613)
|
||||||
|
- Handle missing auth data in the handshake packet correctly (#646)
|
||||||
|
- Do not retry queries when data was written to avoid data corruption (#302, #736)
|
||||||
|
- Cache the connection pointer for error handling before invalidating it (#678)
|
||||||
|
- Fixed imports for appengine/cloudsql (#700)
|
||||||
|
- Fix sending STMT_LONG_DATA for 0 byte data (#734)
|
||||||
|
- Set correct capacity for []bytes read from length-encoded strings (#766)
|
||||||
|
- Make RegisterDial concurrency-safe (#773)
|
||||||
|
|
||||||
|
|
||||||
## Version 1.3 (2016-12-01)
|
## Version 1.3 (2016-12-01)
|
||||||
|
|
||||||
Changes:
|
Changes:
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# Go-MySQL-Driver
|
# Go-MySQL-Driver
|
||||||
|
|
||||||
A MySQL-Driver for Go's [database/sql](http://golang.org/pkg/database/sql) package
|
A MySQL-Driver for Go's [database/sql](https://golang.org/pkg/database/sql/) package
|
||||||
|
|
||||||
![Go-MySQL-Driver logo](https://raw.github.com/wiki/go-sql-driver/mysql/gomysql_m.png "Golang Gopher holding the MySQL Dolphin")
|
![Go-MySQL-Driver logo](https://raw.github.com/wiki/go-sql-driver/mysql/gomysql_m.png "Golang Gopher holding the MySQL Dolphin")
|
||||||
|
|
||||||
|
@ -15,6 +15,9 @@ A MySQL-Driver for Go's [database/sql](http://golang.org/pkg/database/sql) packa
|
||||||
* [Address](#address)
|
* [Address](#address)
|
||||||
* [Parameters](#parameters)
|
* [Parameters](#parameters)
|
||||||
* [Examples](#examples)
|
* [Examples](#examples)
|
||||||
|
* [Connection pool and timeouts](#connection-pool-and-timeouts)
|
||||||
|
* [context.Context Support](#contextcontext-support)
|
||||||
|
* [ColumnType Support](#columntype-support)
|
||||||
* [LOAD DATA LOCAL INFILE support](#load-data-local-infile-support)
|
* [LOAD DATA LOCAL INFILE support](#load-data-local-infile-support)
|
||||||
* [time.Time support](#timetime-support)
|
* [time.Time support](#timetime-support)
|
||||||
* [Unicode support](#unicode-support)
|
* [Unicode support](#unicode-support)
|
||||||
|
@ -26,31 +29,31 @@ A MySQL-Driver for Go's [database/sql](http://golang.org/pkg/database/sql) packa
|
||||||
## Features
|
## Features
|
||||||
* Lightweight and [fast](https://github.com/go-sql-driver/sql-benchmark "golang MySQL-Driver performance")
|
* Lightweight and [fast](https://github.com/go-sql-driver/sql-benchmark "golang MySQL-Driver performance")
|
||||||
* Native Go implementation. No C-bindings, just pure Go
|
* Native Go implementation. No C-bindings, just pure Go
|
||||||
* Connections over TCP/IPv4, TCP/IPv6, Unix domain sockets or [custom protocols](http://godoc.org/github.com/go-sql-driver/mysql#DialFunc)
|
* Connections over TCP/IPv4, TCP/IPv6, Unix domain sockets or [custom protocols](https://godoc.org/github.com/go-sql-driver/mysql#DialFunc)
|
||||||
* Automatic handling of broken connections
|
* Automatic handling of broken connections
|
||||||
* Automatic Connection Pooling *(by database/sql package)*
|
* Automatic Connection Pooling *(by database/sql package)*
|
||||||
* Supports queries larger than 16MB
|
* Supports queries larger than 16MB
|
||||||
* Full [`sql.RawBytes`](http://golang.org/pkg/database/sql/#RawBytes) support.
|
* Full [`sql.RawBytes`](https://golang.org/pkg/database/sql/#RawBytes) support.
|
||||||
* Intelligent `LONG DATA` handling in prepared statements
|
* Intelligent `LONG DATA` handling in prepared statements
|
||||||
* Secure `LOAD DATA LOCAL INFILE` support with file Whitelisting and `io.Reader` support
|
* Secure `LOAD DATA LOCAL INFILE` support with file Whitelisting and `io.Reader` support
|
||||||
* Optional `time.Time` parsing
|
* Optional `time.Time` parsing
|
||||||
* Optional placeholder interpolation
|
* Optional placeholder interpolation
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
* Go 1.2 or higher
|
* Go 1.7 or higher. We aim to support the 3 latest versions of Go.
|
||||||
* MySQL (4.1+), MariaDB, Percona Server, Google CloudSQL or Sphinx (2.2.3+)
|
* MySQL (4.1+), MariaDB, Percona Server, Google CloudSQL or Sphinx (2.2.3+)
|
||||||
|
|
||||||
---------------------------------------
|
---------------------------------------
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
Simple install the package to your [$GOPATH](http://code.google.com/p/go-wiki/wiki/GOPATH "GOPATH") with the [go tool](http://golang.org/cmd/go/ "go command") from shell:
|
Simple install the package to your [$GOPATH](https://github.com/golang/go/wiki/GOPATH "GOPATH") with the [go tool](https://golang.org/cmd/go/ "go command") from shell:
|
||||||
```bash
|
```bash
|
||||||
$ go get github.com/go-sql-driver/mysql
|
$ go get -u github.com/go-sql-driver/mysql
|
||||||
```
|
```
|
||||||
Make sure [Git is installed](http://git-scm.com/downloads) on your machine and in your system's `PATH`.
|
Make sure [Git is installed](https://git-scm.com/downloads) on your machine and in your system's `PATH`.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
_Go MySQL Driver_ is an implementation of Go's `database/sql/driver` interface. You only need to import the driver and can use the full [`database/sql`](http://golang.org/pkg/database/sql) API then.
|
_Go MySQL Driver_ is an implementation of Go's `database/sql/driver` interface. You only need to import the driver and can use the full [`database/sql`](https://golang.org/pkg/database/sql/) API then.
|
||||||
|
|
||||||
Use `mysql` as `driverName` and a valid [DSN](#dsn-data-source-name) as `dataSourceName`:
|
Use `mysql` as `driverName` and a valid [DSN](#dsn-data-source-name) as `dataSourceName`:
|
||||||
```go
|
```go
|
||||||
|
@ -95,13 +98,14 @@ Alternatively, [Config.FormatDSN](https://godoc.org/github.com/go-sql-driver/mys
|
||||||
Passwords can consist of any character. Escaping is **not** necessary.
|
Passwords can consist of any character. Escaping is **not** necessary.
|
||||||
|
|
||||||
#### Protocol
|
#### Protocol
|
||||||
See [net.Dial](http://golang.org/pkg/net/#Dial) for more information which networks are available.
|
See [net.Dial](https://golang.org/pkg/net/#Dial) for more information which networks are available.
|
||||||
In general you should use an Unix domain socket if available and TCP otherwise for best performance.
|
In general you should use an Unix domain socket if available and TCP otherwise for best performance.
|
||||||
|
|
||||||
#### Address
|
#### Address
|
||||||
For TCP and UDP networks, addresses have the form `host:port`.
|
For TCP and UDP networks, addresses have the form `host[:port]`.
|
||||||
|
If `port` is omitted, the default port will be used.
|
||||||
If `host` is a literal IPv6 address, it must be enclosed in square brackets.
|
If `host` is a literal IPv6 address, it must be enclosed in square brackets.
|
||||||
The functions [net.JoinHostPort](http://golang.org/pkg/net/#JoinHostPort) and [net.SplitHostPort](http://golang.org/pkg/net/#SplitHostPort) manipulate addresses in this form.
|
The functions [net.JoinHostPort](https://golang.org/pkg/net/#JoinHostPort) and [net.SplitHostPort](https://golang.org/pkg/net/#SplitHostPort) manipulate addresses in this form.
|
||||||
|
|
||||||
For Unix domain sockets the address is the absolute path to the MySQL-Server-socket, e.g. `/var/run/mysqld/mysqld.sock` or `/tmp/mysql.sock`.
|
For Unix domain sockets the address is the absolute path to the MySQL-Server-socket, e.g. `/var/run/mysqld/mysqld.sock` or `/tmp/mysql.sock`.
|
||||||
|
|
||||||
|
@ -136,9 +140,9 @@ Default: false
|
||||||
```
|
```
|
||||||
Type: bool
|
Type: bool
|
||||||
Valid Values: true, false
|
Valid Values: true, false
|
||||||
Default: false
|
Default: true
|
||||||
```
|
```
|
||||||
`allowNativePasswords=true` allows the usage of the mysql native password method.
|
`allowNativePasswords=false` disallows the usage of MySQL native password method.
|
||||||
|
|
||||||
##### `allowOldPasswords`
|
##### `allowOldPasswords`
|
||||||
|
|
||||||
|
@ -220,19 +224,19 @@ Valid Values: <escaped name>
|
||||||
Default: UTC
|
Default: UTC
|
||||||
```
|
```
|
||||||
|
|
||||||
Sets the location for time.Time values (when using `parseTime=true`). *"Local"* sets the system's location. See [time.LoadLocation](http://golang.org/pkg/time/#LoadLocation) for details.
|
Sets the location for time.Time values (when using `parseTime=true`). *"Local"* sets the system's location. See [time.LoadLocation](https://golang.org/pkg/time/#LoadLocation) for details.
|
||||||
|
|
||||||
Note that this sets the location for time.Time values but does not change MySQL's [time_zone setting](https://dev.mysql.com/doc/refman/5.5/en/time-zone-support.html). For that see the [time_zone system variable](#system-variables), which can also be set as a DSN parameter.
|
Note that this sets the location for time.Time values but does not change MySQL's [time_zone setting](https://dev.mysql.com/doc/refman/5.5/en/time-zone-support.html). For that see the [time_zone system variable](#system-variables), which can also be set as a DSN parameter.
|
||||||
|
|
||||||
Please keep in mind, that param values must be [url.QueryEscape](http://golang.org/pkg/net/url/#QueryEscape)'ed. Alternatively you can manually replace the `/` with `%2F`. For example `US/Pacific` would be `loc=US%2FPacific`.
|
Please keep in mind, that param values must be [url.QueryEscape](https://golang.org/pkg/net/url/#QueryEscape)'ed. Alternatively you can manually replace the `/` with `%2F`. For example `US/Pacific` would be `loc=US%2FPacific`.
|
||||||
|
|
||||||
##### `maxAllowedPacket`
|
##### `maxAllowedPacket`
|
||||||
```
|
```
|
||||||
Type: decimal number
|
Type: decimal number
|
||||||
Default: 0
|
Default: 4194304
|
||||||
```
|
```
|
||||||
|
|
||||||
Max packet size allowed in bytes. Use `maxAllowedPacket=0` to automatically fetch the `max_allowed_packet` variable from server.
|
Max packet size allowed in bytes. The default value is 4 MiB and should be adjusted to match the server settings. `maxAllowedPacket=0` can be used to automatically fetch the `max_allowed_packet` variable from server *on every connection*.
|
||||||
|
|
||||||
##### `multiStatements`
|
##### `multiStatements`
|
||||||
|
|
||||||
|
@ -255,18 +259,19 @@ Default: false
|
||||||
```
|
```
|
||||||
|
|
||||||
`parseTime=true` changes the output type of `DATE` and `DATETIME` values to `time.Time` instead of `[]byte` / `string`
|
`parseTime=true` changes the output type of `DATE` and `DATETIME` values to `time.Time` instead of `[]byte` / `string`
|
||||||
|
The date or datetime like `0000-00-00 00:00:00` is converted into zero value of `time.Time`.
|
||||||
|
|
||||||
|
|
||||||
##### `readTimeout`
|
##### `readTimeout`
|
||||||
|
|
||||||
```
|
```
|
||||||
Type: decimal number
|
Type: duration
|
||||||
Default: 0
|
Default: 0
|
||||||
```
|
```
|
||||||
|
|
||||||
I/O read timeout. The value must be a decimal number with an unit suffix ( *"ms"*, *"s"*, *"m"*, *"h"* ), such as *"30s"*, *"0.5m"* or *"1m30s"*.
|
I/O read timeout. The value must be a decimal number with a unit suffix (*"ms"*, *"s"*, *"m"*, *"h"*), such as *"30s"*, *"0.5m"* or *"1m30s"*.
|
||||||
|
|
||||||
##### `strict`
|
##### `rejectReadOnly`
|
||||||
|
|
||||||
```
|
```
|
||||||
Type: bool
|
Type: bool
|
||||||
|
@ -274,20 +279,50 @@ Valid Values: true, false
|
||||||
Default: false
|
Default: false
|
||||||
```
|
```
|
||||||
|
|
||||||
`strict=true` enables a driver-side strict mode in which MySQL warnings are treated as errors. This mode should not be used in production as it may lead to data corruption in certain situations.
|
|
||||||
|
|
||||||
A server-side strict mode, which is safe for production use, can be set via the [`sql_mode`](https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html) system variable.
|
`rejectReadOnly=true` causes the driver to reject read-only connections. This
|
||||||
|
is for a possible race condition during an automatic failover, where the mysql
|
||||||
|
client gets connected to a read-only replica after the failover.
|
||||||
|
|
||||||
|
Note that this should be a fairly rare case, as an automatic failover normally
|
||||||
|
happens when the primary is down, and the race condition shouldn't happen
|
||||||
|
unless it comes back up online as soon as the failover is kicked off. On the
|
||||||
|
other hand, when this happens, a MySQL application can get stuck on a
|
||||||
|
read-only connection until restarted. It is however fairly easy to reproduce,
|
||||||
|
for example, using a manual failover on AWS Aurora's MySQL-compatible cluster.
|
||||||
|
|
||||||
|
If you are not relying on read-only transactions to reject writes that aren't
|
||||||
|
supposed to happen, setting this on some MySQL providers (such as AWS Aurora)
|
||||||
|
is safer for failovers.
|
||||||
|
|
||||||
|
Note that ERROR 1290 can be returned for a `read-only` server and this option will
|
||||||
|
cause a retry for that error. However the same error number is used for some
|
||||||
|
other cases. You should ensure your application will never cause an ERROR 1290
|
||||||
|
except for `read-only` mode when enabling this option.
|
||||||
|
|
||||||
|
|
||||||
|
##### `serverPubKey`
|
||||||
|
|
||||||
|
```
|
||||||
|
Type: string
|
||||||
|
Valid Values: <name>
|
||||||
|
Default: none
|
||||||
|
```
|
||||||
|
|
||||||
|
Server public keys can be registered with [`mysql.RegisterServerPubKey`](https://godoc.org/github.com/go-sql-driver/mysql#RegisterServerPubKey), which can then be used by the assigned name in the DSN.
|
||||||
|
Public keys are used to transmit encrypted data, e.g. for authentication.
|
||||||
|
If the server's public key is known, it should be set manually to avoid expensive and potentially insecure transmissions of the public key from the server to the client each time it is required.
|
||||||
|
|
||||||
By default MySQL also treats notes as warnings. Use [`sql_notes=false`](http://dev.mysql.com/doc/refman/5.7/en/server-system-variables.html#sysvar_sql_notes) to ignore notes.
|
|
||||||
|
|
||||||
##### `timeout`
|
##### `timeout`
|
||||||
|
|
||||||
```
|
```
|
||||||
Type: decimal number
|
Type: duration
|
||||||
Default: OS default
|
Default: OS default
|
||||||
```
|
```
|
||||||
|
|
||||||
*Driver* side connection timeout. The value must be a decimal number with an unit suffix ( *"ms"*, *"s"*, *"m"*, *"h"* ), such as *"30s"*, *"0.5m"* or *"1m30s"*. To set a server side timeout, use the parameter [`wait_timeout`](http://dev.mysql.com/doc/refman/5.6/en/server-system-variables.html#sysvar_wait_timeout).
|
Timeout for establishing connections, aka dial timeout. The value must be a decimal number with a unit suffix (*"ms"*, *"s"*, *"m"*, *"h"*), such as *"30s"*, *"0.5m"* or *"1m30s"*.
|
||||||
|
|
||||||
|
|
||||||
##### `tls`
|
##### `tls`
|
||||||
|
|
||||||
|
@ -297,16 +332,17 @@ Valid Values: true, false, skip-verify, <name>
|
||||||
Default: false
|
Default: false
|
||||||
```
|
```
|
||||||
|
|
||||||
`tls=true` enables TLS / SSL encrypted connection to the server. Use `skip-verify` if you want to use a self-signed or invalid certificate (server side). Use a custom value registered with [`mysql.RegisterTLSConfig`](http://godoc.org/github.com/go-sql-driver/mysql#RegisterTLSConfig).
|
`tls=true` enables TLS / SSL encrypted connection to the server. Use `skip-verify` if you want to use a self-signed or invalid certificate (server side). Use a custom value registered with [`mysql.RegisterTLSConfig`](https://godoc.org/github.com/go-sql-driver/mysql#RegisterTLSConfig).
|
||||||
|
|
||||||
|
|
||||||
##### `writeTimeout`
|
##### `writeTimeout`
|
||||||
|
|
||||||
```
|
```
|
||||||
Type: decimal number
|
Type: duration
|
||||||
Default: 0
|
Default: 0
|
||||||
```
|
```
|
||||||
|
|
||||||
I/O write timeout. The value must be a decimal number with an unit suffix ( *"ms"*, *"s"*, *"m"*, *"h"* ), such as *"30s"*, *"0.5m"* or *"1m30s"*.
|
I/O write timeout. The value must be a decimal number with a unit suffix (*"ms"*, *"s"*, *"m"*, *"h"*), such as *"30s"*, *"0.5m"* or *"1m30s"*.
|
||||||
|
|
||||||
|
|
||||||
##### System Variables
|
##### System Variables
|
||||||
|
@ -317,9 +353,9 @@ Any other parameters are interpreted as system variables:
|
||||||
* `<string_var>=%27<value>%27`: `SET <string_var>='<value>'`
|
* `<string_var>=%27<value>%27`: `SET <string_var>='<value>'`
|
||||||
|
|
||||||
Rules:
|
Rules:
|
||||||
* The values for string variables must be quoted with '
|
* The values for string variables must be quoted with `'`.
|
||||||
* The values must also be [url.QueryEscape](http://golang.org/pkg/net/url/#QueryEscape)'ed!
|
* The values must also be [url.QueryEscape](http://golang.org/pkg/net/url/#QueryEscape)'ed!
|
||||||
(which implies values of string variables must be wrapped with `%27`)
|
(which implies values of string variables must be wrapped with `%27`).
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
* `autocommit=1`: `SET autocommit=1`
|
* `autocommit=1`: `SET autocommit=1`
|
||||||
|
@ -380,6 +416,18 @@ No Database preselected:
|
||||||
user:password@/
|
user:password@/
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Connection pool and timeouts
|
||||||
|
The connection pool is managed by Go's database/sql package. For details on how to configure the size of the pool and how long connections stay in the pool see `*DB.SetMaxOpenConns`, `*DB.SetMaxIdleConns`, and `*DB.SetConnMaxLifetime` in the [database/sql documentation](https://golang.org/pkg/database/sql/). The read, write, and dial timeouts for each individual connection are configured with the DSN parameters [`readTimeout`](#readtimeout), [`writeTimeout`](#writetimeout), and [`timeout`](#timeout), respectively.
|
||||||
|
|
||||||
|
## `ColumnType` Support
|
||||||
|
This driver supports the [`ColumnType` interface](https://golang.org/pkg/database/sql/#ColumnType) introduced in Go 1.8, with the exception of [`ColumnType.Length()`](https://golang.org/pkg/database/sql/#ColumnType.Length), which is currently not supported.
|
||||||
|
|
||||||
|
## `context.Context` Support
|
||||||
|
Go 1.8 added `database/sql` support for `context.Context`. This driver supports query timeouts and cancellation via contexts.
|
||||||
|
See [context support in the database/sql package](https://golang.org/doc/go1.8#database_sql) for more details.
|
||||||
|
|
||||||
|
|
||||||
### `LOAD DATA LOCAL INFILE` support
|
### `LOAD DATA LOCAL INFILE` support
|
||||||
For this feature you need direct access to the package. Therefore you must change the import path (no `_`):
|
For this feature you need direct access to the package. Therefore you must change the import path (no `_`):
|
||||||
```go
|
```go
|
||||||
|
@ -390,17 +438,17 @@ Files must be whitelisted by registering them with `mysql.RegisterLocalFile(file
|
||||||
|
|
||||||
To use a `io.Reader` a handler function must be registered with `mysql.RegisterReaderHandler(name, handler)` which returns a `io.Reader` or `io.ReadCloser`. The Reader is available with the filepath `Reader::<name>` then. Choose different names for different handlers and `DeregisterReaderHandler` when you don't need it anymore.
|
To use a `io.Reader` a handler function must be registered with `mysql.RegisterReaderHandler(name, handler)` which returns a `io.Reader` or `io.ReadCloser`. The Reader is available with the filepath `Reader::<name>` then. Choose different names for different handlers and `DeregisterReaderHandler` when you don't need it anymore.
|
||||||
|
|
||||||
See the [godoc of Go-MySQL-Driver](http://godoc.org/github.com/go-sql-driver/mysql "golang mysql driver documentation") for details.
|
See the [godoc of Go-MySQL-Driver](https://godoc.org/github.com/go-sql-driver/mysql "golang mysql driver documentation") for details.
|
||||||
|
|
||||||
|
|
||||||
### `time.Time` support
|
### `time.Time` support
|
||||||
The default internal output type of MySQL `DATE` and `DATETIME` values is `[]byte` which allows you to scan the value into a `[]byte`, `string` or `sql.RawBytes` variable in your programm.
|
The default internal output type of MySQL `DATE` and `DATETIME` values is `[]byte` which allows you to scan the value into a `[]byte`, `string` or `sql.RawBytes` variable in your program.
|
||||||
|
|
||||||
However, many want to scan MySQL `DATE` and `DATETIME` values into `time.Time` variables, which is the logical opposite in Go to `DATE` and `DATETIME` in MySQL. You can do that by changing the internal output type from `[]byte` to `time.Time` with the DSN parameter `parseTime=true`. You can set the default [`time.Time` location](http://golang.org/pkg/time/#Location) with the `loc` DSN parameter.
|
However, many want to scan MySQL `DATE` and `DATETIME` values into `time.Time` variables, which is the logical opposite in Go to `DATE` and `DATETIME` in MySQL. You can do that by changing the internal output type from `[]byte` to `time.Time` with the DSN parameter `parseTime=true`. You can set the default [`time.Time` location](https://golang.org/pkg/time/#Location) with the `loc` DSN parameter.
|
||||||
|
|
||||||
**Caution:** As of Go 1.1, this makes `time.Time` the only variable type you can scan `DATE` and `DATETIME` values into. This breaks for example [`sql.RawBytes` support](https://github.com/go-sql-driver/mysql/wiki/Examples#rawbytes).
|
**Caution:** As of Go 1.1, this makes `time.Time` the only variable type you can scan `DATE` and `DATETIME` values into. This breaks for example [`sql.RawBytes` support](https://github.com/go-sql-driver/mysql/wiki/Examples#rawbytes).
|
||||||
|
|
||||||
Alternatively you can use the [`NullTime`](http://godoc.org/github.com/go-sql-driver/mysql#NullTime) type as the scan destination, which works with both `time.Time` and `string` / `[]byte`.
|
Alternatively you can use the [`NullTime`](https://godoc.org/github.com/go-sql-driver/mysql#NullTime) type as the scan destination, which works with both `time.Time` and `string` / `[]byte`.
|
||||||
|
|
||||||
|
|
||||||
### Unicode support
|
### Unicode support
|
||||||
|
@ -412,7 +460,6 @@ Version 1.0 of the driver recommended adding `&charset=utf8` (alias for `SET NAM
|
||||||
|
|
||||||
See http://dev.mysql.com/doc/refman/5.7/en/charset-unicode.html for more details on MySQL's Unicode support.
|
See http://dev.mysql.com/doc/refman/5.7/en/charset-unicode.html for more details on MySQL's Unicode support.
|
||||||
|
|
||||||
|
|
||||||
## Testing / Development
|
## Testing / Development
|
||||||
To run the driver tests you may need to adjust the configuration. See the [Testing Wiki-Page](https://github.com/go-sql-driver/mysql/wiki/Testing "Testing") for details.
|
To run the driver tests you may need to adjust the configuration. See the [Testing Wiki-Page](https://github.com/go-sql-driver/mysql/wiki/Testing "Testing") for details.
|
||||||
|
|
||||||
|
@ -431,13 +478,13 @@ Mozilla summarizes the license scope as follows:
|
||||||
|
|
||||||
|
|
||||||
That means:
|
That means:
|
||||||
* You can **use** the **unchanged** source code both in private and commercially
|
* You can **use** the **unchanged** source code both in private and commercially.
|
||||||
* When distributing, you **must publish** the source code of any **changed files** licensed under the MPL 2.0 under a) the MPL 2.0 itself or b) a compatible license (e.g. GPL 3.0 or Apache License 2.0)
|
* When distributing, you **must publish** the source code of any **changed files** licensed under the MPL 2.0 under a) the MPL 2.0 itself or b) a compatible license (e.g. GPL 3.0 or Apache License 2.0).
|
||||||
* You **needn't publish** the source code of your library as long as the files licensed under the MPL 2.0 are **unchanged**
|
* You **needn't publish** the source code of your library as long as the files licensed under the MPL 2.0 are **unchanged**.
|
||||||
|
|
||||||
Please read the [MPL 2.0 FAQ](http://www.mozilla.org/MPL/2.0/FAQ.html) if you have further questions regarding the license.
|
Please read the [MPL 2.0 FAQ](https://www.mozilla.org/en-US/MPL/2.0/FAQ/) if you have further questions regarding the license.
|
||||||
|
|
||||||
You can read the full terms here: [LICENSE](https://raw.github.com/go-sql-driver/mysql/master/LICENSE)
|
You can read the full terms here: [LICENSE](https://raw.github.com/go-sql-driver/mysql/master/LICENSE).
|
||||||
|
|
||||||
![Go Gopher and MySQL Dolphin](https://raw.github.com/wiki/go-sql-driver/mysql/go-mysql-driver_m.jpg "Golang Gopher transporting the MySQL Dolphin in a wheelbarrow")
|
![Go Gopher and MySQL Dolphin](https://raw.github.com/wiki/go-sql-driver/mysql/go-mysql-driver_m.jpg "Golang Gopher transporting the MySQL Dolphin in a wheelbarrow")
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
package mysql
|
package mysql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"appengine/cloudsql"
|
"google.golang.org/appengine/cloudsql"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|
|
@ -0,0 +1,420 @@
|
||||||
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||||
|
//
|
||||||
|
// Copyright 2018 The Go-MySQL-Driver Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/rsa"
|
||||||
|
"crypto/sha1"
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/x509"
|
||||||
|
"encoding/pem"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// server pub keys registry
|
||||||
|
var (
|
||||||
|
serverPubKeyLock sync.RWMutex
|
||||||
|
serverPubKeyRegistry map[string]*rsa.PublicKey
|
||||||
|
)
|
||||||
|
|
||||||
|
// RegisterServerPubKey registers a server RSA public key which can be used to
|
||||||
|
// send data in a secure manner to the server without receiving the public key
|
||||||
|
// in a potentially insecure way from the server first.
|
||||||
|
// Registered keys can afterwards be used adding serverPubKey=<name> to the DSN.
|
||||||
|
//
|
||||||
|
// Note: The provided rsa.PublicKey instance is exclusively owned by the driver
|
||||||
|
// after registering it and may not be modified.
|
||||||
|
//
|
||||||
|
// data, err := ioutil.ReadFile("mykey.pem")
|
||||||
|
// if err != nil {
|
||||||
|
// log.Fatal(err)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// block, _ := pem.Decode(data)
|
||||||
|
// if block == nil || block.Type != "PUBLIC KEY" {
|
||||||
|
// log.Fatal("failed to decode PEM block containing public key")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// pub, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||||||
|
// if err != nil {
|
||||||
|
// log.Fatal(err)
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// if rsaPubKey, ok := pub.(*rsa.PublicKey); ok {
|
||||||
|
// mysql.RegisterServerPubKey("mykey", rsaPubKey)
|
||||||
|
// } else {
|
||||||
|
// log.Fatal("not a RSA public key")
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
func RegisterServerPubKey(name string, pubKey *rsa.PublicKey) {
|
||||||
|
serverPubKeyLock.Lock()
|
||||||
|
if serverPubKeyRegistry == nil {
|
||||||
|
serverPubKeyRegistry = make(map[string]*rsa.PublicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
serverPubKeyRegistry[name] = pubKey
|
||||||
|
serverPubKeyLock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeregisterServerPubKey removes the public key registered with the given name.
|
||||||
|
func DeregisterServerPubKey(name string) {
|
||||||
|
serverPubKeyLock.Lock()
|
||||||
|
if serverPubKeyRegistry != nil {
|
||||||
|
delete(serverPubKeyRegistry, name)
|
||||||
|
}
|
||||||
|
serverPubKeyLock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getServerPubKey(name string) (pubKey *rsa.PublicKey) {
|
||||||
|
serverPubKeyLock.RLock()
|
||||||
|
if v, ok := serverPubKeyRegistry[name]; ok {
|
||||||
|
pubKey = v
|
||||||
|
}
|
||||||
|
serverPubKeyLock.RUnlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash password using pre 4.1 (old password) method
|
||||||
|
// https://github.com/atcurtis/mariadb/blob/master/mysys/my_rnd.c
|
||||||
|
type myRnd struct {
|
||||||
|
seed1, seed2 uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
const myRndMaxVal = 0x3FFFFFFF
|
||||||
|
|
||||||
|
// Pseudo random number generator
|
||||||
|
func newMyRnd(seed1, seed2 uint32) *myRnd {
|
||||||
|
return &myRnd{
|
||||||
|
seed1: seed1 % myRndMaxVal,
|
||||||
|
seed2: seed2 % myRndMaxVal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tested to be equivalent to MariaDB's floating point variant
|
||||||
|
// http://play.golang.org/p/QHvhd4qved
|
||||||
|
// http://play.golang.org/p/RG0q4ElWDx
|
||||||
|
func (r *myRnd) NextByte() byte {
|
||||||
|
r.seed1 = (r.seed1*3 + r.seed2) % myRndMaxVal
|
||||||
|
r.seed2 = (r.seed1 + r.seed2 + 33) % myRndMaxVal
|
||||||
|
|
||||||
|
return byte(uint64(r.seed1) * 31 / myRndMaxVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate binary hash from byte string using insecure pre 4.1 method
|
||||||
|
func pwHash(password []byte) (result [2]uint32) {
|
||||||
|
var add uint32 = 7
|
||||||
|
var tmp uint32
|
||||||
|
|
||||||
|
result[0] = 1345345333
|
||||||
|
result[1] = 0x12345671
|
||||||
|
|
||||||
|
for _, c := range password {
|
||||||
|
// skip spaces and tabs in password
|
||||||
|
if c == ' ' || c == '\t' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp = uint32(c)
|
||||||
|
result[0] ^= (((result[0] & 63) + add) * tmp) + (result[0] << 8)
|
||||||
|
result[1] += (result[1] << 8) ^ result[0]
|
||||||
|
add += tmp
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove sign bit (1<<31)-1)
|
||||||
|
result[0] &= 0x7FFFFFFF
|
||||||
|
result[1] &= 0x7FFFFFFF
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash password using insecure pre 4.1 method
|
||||||
|
func scrambleOldPassword(scramble []byte, password string) []byte {
|
||||||
|
if len(password) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
scramble = scramble[:8]
|
||||||
|
|
||||||
|
hashPw := pwHash([]byte(password))
|
||||||
|
hashSc := pwHash(scramble)
|
||||||
|
|
||||||
|
r := newMyRnd(hashPw[0]^hashSc[0], hashPw[1]^hashSc[1])
|
||||||
|
|
||||||
|
var out [8]byte
|
||||||
|
for i := range out {
|
||||||
|
out[i] = r.NextByte() + 64
|
||||||
|
}
|
||||||
|
|
||||||
|
mask := r.NextByte()
|
||||||
|
for i := range out {
|
||||||
|
out[i] ^= mask
|
||||||
|
}
|
||||||
|
|
||||||
|
return out[:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash password using 4.1+ method (SHA1)
|
||||||
|
func scramblePassword(scramble []byte, password string) []byte {
|
||||||
|
if len(password) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// stage1Hash = SHA1(password)
|
||||||
|
crypt := sha1.New()
|
||||||
|
crypt.Write([]byte(password))
|
||||||
|
stage1 := crypt.Sum(nil)
|
||||||
|
|
||||||
|
// scrambleHash = SHA1(scramble + SHA1(stage1Hash))
|
||||||
|
// inner Hash
|
||||||
|
crypt.Reset()
|
||||||
|
crypt.Write(stage1)
|
||||||
|
hash := crypt.Sum(nil)
|
||||||
|
|
||||||
|
// outer Hash
|
||||||
|
crypt.Reset()
|
||||||
|
crypt.Write(scramble)
|
||||||
|
crypt.Write(hash)
|
||||||
|
scramble = crypt.Sum(nil)
|
||||||
|
|
||||||
|
// token = scrambleHash XOR stage1Hash
|
||||||
|
for i := range scramble {
|
||||||
|
scramble[i] ^= stage1[i]
|
||||||
|
}
|
||||||
|
return scramble
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash password using MySQL 8+ method (SHA256)
|
||||||
|
func scrambleSHA256Password(scramble []byte, password string) []byte {
|
||||||
|
if len(password) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// XOR(SHA256(password), SHA256(SHA256(SHA256(password)), scramble))
|
||||||
|
|
||||||
|
crypt := sha256.New()
|
||||||
|
crypt.Write([]byte(password))
|
||||||
|
message1 := crypt.Sum(nil)
|
||||||
|
|
||||||
|
crypt.Reset()
|
||||||
|
crypt.Write(message1)
|
||||||
|
message1Hash := crypt.Sum(nil)
|
||||||
|
|
||||||
|
crypt.Reset()
|
||||||
|
crypt.Write(message1Hash)
|
||||||
|
crypt.Write(scramble)
|
||||||
|
message2 := crypt.Sum(nil)
|
||||||
|
|
||||||
|
for i := range message1 {
|
||||||
|
message1[i] ^= message2[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return message1
|
||||||
|
}
|
||||||
|
|
||||||
|
func encryptPassword(password string, seed []byte, pub *rsa.PublicKey) ([]byte, error) {
|
||||||
|
plain := make([]byte, len(password)+1)
|
||||||
|
copy(plain, password)
|
||||||
|
for i := range plain {
|
||||||
|
j := i % len(seed)
|
||||||
|
plain[i] ^= seed[j]
|
||||||
|
}
|
||||||
|
sha1 := sha1.New()
|
||||||
|
return rsa.EncryptOAEP(sha1, rand.Reader, pub, plain, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mysqlConn) sendEncryptedPassword(seed []byte, pub *rsa.PublicKey) error {
|
||||||
|
enc, err := encryptPassword(mc.cfg.Passwd, seed, pub)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return mc.writeAuthSwitchPacket(enc)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mysqlConn) auth(authData []byte, plugin string) ([]byte, error) {
|
||||||
|
switch plugin {
|
||||||
|
case "caching_sha2_password":
|
||||||
|
authResp := scrambleSHA256Password(authData, mc.cfg.Passwd)
|
||||||
|
return authResp, nil
|
||||||
|
|
||||||
|
case "mysql_old_password":
|
||||||
|
if !mc.cfg.AllowOldPasswords {
|
||||||
|
return nil, ErrOldPassword
|
||||||
|
}
|
||||||
|
// Note: there are edge cases where this should work but doesn't;
|
||||||
|
// this is currently "wontfix":
|
||||||
|
// https://github.com/go-sql-driver/mysql/issues/184
|
||||||
|
authResp := append(scrambleOldPassword(authData[:8], mc.cfg.Passwd), 0)
|
||||||
|
return authResp, nil
|
||||||
|
|
||||||
|
case "mysql_clear_password":
|
||||||
|
if !mc.cfg.AllowCleartextPasswords {
|
||||||
|
return nil, ErrCleartextPassword
|
||||||
|
}
|
||||||
|
// http://dev.mysql.com/doc/refman/5.7/en/cleartext-authentication-plugin.html
|
||||||
|
// http://dev.mysql.com/doc/refman/5.7/en/pam-authentication-plugin.html
|
||||||
|
return append([]byte(mc.cfg.Passwd), 0), nil
|
||||||
|
|
||||||
|
case "mysql_native_password":
|
||||||
|
if !mc.cfg.AllowNativePasswords {
|
||||||
|
return nil, ErrNativePassword
|
||||||
|
}
|
||||||
|
// https://dev.mysql.com/doc/internals/en/secure-password-authentication.html
|
||||||
|
// Native password authentication only need and will need 20-byte challenge.
|
||||||
|
authResp := scramblePassword(authData[:20], mc.cfg.Passwd)
|
||||||
|
return authResp, nil
|
||||||
|
|
||||||
|
case "sha256_password":
|
||||||
|
if len(mc.cfg.Passwd) == 0 {
|
||||||
|
return []byte{0}, nil
|
||||||
|
}
|
||||||
|
if mc.cfg.tls != nil || mc.cfg.Net == "unix" {
|
||||||
|
// write cleartext auth packet
|
||||||
|
return append([]byte(mc.cfg.Passwd), 0), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pubKey := mc.cfg.pubKey
|
||||||
|
if pubKey == nil {
|
||||||
|
// request public key from server
|
||||||
|
return []byte{1}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encrypted password
|
||||||
|
enc, err := encryptPassword(mc.cfg.Passwd, authData, pubKey)
|
||||||
|
return enc, err
|
||||||
|
|
||||||
|
default:
|
||||||
|
errLog.Print("unknown auth plugin:", plugin)
|
||||||
|
return nil, ErrUnknownPlugin
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mysqlConn) handleAuthResult(oldAuthData []byte, plugin string) error {
|
||||||
|
// Read Result Packet
|
||||||
|
authData, newPlugin, err := mc.readAuthResult()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle auth plugin switch, if requested
|
||||||
|
if newPlugin != "" {
|
||||||
|
// If CLIENT_PLUGIN_AUTH capability is not supported, no new cipher is
|
||||||
|
// sent and we have to keep using the cipher sent in the init packet.
|
||||||
|
if authData == nil {
|
||||||
|
authData = oldAuthData
|
||||||
|
} else {
|
||||||
|
// copy data from read buffer to owned slice
|
||||||
|
copy(oldAuthData, authData)
|
||||||
|
}
|
||||||
|
|
||||||
|
plugin = newPlugin
|
||||||
|
|
||||||
|
authResp, err := mc.auth(authData, plugin)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err = mc.writeAuthSwitchPacket(authResp); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read Result Packet
|
||||||
|
authData, newPlugin, err = mc.readAuthResult()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Do not allow to change the auth plugin more than once
|
||||||
|
if newPlugin != "" {
|
||||||
|
return ErrMalformPkt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch plugin {
|
||||||
|
|
||||||
|
// https://insidemysql.com/preparing-your-community-connector-for-mysql-8-part-2-sha256/
|
||||||
|
case "caching_sha2_password":
|
||||||
|
switch len(authData) {
|
||||||
|
case 0:
|
||||||
|
return nil // auth successful
|
||||||
|
case 1:
|
||||||
|
switch authData[0] {
|
||||||
|
case cachingSha2PasswordFastAuthSuccess:
|
||||||
|
if err = mc.readResultOK(); err == nil {
|
||||||
|
return nil // auth successful
|
||||||
|
}
|
||||||
|
|
||||||
|
case cachingSha2PasswordPerformFullAuthentication:
|
||||||
|
if mc.cfg.tls != nil || mc.cfg.Net == "unix" {
|
||||||
|
// write cleartext auth packet
|
||||||
|
err = mc.writeAuthSwitchPacket(append([]byte(mc.cfg.Passwd), 0))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pubKey := mc.cfg.pubKey
|
||||||
|
if pubKey == nil {
|
||||||
|
// request public key from server
|
||||||
|
data := mc.buf.takeSmallBuffer(4 + 1)
|
||||||
|
data[4] = cachingSha2PasswordRequestPublicKey
|
||||||
|
mc.writePacket(data)
|
||||||
|
|
||||||
|
// parse public key
|
||||||
|
data, err := mc.readPacket()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
block, _ := pem.Decode(data[1:])
|
||||||
|
pkix, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pubKey = pkix.(*rsa.PublicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// send encrypted password
|
||||||
|
err = mc.sendEncryptedPassword(oldAuthData, pubKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return mc.readResultOK()
|
||||||
|
|
||||||
|
default:
|
||||||
|
return ErrMalformPkt
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return ErrMalformPkt
|
||||||
|
}
|
||||||
|
|
||||||
|
case "sha256_password":
|
||||||
|
switch len(authData) {
|
||||||
|
case 0:
|
||||||
|
return nil // auth successful
|
||||||
|
default:
|
||||||
|
block, _ := pem.Decode(authData)
|
||||||
|
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// send encrypted password
|
||||||
|
err = mc.sendEncryptedPassword(oldAuthData, pub.(*rsa.PublicKey))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return mc.readResultOK()
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil // auth successful
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
|
@ -130,18 +130,18 @@ func (b *buffer) takeBuffer(length int) []byte {
|
||||||
// smaller than defaultBufSize
|
// smaller than defaultBufSize
|
||||||
// Only one buffer (total) can be used at a time.
|
// Only one buffer (total) can be used at a time.
|
||||||
func (b *buffer) takeSmallBuffer(length int) []byte {
|
func (b *buffer) takeSmallBuffer(length int) []byte {
|
||||||
if b.length == 0 {
|
if b.length > 0 {
|
||||||
return b.buf[:length]
|
return nil
|
||||||
}
|
}
|
||||||
return nil
|
return b.buf[:length]
|
||||||
}
|
}
|
||||||
|
|
||||||
// takeCompleteBuffer returns the complete existing buffer.
|
// takeCompleteBuffer returns the complete existing buffer.
|
||||||
// This can be used if the necessary buffer size is unknown.
|
// This can be used if the necessary buffer size is unknown.
|
||||||
// Only one buffer (total) can be used at a time.
|
// Only one buffer (total) can be used at a time.
|
||||||
func (b *buffer) takeCompleteBuffer() []byte {
|
func (b *buffer) takeCompleteBuffer() []byte {
|
||||||
if b.length == 0 {
|
if b.length > 0 {
|
||||||
return b.buf
|
return nil
|
||||||
}
|
}
|
||||||
return nil
|
return b.buf
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
package mysql
|
package mysql
|
||||||
|
|
||||||
const defaultCollation = "utf8_general_ci"
|
const defaultCollation = "utf8_general_ci"
|
||||||
|
const binaryCollation = "binary"
|
||||||
|
|
||||||
// A list of available collations mapped to the internal ID.
|
// A list of available collations mapped to the internal ID.
|
||||||
// To update this map use the following MySQL query:
|
// To update this map use the following MySQL query:
|
||||||
|
|
|
@ -10,12 +10,23 @@ package mysql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql/driver"
|
"database/sql/driver"
|
||||||
|
"io"
|
||||||
"net"
|
"net"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// a copy of context.Context for Go 1.7 and earlier
|
||||||
|
type mysqlContext interface {
|
||||||
|
Done() <-chan struct{}
|
||||||
|
Err() error
|
||||||
|
|
||||||
|
// defined in context.Context, but not used in this driver:
|
||||||
|
// Deadline() (deadline time.Time, ok bool)
|
||||||
|
// Value(key interface{}) interface{}
|
||||||
|
}
|
||||||
|
|
||||||
type mysqlConn struct {
|
type mysqlConn struct {
|
||||||
buf buffer
|
buf buffer
|
||||||
netConn net.Conn
|
netConn net.Conn
|
||||||
|
@ -29,7 +40,14 @@ type mysqlConn struct {
|
||||||
status statusFlag
|
status statusFlag
|
||||||
sequence uint8
|
sequence uint8
|
||||||
parseTime bool
|
parseTime bool
|
||||||
strict bool
|
|
||||||
|
// for context support (Go 1.8+)
|
||||||
|
watching bool
|
||||||
|
watcher chan<- mysqlContext
|
||||||
|
closech chan struct{}
|
||||||
|
finished chan<- struct{}
|
||||||
|
canceled atomicError // set non-nil if conn is canceled
|
||||||
|
closed atomicBool // set when conn is closed, before closech is closed
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handles parameters set in DSN after the connection is established
|
// Handles parameters set in DSN after the connection is established
|
||||||
|
@ -62,22 +80,41 @@ func (mc *mysqlConn) handleParams() (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (mc *mysqlConn) markBadConn(err error) error {
|
||||||
|
if mc == nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err != errBadConnNoWrite {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return driver.ErrBadConn
|
||||||
|
}
|
||||||
|
|
||||||
func (mc *mysqlConn) Begin() (driver.Tx, error) {
|
func (mc *mysqlConn) Begin() (driver.Tx, error) {
|
||||||
if mc.netConn == nil {
|
return mc.begin(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mysqlConn) begin(readOnly bool) (driver.Tx, error) {
|
||||||
|
if mc.closed.IsSet() {
|
||||||
errLog.Print(ErrInvalidConn)
|
errLog.Print(ErrInvalidConn)
|
||||||
return nil, driver.ErrBadConn
|
return nil, driver.ErrBadConn
|
||||||
}
|
}
|
||||||
err := mc.exec("START TRANSACTION")
|
var q string
|
||||||
|
if readOnly {
|
||||||
|
q = "START TRANSACTION READ ONLY"
|
||||||
|
} else {
|
||||||
|
q = "START TRANSACTION"
|
||||||
|
}
|
||||||
|
err := mc.exec(q)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return &mysqlTx{mc}, err
|
return &mysqlTx{mc}, err
|
||||||
}
|
}
|
||||||
|
return nil, mc.markBadConn(err)
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mc *mysqlConn) Close() (err error) {
|
func (mc *mysqlConn) Close() (err error) {
|
||||||
// Makes Close idempotent
|
// Makes Close idempotent
|
||||||
if mc.netConn != nil {
|
if !mc.closed.IsSet() {
|
||||||
err = mc.writeCommandPacket(comQuit)
|
err = mc.writeCommandPacket(comQuit)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,26 +128,39 @@ func (mc *mysqlConn) Close() (err error) {
|
||||||
// is called before auth or on auth failure because MySQL will have already
|
// is called before auth or on auth failure because MySQL will have already
|
||||||
// closed the network connection.
|
// closed the network connection.
|
||||||
func (mc *mysqlConn) cleanup() {
|
func (mc *mysqlConn) cleanup() {
|
||||||
// Makes cleanup idempotent
|
if !mc.closed.TrySet(true) {
|
||||||
if mc.netConn != nil {
|
return
|
||||||
if err := mc.netConn.Close(); err != nil {
|
|
||||||
errLog.Print(err)
|
|
||||||
}
|
|
||||||
mc.netConn = nil
|
|
||||||
}
|
}
|
||||||
mc.cfg = nil
|
|
||||||
mc.buf.nc = nil
|
// Makes cleanup idempotent
|
||||||
|
close(mc.closech)
|
||||||
|
if mc.netConn == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := mc.netConn.Close(); err != nil {
|
||||||
|
errLog.Print(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mysqlConn) error() error {
|
||||||
|
if mc.closed.IsSet() {
|
||||||
|
if err := mc.canceled.Value(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return ErrInvalidConn
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
|
func (mc *mysqlConn) Prepare(query string) (driver.Stmt, error) {
|
||||||
if mc.netConn == nil {
|
if mc.closed.IsSet() {
|
||||||
errLog.Print(ErrInvalidConn)
|
errLog.Print(ErrInvalidConn)
|
||||||
return nil, driver.ErrBadConn
|
return nil, driver.ErrBadConn
|
||||||
}
|
}
|
||||||
// Send command
|
// Send command
|
||||||
err := mc.writeCommandPacketStr(comStmtPrepare, query)
|
err := mc.writeCommandPacketStr(comStmtPrepare, query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, mc.markBadConn(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
stmt := &mysqlStmt{
|
stmt := &mysqlStmt{
|
||||||
|
@ -144,7 +194,7 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin
|
||||||
if buf == nil {
|
if buf == nil {
|
||||||
// can not take the buffer. Something must be wrong with the connection
|
// can not take the buffer. Something must be wrong with the connection
|
||||||
errLog.Print(ErrBusyBuffer)
|
errLog.Print(ErrBusyBuffer)
|
||||||
return "", driver.ErrBadConn
|
return "", ErrInvalidConn
|
||||||
}
|
}
|
||||||
buf = buf[:0]
|
buf = buf[:0]
|
||||||
argPos := 0
|
argPos := 0
|
||||||
|
@ -257,7 +307,7 @@ func (mc *mysqlConn) interpolateParams(query string, args []driver.Value) (strin
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, error) {
|
func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, error) {
|
||||||
if mc.netConn == nil {
|
if mc.closed.IsSet() {
|
||||||
errLog.Print(ErrInvalidConn)
|
errLog.Print(ErrInvalidConn)
|
||||||
return nil, driver.ErrBadConn
|
return nil, driver.ErrBadConn
|
||||||
}
|
}
|
||||||
|
@ -271,7 +321,6 @@ func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, err
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
query = prepared
|
query = prepared
|
||||||
args = nil
|
|
||||||
}
|
}
|
||||||
mc.affectedRows = 0
|
mc.affectedRows = 0
|
||||||
mc.insertId = 0
|
mc.insertId = 0
|
||||||
|
@ -283,32 +332,43 @@ func (mc *mysqlConn) Exec(query string, args []driver.Value) (driver.Result, err
|
||||||
insertId: int64(mc.insertId),
|
insertId: int64(mc.insertId),
|
||||||
}, err
|
}, err
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, mc.markBadConn(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal function to execute commands
|
// Internal function to execute commands
|
||||||
func (mc *mysqlConn) exec(query string) error {
|
func (mc *mysqlConn) exec(query string) error {
|
||||||
// Send command
|
// Send command
|
||||||
err := mc.writeCommandPacketStr(comQuery, query)
|
if err := mc.writeCommandPacketStr(comQuery, query); err != nil {
|
||||||
if err != nil {
|
return mc.markBadConn(err)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read Result
|
// Read Result
|
||||||
resLen, err := mc.readResultSetHeaderPacket()
|
resLen, err := mc.readResultSetHeaderPacket()
|
||||||
if err == nil && resLen > 0 {
|
if err != nil {
|
||||||
if err = mc.readUntilEOF(); err != nil {
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resLen > 0 {
|
||||||
|
// columns
|
||||||
|
if err := mc.readUntilEOF(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = mc.readUntilEOF()
|
// rows
|
||||||
|
if err := mc.readUntilEOF(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return mc.discardResults()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, error) {
|
func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, error) {
|
||||||
if mc.netConn == nil {
|
return mc.query(query, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mysqlConn) query(query string, args []driver.Value) (*textRows, error) {
|
||||||
|
if mc.closed.IsSet() {
|
||||||
errLog.Print(ErrInvalidConn)
|
errLog.Print(ErrInvalidConn)
|
||||||
return nil, driver.ErrBadConn
|
return nil, driver.ErrBadConn
|
||||||
}
|
}
|
||||||
|
@ -322,7 +382,6 @@ func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, erro
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
query = prepared
|
query = prepared
|
||||||
args = nil
|
|
||||||
}
|
}
|
||||||
// Send command
|
// Send command
|
||||||
err := mc.writeCommandPacketStr(comQuery, query)
|
err := mc.writeCommandPacketStr(comQuery, query)
|
||||||
|
@ -335,15 +394,22 @@ func (mc *mysqlConn) Query(query string, args []driver.Value) (driver.Rows, erro
|
||||||
rows.mc = mc
|
rows.mc = mc
|
||||||
|
|
||||||
if resLen == 0 {
|
if resLen == 0 {
|
||||||
// no columns, no more data
|
rows.rs.done = true
|
||||||
return emptyRows{}, nil
|
|
||||||
|
switch err := rows.NextResultSet(); err {
|
||||||
|
case nil, io.EOF:
|
||||||
|
return rows, nil
|
||||||
|
default:
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Columns
|
// Columns
|
||||||
rows.columns, err = mc.readColumns(resLen)
|
rows.rs.columns, err = mc.readColumns(resLen)
|
||||||
return rows, err
|
return rows, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, mc.markBadConn(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gets the value of the given MySQL System Variable
|
// Gets the value of the given MySQL System Variable
|
||||||
|
@ -359,7 +425,7 @@ func (mc *mysqlConn) getSystemVar(name string) ([]byte, error) {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
rows := new(textRows)
|
rows := new(textRows)
|
||||||
rows.mc = mc
|
rows.mc = mc
|
||||||
rows.columns = []mysqlField{{fieldType: fieldTypeVarChar}}
|
rows.rs.columns = []mysqlField{{fieldType: fieldTypeVarChar}}
|
||||||
|
|
||||||
if resLen > 0 {
|
if resLen > 0 {
|
||||||
// Columns
|
// Columns
|
||||||
|
@ -375,3 +441,21 @@ func (mc *mysqlConn) getSystemVar(name string) ([]byte, error) {
|
||||||
}
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// finish is called when the query has canceled.
|
||||||
|
func (mc *mysqlConn) cancel(err error) {
|
||||||
|
mc.canceled.Set(err)
|
||||||
|
mc.cleanup()
|
||||||
|
}
|
||||||
|
|
||||||
|
// finish is called when the query has succeeded.
|
||||||
|
func (mc *mysqlConn) finish() {
|
||||||
|
if !mc.watching || mc.finished == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case mc.finished <- struct{}{}:
|
||||||
|
mc.watching = false
|
||||||
|
case <-mc.closech:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,207 @@
|
||||||
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||||
|
//
|
||||||
|
// Copyright 2012 The Go-MySQL-Driver Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
// +build go1.8
|
||||||
|
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"database/sql/driver"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Ping implements driver.Pinger interface
|
||||||
|
func (mc *mysqlConn) Ping(ctx context.Context) (err error) {
|
||||||
|
if mc.closed.IsSet() {
|
||||||
|
errLog.Print(ErrInvalidConn)
|
||||||
|
return driver.ErrBadConn
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = mc.watchCancel(ctx); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer mc.finish()
|
||||||
|
|
||||||
|
if err = mc.writeCommandPacket(comPing); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return mc.readResultOK()
|
||||||
|
}
|
||||||
|
|
||||||
|
// BeginTx implements driver.ConnBeginTx interface
|
||||||
|
func (mc *mysqlConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
|
||||||
|
if err := mc.watchCancel(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer mc.finish()
|
||||||
|
|
||||||
|
if sql.IsolationLevel(opts.Isolation) != sql.LevelDefault {
|
||||||
|
level, err := mapIsolationLevel(opts.Isolation)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = mc.exec("SET TRANSACTION ISOLATION LEVEL " + level)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mc.begin(opts.ReadOnly)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mysqlConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
|
||||||
|
dargs, err := namedValueToValue(args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := mc.watchCancel(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := mc.query(query, dargs)
|
||||||
|
if err != nil {
|
||||||
|
mc.finish()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rows.finish = mc.finish
|
||||||
|
return rows, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mysqlConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
|
||||||
|
dargs, err := namedValueToValue(args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := mc.watchCancel(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer mc.finish()
|
||||||
|
|
||||||
|
return mc.Exec(query, dargs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mysqlConn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {
|
||||||
|
if err := mc.watchCancel(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
stmt, err := mc.Prepare(query)
|
||||||
|
mc.finish()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
default:
|
||||||
|
case <-ctx.Done():
|
||||||
|
stmt.Close()
|
||||||
|
return nil, ctx.Err()
|
||||||
|
}
|
||||||
|
return stmt, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (stmt *mysqlStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
|
||||||
|
dargs, err := namedValueToValue(args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := stmt.mc.watchCancel(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
rows, err := stmt.query(dargs)
|
||||||
|
if err != nil {
|
||||||
|
stmt.mc.finish()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
rows.finish = stmt.mc.finish
|
||||||
|
return rows, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (stmt *mysqlStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {
|
||||||
|
dargs, err := namedValueToValue(args)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := stmt.mc.watchCancel(ctx); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer stmt.mc.finish()
|
||||||
|
|
||||||
|
return stmt.Exec(dargs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mysqlConn) watchCancel(ctx context.Context) error {
|
||||||
|
if mc.watching {
|
||||||
|
// Reach here if canceled,
|
||||||
|
// so the connection is already invalid
|
||||||
|
mc.cleanup()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// When ctx is already cancelled, don't watch it.
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// When ctx is not cancellable, don't watch it.
|
||||||
|
if ctx.Done() == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// When watcher is not alive, can't watch it.
|
||||||
|
if mc.watcher == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
mc.watching = true
|
||||||
|
mc.watcher <- ctx
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mysqlConn) startWatcher() {
|
||||||
|
watcher := make(chan mysqlContext, 1)
|
||||||
|
mc.watcher = watcher
|
||||||
|
finished := make(chan struct{})
|
||||||
|
mc.finished = finished
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
var ctx mysqlContext
|
||||||
|
select {
|
||||||
|
case ctx = <-watcher:
|
||||||
|
case <-mc.closech:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
mc.cancel(ctx.Err())
|
||||||
|
case <-finished:
|
||||||
|
case <-mc.closech:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mc *mysqlConn) CheckNamedValue(nv *driver.NamedValue) (err error) {
|
||||||
|
nv.Value, err = converter{}.ConvertValue(nv.Value)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetSession implements driver.SessionResetter.
|
||||||
|
// (From Go 1.10)
|
||||||
|
func (mc *mysqlConn) ResetSession(ctx context.Context) error {
|
||||||
|
if mc.closed.IsSet() {
|
||||||
|
return driver.ErrBadConn
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -9,7 +9,9 @@
|
||||||
package mysql
|
package mysql
|
||||||
|
|
||||||
const (
|
const (
|
||||||
minProtocolVersion byte = 10
|
defaultAuthPlugin = "mysql_native_password"
|
||||||
|
defaultMaxAllowedPacket = 4 << 20 // 4 MiB
|
||||||
|
minProtocolVersion = 10
|
||||||
maxPacketSize = 1<<24 - 1
|
maxPacketSize = 1<<24 - 1
|
||||||
timeFormat = "2006-01-02 15:04:05.999999"
|
timeFormat = "2006-01-02 15:04:05.999999"
|
||||||
)
|
)
|
||||||
|
@ -18,10 +20,11 @@ const (
|
||||||
// http://dev.mysql.com/doc/internals/en/client-server-protocol.html
|
// http://dev.mysql.com/doc/internals/en/client-server-protocol.html
|
||||||
|
|
||||||
const (
|
const (
|
||||||
iOK byte = 0x00
|
iOK byte = 0x00
|
||||||
iLocalInFile byte = 0xfb
|
iAuthMoreData byte = 0x01
|
||||||
iEOF byte = 0xfe
|
iLocalInFile byte = 0xfb
|
||||||
iERR byte = 0xff
|
iEOF byte = 0xfe
|
||||||
|
iERR byte = 0xff
|
||||||
)
|
)
|
||||||
|
|
||||||
// https://dev.mysql.com/doc/internals/en/capability-flags.html#packet-Protocol::CapabilityFlags
|
// https://dev.mysql.com/doc/internals/en/capability-flags.html#packet-Protocol::CapabilityFlags
|
||||||
|
@ -87,8 +90,10 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
// https://dev.mysql.com/doc/internals/en/com-query-response.html#packet-Protocol::ColumnType
|
// https://dev.mysql.com/doc/internals/en/com-query-response.html#packet-Protocol::ColumnType
|
||||||
|
type fieldType byte
|
||||||
|
|
||||||
const (
|
const (
|
||||||
fieldTypeDecimal byte = iota
|
fieldTypeDecimal fieldType = iota
|
||||||
fieldTypeTiny
|
fieldTypeTiny
|
||||||
fieldTypeShort
|
fieldTypeShort
|
||||||
fieldTypeLong
|
fieldTypeLong
|
||||||
|
@ -107,7 +112,7 @@ const (
|
||||||
fieldTypeBit
|
fieldTypeBit
|
||||||
)
|
)
|
||||||
const (
|
const (
|
||||||
fieldTypeJSON byte = iota + 0xf5
|
fieldTypeJSON fieldType = iota + 0xf5
|
||||||
fieldTypeNewDecimal
|
fieldTypeNewDecimal
|
||||||
fieldTypeEnum
|
fieldTypeEnum
|
||||||
fieldTypeSet
|
fieldTypeSet
|
||||||
|
@ -161,3 +166,9 @@ const (
|
||||||
statusInTransReadonly
|
statusInTransReadonly
|
||||||
statusSessionStateChanged
|
statusSessionStateChanged
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
cachingSha2PasswordRequestPublicKey = 2
|
||||||
|
cachingSha2PasswordFastAuthSuccess = 3
|
||||||
|
cachingSha2PasswordPerformFullAuthentication = 4
|
||||||
|
)
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
// Package mysql provides a MySQL driver for Go's database/sql package
|
// Package mysql provides a MySQL driver for Go's database/sql package.
|
||||||
//
|
//
|
||||||
// The driver should be used via the database/sql package:
|
// The driver should be used via the database/sql package:
|
||||||
//
|
//
|
||||||
|
@ -20,8 +20,14 @@ import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"database/sql/driver"
|
"database/sql/driver"
|
||||||
"net"
|
"net"
|
||||||
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// watcher interface is used for context support (From Go 1.8)
|
||||||
|
type watcher interface {
|
||||||
|
startWatcher()
|
||||||
|
}
|
||||||
|
|
||||||
// MySQLDriver is exported to make the driver directly accessible.
|
// MySQLDriver is exported to make the driver directly accessible.
|
||||||
// In general the driver is used via the database/sql package.
|
// In general the driver is used via the database/sql package.
|
||||||
type MySQLDriver struct{}
|
type MySQLDriver struct{}
|
||||||
|
@ -30,12 +36,17 @@ type MySQLDriver struct{}
|
||||||
// Custom dial functions must be registered with RegisterDial
|
// Custom dial functions must be registered with RegisterDial
|
||||||
type DialFunc func(addr string) (net.Conn, error)
|
type DialFunc func(addr string) (net.Conn, error)
|
||||||
|
|
||||||
var dials map[string]DialFunc
|
var (
|
||||||
|
dialsLock sync.RWMutex
|
||||||
|
dials map[string]DialFunc
|
||||||
|
)
|
||||||
|
|
||||||
// RegisterDial registers a custom dial function. It can then be used by the
|
// RegisterDial registers a custom dial function. It can then be used by the
|
||||||
// network address mynet(addr), where mynet is the registered new network.
|
// network address mynet(addr), where mynet is the registered new network.
|
||||||
// addr is passed as a parameter to the dial function.
|
// addr is passed as a parameter to the dial function.
|
||||||
func RegisterDial(net string, dial DialFunc) {
|
func RegisterDial(net string, dial DialFunc) {
|
||||||
|
dialsLock.Lock()
|
||||||
|
defer dialsLock.Unlock()
|
||||||
if dials == nil {
|
if dials == nil {
|
||||||
dials = make(map[string]DialFunc)
|
dials = make(map[string]DialFunc)
|
||||||
}
|
}
|
||||||
|
@ -52,16 +63,19 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
|
||||||
mc := &mysqlConn{
|
mc := &mysqlConn{
|
||||||
maxAllowedPacket: maxPacketSize,
|
maxAllowedPacket: maxPacketSize,
|
||||||
maxWriteSize: maxPacketSize - 1,
|
maxWriteSize: maxPacketSize - 1,
|
||||||
|
closech: make(chan struct{}),
|
||||||
}
|
}
|
||||||
mc.cfg, err = ParseDSN(dsn)
|
mc.cfg, err = ParseDSN(dsn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
mc.parseTime = mc.cfg.ParseTime
|
mc.parseTime = mc.cfg.ParseTime
|
||||||
mc.strict = mc.cfg.Strict
|
|
||||||
|
|
||||||
// Connect to Server
|
// Connect to Server
|
||||||
if dial, ok := dials[mc.cfg.Net]; ok {
|
dialsLock.RLock()
|
||||||
|
dial, ok := dials[mc.cfg.Net]
|
||||||
|
dialsLock.RUnlock()
|
||||||
|
if ok {
|
||||||
mc.netConn, err = dial(mc.cfg.Addr)
|
mc.netConn, err = dial(mc.cfg.Addr)
|
||||||
} else {
|
} else {
|
||||||
nd := net.Dialer{Timeout: mc.cfg.Timeout}
|
nd := net.Dialer{Timeout: mc.cfg.Timeout}
|
||||||
|
@ -81,6 +95,11 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Call startWatcher for context support (From Go 1.8)
|
||||||
|
if s, ok := interface{}(mc).(watcher); ok {
|
||||||
|
s.startWatcher()
|
||||||
|
}
|
||||||
|
|
||||||
mc.buf = newBuffer(mc.netConn)
|
mc.buf = newBuffer(mc.netConn)
|
||||||
|
|
||||||
// Set I/O timeouts
|
// Set I/O timeouts
|
||||||
|
@ -88,20 +107,34 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
|
||||||
mc.writeTimeout = mc.cfg.WriteTimeout
|
mc.writeTimeout = mc.cfg.WriteTimeout
|
||||||
|
|
||||||
// Reading Handshake Initialization Packet
|
// Reading Handshake Initialization Packet
|
||||||
cipher, err := mc.readInitPacket()
|
authData, plugin, err := mc.readHandshakePacket()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mc.cleanup()
|
mc.cleanup()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if plugin == "" {
|
||||||
|
plugin = defaultAuthPlugin
|
||||||
|
}
|
||||||
|
|
||||||
// Send Client Authentication Packet
|
// Send Client Authentication Packet
|
||||||
if err = mc.writeAuthPacket(cipher); err != nil {
|
authResp, err := mc.auth(authData, plugin)
|
||||||
|
if err != nil {
|
||||||
|
// try the default auth plugin, if using the requested plugin failed
|
||||||
|
errLog.Print("could not use requested auth plugin '"+plugin+"': ", err.Error())
|
||||||
|
plugin = defaultAuthPlugin
|
||||||
|
authResp, err = mc.auth(authData, plugin)
|
||||||
|
if err != nil {
|
||||||
|
mc.cleanup()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err = mc.writeHandshakeResponsePacket(authResp, plugin); err != nil {
|
||||||
mc.cleanup()
|
mc.cleanup()
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle response to auth packet, switch methods if possible
|
// Handle response to auth packet, switch methods if possible
|
||||||
if err = handleAuthResult(mc, cipher); err != nil {
|
if err = mc.handleAuthResult(authData, plugin); err != nil {
|
||||||
// Authentication failed and MySQL has already closed the connection
|
// Authentication failed and MySQL has already closed the connection
|
||||||
// (https://dev.mysql.com/doc/internals/en/authentication-fails.html).
|
// (https://dev.mysql.com/doc/internals/en/authentication-fails.html).
|
||||||
// Do not send COM_QUIT, just cleanup and return the error.
|
// Do not send COM_QUIT, just cleanup and return the error.
|
||||||
|
@ -134,50 +167,6 @@ func (d MySQLDriver) Open(dsn string) (driver.Conn, error) {
|
||||||
return mc, nil
|
return mc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleAuthResult(mc *mysqlConn, oldCipher []byte) error {
|
|
||||||
// Read Result Packet
|
|
||||||
cipher, err := mc.readResultOK()
|
|
||||||
if err == nil {
|
|
||||||
return nil // auth successful
|
|
||||||
}
|
|
||||||
|
|
||||||
if mc.cfg == nil {
|
|
||||||
return err // auth failed and retry not possible
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retry auth if configured to do so.
|
|
||||||
if mc.cfg.AllowOldPasswords && err == ErrOldPassword {
|
|
||||||
// Retry with old authentication method. Note: there are edge cases
|
|
||||||
// where this should work but doesn't; this is currently "wontfix":
|
|
||||||
// https://github.com/go-sql-driver/mysql/issues/184
|
|
||||||
|
|
||||||
// If CLIENT_PLUGIN_AUTH capability is not supported, no new cipher is
|
|
||||||
// sent and we have to keep using the cipher sent in the init packet.
|
|
||||||
if cipher == nil {
|
|
||||||
cipher = oldCipher
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = mc.writeOldAuthPacket(cipher); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = mc.readResultOK()
|
|
||||||
} else if mc.cfg.AllowCleartextPasswords && err == ErrCleartextPassword {
|
|
||||||
// Retry with clear text password for
|
|
||||||
// http://dev.mysql.com/doc/refman/5.7/en/cleartext-authentication-plugin.html
|
|
||||||
// http://dev.mysql.com/doc/refman/5.7/en/pam-authentication-plugin.html
|
|
||||||
if err = mc.writeClearAuthPacket(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = mc.readResultOK()
|
|
||||||
} else if mc.cfg.AllowNativePasswords && err == ErrNativePassword {
|
|
||||||
if err = mc.writeNativeAuthPacket(cipher); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = mc.readResultOK()
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
sql.Register("mysql", &MySQLDriver{})
|
sql.Register("mysql", &MySQLDriver{})
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,11 +10,13 @@ package mysql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"crypto/rsa"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -27,7 +29,9 @@ var (
|
||||||
errInvalidDSNUnsafeCollation = errors.New("invalid DSN: interpolateParams can not be used with unsafe collations")
|
errInvalidDSNUnsafeCollation = errors.New("invalid DSN: interpolateParams can not be used with unsafe collations")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Config is a configuration parsed from a DSN string
|
// Config is a configuration parsed from a DSN string.
|
||||||
|
// If a new Config is created instead of being parsed from a DSN string,
|
||||||
|
// the NewConfig function should be used, which sets default values.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
User string // Username
|
User string // Username
|
||||||
Passwd string // Password (requires User)
|
Passwd string // Password (requires User)
|
||||||
|
@ -38,6 +42,8 @@ type Config struct {
|
||||||
Collation string // Connection collation
|
Collation string // Connection collation
|
||||||
Loc *time.Location // Location for time.Time values
|
Loc *time.Location // Location for time.Time values
|
||||||
MaxAllowedPacket int // Max packet size allowed
|
MaxAllowedPacket int // Max packet size allowed
|
||||||
|
ServerPubKey string // Server public key name
|
||||||
|
pubKey *rsa.PublicKey // Server public key
|
||||||
TLSConfig string // TLS configuration name
|
TLSConfig string // TLS configuration name
|
||||||
tls *tls.Config // TLS configuration
|
tls *tls.Config // TLS configuration
|
||||||
Timeout time.Duration // Dial timeout
|
Timeout time.Duration // Dial timeout
|
||||||
|
@ -53,7 +59,54 @@ type Config struct {
|
||||||
InterpolateParams bool // Interpolate placeholders into query string
|
InterpolateParams bool // Interpolate placeholders into query string
|
||||||
MultiStatements bool // Allow multiple statements in one query
|
MultiStatements bool // Allow multiple statements in one query
|
||||||
ParseTime bool // Parse time values to time.Time
|
ParseTime bool // Parse time values to time.Time
|
||||||
Strict bool // Return warnings as errors
|
RejectReadOnly bool // Reject read-only connections
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConfig creates a new Config and sets default values.
|
||||||
|
func NewConfig() *Config {
|
||||||
|
return &Config{
|
||||||
|
Collation: defaultCollation,
|
||||||
|
Loc: time.UTC,
|
||||||
|
MaxAllowedPacket: defaultMaxAllowedPacket,
|
||||||
|
AllowNativePasswords: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *Config) normalize() error {
|
||||||
|
if cfg.InterpolateParams && unsafeCollations[cfg.Collation] {
|
||||||
|
return errInvalidDSNUnsafeCollation
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set default network if empty
|
||||||
|
if cfg.Net == "" {
|
||||||
|
cfg.Net = "tcp"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set default address if empty
|
||||||
|
if cfg.Addr == "" {
|
||||||
|
switch cfg.Net {
|
||||||
|
case "tcp":
|
||||||
|
cfg.Addr = "127.0.0.1:3306"
|
||||||
|
case "unix":
|
||||||
|
cfg.Addr = "/tmp/mysql.sock"
|
||||||
|
default:
|
||||||
|
return errors.New("default addr for network '" + cfg.Net + "' unknown")
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if cfg.Net == "tcp" {
|
||||||
|
cfg.Addr = ensureHavePort(cfg.Addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.tls != nil {
|
||||||
|
if cfg.tls.ServerName == "" && !cfg.tls.InsecureSkipVerify {
|
||||||
|
host, _, err := net.SplitHostPort(cfg.Addr)
|
||||||
|
if err == nil {
|
||||||
|
cfg.tls.ServerName = host
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// FormatDSN formats the given Config into a DSN string which can be passed to
|
// FormatDSN formats the given Config into a DSN string which can be passed to
|
||||||
|
@ -102,12 +155,12 @@ func (cfg *Config) FormatDSN() string {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.AllowNativePasswords {
|
if !cfg.AllowNativePasswords {
|
||||||
if hasParam {
|
if hasParam {
|
||||||
buf.WriteString("&allowNativePasswords=true")
|
buf.WriteString("&allowNativePasswords=false")
|
||||||
} else {
|
} else {
|
||||||
hasParam = true
|
hasParam = true
|
||||||
buf.WriteString("?allowNativePasswords=true")
|
buf.WriteString("?allowNativePasswords=false")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -195,15 +248,25 @@ func (cfg *Config) FormatDSN() string {
|
||||||
buf.WriteString(cfg.ReadTimeout.String())
|
buf.WriteString(cfg.ReadTimeout.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Strict {
|
if cfg.RejectReadOnly {
|
||||||
if hasParam {
|
if hasParam {
|
||||||
buf.WriteString("&strict=true")
|
buf.WriteString("&rejectReadOnly=true")
|
||||||
} else {
|
} else {
|
||||||
hasParam = true
|
hasParam = true
|
||||||
buf.WriteString("?strict=true")
|
buf.WriteString("?rejectReadOnly=true")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(cfg.ServerPubKey) > 0 {
|
||||||
|
if hasParam {
|
||||||
|
buf.WriteString("&serverPubKey=")
|
||||||
|
} else {
|
||||||
|
hasParam = true
|
||||||
|
buf.WriteString("?serverPubKey=")
|
||||||
|
}
|
||||||
|
buf.WriteString(url.QueryEscape(cfg.ServerPubKey))
|
||||||
|
}
|
||||||
|
|
||||||
if cfg.Timeout > 0 {
|
if cfg.Timeout > 0 {
|
||||||
if hasParam {
|
if hasParam {
|
||||||
buf.WriteString("&timeout=")
|
buf.WriteString("&timeout=")
|
||||||
|
@ -234,7 +297,7 @@ func (cfg *Config) FormatDSN() string {
|
||||||
buf.WriteString(cfg.WriteTimeout.String())
|
buf.WriteString(cfg.WriteTimeout.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.MaxAllowedPacket > 0 {
|
if cfg.MaxAllowedPacket != defaultMaxAllowedPacket {
|
||||||
if hasParam {
|
if hasParam {
|
||||||
buf.WriteString("&maxAllowedPacket=")
|
buf.WriteString("&maxAllowedPacket=")
|
||||||
} else {
|
} else {
|
||||||
|
@ -247,7 +310,12 @@ func (cfg *Config) FormatDSN() string {
|
||||||
|
|
||||||
// other params
|
// other params
|
||||||
if cfg.Params != nil {
|
if cfg.Params != nil {
|
||||||
for param, value := range cfg.Params {
|
var params []string
|
||||||
|
for param := range cfg.Params {
|
||||||
|
params = append(params, param)
|
||||||
|
}
|
||||||
|
sort.Strings(params)
|
||||||
|
for _, param := range params {
|
||||||
if hasParam {
|
if hasParam {
|
||||||
buf.WriteByte('&')
|
buf.WriteByte('&')
|
||||||
} else {
|
} else {
|
||||||
|
@ -257,7 +325,7 @@ func (cfg *Config) FormatDSN() string {
|
||||||
|
|
||||||
buf.WriteString(param)
|
buf.WriteString(param)
|
||||||
buf.WriteByte('=')
|
buf.WriteByte('=')
|
||||||
buf.WriteString(url.QueryEscape(value))
|
buf.WriteString(url.QueryEscape(cfg.Params[param]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,10 +335,7 @@ func (cfg *Config) FormatDSN() string {
|
||||||
// ParseDSN parses the DSN string to a Config
|
// ParseDSN parses the DSN string to a Config
|
||||||
func ParseDSN(dsn string) (cfg *Config, err error) {
|
func ParseDSN(dsn string) (cfg *Config, err error) {
|
||||||
// New config with some default values
|
// New config with some default values
|
||||||
cfg = &Config{
|
cfg = NewConfig()
|
||||||
Loc: time.UTC,
|
|
||||||
Collation: defaultCollation,
|
|
||||||
}
|
|
||||||
|
|
||||||
// [user[:password]@][net[(addr)]]/dbname[?param1=value1¶mN=valueN]
|
// [user[:password]@][net[(addr)]]/dbname[?param1=value1¶mN=valueN]
|
||||||
// Find the last '/' (since the password or the net addr might contain a '/')
|
// Find the last '/' (since the password or the net addr might contain a '/')
|
||||||
|
@ -338,28 +403,9 @@ func ParseDSN(dsn string) (cfg *Config, err error) {
|
||||||
return nil, errInvalidDSNNoSlash
|
return nil, errInvalidDSNNoSlash
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.InterpolateParams && unsafeCollations[cfg.Collation] {
|
if err = cfg.normalize(); err != nil {
|
||||||
return nil, errInvalidDSNUnsafeCollation
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set default network if empty
|
|
||||||
if cfg.Net == "" {
|
|
||||||
cfg.Net = "tcp"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set default address if empty
|
|
||||||
if cfg.Addr == "" {
|
|
||||||
switch cfg.Net {
|
|
||||||
case "tcp":
|
|
||||||
cfg.Addr = "127.0.0.1:3306"
|
|
||||||
case "unix":
|
|
||||||
cfg.Addr = "/tmp/mysql.sock"
|
|
||||||
default:
|
|
||||||
return nil, errors.New("default addr for network '" + cfg.Net + "' unknown")
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -374,7 +420,6 @@ func parseDSNParams(cfg *Config, params string) (err error) {
|
||||||
|
|
||||||
// cfg params
|
// cfg params
|
||||||
switch value := param[1]; param[0] {
|
switch value := param[1]; param[0] {
|
||||||
|
|
||||||
// Disable INFILE whitelist / enable all files
|
// Disable INFILE whitelist / enable all files
|
||||||
case "allowAllFiles":
|
case "allowAllFiles":
|
||||||
var isBool bool
|
var isBool bool
|
||||||
|
@ -472,14 +517,32 @@ func parseDSNParams(cfg *Config, params string) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strict mode
|
// Reject read-only connections
|
||||||
case "strict":
|
case "rejectReadOnly":
|
||||||
var isBool bool
|
var isBool bool
|
||||||
cfg.Strict, isBool = readBool(value)
|
cfg.RejectReadOnly, isBool = readBool(value)
|
||||||
if !isBool {
|
if !isBool {
|
||||||
return errors.New("invalid bool value: " + value)
|
return errors.New("invalid bool value: " + value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Server public key
|
||||||
|
case "serverPubKey":
|
||||||
|
name, err := url.QueryUnescape(value)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid value for server pub key name: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if pubKey := getServerPubKey(name); pubKey != nil {
|
||||||
|
cfg.ServerPubKey = name
|
||||||
|
cfg.pubKey = pubKey
|
||||||
|
} else {
|
||||||
|
return errors.New("invalid value / unknown server pub key name: " + name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strict mode
|
||||||
|
case "strict":
|
||||||
|
panic("strict mode has been removed. See https://github.com/go-sql-driver/mysql/wiki/strict-mode")
|
||||||
|
|
||||||
// Dial Timeout
|
// Dial Timeout
|
||||||
case "timeout":
|
case "timeout":
|
||||||
cfg.Timeout, err = time.ParseDuration(value)
|
cfg.Timeout, err = time.ParseDuration(value)
|
||||||
|
@ -506,14 +569,7 @@ func parseDSNParams(cfg *Config, params string) (err error) {
|
||||||
return fmt.Errorf("invalid value for TLS config name: %v", err)
|
return fmt.Errorf("invalid value for TLS config name: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if tlsConfig, ok := tlsConfigRegister[name]; ok {
|
if tlsConfig := getTLSConfigClone(name); tlsConfig != nil {
|
||||||
if len(tlsConfig.ServerName) == 0 && !tlsConfig.InsecureSkipVerify {
|
|
||||||
host, _, err := net.SplitHostPort(cfg.Addr)
|
|
||||||
if err == nil {
|
|
||||||
tlsConfig.ServerName = host
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg.TLSConfig = name
|
cfg.TLSConfig = name
|
||||||
cfg.tls = tlsConfig
|
cfg.tls = tlsConfig
|
||||||
} else {
|
} else {
|
||||||
|
@ -546,3 +602,10 @@ func parseDSNParams(cfg *Config, params string) (err error) {
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ensureHavePort(addr string) string {
|
||||||
|
if _, _, err := net.SplitHostPort(addr); err != nil {
|
||||||
|
return net.JoinHostPort(addr, "3306")
|
||||||
|
}
|
||||||
|
return addr
|
||||||
|
}
|
||||||
|
|
|
@ -9,10 +9,8 @@
|
||||||
package mysql
|
package mysql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql/driver"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
@ -31,6 +29,12 @@ var (
|
||||||
ErrPktSyncMul = errors.New("commands out of sync. Did you run multiple statements at once?")
|
ErrPktSyncMul = errors.New("commands out of sync. Did you run multiple statements at once?")
|
||||||
ErrPktTooLarge = errors.New("packet for query is too large. Try adjusting the 'max_allowed_packet' variable on the server")
|
ErrPktTooLarge = errors.New("packet for query is too large. Try adjusting the 'max_allowed_packet' variable on the server")
|
||||||
ErrBusyBuffer = errors.New("busy buffer")
|
ErrBusyBuffer = errors.New("busy buffer")
|
||||||
|
|
||||||
|
// errBadConnNoWrite is used for connection errors where nothing was sent to the database yet.
|
||||||
|
// If this happens first in a function starting a database interaction, it should be replaced by driver.ErrBadConn
|
||||||
|
// to trigger a resend.
|
||||||
|
// See https://github.com/go-sql-driver/mysql/pull/302
|
||||||
|
errBadConnNoWrite = errors.New("bad connection")
|
||||||
)
|
)
|
||||||
|
|
||||||
var errLog = Logger(log.New(os.Stderr, "[mysql] ", log.Ldate|log.Ltime|log.Lshortfile))
|
var errLog = Logger(log.New(os.Stderr, "[mysql] ", log.Ldate|log.Ltime|log.Lshortfile))
|
||||||
|
@ -59,74 +63,3 @@ type MySQLError struct {
|
||||||
func (me *MySQLError) Error() string {
|
func (me *MySQLError) Error() string {
|
||||||
return fmt.Sprintf("Error %d: %s", me.Number, me.Message)
|
return fmt.Sprintf("Error %d: %s", me.Number, me.Message)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MySQLWarnings is an error type which represents a group of one or more MySQL
|
|
||||||
// warnings
|
|
||||||
type MySQLWarnings []MySQLWarning
|
|
||||||
|
|
||||||
func (mws MySQLWarnings) Error() string {
|
|
||||||
var msg string
|
|
||||||
for i, warning := range mws {
|
|
||||||
if i > 0 {
|
|
||||||
msg += "\r\n"
|
|
||||||
}
|
|
||||||
msg += fmt.Sprintf(
|
|
||||||
"%s %s: %s",
|
|
||||||
warning.Level,
|
|
||||||
warning.Code,
|
|
||||||
warning.Message,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return msg
|
|
||||||
}
|
|
||||||
|
|
||||||
// MySQLWarning is an error type which represents a single MySQL warning.
|
|
||||||
// Warnings are returned in groups only. See MySQLWarnings
|
|
||||||
type MySQLWarning struct {
|
|
||||||
Level string
|
|
||||||
Code string
|
|
||||||
Message string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mc *mysqlConn) getWarnings() (err error) {
|
|
||||||
rows, err := mc.Query("SHOW WARNINGS", nil)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var warnings = MySQLWarnings{}
|
|
||||||
var values = make([]driver.Value, 3)
|
|
||||||
|
|
||||||
for {
|
|
||||||
err = rows.Next(values)
|
|
||||||
switch err {
|
|
||||||
case nil:
|
|
||||||
warning := MySQLWarning{}
|
|
||||||
|
|
||||||
if raw, ok := values[0].([]byte); ok {
|
|
||||||
warning.Level = string(raw)
|
|
||||||
} else {
|
|
||||||
warning.Level = fmt.Sprintf("%s", values[0])
|
|
||||||
}
|
|
||||||
if raw, ok := values[1].([]byte); ok {
|
|
||||||
warning.Code = string(raw)
|
|
||||||
} else {
|
|
||||||
warning.Code = fmt.Sprintf("%s", values[1])
|
|
||||||
}
|
|
||||||
if raw, ok := values[2].([]byte); ok {
|
|
||||||
warning.Message = string(raw)
|
|
||||||
} else {
|
|
||||||
warning.Message = fmt.Sprintf("%s", values[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
warnings = append(warnings, warning)
|
|
||||||
|
|
||||||
case io.EOF:
|
|
||||||
return warnings
|
|
||||||
|
|
||||||
default:
|
|
||||||
rows.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,194 @@
|
||||||
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||||
|
//
|
||||||
|
// Copyright 2017 The Go-MySQL-Driver Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (mf *mysqlField) typeDatabaseName() string {
|
||||||
|
switch mf.fieldType {
|
||||||
|
case fieldTypeBit:
|
||||||
|
return "BIT"
|
||||||
|
case fieldTypeBLOB:
|
||||||
|
if mf.charSet != collations[binaryCollation] {
|
||||||
|
return "TEXT"
|
||||||
|
}
|
||||||
|
return "BLOB"
|
||||||
|
case fieldTypeDate:
|
||||||
|
return "DATE"
|
||||||
|
case fieldTypeDateTime:
|
||||||
|
return "DATETIME"
|
||||||
|
case fieldTypeDecimal:
|
||||||
|
return "DECIMAL"
|
||||||
|
case fieldTypeDouble:
|
||||||
|
return "DOUBLE"
|
||||||
|
case fieldTypeEnum:
|
||||||
|
return "ENUM"
|
||||||
|
case fieldTypeFloat:
|
||||||
|
return "FLOAT"
|
||||||
|
case fieldTypeGeometry:
|
||||||
|
return "GEOMETRY"
|
||||||
|
case fieldTypeInt24:
|
||||||
|
return "MEDIUMINT"
|
||||||
|
case fieldTypeJSON:
|
||||||
|
return "JSON"
|
||||||
|
case fieldTypeLong:
|
||||||
|
return "INT"
|
||||||
|
case fieldTypeLongBLOB:
|
||||||
|
if mf.charSet != collations[binaryCollation] {
|
||||||
|
return "LONGTEXT"
|
||||||
|
}
|
||||||
|
return "LONGBLOB"
|
||||||
|
case fieldTypeLongLong:
|
||||||
|
return "BIGINT"
|
||||||
|
case fieldTypeMediumBLOB:
|
||||||
|
if mf.charSet != collations[binaryCollation] {
|
||||||
|
return "MEDIUMTEXT"
|
||||||
|
}
|
||||||
|
return "MEDIUMBLOB"
|
||||||
|
case fieldTypeNewDate:
|
||||||
|
return "DATE"
|
||||||
|
case fieldTypeNewDecimal:
|
||||||
|
return "DECIMAL"
|
||||||
|
case fieldTypeNULL:
|
||||||
|
return "NULL"
|
||||||
|
case fieldTypeSet:
|
||||||
|
return "SET"
|
||||||
|
case fieldTypeShort:
|
||||||
|
return "SMALLINT"
|
||||||
|
case fieldTypeString:
|
||||||
|
if mf.charSet == collations[binaryCollation] {
|
||||||
|
return "BINARY"
|
||||||
|
}
|
||||||
|
return "CHAR"
|
||||||
|
case fieldTypeTime:
|
||||||
|
return "TIME"
|
||||||
|
case fieldTypeTimestamp:
|
||||||
|
return "TIMESTAMP"
|
||||||
|
case fieldTypeTiny:
|
||||||
|
return "TINYINT"
|
||||||
|
case fieldTypeTinyBLOB:
|
||||||
|
if mf.charSet != collations[binaryCollation] {
|
||||||
|
return "TINYTEXT"
|
||||||
|
}
|
||||||
|
return "TINYBLOB"
|
||||||
|
case fieldTypeVarChar:
|
||||||
|
if mf.charSet == collations[binaryCollation] {
|
||||||
|
return "VARBINARY"
|
||||||
|
}
|
||||||
|
return "VARCHAR"
|
||||||
|
case fieldTypeVarString:
|
||||||
|
if mf.charSet == collations[binaryCollation] {
|
||||||
|
return "VARBINARY"
|
||||||
|
}
|
||||||
|
return "VARCHAR"
|
||||||
|
case fieldTypeYear:
|
||||||
|
return "YEAR"
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
scanTypeFloat32 = reflect.TypeOf(float32(0))
|
||||||
|
scanTypeFloat64 = reflect.TypeOf(float64(0))
|
||||||
|
scanTypeInt8 = reflect.TypeOf(int8(0))
|
||||||
|
scanTypeInt16 = reflect.TypeOf(int16(0))
|
||||||
|
scanTypeInt32 = reflect.TypeOf(int32(0))
|
||||||
|
scanTypeInt64 = reflect.TypeOf(int64(0))
|
||||||
|
scanTypeNullFloat = reflect.TypeOf(sql.NullFloat64{})
|
||||||
|
scanTypeNullInt = reflect.TypeOf(sql.NullInt64{})
|
||||||
|
scanTypeNullTime = reflect.TypeOf(NullTime{})
|
||||||
|
scanTypeUint8 = reflect.TypeOf(uint8(0))
|
||||||
|
scanTypeUint16 = reflect.TypeOf(uint16(0))
|
||||||
|
scanTypeUint32 = reflect.TypeOf(uint32(0))
|
||||||
|
scanTypeUint64 = reflect.TypeOf(uint64(0))
|
||||||
|
scanTypeRawBytes = reflect.TypeOf(sql.RawBytes{})
|
||||||
|
scanTypeUnknown = reflect.TypeOf(new(interface{}))
|
||||||
|
)
|
||||||
|
|
||||||
|
type mysqlField struct {
|
||||||
|
tableName string
|
||||||
|
name string
|
||||||
|
length uint32
|
||||||
|
flags fieldFlag
|
||||||
|
fieldType fieldType
|
||||||
|
decimals byte
|
||||||
|
charSet uint8
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mf *mysqlField) scanType() reflect.Type {
|
||||||
|
switch mf.fieldType {
|
||||||
|
case fieldTypeTiny:
|
||||||
|
if mf.flags&flagNotNULL != 0 {
|
||||||
|
if mf.flags&flagUnsigned != 0 {
|
||||||
|
return scanTypeUint8
|
||||||
|
}
|
||||||
|
return scanTypeInt8
|
||||||
|
}
|
||||||
|
return scanTypeNullInt
|
||||||
|
|
||||||
|
case fieldTypeShort, fieldTypeYear:
|
||||||
|
if mf.flags&flagNotNULL != 0 {
|
||||||
|
if mf.flags&flagUnsigned != 0 {
|
||||||
|
return scanTypeUint16
|
||||||
|
}
|
||||||
|
return scanTypeInt16
|
||||||
|
}
|
||||||
|
return scanTypeNullInt
|
||||||
|
|
||||||
|
case fieldTypeInt24, fieldTypeLong:
|
||||||
|
if mf.flags&flagNotNULL != 0 {
|
||||||
|
if mf.flags&flagUnsigned != 0 {
|
||||||
|
return scanTypeUint32
|
||||||
|
}
|
||||||
|
return scanTypeInt32
|
||||||
|
}
|
||||||
|
return scanTypeNullInt
|
||||||
|
|
||||||
|
case fieldTypeLongLong:
|
||||||
|
if mf.flags&flagNotNULL != 0 {
|
||||||
|
if mf.flags&flagUnsigned != 0 {
|
||||||
|
return scanTypeUint64
|
||||||
|
}
|
||||||
|
return scanTypeInt64
|
||||||
|
}
|
||||||
|
return scanTypeNullInt
|
||||||
|
|
||||||
|
case fieldTypeFloat:
|
||||||
|
if mf.flags&flagNotNULL != 0 {
|
||||||
|
return scanTypeFloat32
|
||||||
|
}
|
||||||
|
return scanTypeNullFloat
|
||||||
|
|
||||||
|
case fieldTypeDouble:
|
||||||
|
if mf.flags&flagNotNULL != 0 {
|
||||||
|
return scanTypeFloat64
|
||||||
|
}
|
||||||
|
return scanTypeNullFloat
|
||||||
|
|
||||||
|
case fieldTypeDecimal, fieldTypeNewDecimal, fieldTypeVarChar,
|
||||||
|
fieldTypeBit, fieldTypeEnum, fieldTypeSet, fieldTypeTinyBLOB,
|
||||||
|
fieldTypeMediumBLOB, fieldTypeLongBLOB, fieldTypeBLOB,
|
||||||
|
fieldTypeVarString, fieldTypeString, fieldTypeGeometry, fieldTypeJSON,
|
||||||
|
fieldTypeTime:
|
||||||
|
return scanTypeRawBytes
|
||||||
|
|
||||||
|
case fieldTypeDate, fieldTypeNewDate,
|
||||||
|
fieldTypeTimestamp, fieldTypeDateTime:
|
||||||
|
// NullTime is always returned for more consistent behavior as it can
|
||||||
|
// handle both cases of parseTime regardless if the field is nullable.
|
||||||
|
return scanTypeNullTime
|
||||||
|
|
||||||
|
default:
|
||||||
|
return scanTypeUnknown
|
||||||
|
}
|
||||||
|
}
|
|
@ -147,7 +147,8 @@ func (mc *mysqlConn) handleInFileRequest(name string) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// send content packets
|
// send content packets
|
||||||
if err == nil {
|
// if packetSize == 0, the Reader contains no data
|
||||||
|
if err == nil && packetSize > 0 {
|
||||||
data := make([]byte, 4+packetSize)
|
data := make([]byte, 4+packetSize)
|
||||||
var n int
|
var n int
|
||||||
for err == nil {
|
for err == nil {
|
||||||
|
@ -173,8 +174,7 @@ func (mc *mysqlConn) handleInFileRequest(name string) (err error) {
|
||||||
|
|
||||||
// read OK packet
|
// read OK packet
|
||||||
if err == nil {
|
if err == nil {
|
||||||
_, err = mc.readResultOK()
|
return mc.readResultOK()
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mc.readPacket()
|
mc.readPacket()
|
||||||
|
|
|
@ -30,9 +30,12 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
|
||||||
// read packet header
|
// read packet header
|
||||||
data, err := mc.buf.readNext(4)
|
data, err := mc.buf.readNext(4)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if cerr := mc.canceled.Value(); cerr != nil {
|
||||||
|
return nil, cerr
|
||||||
|
}
|
||||||
errLog.Print(err)
|
errLog.Print(err)
|
||||||
mc.Close()
|
mc.Close()
|
||||||
return nil, driver.ErrBadConn
|
return nil, ErrInvalidConn
|
||||||
}
|
}
|
||||||
|
|
||||||
// packet length [24 bit]
|
// packet length [24 bit]
|
||||||
|
@ -54,7 +57,7 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
|
||||||
if prevData == nil {
|
if prevData == nil {
|
||||||
errLog.Print(ErrMalformPkt)
|
errLog.Print(ErrMalformPkt)
|
||||||
mc.Close()
|
mc.Close()
|
||||||
return nil, driver.ErrBadConn
|
return nil, ErrInvalidConn
|
||||||
}
|
}
|
||||||
|
|
||||||
return prevData, nil
|
return prevData, nil
|
||||||
|
@ -63,9 +66,12 @@ func (mc *mysqlConn) readPacket() ([]byte, error) {
|
||||||
// read packet body [pktLen bytes]
|
// read packet body [pktLen bytes]
|
||||||
data, err = mc.buf.readNext(pktLen)
|
data, err = mc.buf.readNext(pktLen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if cerr := mc.canceled.Value(); cerr != nil {
|
||||||
|
return nil, cerr
|
||||||
|
}
|
||||||
errLog.Print(err)
|
errLog.Print(err)
|
||||||
mc.Close()
|
mc.Close()
|
||||||
return nil, driver.ErrBadConn
|
return nil, ErrInvalidConn
|
||||||
}
|
}
|
||||||
|
|
||||||
// return data if this was the last packet
|
// return data if this was the last packet
|
||||||
|
@ -125,33 +131,47 @@ func (mc *mysqlConn) writePacket(data []byte) error {
|
||||||
|
|
||||||
// Handle error
|
// Handle error
|
||||||
if err == nil { // n != len(data)
|
if err == nil { // n != len(data)
|
||||||
|
mc.cleanup()
|
||||||
errLog.Print(ErrMalformPkt)
|
errLog.Print(ErrMalformPkt)
|
||||||
} else {
|
} else {
|
||||||
|
if cerr := mc.canceled.Value(); cerr != nil {
|
||||||
|
return cerr
|
||||||
|
}
|
||||||
|
if n == 0 && pktLen == len(data)-4 {
|
||||||
|
// only for the first loop iteration when nothing was written yet
|
||||||
|
return errBadConnNoWrite
|
||||||
|
}
|
||||||
|
mc.cleanup()
|
||||||
errLog.Print(err)
|
errLog.Print(err)
|
||||||
}
|
}
|
||||||
return driver.ErrBadConn
|
return ErrInvalidConn
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************************************************************
|
/******************************************************************************
|
||||||
* Initialisation Process *
|
* Initialization Process *
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
// Handshake Initialization Packet
|
// Handshake Initialization Packet
|
||||||
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::Handshake
|
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::Handshake
|
||||||
func (mc *mysqlConn) readInitPacket() ([]byte, error) {
|
func (mc *mysqlConn) readHandshakePacket() (data []byte, plugin string, err error) {
|
||||||
data, err := mc.readPacket()
|
data, err = mc.readPacket()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
// for init we can rewrite this to ErrBadConn for sql.Driver to retry, since
|
||||||
|
// in connection initialization we don't risk retrying non-idempotent actions.
|
||||||
|
if err == ErrInvalidConn {
|
||||||
|
return nil, "", driver.ErrBadConn
|
||||||
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if data[0] == iERR {
|
if data[0] == iERR {
|
||||||
return nil, mc.handleErrorPacket(data)
|
return nil, "", mc.handleErrorPacket(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// protocol version [1 byte]
|
// protocol version [1 byte]
|
||||||
if data[0] < minProtocolVersion {
|
if data[0] < minProtocolVersion {
|
||||||
return nil, fmt.Errorf(
|
return nil, "", fmt.Errorf(
|
||||||
"unsupported protocol version %d. Version %d or higher is required",
|
"unsupported protocol version %d. Version %d or higher is required",
|
||||||
data[0],
|
data[0],
|
||||||
minProtocolVersion,
|
minProtocolVersion,
|
||||||
|
@ -163,7 +183,7 @@ func (mc *mysqlConn) readInitPacket() ([]byte, error) {
|
||||||
pos := 1 + bytes.IndexByte(data[1:], 0x00) + 1 + 4
|
pos := 1 + bytes.IndexByte(data[1:], 0x00) + 1 + 4
|
||||||
|
|
||||||
// first part of the password cipher [8 bytes]
|
// first part of the password cipher [8 bytes]
|
||||||
cipher := data[pos : pos+8]
|
authData := data[pos : pos+8]
|
||||||
|
|
||||||
// (filler) always 0x00 [1 byte]
|
// (filler) always 0x00 [1 byte]
|
||||||
pos += 8 + 1
|
pos += 8 + 1
|
||||||
|
@ -171,10 +191,10 @@ func (mc *mysqlConn) readInitPacket() ([]byte, error) {
|
||||||
// capability flags (lower 2 bytes) [2 bytes]
|
// capability flags (lower 2 bytes) [2 bytes]
|
||||||
mc.flags = clientFlag(binary.LittleEndian.Uint16(data[pos : pos+2]))
|
mc.flags = clientFlag(binary.LittleEndian.Uint16(data[pos : pos+2]))
|
||||||
if mc.flags&clientProtocol41 == 0 {
|
if mc.flags&clientProtocol41 == 0 {
|
||||||
return nil, ErrOldProtocol
|
return nil, "", ErrOldProtocol
|
||||||
}
|
}
|
||||||
if mc.flags&clientSSL == 0 && mc.cfg.tls != nil {
|
if mc.flags&clientSSL == 0 && mc.cfg.tls != nil {
|
||||||
return nil, ErrNoTLS
|
return nil, "", ErrNoTLS
|
||||||
}
|
}
|
||||||
pos += 2
|
pos += 2
|
||||||
|
|
||||||
|
@ -198,32 +218,32 @@ func (mc *mysqlConn) readInitPacket() ([]byte, error) {
|
||||||
//
|
//
|
||||||
// The official Python library uses the fixed length 12
|
// The official Python library uses the fixed length 12
|
||||||
// which seems to work but technically could have a hidden bug.
|
// which seems to work but technically could have a hidden bug.
|
||||||
cipher = append(cipher, data[pos:pos+12]...)
|
authData = append(authData, data[pos:pos+12]...)
|
||||||
|
pos += 13
|
||||||
|
|
||||||
// TODO: Verify string termination
|
|
||||||
// EOF if version (>= 5.5.7 and < 5.5.10) or (>= 5.6.0 and < 5.6.2)
|
// EOF if version (>= 5.5.7 and < 5.5.10) or (>= 5.6.0 and < 5.6.2)
|
||||||
// \NUL otherwise
|
// \NUL otherwise
|
||||||
//
|
if end := bytes.IndexByte(data[pos:], 0x00); end != -1 {
|
||||||
//if data[len(data)-1] == 0 {
|
plugin = string(data[pos : pos+end])
|
||||||
// return
|
} else {
|
||||||
//}
|
plugin = string(data[pos:])
|
||||||
//return ErrMalformPkt
|
}
|
||||||
|
|
||||||
// make a memory safe copy of the cipher slice
|
// make a memory safe copy of the cipher slice
|
||||||
var b [20]byte
|
var b [20]byte
|
||||||
copy(b[:], cipher)
|
copy(b[:], authData)
|
||||||
return b[:], nil
|
return b[:], plugin, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// make a memory safe copy of the cipher slice
|
// make a memory safe copy of the cipher slice
|
||||||
var b [8]byte
|
var b [8]byte
|
||||||
copy(b[:], cipher)
|
copy(b[:], authData)
|
||||||
return b[:], nil
|
return b[:], plugin, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client Authentication Packet
|
// Client Authentication Packet
|
||||||
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::HandshakeResponse
|
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::HandshakeResponse
|
||||||
func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {
|
func (mc *mysqlConn) writeHandshakeResponsePacket(authResp []byte, plugin string) error {
|
||||||
// Adjust client flags based on server support
|
// Adjust client flags based on server support
|
||||||
clientFlags := clientProtocol41 |
|
clientFlags := clientProtocol41 |
|
||||||
clientSecureConn |
|
clientSecureConn |
|
||||||
|
@ -247,10 +267,17 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {
|
||||||
clientFlags |= clientMultiStatements
|
clientFlags |= clientMultiStatements
|
||||||
}
|
}
|
||||||
|
|
||||||
// User Password
|
// encode length of the auth plugin data
|
||||||
scrambleBuff := scramblePassword(cipher, []byte(mc.cfg.Passwd))
|
var authRespLEIBuf [9]byte
|
||||||
|
authRespLen := len(authResp)
|
||||||
|
authRespLEI := appendLengthEncodedInteger(authRespLEIBuf[:0], uint64(authRespLen))
|
||||||
|
if len(authRespLEI) > 1 {
|
||||||
|
// if the length can not be written in 1 byte, it must be written as a
|
||||||
|
// length encoded integer
|
||||||
|
clientFlags |= clientPluginAuthLenEncClientData
|
||||||
|
}
|
||||||
|
|
||||||
pktLen := 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1 + 1 + len(scrambleBuff) + 21 + 1
|
pktLen := 4 + 4 + 1 + 23 + len(mc.cfg.User) + 1 + len(authRespLEI) + len(authResp) + 21 + 1
|
||||||
|
|
||||||
// To specify a db name
|
// To specify a db name
|
||||||
if n := len(mc.cfg.DBName); n > 0 {
|
if n := len(mc.cfg.DBName); n > 0 {
|
||||||
|
@ -261,9 +288,9 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {
|
||||||
// Calculate packet length and get buffer with that size
|
// Calculate packet length and get buffer with that size
|
||||||
data := mc.buf.takeSmallBuffer(pktLen + 4)
|
data := mc.buf.takeSmallBuffer(pktLen + 4)
|
||||||
if data == nil {
|
if data == nil {
|
||||||
// can not take the buffer. Something must be wrong with the connection
|
// cannot take the buffer. Something must be wrong with the connection
|
||||||
errLog.Print(ErrBusyBuffer)
|
errLog.Print(ErrBusyBuffer)
|
||||||
return driver.ErrBadConn
|
return errBadConnNoWrite
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClientFlags [32 bit]
|
// ClientFlags [32 bit]
|
||||||
|
@ -318,9 +345,9 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {
|
||||||
data[pos] = 0x00
|
data[pos] = 0x00
|
||||||
pos++
|
pos++
|
||||||
|
|
||||||
// ScrambleBuffer [length encoded integer]
|
// Auth Data [length encoded integer]
|
||||||
data[pos] = byte(len(scrambleBuff))
|
pos += copy(data[pos:], authRespLEI)
|
||||||
pos += 1 + copy(data[pos+1:], scrambleBuff)
|
pos += copy(data[pos:], authResp)
|
||||||
|
|
||||||
// Databasename [null terminated string]
|
// Databasename [null terminated string]
|
||||||
if len(mc.cfg.DBName) > 0 {
|
if len(mc.cfg.DBName) > 0 {
|
||||||
|
@ -329,72 +356,26 @@ func (mc *mysqlConn) writeAuthPacket(cipher []byte) error {
|
||||||
pos++
|
pos++
|
||||||
}
|
}
|
||||||
|
|
||||||
// Assume native client during response
|
pos += copy(data[pos:], plugin)
|
||||||
pos += copy(data[pos:], "mysql_native_password")
|
|
||||||
data[pos] = 0x00
|
data[pos] = 0x00
|
||||||
|
pos++
|
||||||
|
|
||||||
// Send Auth packet
|
// Send Auth packet
|
||||||
return mc.writePacket(data)
|
return mc.writePacket(data[:pos])
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client old authentication packet
|
|
||||||
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse
|
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse
|
||||||
func (mc *mysqlConn) writeOldAuthPacket(cipher []byte) error {
|
func (mc *mysqlConn) writeAuthSwitchPacket(authData []byte) error {
|
||||||
// User password
|
pktLen := 4 + len(authData)
|
||||||
scrambleBuff := scrambleOldPassword(cipher, []byte(mc.cfg.Passwd))
|
data := mc.buf.takeSmallBuffer(pktLen)
|
||||||
|
|
||||||
// Calculate the packet length and add a tailing 0
|
|
||||||
pktLen := len(scrambleBuff) + 1
|
|
||||||
data := mc.buf.takeSmallBuffer(4 + pktLen)
|
|
||||||
if data == nil {
|
if data == nil {
|
||||||
// can not take the buffer. Something must be wrong with the connection
|
// cannot take the buffer. Something must be wrong with the connection
|
||||||
errLog.Print(ErrBusyBuffer)
|
errLog.Print(ErrBusyBuffer)
|
||||||
return driver.ErrBadConn
|
return errBadConnNoWrite
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the scrambled password [null terminated string]
|
// Add the auth data [EOF]
|
||||||
copy(data[4:], scrambleBuff)
|
copy(data[4:], authData)
|
||||||
data[4+pktLen-1] = 0x00
|
|
||||||
|
|
||||||
return mc.writePacket(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Client clear text authentication packet
|
|
||||||
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse
|
|
||||||
func (mc *mysqlConn) writeClearAuthPacket() error {
|
|
||||||
// Calculate the packet length and add a tailing 0
|
|
||||||
pktLen := len(mc.cfg.Passwd) + 1
|
|
||||||
data := mc.buf.takeSmallBuffer(4 + pktLen)
|
|
||||||
if data == nil {
|
|
||||||
// can not take the buffer. Something must be wrong with the connection
|
|
||||||
errLog.Print(ErrBusyBuffer)
|
|
||||||
return driver.ErrBadConn
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the clear password [null terminated string]
|
|
||||||
copy(data[4:], mc.cfg.Passwd)
|
|
||||||
data[4+pktLen-1] = 0x00
|
|
||||||
|
|
||||||
return mc.writePacket(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Native password authentication method
|
|
||||||
// http://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::AuthSwitchResponse
|
|
||||||
func (mc *mysqlConn) writeNativeAuthPacket(cipher []byte) error {
|
|
||||||
scrambleBuff := scramblePassword(cipher, []byte(mc.cfg.Passwd))
|
|
||||||
|
|
||||||
// Calculate the packet length and add a tailing 0
|
|
||||||
pktLen := len(scrambleBuff)
|
|
||||||
data := mc.buf.takeSmallBuffer(4 + pktLen)
|
|
||||||
if data == nil {
|
|
||||||
// can not take the buffer. Something must be wrong with the connection
|
|
||||||
errLog.Print(ErrBusyBuffer)
|
|
||||||
return driver.ErrBadConn
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add the scramble
|
|
||||||
copy(data[4:], scrambleBuff)
|
|
||||||
|
|
||||||
return mc.writePacket(data)
|
return mc.writePacket(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -408,9 +389,9 @@ func (mc *mysqlConn) writeCommandPacket(command byte) error {
|
||||||
|
|
||||||
data := mc.buf.takeSmallBuffer(4 + 1)
|
data := mc.buf.takeSmallBuffer(4 + 1)
|
||||||
if data == nil {
|
if data == nil {
|
||||||
// can not take the buffer. Something must be wrong with the connection
|
// cannot take the buffer. Something must be wrong with the connection
|
||||||
errLog.Print(ErrBusyBuffer)
|
errLog.Print(ErrBusyBuffer)
|
||||||
return driver.ErrBadConn
|
return errBadConnNoWrite
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add command byte
|
// Add command byte
|
||||||
|
@ -427,9 +408,9 @@ func (mc *mysqlConn) writeCommandPacketStr(command byte, arg string) error {
|
||||||
pktLen := 1 + len(arg)
|
pktLen := 1 + len(arg)
|
||||||
data := mc.buf.takeBuffer(pktLen + 4)
|
data := mc.buf.takeBuffer(pktLen + 4)
|
||||||
if data == nil {
|
if data == nil {
|
||||||
// can not take the buffer. Something must be wrong with the connection
|
// cannot take the buffer. Something must be wrong with the connection
|
||||||
errLog.Print(ErrBusyBuffer)
|
errLog.Print(ErrBusyBuffer)
|
||||||
return driver.ErrBadConn
|
return errBadConnNoWrite
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add command byte
|
// Add command byte
|
||||||
|
@ -448,9 +429,9 @@ func (mc *mysqlConn) writeCommandPacketUint32(command byte, arg uint32) error {
|
||||||
|
|
||||||
data := mc.buf.takeSmallBuffer(4 + 1 + 4)
|
data := mc.buf.takeSmallBuffer(4 + 1 + 4)
|
||||||
if data == nil {
|
if data == nil {
|
||||||
// can not take the buffer. Something must be wrong with the connection
|
// cannot take the buffer. Something must be wrong with the connection
|
||||||
errLog.Print(ErrBusyBuffer)
|
errLog.Print(ErrBusyBuffer)
|
||||||
return driver.ErrBadConn
|
return errBadConnNoWrite
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add command byte
|
// Add command byte
|
||||||
|
@ -470,44 +451,50 @@ func (mc *mysqlConn) writeCommandPacketUint32(command byte, arg uint32) error {
|
||||||
* Result Packets *
|
* Result Packets *
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
|
||||||
// Returns error if Packet is not an 'Result OK'-Packet
|
func (mc *mysqlConn) readAuthResult() ([]byte, string, error) {
|
||||||
func (mc *mysqlConn) readResultOK() ([]byte, error) {
|
|
||||||
data, err := mc.readPacket()
|
data, err := mc.readPacket()
|
||||||
if err == nil {
|
if err != nil {
|
||||||
// packet indicator
|
return nil, "", err
|
||||||
switch data[0] {
|
|
||||||
|
|
||||||
case iOK:
|
|
||||||
return nil, mc.handleOkPacket(data)
|
|
||||||
|
|
||||||
case iEOF:
|
|
||||||
if len(data) > 1 {
|
|
||||||
pluginEndIndex := bytes.IndexByte(data, 0x00)
|
|
||||||
plugin := string(data[1:pluginEndIndex])
|
|
||||||
cipher := data[pluginEndIndex+1 : len(data)-1]
|
|
||||||
|
|
||||||
if plugin == "mysql_old_password" {
|
|
||||||
// using old_passwords
|
|
||||||
return cipher, ErrOldPassword
|
|
||||||
} else if plugin == "mysql_clear_password" {
|
|
||||||
// using clear text password
|
|
||||||
return cipher, ErrCleartextPassword
|
|
||||||
} else if plugin == "mysql_native_password" {
|
|
||||||
// using mysql default authentication method
|
|
||||||
return cipher, ErrNativePassword
|
|
||||||
} else {
|
|
||||||
return cipher, ErrUnknownPlugin
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::OldAuthSwitchRequest
|
|
||||||
return nil, ErrOldPassword
|
|
||||||
}
|
|
||||||
|
|
||||||
default: // Error otherwise
|
|
||||||
return nil, mc.handleErrorPacket(data)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return nil, err
|
|
||||||
|
// packet indicator
|
||||||
|
switch data[0] {
|
||||||
|
|
||||||
|
case iOK:
|
||||||
|
return nil, "", mc.handleOkPacket(data)
|
||||||
|
|
||||||
|
case iAuthMoreData:
|
||||||
|
return data[1:], "", err
|
||||||
|
|
||||||
|
case iEOF:
|
||||||
|
if len(data) == 1 {
|
||||||
|
// https://dev.mysql.com/doc/internals/en/connection-phase-packets.html#packet-Protocol::OldAuthSwitchRequest
|
||||||
|
return nil, "mysql_old_password", nil
|
||||||
|
}
|
||||||
|
pluginEndIndex := bytes.IndexByte(data, 0x00)
|
||||||
|
if pluginEndIndex < 0 {
|
||||||
|
return nil, "", ErrMalformPkt
|
||||||
|
}
|
||||||
|
plugin := string(data[1:pluginEndIndex])
|
||||||
|
authData := data[pluginEndIndex+1:]
|
||||||
|
return authData, plugin, nil
|
||||||
|
|
||||||
|
default: // Error otherwise
|
||||||
|
return nil, "", mc.handleErrorPacket(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns error if Packet is not an 'Result OK'-Packet
|
||||||
|
func (mc *mysqlConn) readResultOK() error {
|
||||||
|
data, err := mc.readPacket()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if data[0] == iOK {
|
||||||
|
return mc.handleOkPacket(data)
|
||||||
|
}
|
||||||
|
return mc.handleErrorPacket(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Result Set Header Packet
|
// Result Set Header Packet
|
||||||
|
@ -550,6 +537,22 @@ func (mc *mysqlConn) handleErrorPacket(data []byte) error {
|
||||||
// Error Number [16 bit uint]
|
// Error Number [16 bit uint]
|
||||||
errno := binary.LittleEndian.Uint16(data[1:3])
|
errno := binary.LittleEndian.Uint16(data[1:3])
|
||||||
|
|
||||||
|
// 1792: ER_CANT_EXECUTE_IN_READ_ONLY_TRANSACTION
|
||||||
|
// 1290: ER_OPTION_PREVENTS_STATEMENT (returned by Aurora during failover)
|
||||||
|
if (errno == 1792 || errno == 1290) && mc.cfg.RejectReadOnly {
|
||||||
|
// Oops; we are connected to a read-only connection, and won't be able
|
||||||
|
// to issue any write statements. Since RejectReadOnly is configured,
|
||||||
|
// we throw away this connection hoping this one would have write
|
||||||
|
// permission. This is specifically for a possible race condition
|
||||||
|
// during failover (e.g. on AWS Aurora). See README.md for more.
|
||||||
|
//
|
||||||
|
// We explicitly close the connection before returning
|
||||||
|
// driver.ErrBadConn to ensure that `database/sql` purges this
|
||||||
|
// connection and initiates a new one for next statement next time.
|
||||||
|
mc.Close()
|
||||||
|
return driver.ErrBadConn
|
||||||
|
}
|
||||||
|
|
||||||
pos := 3
|
pos := 3
|
||||||
|
|
||||||
// SQL State [optional: # + 5bytes string]
|
// SQL State [optional: # + 5bytes string]
|
||||||
|
@ -584,19 +587,12 @@ func (mc *mysqlConn) handleOkPacket(data []byte) error {
|
||||||
|
|
||||||
// server_status [2 bytes]
|
// server_status [2 bytes]
|
||||||
mc.status = readStatus(data[1+n+m : 1+n+m+2])
|
mc.status = readStatus(data[1+n+m : 1+n+m+2])
|
||||||
if err := mc.discardResults(); err != nil {
|
if mc.status&statusMoreResultsExists != 0 {
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// warning count [2 bytes]
|
|
||||||
if !mc.strict {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
pos := 1 + n + m + 2
|
// warning count [2 bytes]
|
||||||
if binary.LittleEndian.Uint16(data[pos:pos+2]) > 0 {
|
|
||||||
return mc.getWarnings()
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -668,14 +664,21 @@ func (mc *mysqlConn) readColumns(count int) ([]mysqlField, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
pos += n
|
||||||
|
|
||||||
// Filler [uint8]
|
// Filler [uint8]
|
||||||
|
pos++
|
||||||
|
|
||||||
// Charset [charset, collation uint8]
|
// Charset [charset, collation uint8]
|
||||||
|
columns[i].charSet = data[pos]
|
||||||
|
pos += 2
|
||||||
|
|
||||||
// Length [uint32]
|
// Length [uint32]
|
||||||
pos += n + 1 + 2 + 4
|
columns[i].length = binary.LittleEndian.Uint32(data[pos : pos+4])
|
||||||
|
pos += 4
|
||||||
|
|
||||||
// Field type [uint8]
|
// Field type [uint8]
|
||||||
columns[i].fieldType = data[pos]
|
columns[i].fieldType = fieldType(data[pos])
|
||||||
pos++
|
pos++
|
||||||
|
|
||||||
// Flags [uint16]
|
// Flags [uint16]
|
||||||
|
@ -698,6 +701,10 @@ func (mc *mysqlConn) readColumns(count int) ([]mysqlField, error) {
|
||||||
func (rows *textRows) readRow(dest []driver.Value) error {
|
func (rows *textRows) readRow(dest []driver.Value) error {
|
||||||
mc := rows.mc
|
mc := rows.mc
|
||||||
|
|
||||||
|
if rows.rs.done {
|
||||||
|
return io.EOF
|
||||||
|
}
|
||||||
|
|
||||||
data, err := mc.readPacket()
|
data, err := mc.readPacket()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -707,15 +714,11 @@ func (rows *textRows) readRow(dest []driver.Value) error {
|
||||||
if data[0] == iEOF && len(data) == 5 {
|
if data[0] == iEOF && len(data) == 5 {
|
||||||
// server_status [2 bytes]
|
// server_status [2 bytes]
|
||||||
rows.mc.status = readStatus(data[3:])
|
rows.mc.status = readStatus(data[3:])
|
||||||
err = rows.mc.discardResults()
|
rows.rs.done = true
|
||||||
if err == nil {
|
if !rows.HasNextResultSet() {
|
||||||
err = io.EOF
|
rows.mc = nil
|
||||||
} else {
|
|
||||||
// connection unusable
|
|
||||||
rows.mc.Close()
|
|
||||||
}
|
}
|
||||||
rows.mc = nil
|
return io.EOF
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
if data[0] == iERR {
|
if data[0] == iERR {
|
||||||
rows.mc = nil
|
rows.mc = nil
|
||||||
|
@ -736,7 +739,7 @@ func (rows *textRows) readRow(dest []driver.Value) error {
|
||||||
if !mc.parseTime {
|
if !mc.parseTime {
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
switch rows.columns[i].fieldType {
|
switch rows.rs.columns[i].fieldType {
|
||||||
case fieldTypeTimestamp, fieldTypeDateTime,
|
case fieldTypeTimestamp, fieldTypeDateTime,
|
||||||
fieldTypeDate, fieldTypeNewDate:
|
fieldTypeDate, fieldTypeNewDate:
|
||||||
dest[i], err = parseDateTime(
|
dest[i], err = parseDateTime(
|
||||||
|
@ -808,14 +811,7 @@ func (stmt *mysqlStmt) readPrepareResultPacket() (uint16, error) {
|
||||||
// Reserved [8 bit]
|
// Reserved [8 bit]
|
||||||
|
|
||||||
// Warning count [16 bit uint]
|
// Warning count [16 bit uint]
|
||||||
if !stmt.mc.strict {
|
|
||||||
return columnCount, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for warnings count > 0, only available in MySQL > 4.1
|
|
||||||
if len(data) >= 12 && binary.LittleEndian.Uint16(data[10:12]) > 0 {
|
|
||||||
return columnCount, stmt.mc.getWarnings()
|
|
||||||
}
|
|
||||||
return columnCount, nil
|
return columnCount, nil
|
||||||
}
|
}
|
||||||
return 0, err
|
return 0, err
|
||||||
|
@ -832,7 +828,7 @@ func (stmt *mysqlStmt) writeCommandLongData(paramID int, arg []byte) error {
|
||||||
// 2 bytes paramID
|
// 2 bytes paramID
|
||||||
const dataOffset = 1 + 4 + 2
|
const dataOffset = 1 + 4 + 2
|
||||||
|
|
||||||
// Can not use the write buffer since
|
// Cannot use the write buffer since
|
||||||
// a) the buffer is too small
|
// a) the buffer is too small
|
||||||
// b) it is in use
|
// b) it is in use
|
||||||
data := make([]byte, 4+1+4+2+len(arg))
|
data := make([]byte, 4+1+4+2+len(arg))
|
||||||
|
@ -887,6 +883,12 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
|
||||||
const minPktLen = 4 + 1 + 4 + 1 + 4
|
const minPktLen = 4 + 1 + 4 + 1 + 4
|
||||||
mc := stmt.mc
|
mc := stmt.mc
|
||||||
|
|
||||||
|
// Determine threshould dynamically to avoid packet size shortage.
|
||||||
|
longDataSize := mc.maxAllowedPacket / (stmt.paramCount + 1)
|
||||||
|
if longDataSize < 64 {
|
||||||
|
longDataSize = 64
|
||||||
|
}
|
||||||
|
|
||||||
// Reset packet-sequence
|
// Reset packet-sequence
|
||||||
mc.sequence = 0
|
mc.sequence = 0
|
||||||
|
|
||||||
|
@ -898,9 +900,9 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
|
||||||
data = mc.buf.takeCompleteBuffer()
|
data = mc.buf.takeCompleteBuffer()
|
||||||
}
|
}
|
||||||
if data == nil {
|
if data == nil {
|
||||||
// can not take the buffer. Something must be wrong with the connection
|
// cannot take the buffer. Something must be wrong with the connection
|
||||||
errLog.Print(ErrBusyBuffer)
|
errLog.Print(ErrBusyBuffer)
|
||||||
return driver.ErrBadConn
|
return errBadConnNoWrite
|
||||||
}
|
}
|
||||||
|
|
||||||
// command [1 byte]
|
// command [1 byte]
|
||||||
|
@ -959,7 +961,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
|
||||||
// build NULL-bitmap
|
// build NULL-bitmap
|
||||||
if arg == nil {
|
if arg == nil {
|
||||||
nullMask[i/8] |= 1 << (uint(i) & 7)
|
nullMask[i/8] |= 1 << (uint(i) & 7)
|
||||||
paramTypes[i+i] = fieldTypeNULL
|
paramTypes[i+i] = byte(fieldTypeNULL)
|
||||||
paramTypes[i+i+1] = 0x00
|
paramTypes[i+i+1] = 0x00
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -967,7 +969,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
|
||||||
// cache types and values
|
// cache types and values
|
||||||
switch v := arg.(type) {
|
switch v := arg.(type) {
|
||||||
case int64:
|
case int64:
|
||||||
paramTypes[i+i] = fieldTypeLongLong
|
paramTypes[i+i] = byte(fieldTypeLongLong)
|
||||||
paramTypes[i+i+1] = 0x00
|
paramTypes[i+i+1] = 0x00
|
||||||
|
|
||||||
if cap(paramValues)-len(paramValues)-8 >= 0 {
|
if cap(paramValues)-len(paramValues)-8 >= 0 {
|
||||||
|
@ -983,7 +985,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
case float64:
|
case float64:
|
||||||
paramTypes[i+i] = fieldTypeDouble
|
paramTypes[i+i] = byte(fieldTypeDouble)
|
||||||
paramTypes[i+i+1] = 0x00
|
paramTypes[i+i+1] = 0x00
|
||||||
|
|
||||||
if cap(paramValues)-len(paramValues)-8 >= 0 {
|
if cap(paramValues)-len(paramValues)-8 >= 0 {
|
||||||
|
@ -999,7 +1001,7 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
case bool:
|
case bool:
|
||||||
paramTypes[i+i] = fieldTypeTiny
|
paramTypes[i+i] = byte(fieldTypeTiny)
|
||||||
paramTypes[i+i+1] = 0x00
|
paramTypes[i+i+1] = 0x00
|
||||||
|
|
||||||
if v {
|
if v {
|
||||||
|
@ -1011,10 +1013,10 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
|
||||||
case []byte:
|
case []byte:
|
||||||
// Common case (non-nil value) first
|
// Common case (non-nil value) first
|
||||||
if v != nil {
|
if v != nil {
|
||||||
paramTypes[i+i] = fieldTypeString
|
paramTypes[i+i] = byte(fieldTypeString)
|
||||||
paramTypes[i+i+1] = 0x00
|
paramTypes[i+i+1] = 0x00
|
||||||
|
|
||||||
if len(v) < mc.maxAllowedPacket-pos-len(paramValues)-(len(args)-(i+1))*64 {
|
if len(v) < longDataSize {
|
||||||
paramValues = appendLengthEncodedInteger(paramValues,
|
paramValues = appendLengthEncodedInteger(paramValues,
|
||||||
uint64(len(v)),
|
uint64(len(v)),
|
||||||
)
|
)
|
||||||
|
@ -1029,14 +1031,14 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
|
||||||
|
|
||||||
// Handle []byte(nil) as a NULL value
|
// Handle []byte(nil) as a NULL value
|
||||||
nullMask[i/8] |= 1 << (uint(i) & 7)
|
nullMask[i/8] |= 1 << (uint(i) & 7)
|
||||||
paramTypes[i+i] = fieldTypeNULL
|
paramTypes[i+i] = byte(fieldTypeNULL)
|
||||||
paramTypes[i+i+1] = 0x00
|
paramTypes[i+i+1] = 0x00
|
||||||
|
|
||||||
case string:
|
case string:
|
||||||
paramTypes[i+i] = fieldTypeString
|
paramTypes[i+i] = byte(fieldTypeString)
|
||||||
paramTypes[i+i+1] = 0x00
|
paramTypes[i+i+1] = 0x00
|
||||||
|
|
||||||
if len(v) < mc.maxAllowedPacket-pos-len(paramValues)-(len(args)-(i+1))*64 {
|
if len(v) < longDataSize {
|
||||||
paramValues = appendLengthEncodedInteger(paramValues,
|
paramValues = appendLengthEncodedInteger(paramValues,
|
||||||
uint64(len(v)),
|
uint64(len(v)),
|
||||||
)
|
)
|
||||||
|
@ -1048,23 +1050,25 @@ func (stmt *mysqlStmt) writeExecutePacket(args []driver.Value) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
case time.Time:
|
case time.Time:
|
||||||
paramTypes[i+i] = fieldTypeString
|
paramTypes[i+i] = byte(fieldTypeString)
|
||||||
paramTypes[i+i+1] = 0x00
|
paramTypes[i+i+1] = 0x00
|
||||||
|
|
||||||
var val []byte
|
var a [64]byte
|
||||||
|
var b = a[:0]
|
||||||
|
|
||||||
if v.IsZero() {
|
if v.IsZero() {
|
||||||
val = []byte("0000-00-00")
|
b = append(b, "0000-00-00"...)
|
||||||
} else {
|
} else {
|
||||||
val = []byte(v.In(mc.cfg.Loc).Format(timeFormat))
|
b = v.In(mc.cfg.Loc).AppendFormat(b, timeFormat)
|
||||||
}
|
}
|
||||||
|
|
||||||
paramValues = appendLengthEncodedInteger(paramValues,
|
paramValues = appendLengthEncodedInteger(paramValues,
|
||||||
uint64(len(val)),
|
uint64(len(b)),
|
||||||
)
|
)
|
||||||
paramValues = append(paramValues, val...)
|
paramValues = append(paramValues, b...)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("can not convert type: %T", arg)
|
return fmt.Errorf("cannot convert type: %T", arg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1097,8 +1101,6 @@ func (mc *mysqlConn) discardResults() error {
|
||||||
if err := mc.readUntilEOF(); err != nil {
|
if err := mc.readUntilEOF(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
mc.status &^= statusMoreResultsExists
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -1116,20 +1118,17 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
|
||||||
// EOF Packet
|
// EOF Packet
|
||||||
if data[0] == iEOF && len(data) == 5 {
|
if data[0] == iEOF && len(data) == 5 {
|
||||||
rows.mc.status = readStatus(data[3:])
|
rows.mc.status = readStatus(data[3:])
|
||||||
err = rows.mc.discardResults()
|
rows.rs.done = true
|
||||||
if err == nil {
|
if !rows.HasNextResultSet() {
|
||||||
err = io.EOF
|
rows.mc = nil
|
||||||
} else {
|
|
||||||
// connection unusable
|
|
||||||
rows.mc.Close()
|
|
||||||
}
|
}
|
||||||
rows.mc = nil
|
return io.EOF
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
mc := rows.mc
|
||||||
rows.mc = nil
|
rows.mc = nil
|
||||||
|
|
||||||
// Error otherwise
|
// Error otherwise
|
||||||
return rows.mc.handleErrorPacket(data)
|
return mc.handleErrorPacket(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NULL-bitmap, [(column-count + 7 + 2) / 8 bytes]
|
// NULL-bitmap, [(column-count + 7 + 2) / 8 bytes]
|
||||||
|
@ -1145,14 +1144,14 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert to byte-coded string
|
// Convert to byte-coded string
|
||||||
switch rows.columns[i].fieldType {
|
switch rows.rs.columns[i].fieldType {
|
||||||
case fieldTypeNULL:
|
case fieldTypeNULL:
|
||||||
dest[i] = nil
|
dest[i] = nil
|
||||||
continue
|
continue
|
||||||
|
|
||||||
// Numeric Types
|
// Numeric Types
|
||||||
case fieldTypeTiny:
|
case fieldTypeTiny:
|
||||||
if rows.columns[i].flags&flagUnsigned != 0 {
|
if rows.rs.columns[i].flags&flagUnsigned != 0 {
|
||||||
dest[i] = int64(data[pos])
|
dest[i] = int64(data[pos])
|
||||||
} else {
|
} else {
|
||||||
dest[i] = int64(int8(data[pos]))
|
dest[i] = int64(int8(data[pos]))
|
||||||
|
@ -1161,7 +1160,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
|
||||||
continue
|
continue
|
||||||
|
|
||||||
case fieldTypeShort, fieldTypeYear:
|
case fieldTypeShort, fieldTypeYear:
|
||||||
if rows.columns[i].flags&flagUnsigned != 0 {
|
if rows.rs.columns[i].flags&flagUnsigned != 0 {
|
||||||
dest[i] = int64(binary.LittleEndian.Uint16(data[pos : pos+2]))
|
dest[i] = int64(binary.LittleEndian.Uint16(data[pos : pos+2]))
|
||||||
} else {
|
} else {
|
||||||
dest[i] = int64(int16(binary.LittleEndian.Uint16(data[pos : pos+2])))
|
dest[i] = int64(int16(binary.LittleEndian.Uint16(data[pos : pos+2])))
|
||||||
|
@ -1170,7 +1169,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
|
||||||
continue
|
continue
|
||||||
|
|
||||||
case fieldTypeInt24, fieldTypeLong:
|
case fieldTypeInt24, fieldTypeLong:
|
||||||
if rows.columns[i].flags&flagUnsigned != 0 {
|
if rows.rs.columns[i].flags&flagUnsigned != 0 {
|
||||||
dest[i] = int64(binary.LittleEndian.Uint32(data[pos : pos+4]))
|
dest[i] = int64(binary.LittleEndian.Uint32(data[pos : pos+4]))
|
||||||
} else {
|
} else {
|
||||||
dest[i] = int64(int32(binary.LittleEndian.Uint32(data[pos : pos+4])))
|
dest[i] = int64(int32(binary.LittleEndian.Uint32(data[pos : pos+4])))
|
||||||
|
@ -1179,7 +1178,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
|
||||||
continue
|
continue
|
||||||
|
|
||||||
case fieldTypeLongLong:
|
case fieldTypeLongLong:
|
||||||
if rows.columns[i].flags&flagUnsigned != 0 {
|
if rows.rs.columns[i].flags&flagUnsigned != 0 {
|
||||||
val := binary.LittleEndian.Uint64(data[pos : pos+8])
|
val := binary.LittleEndian.Uint64(data[pos : pos+8])
|
||||||
if val > math.MaxInt64 {
|
if val > math.MaxInt64 {
|
||||||
dest[i] = uint64ToString(val)
|
dest[i] = uint64ToString(val)
|
||||||
|
@ -1193,7 +1192,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
|
||||||
continue
|
continue
|
||||||
|
|
||||||
case fieldTypeFloat:
|
case fieldTypeFloat:
|
||||||
dest[i] = float32(math.Float32frombits(binary.LittleEndian.Uint32(data[pos : pos+4])))
|
dest[i] = math.Float32frombits(binary.LittleEndian.Uint32(data[pos : pos+4]))
|
||||||
pos += 4
|
pos += 4
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -1233,10 +1232,10 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
|
||||||
case isNull:
|
case isNull:
|
||||||
dest[i] = nil
|
dest[i] = nil
|
||||||
continue
|
continue
|
||||||
case rows.columns[i].fieldType == fieldTypeTime:
|
case rows.rs.columns[i].fieldType == fieldTypeTime:
|
||||||
// database/sql does not support an equivalent to TIME, return a string
|
// database/sql does not support an equivalent to TIME, return a string
|
||||||
var dstlen uint8
|
var dstlen uint8
|
||||||
switch decimals := rows.columns[i].decimals; decimals {
|
switch decimals := rows.rs.columns[i].decimals; decimals {
|
||||||
case 0x00, 0x1f:
|
case 0x00, 0x1f:
|
||||||
dstlen = 8
|
dstlen = 8
|
||||||
case 1, 2, 3, 4, 5, 6:
|
case 1, 2, 3, 4, 5, 6:
|
||||||
|
@ -1244,18 +1243,18 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"protocol error, illegal decimals value %d",
|
"protocol error, illegal decimals value %d",
|
||||||
rows.columns[i].decimals,
|
rows.rs.columns[i].decimals,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], dstlen, true)
|
dest[i], err = formatBinaryTime(data[pos:pos+int(num)], dstlen)
|
||||||
case rows.mc.parseTime:
|
case rows.mc.parseTime:
|
||||||
dest[i], err = parseBinaryDateTime(num, data[pos:], rows.mc.cfg.Loc)
|
dest[i], err = parseBinaryDateTime(num, data[pos:], rows.mc.cfg.Loc)
|
||||||
default:
|
default:
|
||||||
var dstlen uint8
|
var dstlen uint8
|
||||||
if rows.columns[i].fieldType == fieldTypeDate {
|
if rows.rs.columns[i].fieldType == fieldTypeDate {
|
||||||
dstlen = 10
|
dstlen = 10
|
||||||
} else {
|
} else {
|
||||||
switch decimals := rows.columns[i].decimals; decimals {
|
switch decimals := rows.rs.columns[i].decimals; decimals {
|
||||||
case 0x00, 0x1f:
|
case 0x00, 0x1f:
|
||||||
dstlen = 19
|
dstlen = 19
|
||||||
case 1, 2, 3, 4, 5, 6:
|
case 1, 2, 3, 4, 5, 6:
|
||||||
|
@ -1263,11 +1262,11 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf(
|
return fmt.Errorf(
|
||||||
"protocol error, illegal decimals value %d",
|
"protocol error, illegal decimals value %d",
|
||||||
rows.columns[i].decimals,
|
rows.rs.columns[i].decimals,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], dstlen, false)
|
dest[i], err = formatBinaryDateTime(data[pos:pos+int(num)], dstlen)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -1279,7 +1278,7 @@ func (rows *binaryRows) readRow(dest []driver.Value) error {
|
||||||
|
|
||||||
// Please report if this happens!
|
// Please report if this happens!
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unknown field type %d", rows.columns[i].fieldType)
|
return fmt.Errorf("unknown field type %d", rows.rs.columns[i].fieldType)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,19 +11,20 @@ package mysql
|
||||||
import (
|
import (
|
||||||
"database/sql/driver"
|
"database/sql/driver"
|
||||||
"io"
|
"io"
|
||||||
|
"math"
|
||||||
|
"reflect"
|
||||||
)
|
)
|
||||||
|
|
||||||
type mysqlField struct {
|
type resultSet struct {
|
||||||
tableName string
|
columns []mysqlField
|
||||||
name string
|
columnNames []string
|
||||||
flags fieldFlag
|
done bool
|
||||||
fieldType byte
|
|
||||||
decimals byte
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type mysqlRows struct {
|
type mysqlRows struct {
|
||||||
mc *mysqlConn
|
mc *mysqlConn
|
||||||
columns []mysqlField
|
rs resultSet
|
||||||
|
finish func()
|
||||||
}
|
}
|
||||||
|
|
||||||
type binaryRows struct {
|
type binaryRows struct {
|
||||||
|
@ -34,37 +35,86 @@ type textRows struct {
|
||||||
mysqlRows
|
mysqlRows
|
||||||
}
|
}
|
||||||
|
|
||||||
type emptyRows struct{}
|
|
||||||
|
|
||||||
func (rows *mysqlRows) Columns() []string {
|
func (rows *mysqlRows) Columns() []string {
|
||||||
columns := make([]string, len(rows.columns))
|
if rows.rs.columnNames != nil {
|
||||||
|
return rows.rs.columnNames
|
||||||
|
}
|
||||||
|
|
||||||
|
columns := make([]string, len(rows.rs.columns))
|
||||||
if rows.mc != nil && rows.mc.cfg.ColumnsWithAlias {
|
if rows.mc != nil && rows.mc.cfg.ColumnsWithAlias {
|
||||||
for i := range columns {
|
for i := range columns {
|
||||||
if tableName := rows.columns[i].tableName; len(tableName) > 0 {
|
if tableName := rows.rs.columns[i].tableName; len(tableName) > 0 {
|
||||||
columns[i] = tableName + "." + rows.columns[i].name
|
columns[i] = tableName + "." + rows.rs.columns[i].name
|
||||||
} else {
|
} else {
|
||||||
columns[i] = rows.columns[i].name
|
columns[i] = rows.rs.columns[i].name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for i := range columns {
|
for i := range columns {
|
||||||
columns[i] = rows.columns[i].name
|
columns[i] = rows.rs.columns[i].name
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rows.rs.columnNames = columns
|
||||||
return columns
|
return columns
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rows *mysqlRows) Close() error {
|
func (rows *mysqlRows) ColumnTypeDatabaseTypeName(i int) string {
|
||||||
|
return rows.rs.columns[i].typeDatabaseName()
|
||||||
|
}
|
||||||
|
|
||||||
|
// func (rows *mysqlRows) ColumnTypeLength(i int) (length int64, ok bool) {
|
||||||
|
// return int64(rows.rs.columns[i].length), true
|
||||||
|
// }
|
||||||
|
|
||||||
|
func (rows *mysqlRows) ColumnTypeNullable(i int) (nullable, ok bool) {
|
||||||
|
return rows.rs.columns[i].flags&flagNotNULL == 0, true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rows *mysqlRows) ColumnTypePrecisionScale(i int) (int64, int64, bool) {
|
||||||
|
column := rows.rs.columns[i]
|
||||||
|
decimals := int64(column.decimals)
|
||||||
|
|
||||||
|
switch column.fieldType {
|
||||||
|
case fieldTypeDecimal, fieldTypeNewDecimal:
|
||||||
|
if decimals > 0 {
|
||||||
|
return int64(column.length) - 2, decimals, true
|
||||||
|
}
|
||||||
|
return int64(column.length) - 1, decimals, true
|
||||||
|
case fieldTypeTimestamp, fieldTypeDateTime, fieldTypeTime:
|
||||||
|
return decimals, decimals, true
|
||||||
|
case fieldTypeFloat, fieldTypeDouble:
|
||||||
|
if decimals == 0x1f {
|
||||||
|
return math.MaxInt64, math.MaxInt64, true
|
||||||
|
}
|
||||||
|
return math.MaxInt64, decimals, true
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, 0, false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rows *mysqlRows) ColumnTypeScanType(i int) reflect.Type {
|
||||||
|
return rows.rs.columns[i].scanType()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rows *mysqlRows) Close() (err error) {
|
||||||
|
if f := rows.finish; f != nil {
|
||||||
|
f()
|
||||||
|
rows.finish = nil
|
||||||
|
}
|
||||||
|
|
||||||
mc := rows.mc
|
mc := rows.mc
|
||||||
if mc == nil {
|
if mc == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if mc.netConn == nil {
|
if err := mc.error(); err != nil {
|
||||||
return ErrInvalidConn
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove unread packets from stream
|
// Remove unread packets from stream
|
||||||
err := mc.readUntilEOF()
|
if !rows.rs.done {
|
||||||
|
err = mc.readUntilEOF()
|
||||||
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if err = mc.discardResults(); err != nil {
|
if err = mc.discardResults(); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -75,10 +125,66 @@ func (rows *mysqlRows) Close() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rows *mysqlRows) HasNextResultSet() (b bool) {
|
||||||
|
if rows.mc == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return rows.mc.status&statusMoreResultsExists != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rows *mysqlRows) nextResultSet() (int, error) {
|
||||||
|
if rows.mc == nil {
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
if err := rows.mc.error(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove unread packets from stream
|
||||||
|
if !rows.rs.done {
|
||||||
|
if err := rows.mc.readUntilEOF(); err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
rows.rs.done = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !rows.HasNextResultSet() {
|
||||||
|
rows.mc = nil
|
||||||
|
return 0, io.EOF
|
||||||
|
}
|
||||||
|
rows.rs = resultSet{}
|
||||||
|
return rows.mc.readResultSetHeaderPacket()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rows *mysqlRows) nextNotEmptyResultSet() (int, error) {
|
||||||
|
for {
|
||||||
|
resLen, err := rows.nextResultSet()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if resLen > 0 {
|
||||||
|
return resLen, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
rows.rs.done = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rows *binaryRows) NextResultSet() error {
|
||||||
|
resLen, err := rows.nextNotEmptyResultSet()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rows.rs.columns, err = rows.mc.readColumns(resLen)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (rows *binaryRows) Next(dest []driver.Value) error {
|
func (rows *binaryRows) Next(dest []driver.Value) error {
|
||||||
if mc := rows.mc; mc != nil {
|
if mc := rows.mc; mc != nil {
|
||||||
if mc.netConn == nil {
|
if err := mc.error(); err != nil {
|
||||||
return ErrInvalidConn
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch next row from stream
|
// Fetch next row from stream
|
||||||
|
@ -87,10 +193,20 @@ func (rows *binaryRows) Next(dest []driver.Value) error {
|
||||||
return io.EOF
|
return io.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rows *textRows) NextResultSet() (err error) {
|
||||||
|
resLen, err := rows.nextNotEmptyResultSet()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rows.rs.columns, err = rows.mc.readColumns(resLen)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
func (rows *textRows) Next(dest []driver.Value) error {
|
func (rows *textRows) Next(dest []driver.Value) error {
|
||||||
if mc := rows.mc; mc != nil {
|
if mc := rows.mc; mc != nil {
|
||||||
if mc.netConn == nil {
|
if err := mc.error(); err != nil {
|
||||||
return ErrInvalidConn
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch next row from stream
|
// Fetch next row from stream
|
||||||
|
@ -98,15 +214,3 @@ func (rows *textRows) Next(dest []driver.Value) error {
|
||||||
}
|
}
|
||||||
return io.EOF
|
return io.EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rows emptyRows) Columns() []string {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rows emptyRows) Close() error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rows emptyRows) Next(dest []driver.Value) error {
|
|
||||||
return io.EOF
|
|
||||||
}
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ package mysql
|
||||||
import (
|
import (
|
||||||
"database/sql/driver"
|
"database/sql/driver"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
)
|
)
|
||||||
|
@ -19,11 +20,10 @@ type mysqlStmt struct {
|
||||||
mc *mysqlConn
|
mc *mysqlConn
|
||||||
id uint32
|
id uint32
|
||||||
paramCount int
|
paramCount int
|
||||||
columns []mysqlField // cached from the first query
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (stmt *mysqlStmt) Close() error {
|
func (stmt *mysqlStmt) Close() error {
|
||||||
if stmt.mc == nil || stmt.mc.netConn == nil {
|
if stmt.mc == nil || stmt.mc.closed.IsSet() {
|
||||||
// driver.Stmt.Close can be called more than once, thus this function
|
// driver.Stmt.Close can be called more than once, thus this function
|
||||||
// has to be idempotent.
|
// has to be idempotent.
|
||||||
// See also Issue #450 and golang/go#16019.
|
// See also Issue #450 and golang/go#16019.
|
||||||
|
@ -45,14 +45,14 @@ func (stmt *mysqlStmt) ColumnConverter(idx int) driver.ValueConverter {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) {
|
func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) {
|
||||||
if stmt.mc.netConn == nil {
|
if stmt.mc.closed.IsSet() {
|
||||||
errLog.Print(ErrInvalidConn)
|
errLog.Print(ErrInvalidConn)
|
||||||
return nil, driver.ErrBadConn
|
return nil, driver.ErrBadConn
|
||||||
}
|
}
|
||||||
// Send command
|
// Send command
|
||||||
err := stmt.writeExecutePacket(args)
|
err := stmt.writeExecutePacket(args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, stmt.mc.markBadConn(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
mc := stmt.mc
|
mc := stmt.mc
|
||||||
|
@ -62,37 +62,45 @@ func (stmt *mysqlStmt) Exec(args []driver.Value) (driver.Result, error) {
|
||||||
|
|
||||||
// Read Result
|
// Read Result
|
||||||
resLen, err := mc.readResultSetHeaderPacket()
|
resLen, err := mc.readResultSetHeaderPacket()
|
||||||
if err == nil {
|
if err != nil {
|
||||||
if resLen > 0 {
|
return nil, err
|
||||||
// Columns
|
}
|
||||||
err = mc.readUntilEOF()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rows
|
if resLen > 0 {
|
||||||
err = mc.readUntilEOF()
|
// Columns
|
||||||
|
if err = mc.readUntilEOF(); err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
if err == nil {
|
|
||||||
return &mysqlResult{
|
// Rows
|
||||||
affectedRows: int64(mc.affectedRows),
|
if err := mc.readUntilEOF(); err != nil {
|
||||||
insertId: int64(mc.insertId),
|
return nil, err
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, err
|
if err := mc.discardResults(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &mysqlResult{
|
||||||
|
affectedRows: int64(mc.affectedRows),
|
||||||
|
insertId: int64(mc.insertId),
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) {
|
func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) {
|
||||||
if stmt.mc.netConn == nil {
|
return stmt.query(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (stmt *mysqlStmt) query(args []driver.Value) (*binaryRows, error) {
|
||||||
|
if stmt.mc.closed.IsSet() {
|
||||||
errLog.Print(ErrInvalidConn)
|
errLog.Print(ErrInvalidConn)
|
||||||
return nil, driver.ErrBadConn
|
return nil, driver.ErrBadConn
|
||||||
}
|
}
|
||||||
// Send command
|
// Send command
|
||||||
err := stmt.writeExecutePacket(args)
|
err := stmt.writeExecutePacket(args)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, stmt.mc.markBadConn(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
mc := stmt.mc
|
mc := stmt.mc
|
||||||
|
@ -107,14 +115,15 @@ func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) {
|
||||||
|
|
||||||
if resLen > 0 {
|
if resLen > 0 {
|
||||||
rows.mc = mc
|
rows.mc = mc
|
||||||
// Columns
|
rows.rs.columns, err = mc.readColumns(resLen)
|
||||||
// If not cached, read them and cache them
|
} else {
|
||||||
if stmt.columns == nil {
|
rows.rs.done = true
|
||||||
rows.columns, err = mc.readColumns(resLen)
|
|
||||||
stmt.columns = rows.columns
|
switch err := rows.NextResultSet(); err {
|
||||||
} else {
|
case nil, io.EOF:
|
||||||
rows.columns = stmt.columns
|
return rows, nil
|
||||||
err = mc.readUntilEOF()
|
default:
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -123,19 +132,36 @@ func (stmt *mysqlStmt) Query(args []driver.Value) (driver.Rows, error) {
|
||||||
|
|
||||||
type converter struct{}
|
type converter struct{}
|
||||||
|
|
||||||
|
// ConvertValue mirrors the reference/default converter in database/sql/driver
|
||||||
|
// with _one_ exception. We support uint64 with their high bit and the default
|
||||||
|
// implementation does not. This function should be kept in sync with
|
||||||
|
// database/sql/driver defaultConverter.ConvertValue() except for that
|
||||||
|
// deliberate difference.
|
||||||
func (c converter) ConvertValue(v interface{}) (driver.Value, error) {
|
func (c converter) ConvertValue(v interface{}) (driver.Value, error) {
|
||||||
if driver.IsValue(v) {
|
if driver.IsValue(v) {
|
||||||
return v, nil
|
return v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if vr, ok := v.(driver.Valuer); ok {
|
||||||
|
sv, err := callValuerValue(vr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if !driver.IsValue(sv) {
|
||||||
|
return nil, fmt.Errorf("non-Value type %T returned from Value", sv)
|
||||||
|
}
|
||||||
|
return sv, nil
|
||||||
|
}
|
||||||
|
|
||||||
rv := reflect.ValueOf(v)
|
rv := reflect.ValueOf(v)
|
||||||
switch rv.Kind() {
|
switch rv.Kind() {
|
||||||
case reflect.Ptr:
|
case reflect.Ptr:
|
||||||
// indirect pointers
|
// indirect pointers
|
||||||
if rv.IsNil() {
|
if rv.IsNil() {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
} else {
|
||||||
|
return c.ConvertValue(rv.Elem().Interface())
|
||||||
}
|
}
|
||||||
return c.ConvertValue(rv.Elem().Interface())
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||||
return rv.Int(), nil
|
return rv.Int(), nil
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
|
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32:
|
||||||
|
@ -148,6 +174,38 @@ func (c converter) ConvertValue(v interface{}) (driver.Value, error) {
|
||||||
return int64(u64), nil
|
return int64(u64), nil
|
||||||
case reflect.Float32, reflect.Float64:
|
case reflect.Float32, reflect.Float64:
|
||||||
return rv.Float(), nil
|
return rv.Float(), nil
|
||||||
|
case reflect.Bool:
|
||||||
|
return rv.Bool(), nil
|
||||||
|
case reflect.Slice:
|
||||||
|
ek := rv.Type().Elem().Kind()
|
||||||
|
if ek == reflect.Uint8 {
|
||||||
|
return rv.Bytes(), nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unsupported type %T, a slice of %s", v, ek)
|
||||||
|
case reflect.String:
|
||||||
|
return rv.String(), nil
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("unsupported type %T, a %s", v, rv.Kind())
|
return nil, fmt.Errorf("unsupported type %T, a %s", v, rv.Kind())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var valuerReflectType = reflect.TypeOf((*driver.Valuer)(nil)).Elem()
|
||||||
|
|
||||||
|
// callValuerValue returns vr.Value(), with one exception:
|
||||||
|
// If vr.Value is an auto-generated method on a pointer type and the
|
||||||
|
// pointer is nil, it would panic at runtime in the panicwrap
|
||||||
|
// method. Treat it like nil instead.
|
||||||
|
//
|
||||||
|
// This is so people can implement driver.Value on value types and
|
||||||
|
// still use nil pointers to those types to mean nil/NULL, just like
|
||||||
|
// string/*string.
|
||||||
|
//
|
||||||
|
// This is an exact copy of the same-named unexported function from the
|
||||||
|
// database/sql package.
|
||||||
|
func callValuerValue(vr driver.Valuer) (v driver.Value, err error) {
|
||||||
|
if rv := reflect.ValueOf(vr); rv.Kind() == reflect.Ptr &&
|
||||||
|
rv.IsNil() &&
|
||||||
|
rv.Type().Elem().Implements(valuerReflectType) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return vr.Value()
|
||||||
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ type mysqlTx struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tx *mysqlTx) Commit() (err error) {
|
func (tx *mysqlTx) Commit() (err error) {
|
||||||
if tx.mc == nil || tx.mc.netConn == nil {
|
if tx.mc == nil || tx.mc.closed.IsSet() {
|
||||||
return ErrInvalidConn
|
return ErrInvalidConn
|
||||||
}
|
}
|
||||||
err = tx.mc.exec("COMMIT")
|
err = tx.mc.exec("COMMIT")
|
||||||
|
@ -22,7 +22,7 @@ func (tx *mysqlTx) Commit() (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tx *mysqlTx) Rollback() (err error) {
|
func (tx *mysqlTx) Rollback() (err error) {
|
||||||
if tx.mc == nil || tx.mc.netConn == nil {
|
if tx.mc == nil || tx.mc.closed.IsSet() {
|
||||||
return ErrInvalidConn
|
return ErrInvalidConn
|
||||||
}
|
}
|
||||||
err = tx.mc.exec("ROLLBACK")
|
err = tx.mc.exec("ROLLBACK")
|
||||||
|
|
|
@ -9,23 +9,30 @@
|
||||||
package mysql
|
package mysql
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha1"
|
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"database/sql/driver"
|
"database/sql/driver"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Registry for custom tls.Configs
|
||||||
var (
|
var (
|
||||||
tlsConfigRegister map[string]*tls.Config // Register for custom tls.Configs
|
tlsConfigLock sync.RWMutex
|
||||||
|
tlsConfigRegistry map[string]*tls.Config
|
||||||
)
|
)
|
||||||
|
|
||||||
// RegisterTLSConfig registers a custom tls.Config to be used with sql.Open.
|
// RegisterTLSConfig registers a custom tls.Config to be used with sql.Open.
|
||||||
// Use the key as a value in the DSN where tls=value.
|
// Use the key as a value in the DSN where tls=value.
|
||||||
//
|
//
|
||||||
|
// Note: The provided tls.Config is exclusively owned by the driver after
|
||||||
|
// registering it.
|
||||||
|
//
|
||||||
// rootCertPool := x509.NewCertPool()
|
// rootCertPool := x509.NewCertPool()
|
||||||
// pem, err := ioutil.ReadFile("/path/ca-cert.pem")
|
// pem, err := ioutil.ReadFile("/path/ca-cert.pem")
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
|
@ -51,19 +58,32 @@ func RegisterTLSConfig(key string, config *tls.Config) error {
|
||||||
return fmt.Errorf("key '%s' is reserved", key)
|
return fmt.Errorf("key '%s' is reserved", key)
|
||||||
}
|
}
|
||||||
|
|
||||||
if tlsConfigRegister == nil {
|
tlsConfigLock.Lock()
|
||||||
tlsConfigRegister = make(map[string]*tls.Config)
|
if tlsConfigRegistry == nil {
|
||||||
|
tlsConfigRegistry = make(map[string]*tls.Config)
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsConfigRegister[key] = config
|
tlsConfigRegistry[key] = config
|
||||||
|
tlsConfigLock.Unlock()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeregisterTLSConfig removes the tls.Config associated with key.
|
// DeregisterTLSConfig removes the tls.Config associated with key.
|
||||||
func DeregisterTLSConfig(key string) {
|
func DeregisterTLSConfig(key string) {
|
||||||
if tlsConfigRegister != nil {
|
tlsConfigLock.Lock()
|
||||||
delete(tlsConfigRegister, key)
|
if tlsConfigRegistry != nil {
|
||||||
|
delete(tlsConfigRegistry, key)
|
||||||
}
|
}
|
||||||
|
tlsConfigLock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTLSConfigClone(key string) (config *tls.Config) {
|
||||||
|
tlsConfigLock.RLock()
|
||||||
|
if v, ok := tlsConfigRegistry[key]; ok {
|
||||||
|
config = cloneTLSConfig(v)
|
||||||
|
}
|
||||||
|
tlsConfigLock.RUnlock()
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the bool value of the input.
|
// Returns the bool value of the input.
|
||||||
|
@ -80,119 +100,6 @@ func readBool(input string) (value bool, valid bool) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************************************************************
|
|
||||||
* Authentication *
|
|
||||||
******************************************************************************/
|
|
||||||
|
|
||||||
// Encrypt password using 4.1+ method
|
|
||||||
func scramblePassword(scramble, password []byte) []byte {
|
|
||||||
if len(password) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// stage1Hash = SHA1(password)
|
|
||||||
crypt := sha1.New()
|
|
||||||
crypt.Write(password)
|
|
||||||
stage1 := crypt.Sum(nil)
|
|
||||||
|
|
||||||
// scrambleHash = SHA1(scramble + SHA1(stage1Hash))
|
|
||||||
// inner Hash
|
|
||||||
crypt.Reset()
|
|
||||||
crypt.Write(stage1)
|
|
||||||
hash := crypt.Sum(nil)
|
|
||||||
|
|
||||||
// outer Hash
|
|
||||||
crypt.Reset()
|
|
||||||
crypt.Write(scramble)
|
|
||||||
crypt.Write(hash)
|
|
||||||
scramble = crypt.Sum(nil)
|
|
||||||
|
|
||||||
// token = scrambleHash XOR stage1Hash
|
|
||||||
for i := range scramble {
|
|
||||||
scramble[i] ^= stage1[i]
|
|
||||||
}
|
|
||||||
return scramble
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encrypt password using pre 4.1 (old password) method
|
|
||||||
// https://github.com/atcurtis/mariadb/blob/master/mysys/my_rnd.c
|
|
||||||
type myRnd struct {
|
|
||||||
seed1, seed2 uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
const myRndMaxVal = 0x3FFFFFFF
|
|
||||||
|
|
||||||
// Pseudo random number generator
|
|
||||||
func newMyRnd(seed1, seed2 uint32) *myRnd {
|
|
||||||
return &myRnd{
|
|
||||||
seed1: seed1 % myRndMaxVal,
|
|
||||||
seed2: seed2 % myRndMaxVal,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tested to be equivalent to MariaDB's floating point variant
|
|
||||||
// http://play.golang.org/p/QHvhd4qved
|
|
||||||
// http://play.golang.org/p/RG0q4ElWDx
|
|
||||||
func (r *myRnd) NextByte() byte {
|
|
||||||
r.seed1 = (r.seed1*3 + r.seed2) % myRndMaxVal
|
|
||||||
r.seed2 = (r.seed1 + r.seed2 + 33) % myRndMaxVal
|
|
||||||
|
|
||||||
return byte(uint64(r.seed1) * 31 / myRndMaxVal)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate binary hash from byte string using insecure pre 4.1 method
|
|
||||||
func pwHash(password []byte) (result [2]uint32) {
|
|
||||||
var add uint32 = 7
|
|
||||||
var tmp uint32
|
|
||||||
|
|
||||||
result[0] = 1345345333
|
|
||||||
result[1] = 0x12345671
|
|
||||||
|
|
||||||
for _, c := range password {
|
|
||||||
// skip spaces and tabs in password
|
|
||||||
if c == ' ' || c == '\t' {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
tmp = uint32(c)
|
|
||||||
result[0] ^= (((result[0] & 63) + add) * tmp) + (result[0] << 8)
|
|
||||||
result[1] += (result[1] << 8) ^ result[0]
|
|
||||||
add += tmp
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove sign bit (1<<31)-1)
|
|
||||||
result[0] &= 0x7FFFFFFF
|
|
||||||
result[1] &= 0x7FFFFFFF
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encrypt password using insecure pre 4.1 method
|
|
||||||
func scrambleOldPassword(scramble, password []byte) []byte {
|
|
||||||
if len(password) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
scramble = scramble[:8]
|
|
||||||
|
|
||||||
hashPw := pwHash(password)
|
|
||||||
hashSc := pwHash(scramble)
|
|
||||||
|
|
||||||
r := newMyRnd(hashPw[0]^hashSc[0], hashPw[1]^hashSc[1])
|
|
||||||
|
|
||||||
var out [8]byte
|
|
||||||
for i := range out {
|
|
||||||
out[i] = r.NextByte() + 64
|
|
||||||
}
|
|
||||||
|
|
||||||
mask := r.NextByte()
|
|
||||||
for i := range out {
|
|
||||||
out[i] ^= mask
|
|
||||||
}
|
|
||||||
|
|
||||||
return out[:]
|
|
||||||
}
|
|
||||||
|
|
||||||
/******************************************************************************
|
/******************************************************************************
|
||||||
* Time related utils *
|
* Time related utils *
|
||||||
******************************************************************************/
|
******************************************************************************/
|
||||||
|
@ -321,87 +228,104 @@ var zeroDateTime = []byte("0000-00-00 00:00:00.000000")
|
||||||
const digits01 = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
|
const digits01 = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"
|
||||||
const digits10 = "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999"
|
const digits10 = "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999"
|
||||||
|
|
||||||
func formatBinaryDateTime(src []byte, length uint8, justTime bool) (driver.Value, error) {
|
func appendMicrosecs(dst, src []byte, decimals int) []byte {
|
||||||
|
if decimals <= 0 {
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
if len(src) == 0 {
|
||||||
|
return append(dst, ".000000"[:decimals+1]...)
|
||||||
|
}
|
||||||
|
|
||||||
|
microsecs := binary.LittleEndian.Uint32(src[:4])
|
||||||
|
p1 := byte(microsecs / 10000)
|
||||||
|
microsecs -= 10000 * uint32(p1)
|
||||||
|
p2 := byte(microsecs / 100)
|
||||||
|
microsecs -= 100 * uint32(p2)
|
||||||
|
p3 := byte(microsecs)
|
||||||
|
|
||||||
|
switch decimals {
|
||||||
|
default:
|
||||||
|
return append(dst, '.',
|
||||||
|
digits10[p1], digits01[p1],
|
||||||
|
digits10[p2], digits01[p2],
|
||||||
|
digits10[p3], digits01[p3],
|
||||||
|
)
|
||||||
|
case 1:
|
||||||
|
return append(dst, '.',
|
||||||
|
digits10[p1],
|
||||||
|
)
|
||||||
|
case 2:
|
||||||
|
return append(dst, '.',
|
||||||
|
digits10[p1], digits01[p1],
|
||||||
|
)
|
||||||
|
case 3:
|
||||||
|
return append(dst, '.',
|
||||||
|
digits10[p1], digits01[p1],
|
||||||
|
digits10[p2],
|
||||||
|
)
|
||||||
|
case 4:
|
||||||
|
return append(dst, '.',
|
||||||
|
digits10[p1], digits01[p1],
|
||||||
|
digits10[p2], digits01[p2],
|
||||||
|
)
|
||||||
|
case 5:
|
||||||
|
return append(dst, '.',
|
||||||
|
digits10[p1], digits01[p1],
|
||||||
|
digits10[p2], digits01[p2],
|
||||||
|
digits10[p3],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatBinaryDateTime(src []byte, length uint8) (driver.Value, error) {
|
||||||
// length expects the deterministic length of the zero value,
|
// length expects the deterministic length of the zero value,
|
||||||
// negative time and 100+ hours are automatically added if needed
|
// negative time and 100+ hours are automatically added if needed
|
||||||
if len(src) == 0 {
|
if len(src) == 0 {
|
||||||
if justTime {
|
|
||||||
return zeroDateTime[11 : 11+length], nil
|
|
||||||
}
|
|
||||||
return zeroDateTime[:length], nil
|
return zeroDateTime[:length], nil
|
||||||
}
|
}
|
||||||
var dst []byte // return value
|
var dst []byte // return value
|
||||||
var pt, p1, p2, p3 byte // current digit pair
|
var p1, p2, p3 byte // current digit pair
|
||||||
var zOffs byte // offset of value in zeroDateTime
|
|
||||||
if justTime {
|
switch length {
|
||||||
switch length {
|
case 10, 19, 21, 22, 23, 24, 25, 26:
|
||||||
case
|
default:
|
||||||
8, // time (can be up to 10 when negative and 100+ hours)
|
t := "DATE"
|
||||||
10, 11, 12, 13, 14, 15: // time with fractional seconds
|
if length > 10 {
|
||||||
default:
|
t += "TIME"
|
||||||
return nil, fmt.Errorf("illegal TIME length %d", length)
|
|
||||||
}
|
}
|
||||||
switch len(src) {
|
return nil, fmt.Errorf("illegal %s length %d", t, length)
|
||||||
case 8, 12:
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("invalid TIME packet length %d", len(src))
|
|
||||||
}
|
|
||||||
// +2 to enable negative time and 100+ hours
|
|
||||||
dst = make([]byte, 0, length+2)
|
|
||||||
if src[0] == 1 {
|
|
||||||
dst = append(dst, '-')
|
|
||||||
}
|
|
||||||
if src[1] != 0 {
|
|
||||||
hour := uint16(src[1])*24 + uint16(src[5])
|
|
||||||
pt = byte(hour / 100)
|
|
||||||
p1 = byte(hour - 100*uint16(pt))
|
|
||||||
dst = append(dst, digits01[pt])
|
|
||||||
} else {
|
|
||||||
p1 = src[5]
|
|
||||||
}
|
|
||||||
zOffs = 11
|
|
||||||
src = src[6:]
|
|
||||||
} else {
|
|
||||||
switch length {
|
|
||||||
case 10, 19, 21, 22, 23, 24, 25, 26:
|
|
||||||
default:
|
|
||||||
t := "DATE"
|
|
||||||
if length > 10 {
|
|
||||||
t += "TIME"
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("illegal %s length %d", t, length)
|
|
||||||
}
|
|
||||||
switch len(src) {
|
|
||||||
case 4, 7, 11:
|
|
||||||
default:
|
|
||||||
t := "DATE"
|
|
||||||
if length > 10 {
|
|
||||||
t += "TIME"
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("illegal %s packet length %d", t, len(src))
|
|
||||||
}
|
|
||||||
dst = make([]byte, 0, length)
|
|
||||||
// start with the date
|
|
||||||
year := binary.LittleEndian.Uint16(src[:2])
|
|
||||||
pt = byte(year / 100)
|
|
||||||
p1 = byte(year - 100*uint16(pt))
|
|
||||||
p2, p3 = src[2], src[3]
|
|
||||||
dst = append(dst,
|
|
||||||
digits10[pt], digits01[pt],
|
|
||||||
digits10[p1], digits01[p1], '-',
|
|
||||||
digits10[p2], digits01[p2], '-',
|
|
||||||
digits10[p3], digits01[p3],
|
|
||||||
)
|
|
||||||
if length == 10 {
|
|
||||||
return dst, nil
|
|
||||||
}
|
|
||||||
if len(src) == 4 {
|
|
||||||
return append(dst, zeroDateTime[10:length]...), nil
|
|
||||||
}
|
|
||||||
dst = append(dst, ' ')
|
|
||||||
p1 = src[4] // hour
|
|
||||||
src = src[5:]
|
|
||||||
}
|
}
|
||||||
|
switch len(src) {
|
||||||
|
case 4, 7, 11:
|
||||||
|
default:
|
||||||
|
t := "DATE"
|
||||||
|
if length > 10 {
|
||||||
|
t += "TIME"
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("illegal %s packet length %d", t, len(src))
|
||||||
|
}
|
||||||
|
dst = make([]byte, 0, length)
|
||||||
|
// start with the date
|
||||||
|
year := binary.LittleEndian.Uint16(src[:2])
|
||||||
|
pt := year / 100
|
||||||
|
p1 = byte(year - 100*uint16(pt))
|
||||||
|
p2, p3 = src[2], src[3]
|
||||||
|
dst = append(dst,
|
||||||
|
digits10[pt], digits01[pt],
|
||||||
|
digits10[p1], digits01[p1], '-',
|
||||||
|
digits10[p2], digits01[p2], '-',
|
||||||
|
digits10[p3], digits01[p3],
|
||||||
|
)
|
||||||
|
if length == 10 {
|
||||||
|
return dst, nil
|
||||||
|
}
|
||||||
|
if len(src) == 4 {
|
||||||
|
return append(dst, zeroDateTime[10:length]...), nil
|
||||||
|
}
|
||||||
|
dst = append(dst, ' ')
|
||||||
|
p1 = src[4] // hour
|
||||||
|
src = src[5:]
|
||||||
|
|
||||||
// p1 is 2-digit hour, src is after hour
|
// p1 is 2-digit hour, src is after hour
|
||||||
p2, p3 = src[0], src[1]
|
p2, p3 = src[0], src[1]
|
||||||
dst = append(dst,
|
dst = append(dst,
|
||||||
|
@ -409,51 +333,49 @@ func formatBinaryDateTime(src []byte, length uint8, justTime bool) (driver.Value
|
||||||
digits10[p2], digits01[p2], ':',
|
digits10[p2], digits01[p2], ':',
|
||||||
digits10[p3], digits01[p3],
|
digits10[p3], digits01[p3],
|
||||||
)
|
)
|
||||||
if length <= byte(len(dst)) {
|
return appendMicrosecs(dst, src[2:], int(length)-20), nil
|
||||||
return dst, nil
|
}
|
||||||
}
|
|
||||||
src = src[2:]
|
func formatBinaryTime(src []byte, length uint8) (driver.Value, error) {
|
||||||
|
// length expects the deterministic length of the zero value,
|
||||||
|
// negative time and 100+ hours are automatically added if needed
|
||||||
if len(src) == 0 {
|
if len(src) == 0 {
|
||||||
return append(dst, zeroDateTime[19:zOffs+length]...), nil
|
return zeroDateTime[11 : 11+length], nil
|
||||||
}
|
}
|
||||||
microsecs := binary.LittleEndian.Uint32(src[:4])
|
var dst []byte // return value
|
||||||
p1 = byte(microsecs / 10000)
|
|
||||||
microsecs -= 10000 * uint32(p1)
|
switch length {
|
||||||
p2 = byte(microsecs / 100)
|
case
|
||||||
microsecs -= 100 * uint32(p2)
|
8, // time (can be up to 10 when negative and 100+ hours)
|
||||||
p3 = byte(microsecs)
|
10, 11, 12, 13, 14, 15: // time with fractional seconds
|
||||||
switch decimals := zOffs + length - 20; decimals {
|
|
||||||
default:
|
default:
|
||||||
return append(dst, '.',
|
return nil, fmt.Errorf("illegal TIME length %d", length)
|
||||||
digits10[p1], digits01[p1],
|
|
||||||
digits10[p2], digits01[p2],
|
|
||||||
digits10[p3], digits01[p3],
|
|
||||||
), nil
|
|
||||||
case 1:
|
|
||||||
return append(dst, '.',
|
|
||||||
digits10[p1],
|
|
||||||
), nil
|
|
||||||
case 2:
|
|
||||||
return append(dst, '.',
|
|
||||||
digits10[p1], digits01[p1],
|
|
||||||
), nil
|
|
||||||
case 3:
|
|
||||||
return append(dst, '.',
|
|
||||||
digits10[p1], digits01[p1],
|
|
||||||
digits10[p2],
|
|
||||||
), nil
|
|
||||||
case 4:
|
|
||||||
return append(dst, '.',
|
|
||||||
digits10[p1], digits01[p1],
|
|
||||||
digits10[p2], digits01[p2],
|
|
||||||
), nil
|
|
||||||
case 5:
|
|
||||||
return append(dst, '.',
|
|
||||||
digits10[p1], digits01[p1],
|
|
||||||
digits10[p2], digits01[p2],
|
|
||||||
digits10[p3],
|
|
||||||
), nil
|
|
||||||
}
|
}
|
||||||
|
switch len(src) {
|
||||||
|
case 8, 12:
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid TIME packet length %d", len(src))
|
||||||
|
}
|
||||||
|
// +2 to enable negative time and 100+ hours
|
||||||
|
dst = make([]byte, 0, length+2)
|
||||||
|
if src[0] == 1 {
|
||||||
|
dst = append(dst, '-')
|
||||||
|
}
|
||||||
|
days := binary.LittleEndian.Uint32(src[1:5])
|
||||||
|
hours := int64(days)*24 + int64(src[5])
|
||||||
|
|
||||||
|
if hours >= 100 {
|
||||||
|
dst = strconv.AppendInt(dst, hours, 10)
|
||||||
|
} else {
|
||||||
|
dst = append(dst, digits10[hours], digits01[hours])
|
||||||
|
}
|
||||||
|
|
||||||
|
min, sec := src[6], src[7]
|
||||||
|
dst = append(dst, ':',
|
||||||
|
digits10[min], digits01[min], ':',
|
||||||
|
digits10[sec], digits01[sec],
|
||||||
|
)
|
||||||
|
return appendMicrosecs(dst, src[8:], int(length)-9), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************************************************************
|
/******************************************************************************
|
||||||
|
@ -519,7 +441,7 @@ func readLengthEncodedString(b []byte) ([]byte, bool, int, error) {
|
||||||
|
|
||||||
// Check data length
|
// Check data length
|
||||||
if len(b) >= n {
|
if len(b) >= n {
|
||||||
return b[n-int(num) : n], false, n, nil
|
return b[n-int(num) : n : n], false, n, nil
|
||||||
}
|
}
|
||||||
return nil, false, n, io.EOF
|
return nil, false, n, io.EOF
|
||||||
}
|
}
|
||||||
|
@ -548,8 +470,8 @@ func readLengthEncodedInteger(b []byte) (uint64, bool, int) {
|
||||||
if len(b) == 0 {
|
if len(b) == 0 {
|
||||||
return 0, true, 1
|
return 0, true, 1
|
||||||
}
|
}
|
||||||
switch b[0] {
|
|
||||||
|
|
||||||
|
switch b[0] {
|
||||||
// 251: NULL
|
// 251: NULL
|
||||||
case 0xfb:
|
case 0xfb:
|
||||||
return 0, true, 1
|
return 0, true, 1
|
||||||
|
@ -738,3 +660,67 @@ func escapeStringQuotes(buf []byte, v string) []byte {
|
||||||
|
|
||||||
return buf[:pos]
|
return buf[:pos]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/******************************************************************************
|
||||||
|
* Sync utils *
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
// noCopy may be embedded into structs which must not be copied
|
||||||
|
// after the first use.
|
||||||
|
//
|
||||||
|
// See https://github.com/golang/go/issues/8005#issuecomment-190753527
|
||||||
|
// for details.
|
||||||
|
type noCopy struct{}
|
||||||
|
|
||||||
|
// Lock is a no-op used by -copylocks checker from `go vet`.
|
||||||
|
func (*noCopy) Lock() {}
|
||||||
|
|
||||||
|
// atomicBool is a wrapper around uint32 for usage as a boolean value with
|
||||||
|
// atomic access.
|
||||||
|
type atomicBool struct {
|
||||||
|
_noCopy noCopy
|
||||||
|
value uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSet returns wether the current boolean value is true
|
||||||
|
func (ab *atomicBool) IsSet() bool {
|
||||||
|
return atomic.LoadUint32(&ab.value) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets the value of the bool regardless of the previous value
|
||||||
|
func (ab *atomicBool) Set(value bool) {
|
||||||
|
if value {
|
||||||
|
atomic.StoreUint32(&ab.value, 1)
|
||||||
|
} else {
|
||||||
|
atomic.StoreUint32(&ab.value, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrySet sets the value of the bool and returns wether the value changed
|
||||||
|
func (ab *atomicBool) TrySet(value bool) bool {
|
||||||
|
if value {
|
||||||
|
return atomic.SwapUint32(&ab.value, 1) == 0
|
||||||
|
}
|
||||||
|
return atomic.SwapUint32(&ab.value, 0) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// atomicError is a wrapper for atomically accessed error values
|
||||||
|
type atomicError struct {
|
||||||
|
_noCopy noCopy
|
||||||
|
value atomic.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets the error value regardless of the previous value.
|
||||||
|
// The value must not be nil
|
||||||
|
func (ae *atomicError) Set(value error) {
|
||||||
|
ae.value.Store(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value returns the current error value
|
||||||
|
func (ae *atomicError) Value() error {
|
||||||
|
if v := ae.value.Load(); v != nil {
|
||||||
|
// this will panic if the value doesn't implement the error interface
|
||||||
|
return v.(error)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||||
|
//
|
||||||
|
// Copyright 2017 The Go-MySQL-Driver Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
// +build go1.7
|
||||||
|
// +build !go1.8
|
||||||
|
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
import "crypto/tls"
|
||||||
|
|
||||||
|
func cloneTLSConfig(c *tls.Config) *tls.Config {
|
||||||
|
return &tls.Config{
|
||||||
|
Rand: c.Rand,
|
||||||
|
Time: c.Time,
|
||||||
|
Certificates: c.Certificates,
|
||||||
|
NameToCertificate: c.NameToCertificate,
|
||||||
|
GetCertificate: c.GetCertificate,
|
||||||
|
RootCAs: c.RootCAs,
|
||||||
|
NextProtos: c.NextProtos,
|
||||||
|
ServerName: c.ServerName,
|
||||||
|
ClientAuth: c.ClientAuth,
|
||||||
|
ClientCAs: c.ClientCAs,
|
||||||
|
InsecureSkipVerify: c.InsecureSkipVerify,
|
||||||
|
CipherSuites: c.CipherSuites,
|
||||||
|
PreferServerCipherSuites: c.PreferServerCipherSuites,
|
||||||
|
SessionTicketsDisabled: c.SessionTicketsDisabled,
|
||||||
|
SessionTicketKey: c.SessionTicketKey,
|
||||||
|
ClientSessionCache: c.ClientSessionCache,
|
||||||
|
MinVersion: c.MinVersion,
|
||||||
|
MaxVersion: c.MaxVersion,
|
||||||
|
CurvePreferences: c.CurvePreferences,
|
||||||
|
DynamicRecordSizingDisabled: c.DynamicRecordSizingDisabled,
|
||||||
|
Renegotiation: c.Renegotiation,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
// Go MySQL Driver - A MySQL-Driver for Go's database/sql package
|
||||||
|
//
|
||||||
|
// Copyright 2017 The Go-MySQL-Driver Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
// License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||||
|
// You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
|
// +build go1.8
|
||||||
|
|
||||||
|
package mysql
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/tls"
|
||||||
|
"database/sql"
|
||||||
|
"database/sql/driver"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func cloneTLSConfig(c *tls.Config) *tls.Config {
|
||||||
|
return c.Clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
func namedValueToValue(named []driver.NamedValue) ([]driver.Value, error) {
|
||||||
|
dargs := make([]driver.Value, len(named))
|
||||||
|
for n, param := range named {
|
||||||
|
if len(param.Name) > 0 {
|
||||||
|
// TODO: support the use of Named Parameters #561
|
||||||
|
return nil, errors.New("mysql: driver does not support the use of Named Parameters")
|
||||||
|
}
|
||||||
|
dargs[n] = param.Value
|
||||||
|
}
|
||||||
|
return dargs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapIsolationLevel(level driver.IsolationLevel) (string, error) {
|
||||||
|
switch sql.IsolationLevel(level) {
|
||||||
|
case sql.LevelRepeatableRead:
|
||||||
|
return "REPEATABLE READ", nil
|
||||||
|
case sql.LevelReadCommitted:
|
||||||
|
return "READ COMMITTED", nil
|
||||||
|
case sql.LevelReadUncommitted:
|
||||||
|
return "READ UNCOMMITTED", nil
|
||||||
|
case sql.LevelSerializable:
|
||||||
|
return "SERIALIZABLE", nil
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("mysql: unsupported isolation level: %v", level)
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,6 +20,8 @@ explains how to use `database/sql` along with sqlx.
|
||||||
|
|
||||||
## Recent Changes
|
## Recent Changes
|
||||||
|
|
||||||
|
* The [introduction](https://github.com/jmoiron/sqlx/pull/387) of `sql.ColumnType` sets the required minimum Go version to 1.8.
|
||||||
|
|
||||||
* sqlx/types.JsonText has been renamed to JSONText to follow Go naming conventions.
|
* sqlx/types.JsonText has been renamed to JSONText to follow Go naming conventions.
|
||||||
|
|
||||||
This breaks backwards compatibility, but it's in a way that is trivially fixable
|
This breaks backwards compatibility, but it's in a way that is trivially fixable
|
||||||
|
|
|
@ -2,6 +2,7 @@ package sqlx
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"database/sql/driver"
|
||||||
"errors"
|
"errors"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -16,6 +17,7 @@ const (
|
||||||
QUESTION
|
QUESTION
|
||||||
DOLLAR
|
DOLLAR
|
||||||
NAMED
|
NAMED
|
||||||
|
AT
|
||||||
)
|
)
|
||||||
|
|
||||||
// BindType returns the bindtype for a given database given a drivername.
|
// BindType returns the bindtype for a given database given a drivername.
|
||||||
|
@ -29,6 +31,8 @@ func BindType(driverName string) int {
|
||||||
return QUESTION
|
return QUESTION
|
||||||
case "oci8", "ora", "goracle":
|
case "oci8", "ora", "goracle":
|
||||||
return NAMED
|
return NAMED
|
||||||
|
case "sqlserver":
|
||||||
|
return AT
|
||||||
}
|
}
|
||||||
return UNKNOWN
|
return UNKNOWN
|
||||||
}
|
}
|
||||||
|
@ -56,6 +60,8 @@ func Rebind(bindType int, query string) string {
|
||||||
rqb = append(rqb, '$')
|
rqb = append(rqb, '$')
|
||||||
case NAMED:
|
case NAMED:
|
||||||
rqb = append(rqb, ':', 'a', 'r', 'g')
|
rqb = append(rqb, ':', 'a', 'r', 'g')
|
||||||
|
case AT:
|
||||||
|
rqb = append(rqb, '@', 'p')
|
||||||
}
|
}
|
||||||
|
|
||||||
j++
|
j++
|
||||||
|
@ -110,6 +116,9 @@ func In(query string, args ...interface{}) (string, []interface{}, error) {
|
||||||
meta := make([]argMeta, len(args))
|
meta := make([]argMeta, len(args))
|
||||||
|
|
||||||
for i, arg := range args {
|
for i, arg := range args {
|
||||||
|
if a, ok := arg.(driver.Valuer); ok {
|
||||||
|
arg, _ = a.Value()
|
||||||
|
}
|
||||||
v := reflect.ValueOf(arg)
|
v := reflect.ValueOf(arg)
|
||||||
t := reflectx.Deref(v.Type())
|
t := reflectx.Deref(v.Type())
|
||||||
|
|
||||||
|
@ -137,7 +146,7 @@ func In(query string, args ...interface{}) (string, []interface{}, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
newArgs := make([]interface{}, 0, flatArgsCount)
|
newArgs := make([]interface{}, 0, flatArgsCount)
|
||||||
buf := bytes.NewBuffer(make([]byte, 0, len(query)+len(", ?")*flatArgsCount))
|
buf := make([]byte, 0, len(query)+len(", ?")*flatArgsCount)
|
||||||
|
|
||||||
var arg, offset int
|
var arg, offset int
|
||||||
|
|
||||||
|
@ -163,10 +172,10 @@ func In(query string, args ...interface{}) (string, []interface{}, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// write everything up to and including our ? character
|
// write everything up to and including our ? character
|
||||||
buf.WriteString(query[:offset+i+1])
|
buf = append(buf, query[:offset+i+1]...)
|
||||||
|
|
||||||
for si := 1; si < argMeta.length; si++ {
|
for si := 1; si < argMeta.length; si++ {
|
||||||
buf.WriteString(", ?")
|
buf = append(buf, ", ?"...)
|
||||||
}
|
}
|
||||||
|
|
||||||
newArgs = appendReflectSlice(newArgs, argMeta.v, argMeta.length)
|
newArgs = appendReflectSlice(newArgs, argMeta.v, argMeta.length)
|
||||||
|
@ -177,13 +186,13 @@ func In(query string, args ...interface{}) (string, []interface{}, error) {
|
||||||
offset = 0
|
offset = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
buf.WriteString(query)
|
buf = append(buf, query...)
|
||||||
|
|
||||||
if arg < len(meta) {
|
if arg < len(meta) {
|
||||||
return "", nil, errors.New("number of bindVars less than number arguments")
|
return "", nil, errors.New("number of bindVars less than number arguments")
|
||||||
}
|
}
|
||||||
|
|
||||||
return buf.String(), newArgs, nil
|
return string(buf), newArgs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func appendReflectSlice(args []interface{}, v reflect.Value, vlen int) []interface{} {
|
func appendReflectSlice(args []interface{}, v reflect.Value, vlen int) []interface{} {
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
module github.com/jmoiron/sqlx
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/go-sql-driver/mysql v1.4.0
|
||||||
|
github.com/lib/pq v1.0.0
|
||||||
|
github.com/mattn/go-sqlite3 v1.9.0
|
||||||
|
)
|
|
@ -0,0 +1,6 @@
|
||||||
|
github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk=
|
||||||
|
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
|
github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A=
|
||||||
|
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
|
github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4=
|
||||||
|
github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
|
|
@ -259,6 +259,10 @@ func compileNamedQuery(qs []byte, bindType int) (query string, names []string, e
|
||||||
}
|
}
|
||||||
inName = true
|
inName = true
|
||||||
name = []byte{}
|
name = []byte{}
|
||||||
|
} else if inName && i > 0 && b == '=' {
|
||||||
|
rebound = append(rebound, ':', '=')
|
||||||
|
inName = false
|
||||||
|
continue
|
||||||
// if we're in a name, and this is an allowed character, continue
|
// if we're in a name, and this is an allowed character, continue
|
||||||
} else if inName && (unicode.IsOneOf(allowedBindRunes, rune(b)) || b == '_' || b == '.') && i != last {
|
} else if inName && (unicode.IsOneOf(allowedBindRunes, rune(b)) || b == '_' || b == '.') && i != last {
|
||||||
// append the byte to the name if we are in a name and not on the last byte
|
// append the byte to the name if we are in a name and not on the last byte
|
||||||
|
@ -287,6 +291,12 @@ func compileNamedQuery(qs []byte, bindType int) (query string, names []string, e
|
||||||
rebound = append(rebound, byte(b))
|
rebound = append(rebound, byte(b))
|
||||||
}
|
}
|
||||||
currentVar++
|
currentVar++
|
||||||
|
case AT:
|
||||||
|
rebound = append(rebound, '@', 'p')
|
||||||
|
for _, b := range strconv.Itoa(currentVar) {
|
||||||
|
rebound = append(rebound, byte(b))
|
||||||
|
}
|
||||||
|
currentVar++
|
||||||
}
|
}
|
||||||
// add this byte to string unless it was not part of the name
|
// add this byte to string unless it was not part of the name
|
||||||
if i != last {
|
if i != last {
|
||||||
|
|
|
@ -471,8 +471,6 @@ func (tx *Tx) Stmtx(stmt interface{}) *Stmt {
|
||||||
s = v.Stmt
|
s = v.Stmt
|
||||||
case *Stmt:
|
case *Stmt:
|
||||||
s = v.Stmt
|
s = v.Stmt
|
||||||
case sql.Stmt:
|
|
||||||
s = &v
|
|
||||||
case *sql.Stmt:
|
case *sql.Stmt:
|
||||||
s = v
|
s = v
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -217,8 +217,6 @@ func (tx *Tx) StmtxContext(ctx context.Context, stmt interface{}) *Stmt {
|
||||||
s = v.Stmt
|
s = v.Stmt
|
||||||
case *Stmt:
|
case *Stmt:
|
||||||
s = v.Stmt
|
s = v.Stmt
|
||||||
case sql.Stmt:
|
|
||||||
s = &v
|
|
||||||
case *sql.Stmt:
|
case *sql.Stmt:
|
||||||
s = v
|
s = v
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
language: go
|
language: go
|
||||||
go:
|
go:
|
||||||
- 1.9.x
|
- 1.11.x
|
||||||
- 1.10.x
|
|
||||||
- tip
|
- tip
|
||||||
|
env:
|
||||||
|
- GO111MODULE=on
|
||||||
install:
|
install:
|
||||||
- make dependency
|
- go get -v golang.org/x/lint/golint
|
||||||
script:
|
script:
|
||||||
- make test
|
- golint -set_exit_status ./...
|
||||||
|
- go test -race -coverprofile=coverage.txt -covermode=atomic ./...
|
||||||
after_success:
|
after_success:
|
||||||
- bash <(curl -s https://codecov.io/bash)
|
- bash <(curl -s https://codecov.io/bash)
|
||||||
matrix:
|
matrix:
|
||||||
|
|
|
@ -1,75 +0,0 @@
|
||||||
# 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/dgrijalva/jwt-go"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "06ea1031745cb8b3dab3f6a236daf2b0aa468b7e"
|
|
||||||
version = "v3.2.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/labstack/gommon"
|
|
||||||
packages = ["bytes","color","log","random"]
|
|
||||||
revision = "6fe1405d73ec4bd4cd8a4ac8e2a2b2bf95d03954"
|
|
||||||
version = "0.2.4"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/mattn/go-colorable"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072"
|
|
||||||
version = "v0.0.9"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/mattn/go-isatty"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39"
|
|
||||||
version = "v0.0.3"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/pmezard/go-difflib"
|
|
||||||
packages = ["difflib"]
|
|
||||||
revision = "792786c7400a136282c1664665ae0a8db921c6c2"
|
|
||||||
version = "v1.0.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/stretchr/testify"
|
|
||||||
packages = ["assert"]
|
|
||||||
revision = "12b6f73e6084dad08a7c6e575284b177ecafbc71"
|
|
||||||
version = "v1.2.1"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/valyala/bytebufferpool"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "e746df99fe4a3986f4d4f79e13c1e0117ce9c2f7"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/valyala/fasttemplate"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "dcecefd839c4193db0d35b88ec65b4c12d360ab0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "golang.org/x/crypto"
|
|
||||||
packages = ["acme","acme/autocert"]
|
|
||||||
revision = "182114d582623c1caa54f73de9c7224e23a48487"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "golang.org/x/sys"
|
|
||||||
packages = ["unix"]
|
|
||||||
revision = "c28acc882ebcbfbe8ce9f0f14b9ac26ee138dd51"
|
|
||||||
|
|
||||||
[solve-meta]
|
|
||||||
analyzer-name = "dep"
|
|
||||||
analyzer-version = 1
|
|
||||||
inputs-digest = "9c7b45e80fe353405800cf01f429b3a203cfb8d4468a04c64a908e11a98ea764"
|
|
||||||
solver-name = "gps-cdcl"
|
|
||||||
solver-version = 1
|
|
|
@ -1,42 +0,0 @@
|
||||||
|
|
||||||
# Gopkg.toml example
|
|
||||||
#
|
|
||||||
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
|
||||||
# for detailed Gopkg.toml documentation.
|
|
||||||
#
|
|
||||||
# required = ["github.com/user/thing/cmd/thing"]
|
|
||||||
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
|
||||||
#
|
|
||||||
# [[constraint]]
|
|
||||||
# name = "github.com/user/project"
|
|
||||||
# version = "1.0.0"
|
|
||||||
#
|
|
||||||
# [[constraint]]
|
|
||||||
# name = "github.com/user/project2"
|
|
||||||
# branch = "dev"
|
|
||||||
# source = "github.com/myfork/project2"
|
|
||||||
#
|
|
||||||
# [[override]]
|
|
||||||
# name = "github.com/x/y"
|
|
||||||
# version = "2.4.0"
|
|
||||||
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/dgrijalva/jwt-go"
|
|
||||||
version = "3.2.0"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/labstack/gommon"
|
|
||||||
version = "0.2.4"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/stretchr/testify"
|
|
||||||
version = "1.2.1"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/valyala/fasttemplate"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "master"
|
|
||||||
name = "golang.org/x/crypto"
|
|
|
@ -1,17 +1,3 @@
|
||||||
DEP_VERSION=0.4.1
|
|
||||||
|
|
||||||
dependency:
|
|
||||||
curl -fsSL -o ${GOPATH}/bin/dep https://github.com/golang/dep/releases/download/v${DEP_VERSION}/dep-linux-amd64
|
|
||||||
chmod +x ${GOPATH}/bin/dep
|
|
||||||
dep ensure
|
|
||||||
|
|
||||||
test:
|
|
||||||
echo "" > coverage.txt
|
|
||||||
for d in $(shell go list ./... | grep -v vendor); do \
|
|
||||||
go test -race -coverprofile=profile.out -covermode=atomic $$d || exit 1; \
|
|
||||||
[ -f profile.out ] && cat profile.out >> coverage.txt && rm profile.out; \
|
|
||||||
done
|
|
||||||
|
|
||||||
tag:
|
tag:
|
||||||
@git tag `grep -P '^\tversion = ' echo.go|cut -f2 -d'"'`
|
@git tag `grep -P '^\tversion = ' echo.go|cut -f2 -d'"'`
|
||||||
@git tag|grep -v ^v
|
@git tag|grep -v ^v
|
||||||
|
|
|
@ -32,7 +32,7 @@ Date: 2018/03/15<br>
|
||||||
Source: https://github.com/vishr/web-framework-benchmark<br>
|
Source: https://github.com/vishr/web-framework-benchmark<br>
|
||||||
Lower is better!
|
Lower is better!
|
||||||
|
|
||||||
<img src="https://api.labstack.com/chart/bar?values=37223,55382,2985,5265|42013,59865,3350,6424&labels=Static,GitHub%20API,Parse%20API,Gplus%20API&titles=Echo,Gin&colors=lightseagreen,goldenrod&x_title=Routes&y_title=ns/op">
|
<img src="https://i.imgur.com/I32VdMJ.png">
|
||||||
|
|
||||||
## [Guide](https://echo.labstack.com/guide)
|
## [Guide](https://echo.labstack.com/guide)
|
||||||
|
|
||||||
|
|
|
@ -31,9 +31,9 @@ type (
|
||||||
func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {
|
func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {
|
||||||
req := c.Request()
|
req := c.Request()
|
||||||
if req.ContentLength == 0 {
|
if req.ContentLength == 0 {
|
||||||
if req.Method == GET || req.Method == DELETE {
|
if req.Method == http.MethodGet || req.Method == http.MethodDelete {
|
||||||
if err = b.bindData(i, c.QueryParams(), "query"); err != nil {
|
if err = b.bindData(i, c.QueryParams(), "query"); err != nil {
|
||||||
return NewHTTPError(http.StatusBadRequest, err.Error())
|
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -44,30 +44,32 @@ func (b *DefaultBinder) Bind(i interface{}, c Context) (err error) {
|
||||||
case strings.HasPrefix(ctype, MIMEApplicationJSON):
|
case strings.HasPrefix(ctype, MIMEApplicationJSON):
|
||||||
if err = json.NewDecoder(req.Body).Decode(i); err != nil {
|
if err = json.NewDecoder(req.Body).Decode(i); err != nil {
|
||||||
if ute, ok := err.(*json.UnmarshalTypeError); ok {
|
if ute, ok := err.(*json.UnmarshalTypeError); ok {
|
||||||
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unmarshal type error: expected=%v, got=%v, offset=%v", ute.Type, ute.Value, ute.Offset))
|
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unmarshal type error: expected=%v, got=%v, field=%v, offset=%v", ute.Type, ute.Value, ute.Field, ute.Offset)).SetInternal(err)
|
||||||
} else if se, ok := err.(*json.SyntaxError); ok {
|
} else if se, ok := err.(*json.SyntaxError); ok {
|
||||||
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: offset=%v, error=%v", se.Offset, se.Error()))
|
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: offset=%v, error=%v", se.Offset, se.Error())).SetInternal(err)
|
||||||
} else {
|
} else {
|
||||||
return NewHTTPError(http.StatusBadRequest, err.Error())
|
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
|
||||||
}
|
}
|
||||||
|
return NewHTTPError(http.StatusBadRequest, err.Error())
|
||||||
}
|
}
|
||||||
case strings.HasPrefix(ctype, MIMEApplicationXML), strings.HasPrefix(ctype, MIMETextXML):
|
case strings.HasPrefix(ctype, MIMEApplicationXML), strings.HasPrefix(ctype, MIMETextXML):
|
||||||
if err = xml.NewDecoder(req.Body).Decode(i); err != nil {
|
if err = xml.NewDecoder(req.Body).Decode(i); err != nil {
|
||||||
if ute, ok := err.(*xml.UnsupportedTypeError); ok {
|
if ute, ok := err.(*xml.UnsupportedTypeError); ok {
|
||||||
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unsupported type error: type=%v, error=%v", ute.Type, ute.Error()))
|
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Unsupported type error: type=%v, error=%v", ute.Type, ute.Error())).SetInternal(err)
|
||||||
} else if se, ok := err.(*xml.SyntaxError); ok {
|
} else if se, ok := err.(*xml.SyntaxError); ok {
|
||||||
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: line=%v, error=%v", se.Line, se.Error()))
|
return NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Syntax error: line=%v, error=%v", se.Line, se.Error())).SetInternal(err)
|
||||||
} else {
|
} else {
|
||||||
return NewHTTPError(http.StatusBadRequest, err.Error())
|
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
|
||||||
}
|
}
|
||||||
|
return NewHTTPError(http.StatusBadRequest, err.Error())
|
||||||
}
|
}
|
||||||
case strings.HasPrefix(ctype, MIMEApplicationForm), strings.HasPrefix(ctype, MIMEMultipartForm):
|
case strings.HasPrefix(ctype, MIMEApplicationForm), strings.HasPrefix(ctype, MIMEMultipartForm):
|
||||||
params, err := c.FormParams()
|
params, err := c.FormParams()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return NewHTTPError(http.StatusBadRequest, err.Error())
|
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
|
||||||
}
|
}
|
||||||
if err = b.bindData(i, params, "form"); err != nil {
|
if err = b.bindData(i, params, "form"); err != nil {
|
||||||
return NewHTTPError(http.StatusBadRequest, err.Error())
|
return NewHTTPError(http.StatusBadRequest, err.Error()).SetInternal(err)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
return ErrUnsupportedMediaType
|
return ErrUnsupportedMediaType
|
||||||
|
@ -96,14 +98,29 @@ func (b *DefaultBinder) bindData(ptr interface{}, data map[string][]string, tag
|
||||||
inputFieldName = typeField.Name
|
inputFieldName = typeField.Name
|
||||||
// If tag is nil, we inspect if the field is a struct.
|
// If tag is nil, we inspect if the field is a struct.
|
||||||
if _, ok := bindUnmarshaler(structField); !ok && structFieldKind == reflect.Struct {
|
if _, ok := bindUnmarshaler(structField); !ok && structFieldKind == reflect.Struct {
|
||||||
err := b.bindData(structField.Addr().Interface(), data, tag)
|
if err := b.bindData(structField.Addr().Interface(), data, tag); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inputValue, exists := data[inputFieldName]
|
inputValue, exists := data[inputFieldName]
|
||||||
|
if !exists {
|
||||||
|
// Go json.Unmarshal supports case insensitive binding. However the
|
||||||
|
// url params are bound case sensitive which is inconsistent. To
|
||||||
|
// fix this we must check all of the map values in a
|
||||||
|
// case-insensitive search.
|
||||||
|
inputFieldName = strings.ToLower(inputFieldName)
|
||||||
|
for k, v := range data {
|
||||||
|
if strings.ToLower(k) == inputFieldName {
|
||||||
|
inputValue = v
|
||||||
|
exists = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !exists {
|
if !exists {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -126,10 +143,9 @@ func (b *DefaultBinder) bindData(ptr interface{}, data map[string][]string, tag
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val.Field(i).Set(slice)
|
val.Field(i).Set(slice)
|
||||||
} else {
|
} else if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil {
|
||||||
if err := setWithProperType(typeField.Type.Kind(), inputValue[0], structField); err != nil {
|
return err
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
|
|
@ -204,6 +204,7 @@ type (
|
||||||
const (
|
const (
|
||||||
defaultMemory = 32 << 20 // 32 MB
|
defaultMemory = 32 << 20 // 32 MB
|
||||||
indexPage = "index.html"
|
indexPage = "index.html"
|
||||||
|
defaultIndent = " "
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *context) writeContentType(value string) {
|
func (c *context) writeContentType(value string) {
|
||||||
|
@ -256,14 +257,13 @@ func (c *context) Scheme() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *context) RealIP() string {
|
func (c *context) RealIP() string {
|
||||||
ra := c.request.RemoteAddr
|
|
||||||
if ip := c.request.Header.Get(HeaderXForwardedFor); ip != "" {
|
if ip := c.request.Header.Get(HeaderXForwardedFor); ip != "" {
|
||||||
ra = strings.Split(ip, ", ")[0]
|
return strings.Split(ip, ", ")[0]
|
||||||
} else if ip := c.request.Header.Get(HeaderXRealIP); ip != "" {
|
|
||||||
ra = ip
|
|
||||||
} else {
|
|
||||||
ra, _, _ = net.SplitHostPort(ra)
|
|
||||||
}
|
}
|
||||||
|
if ip := c.request.Header.Get(HeaderXRealIP); ip != "" {
|
||||||
|
return ip
|
||||||
|
}
|
||||||
|
ra, _, _ := net.SplitHostPort(c.request.RemoteAddr)
|
||||||
return ra
|
return ra
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -404,24 +404,46 @@ func (c *context) String(code int, s string) (err error) {
|
||||||
return c.Blob(code, MIMETextPlainCharsetUTF8, []byte(s))
|
return c.Blob(code, MIMETextPlainCharsetUTF8, []byte(s))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *context) JSON(code int, i interface{}) (err error) {
|
func (c *context) jsonPBlob(code int, callback string, i interface{}) (err error) {
|
||||||
|
enc := json.NewEncoder(c.response)
|
||||||
_, pretty := c.QueryParams()["pretty"]
|
_, pretty := c.QueryParams()["pretty"]
|
||||||
if c.echo.Debug || pretty {
|
if c.echo.Debug || pretty {
|
||||||
return c.JSONPretty(code, i, " ")
|
enc.SetIndent("", " ")
|
||||||
}
|
}
|
||||||
b, err := json.Marshal(i)
|
c.writeContentType(MIMEApplicationJavaScriptCharsetUTF8)
|
||||||
if err != nil {
|
c.response.WriteHeader(code)
|
||||||
|
if _, err = c.response.Write([]byte(callback + "(")); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return c.JSONBlob(code, b)
|
if err = enc.Encode(i); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if _, err = c.response.Write([]byte(");")); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) json(code int, i interface{}, indent string) error {
|
||||||
|
enc := json.NewEncoder(c.response)
|
||||||
|
if indent != "" {
|
||||||
|
enc.SetIndent("", indent)
|
||||||
|
}
|
||||||
|
c.writeContentType(MIMEApplicationJSONCharsetUTF8)
|
||||||
|
c.response.WriteHeader(code)
|
||||||
|
return enc.Encode(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) JSON(code int, i interface{}) (err error) {
|
||||||
|
indent := ""
|
||||||
|
if _, pretty := c.QueryParams()["pretty"]; c.echo.Debug || pretty {
|
||||||
|
indent = defaultIndent
|
||||||
|
}
|
||||||
|
return c.json(code, i, indent)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *context) JSONPretty(code int, i interface{}, indent string) (err error) {
|
func (c *context) JSONPretty(code int, i interface{}, indent string) (err error) {
|
||||||
b, err := json.MarshalIndent(i, "", indent)
|
return c.json(code, i, indent)
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return c.JSONBlob(code, b)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *context) JSONBlob(code int, b []byte) (err error) {
|
func (c *context) JSONBlob(code int, b []byte) (err error) {
|
||||||
|
@ -429,11 +451,7 @@ func (c *context) JSONBlob(code int, b []byte) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *context) JSONP(code int, callback string, i interface{}) (err error) {
|
func (c *context) JSONP(code int, callback string, i interface{}) (err error) {
|
||||||
b, err := json.Marshal(i)
|
return c.jsonPBlob(code, callback, i)
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return c.JSONPBlob(code, callback, b)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *context) JSONPBlob(code int, callback string, b []byte) (err error) {
|
func (c *context) JSONPBlob(code int, callback string, b []byte) (err error) {
|
||||||
|
@ -449,24 +467,29 @@ func (c *context) JSONPBlob(code int, callback string, b []byte) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *context) XML(code int, i interface{}) (err error) {
|
func (c *context) xml(code int, i interface{}, indent string) (err error) {
|
||||||
_, pretty := c.QueryParams()["pretty"]
|
c.writeContentType(MIMEApplicationXMLCharsetUTF8)
|
||||||
if c.echo.Debug || pretty {
|
c.response.WriteHeader(code)
|
||||||
return c.XMLPretty(code, i, " ")
|
enc := xml.NewEncoder(c.response)
|
||||||
|
if indent != "" {
|
||||||
|
enc.Indent("", indent)
|
||||||
}
|
}
|
||||||
b, err := xml.Marshal(i)
|
if _, err = c.response.Write([]byte(xml.Header)); err != nil {
|
||||||
if err != nil {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return c.XMLBlob(code, b)
|
return enc.Encode(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *context) XML(code int, i interface{}) (err error) {
|
||||||
|
indent := ""
|
||||||
|
if _, pretty := c.QueryParams()["pretty"]; c.echo.Debug || pretty {
|
||||||
|
indent = defaultIndent
|
||||||
|
}
|
||||||
|
return c.xml(code, i, indent)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *context) XMLPretty(code int, i interface{}, indent string) (err error) {
|
func (c *context) XMLPretty(code int, i interface{}, indent string) (err error) {
|
||||||
b, err := xml.MarshalIndent(i, "", indent)
|
return c.xml(code, i, indent)
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
return c.XMLBlob(code, b)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *context) XMLBlob(code int, b []byte) (err error) {
|
func (c *context) XMLBlob(code int, b []byte) (err error) {
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue