Start Python rewrite
This commit is contained in:
parent
f06c6dd767
commit
4550fa25f7
167 changed files with 98 additions and 265573 deletions
15
.editorconfig
Normal file
15
.editorconfig
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = tab
|
||||||
|
indent_size = 4
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.py]
|
||||||
|
max_line_length = 99
|
||||||
|
|
||||||
|
[*.{yaml,yml,py}]
|
||||||
|
indent_style = space
|
14
.gitignore
vendored
14
.gitignore
vendored
|
@ -1,6 +1,11 @@
|
||||||
.idea
|
.idea
|
||||||
.vscode
|
.vscode
|
||||||
|
|
||||||
|
.venv
|
||||||
|
pip-selfcheck.json
|
||||||
|
*.pyc
|
||||||
|
__pycache__
|
||||||
|
|
||||||
*.db
|
*.db
|
||||||
*.yaml
|
*.yaml
|
||||||
!example-config.yaml
|
!example-config.yaml
|
||||||
|
@ -8,12 +13,3 @@
|
||||||
logs/
|
logs/
|
||||||
|
|
||||||
plugins/
|
plugins/
|
||||||
|
|
||||||
# Bots under maubot.xyz
|
|
||||||
jesaribot/
|
|
||||||
sed/
|
|
||||||
github/
|
|
||||||
gitlab/
|
|
||||||
rss/
|
|
||||||
factorial/
|
|
||||||
dictionary/
|
|
||||||
|
|
20
Dockerfile
20
Dockerfile
|
@ -1,20 +0,0 @@
|
||||||
FROM golang:1-alpine AS builder
|
|
||||||
|
|
||||||
RUN apk add --no-cache git ca-certificates gcc musl-dev
|
|
||||||
RUN wget -qO /usr/local/bin/dep https://github.com/golang/dep/releases/download/v0.5.0/dep-linux-amd64
|
|
||||||
RUN chmod +x /usr/local/bin/dep
|
|
||||||
|
|
||||||
COPY Gopkg.lock Gopkg.toml /go/src/maubot.xyz/
|
|
||||||
WORKDIR /go/src/maubot.xyz/
|
|
||||||
RUN dep ensure -vendor-only
|
|
||||||
|
|
||||||
COPY . /go/src/maubot.xyz/
|
|
||||||
RUN go build -o /usr/bin/maubot maubot.xyz/cmd/maubot
|
|
||||||
|
|
||||||
|
|
||||||
FROM alpine
|
|
||||||
|
|
||||||
RUN apk add --no-cache ca-certificates
|
|
||||||
COPY --from=builder /usr/bin/maubot /usr/bin/maubot
|
|
||||||
|
|
||||||
CMD ["/usr/bin/maubot", "-c", "/etc/maubot/config.yaml"]
|
|
75
Gopkg.lock
generated
75
Gopkg.lock
generated
|
@ -1,75 +0,0 @@
|
||||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
|
||||||
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/gorilla/context"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "08b5f424b9271eedf6f9f0ce86cb9396ed337a42"
|
|
||||||
version = "v1.1.1"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/gorilla/mux"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "e3702bed27f0d39777b0b37b664b6280e8ef8fbf"
|
|
||||||
version = "v1.6.2"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/mattn/go-sqlite3"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "25ecb14adfc7543176f7d85291ec7dba82c6f7e4"
|
|
||||||
version = "v1.9.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/shurcooL/sanitized_anchor_name"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "86672fcb3f950f35f2e675df2240550f2a50762f"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "golang.org/x/net"
|
|
||||||
packages = [
|
|
||||||
"html",
|
|
||||||
"html/atom"
|
|
||||||
]
|
|
||||||
revision = "26e67e76b6c3f6ce91f7c52def5af501b4e0f3a2"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "gopkg.in/russross/blackfriday.v2"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "cadec560ec52d93835bf2f15bd794700d3a2473b"
|
|
||||||
version = "v2.0.0"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "gopkg.in/yaml.v2"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
|
|
||||||
version = "v2.2.1"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "maunium.net/go/gomatrix"
|
|
||||||
packages = [
|
|
||||||
".",
|
|
||||||
"format"
|
|
||||||
]
|
|
||||||
revision = "920b154a410aeb5a55200d7b21363732abff3502"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "maunium.net/go/mauflag"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "8337821952ba5e919673bd62c502d43474e5e71d"
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "maunium.net/go/maulogger"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "ed98745dedb5f9296c1b2a0ed9424d7347d7e7d4"
|
|
||||||
|
|
||||||
[solve-meta]
|
|
||||||
analyzer-name = "dep"
|
|
||||||
analyzer-version = 1
|
|
||||||
inputs-digest = "6b56fff780b66591381a1d1c4572951bbad3deea30e0774d34721d89adeee379"
|
|
||||||
solver-name = "gps-cdcl"
|
|
||||||
solver-version = 1
|
|
58
Gopkg.toml
58
Gopkg.toml
|
@ -1,58 +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/gorilla/mux"
|
|
||||||
version = "1.6.2"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "github.com/mattn/go-sqlite3"
|
|
||||||
version = "1.9.0"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "master"
|
|
||||||
name = "golang.org/x/net"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "gopkg.in/yaml.v2"
|
|
||||||
version = "2.2.1"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "master"
|
|
||||||
name = "maunium.net/go/gomatrix"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "master"
|
|
||||||
name = "maunium.net/go/mauflag"
|
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
name = "maunium.net/go/maulogger"
|
|
||||||
branch = "master"
|
|
||||||
|
|
||||||
[prune]
|
|
||||||
go-tests = true
|
|
||||||
unused-packages = true
|
|
|
@ -1,5 +1,5 @@
|
||||||
# maubot
|
# maubot
|
||||||
A plugin-based [Matrix](https://matrix.org) bot system written in Go.
|
A plugin-based [Matrix](https://matrix.org) bot system written in Python.
|
||||||
|
|
||||||
Work in progress. Please come back later.
|
Work in progress. Please come back later.
|
||||||
|
|
||||||
|
@ -9,3 +9,5 @@ Matrix room: [#maubot:maunium.net](https://matrix.to/#/#maubot:maunium.net)
|
||||||
## Plugins
|
## Plugins
|
||||||
* [jesaribot](https://github.com/maubot/jesaribot) - A simple bot that replies with an image when you say "jesari".
|
* [jesaribot](https://github.com/maubot/jesaribot) - A simple bot that replies with an image when you say "jesari".
|
||||||
* [sed](https://github.com/maubot/sed) - A bot to do sed-like replacements.
|
* [sed](https://github.com/maubot/sed) - A bot to do sed-like replacements.
|
||||||
|
* [factorial](https://github.com/maubot/factorial) - A bot to calculate unexpected factorials.
|
||||||
|
* [dictionary](https://github.com/maubot/dictionary) - A bot that provides dictionary definitions for words.
|
||||||
|
|
77
app/bot.go
77
app/bot.go
|
@ -1,77 +0,0 @@
|
||||||
// maubot - A plugin-based Matrix bot system written in Go.
|
|
||||||
// Copyright (C) 2018 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"maubot.xyz"
|
|
||||||
"maubot.xyz/config"
|
|
||||||
"maubot.xyz/database"
|
|
||||||
"maubot.xyz/matrix"
|
|
||||||
log "maunium.net/go/maulogger"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Bot struct {
|
|
||||||
Config *config.MainConfig
|
|
||||||
Database *database.Database
|
|
||||||
Clients map[string]*matrix.Client
|
|
||||||
PluginCreators map[string]*maubot.PluginCreator
|
|
||||||
Plugins map[string]*PluginWrapper
|
|
||||||
Server *http.Server
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(config *config.MainConfig) *Bot {
|
|
||||||
return &Bot{
|
|
||||||
Config: config,
|
|
||||||
Clients: make(map[string]*matrix.Client),
|
|
||||||
Plugins: make(map[string]*PluginWrapper),
|
|
||||||
PluginCreators: make(map[string]*maubot.PluginCreator),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bot *Bot) Init() {
|
|
||||||
bot.initDatabase()
|
|
||||||
bot.initClients()
|
|
||||||
bot.initServer()
|
|
||||||
bot.loadPlugins()
|
|
||||||
bot.createPlugins()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bot *Bot) Start() {
|
|
||||||
go bot.startClients()
|
|
||||||
go bot.startServer()
|
|
||||||
bot.startPlugins()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bot *Bot) Stop() {
|
|
||||||
bot.stopPlugins()
|
|
||||||
bot.stopServer()
|
|
||||||
bot.stopClients()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bot *Bot) initDatabase() {
|
|
||||||
log.Debugln("Initializing database")
|
|
||||||
bot.Database = &bot.Config.Database
|
|
||||||
err := bot.Database.Connect()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln("Failed to connect to database:", err)
|
|
||||||
os.Exit(2)
|
|
||||||
}
|
|
||||||
bot.Database.CreateTables()
|
|
||||||
}
|
|
59
app/http.go
59
app/http.go
|
@ -1,59 +0,0 @@
|
||||||
// maubot - A plugin-based Matrix bot system written in Go.
|
|
||||||
// Copyright (C) 2018 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
log "maunium.net/go/maulogger"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (bot *Bot) initServer() {
|
|
||||||
log.Debugln("Initializing HTTP server")
|
|
||||||
r := mux.NewRouter()
|
|
||||||
http.Handle(bot.Config.Server.BasePath, r)
|
|
||||||
bot.Server = &http.Server{
|
|
||||||
Addr: bot.Config.Server.Listen,
|
|
||||||
WriteTimeout: time.Second * 15,
|
|
||||||
ReadTimeout: time.Second * 15,
|
|
||||||
IdleTimeout: time.Second * 60,
|
|
||||||
Handler: r,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bot *Bot) startServer() {
|
|
||||||
log.Debugf("Listening at http://%s%s\n", bot.Server.Addr, bot.Config.Server.BasePath)
|
|
||||||
if err := bot.Server.ListenAndServe(); err != nil {
|
|
||||||
log.Fatalln("HTTP server errored:", err)
|
|
||||||
bot.Server = nil
|
|
||||||
bot.Stop()
|
|
||||||
os.Exit(10)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bot *Bot) stopServer() {
|
|
||||||
if bot.Server != nil {
|
|
||||||
log.Debugln("Stopping HTTP server")
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
|
|
||||||
defer cancel()
|
|
||||||
bot.Server.Shutdown(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,58 +0,0 @@
|
||||||
// maubot - A plugin-based Matrix bot system written in Go.
|
|
||||||
// Copyright (C) 2018 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"maubot.xyz/matrix"
|
|
||||||
log "maunium.net/go/maulogger"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (bot *Bot) initClients() {
|
|
||||||
log.Debugln("Initializing Matrix clients")
|
|
||||||
clients := bot.Database.MatrixClient.GetAll()
|
|
||||||
for _, client := range clients {
|
|
||||||
mxClient, err := matrix.NewClient(client)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to create client to %s as %s: %v\n", client.Homeserver, client.UserID, err)
|
|
||||||
os.Exit(3)
|
|
||||||
}
|
|
||||||
log.Debugln("Initialized user", client.UserID, "with homeserver", client.Homeserver)
|
|
||||||
bot.Clients[client.UserID] = mxClient
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bot *Bot) startClients() {
|
|
||||||
log.Debugln("Starting Matrix syncer")
|
|
||||||
for _, client := range bot.Clients {
|
|
||||||
go func() {
|
|
||||||
client.SetAvatarURL(client.DB.AvatarURL)
|
|
||||||
client.SetDisplayName(client.DB.DisplayName)
|
|
||||||
}()
|
|
||||||
if client.DB.Sync {
|
|
||||||
client.Sync()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bot *Bot) stopClients() {
|
|
||||||
log.Debugln("Stopping Matrix syncers")
|
|
||||||
for _, client := range bot.Clients {
|
|
||||||
client.StopSync()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,81 +0,0 @@
|
||||||
// maubot - A plugin-based Matrix bot system written in Go.
|
|
||||||
// Copyright (C) 2018 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"plugin"
|
|
||||||
|
|
||||||
"maubot.xyz"
|
|
||||||
"maubot.xyz/database"
|
|
||||||
)
|
|
||||||
|
|
||||||
type PluginWrapper struct {
|
|
||||||
maubot.Plugin
|
|
||||||
Creator *maubot.PluginCreator
|
|
||||||
DB *database.Plugin
|
|
||||||
}
|
|
||||||
|
|
||||||
func LoadPlugin(path string) (*maubot.PluginCreator, error) {
|
|
||||||
rawPlugin, err := plugin.Open(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to open: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pluginCreatorSymbol, err := rawPlugin.Lookup("Plugin")
|
|
||||||
if err == nil {
|
|
||||||
pluginCreator, ok := pluginCreatorSymbol.(*maubot.PluginCreator)
|
|
||||||
if ok {
|
|
||||||
pluginCreator.Path = path
|
|
||||||
return pluginCreator, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pluginCreatorFuncSymbol, err := rawPlugin.Lookup("Create")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("symbol \"Create\" not found: %v", err)
|
|
||||||
}
|
|
||||||
pluginCreatorFunc, ok := pluginCreatorFuncSymbol.(maubot.PluginCreatorFunc)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("symbol \"Create\" does not implement maubot.PluginCreator")
|
|
||||||
}
|
|
||||||
|
|
||||||
nameSymbol, err := rawPlugin.Lookup("Name")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("symbol \"Name\" not found: %v", err)
|
|
||||||
}
|
|
||||||
name, ok := nameSymbol.(string)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("symbol \"Name\" is not a string")
|
|
||||||
}
|
|
||||||
|
|
||||||
versionSymbol, err := rawPlugin.Lookup("Version")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("symbol \"Version\" not found: %v", err)
|
|
||||||
}
|
|
||||||
version, ok := versionSymbol.(string)
|
|
||||||
if !ok {
|
|
||||||
return nil, fmt.Errorf("symbol \"Version\" is not a string")
|
|
||||||
}
|
|
||||||
|
|
||||||
return &maubot.PluginCreator{
|
|
||||||
Create: pluginCreatorFunc,
|
|
||||||
Name: name,
|
|
||||||
Version: version,
|
|
||||||
Path: path,
|
|
||||||
}, nil
|
|
||||||
}
|
|
112
app/plugins.go
112
app/plugins.go
|
@ -1,112 +0,0 @@
|
||||||
// maubot - A plugin-based Matrix bot system written in Go.
|
|
||||||
// Copyright (C) 2018 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
log "maunium.net/go/maulogger"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (bot *Bot) loadPlugin(dir, fileName string) {
|
|
||||||
ext := fileName[len(fileName)-4:]
|
|
||||||
if ext != ".mbp" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
path := filepath.Join(dir, fileName)
|
|
||||||
|
|
||||||
pluginCreator, err := LoadPlugin(path)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to load plugin at %s: %v\n", path, err)
|
|
||||||
os.Exit(4)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, exists := bot.PluginCreators[pluginCreator.Name]
|
|
||||||
if exists {
|
|
||||||
log.Debugf("Skipping plugin at %s: plugin with same name already loaded", path)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
bot.PluginCreators[pluginCreator.Name] = pluginCreator
|
|
||||||
log.Debugf("Loaded plugin creator %s v%s\n", pluginCreator.Name, pluginCreator.Version)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bot *Bot) loadPlugins() {
|
|
||||||
for _, dir := range bot.Config.PluginDirs {
|
|
||||||
files, err := ioutil.ReadDir(dir)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Failed to read plugin directory %s: %v\n", dir, err)
|
|
||||||
os.Exit(4)
|
|
||||||
}
|
|
||||||
for _, file := range files {
|
|
||||||
bot.loadPlugin(dir, file.Name())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bot *Bot) createPlugins() {
|
|
||||||
log.Debugln("Creating plugin instances")
|
|
||||||
plugins := bot.Database.Plugin.GetAll()
|
|
||||||
for _, plugin := range plugins {
|
|
||||||
if !plugin.Enabled {
|
|
||||||
log.Debugln("Skipping disabled plugin", plugin.ID)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
creator, ok := bot.PluginCreators[plugin.Type]
|
|
||||||
if !ok {
|
|
||||||
log.Errorln("Plugin creator", plugin.Type, "for", plugin.ID, "not found, disabling plugin...")
|
|
||||||
plugin.Enabled = false
|
|
||||||
plugin.Update()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
client, ok := bot.Clients[plugin.UserID]
|
|
||||||
if !ok {
|
|
||||||
log.Errorln("Client", plugin.UserID, "for", plugin.ID, "not found, disabling plugin...")
|
|
||||||
plugin.Enabled = false
|
|
||||||
plugin.Update()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("Created plugin %s (type %s v%s)\n", plugin.ID, creator.Name, creator.Version)
|
|
||||||
bot.Plugins[plugin.ID] = &PluginWrapper{
|
|
||||||
Plugin: creator.Create(client.Proxy(plugin.ID), log.Sub(plugin.ID)),
|
|
||||||
Creator: creator,
|
|
||||||
DB: plugin,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bot *Bot) startPlugins() {
|
|
||||||
log.Debugln("Starting plugin instances...")
|
|
||||||
for _, plugin := range bot.Plugins {
|
|
||||||
log.Debugf("Starting plugin %s (type %s v%s)\n", plugin.DB.ID, plugin.Creator.Name, plugin.Creator.Version)
|
|
||||||
go plugin.Start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (bot *Bot) stopPlugins() {
|
|
||||||
log.Debugln("Stopping plugin instances...")
|
|
||||||
for _, plugin := range bot.Plugins {
|
|
||||||
log.Debugf("Stopping plugin %s (type %s v%s)\n", plugin.DB.ID, plugin.Creator.Name, plugin.Creator.Version)
|
|
||||||
plugin.Stop()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,70 +0,0 @@
|
||||||
// maubot - A plugin-based Matrix bot system written in Go.
|
|
||||||
// Copyright (C) 2018 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"syscall"
|
|
||||||
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
|
||||||
"maubot.xyz/app"
|
|
||||||
"maubot.xyz/config"
|
|
||||||
flag "maunium.net/go/mauflag"
|
|
||||||
log "maunium.net/go/maulogger"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
flag.SetHelpTitles("maubot - A plugin-based Matrix bot system written in Go.", "maubot [-c /path/to/config] [-h]")
|
|
||||||
configPath := flag.MakeFull("c", "config", "The path to the main config file", "maubot.yaml").String()
|
|
||||||
wantHelp, _ := flag.MakeHelpFlag()
|
|
||||||
|
|
||||||
err := flag.Parse()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
flag.PrintHelp()
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if *wantHelp {
|
|
||||||
flag.PrintHelp()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg := &config.MainConfig{}
|
|
||||||
err = cfg.Load(*configPath)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Failed to load config:", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
cfg.Logging.Configure(log.DefaultLogger)
|
|
||||||
log.OpenFile()
|
|
||||||
log.Debugln("Logger configured")
|
|
||||||
|
|
||||||
bot := app.New(cfg)
|
|
||||||
bot.Init()
|
|
||||||
bot.Start()
|
|
||||||
|
|
||||||
c := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
|
||||||
<-c
|
|
||||||
log.Debugln("Interrupt received, stopping components...")
|
|
||||||
bot.Stop()
|
|
||||||
log.Debugln("Components stopped, bye!")
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
168
commands.go
168
commands.go
|
@ -1,168 +0,0 @@
|
||||||
// maubot - A plugin-based Matrix bot system written in Go.
|
|
||||||
// Copyright (C) 2018 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package maubot
|
|
||||||
|
|
||||||
type CommandHandler func(*Event) CommandHandlerResult
|
|
||||||
|
|
||||||
type CommandSpec struct {
|
|
||||||
Commands []Command `json:"commands,omitempty"`
|
|
||||||
PassiveCommands []PassiveCommand `json:"passive_commands,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (spec *CommandSpec) Clone() *CommandSpec {
|
|
||||||
return &CommandSpec{
|
|
||||||
Commands: append([]Command(nil), spec.Commands...),
|
|
||||||
PassiveCommands: append([]PassiveCommand(nil), spec.PassiveCommands...),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (spec *CommandSpec) Merge(otherSpecs ...*CommandSpec) {
|
|
||||||
for _, otherSpec := range otherSpecs {
|
|
||||||
spec.Commands = append(spec.Commands, otherSpec.Commands...)
|
|
||||||
spec.PassiveCommands = append(spec.PassiveCommands, otherSpec.PassiveCommands...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (spec *CommandSpec) Equals(otherSpec *CommandSpec) bool {
|
|
||||||
if otherSpec == nil ||
|
|
||||||
len(spec.Commands) != len(otherSpec.Commands) ||
|
|
||||||
len(spec.PassiveCommands) != len(otherSpec.PassiveCommands) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for index, cmd := range spec.Commands {
|
|
||||||
otherCmd := otherSpec.Commands[index]
|
|
||||||
if !cmd.Equals(otherCmd) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for index, cmd := range spec.PassiveCommands {
|
|
||||||
otherCmd := otherSpec.PassiveCommands[index]
|
|
||||||
if !cmd.Equals(otherCmd) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
type Command struct {
|
|
||||||
Syntax string `json:"syntax"`
|
|
||||||
Description string `json:"description,omitempty"`
|
|
||||||
Arguments ArgumentMap `json:"arguments"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cmd Command) Equals(otherCmd Command) bool {
|
|
||||||
return cmd.Syntax == otherCmd.Syntax &&
|
|
||||||
cmd.Description == otherCmd.Description &&
|
|
||||||
cmd.Arguments.Equals(otherCmd.Arguments)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ArgumentMap map[string]Argument
|
|
||||||
|
|
||||||
func (argMap ArgumentMap) Equals(otherMap ArgumentMap) bool {
|
|
||||||
if len(argMap) != len(otherMap) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for name, argument := range argMap {
|
|
||||||
otherArgument, ok := otherMap[name]
|
|
||||||
if !ok || !argument.Equals(otherArgument) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
type Argument struct {
|
|
||||||
Matches string `json:"matches"`
|
|
||||||
Required bool `json:"required"`
|
|
||||||
Description string `json:"description,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (arg Argument) Equals(otherArg Argument) bool {
|
|
||||||
return arg.Matches == otherArg.Matches &&
|
|
||||||
arg.Required == otherArg.Required &&
|
|
||||||
arg.Description == otherArg.Description
|
|
||||||
}
|
|
||||||
|
|
||||||
// Common PassiveCommand MatchAgainst targets.
|
|
||||||
const (
|
|
||||||
MatchAgainstBody = "body"
|
|
||||||
)
|
|
||||||
|
|
||||||
// JSONLeftEquals checks if the given JSON-parsed interfaces are equal.
|
|
||||||
// Extra properties in the right interface are ignored.
|
|
||||||
func JSONLeftEquals(left, right interface{}) bool {
|
|
||||||
switch val := left.(type) {
|
|
||||||
case nil:
|
|
||||||
return right == nil
|
|
||||||
case bool:
|
|
||||||
rightVal, ok := right.(bool)
|
|
||||||
return ok && rightVal
|
|
||||||
case float64:
|
|
||||||
rightVal, ok := right.(float64)
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return val == rightVal
|
|
||||||
case string:
|
|
||||||
rightVal, ok := right.(string)
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return val == rightVal
|
|
||||||
case []interface{}:
|
|
||||||
rightVal, ok := right.([]interface{})
|
|
||||||
if !ok || len(val) != len(rightVal) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for index, leftChild := range val {
|
|
||||||
rightChild := rightVal[index]
|
|
||||||
if !JSONLeftEquals(leftChild, rightChild) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case map[string]interface{}:
|
|
||||||
rightVal, ok := right.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for key, leftChild := range val {
|
|
||||||
rightChild, ok := rightVal[key]
|
|
||||||
if !ok || !JSONLeftEquals(leftChild, rightChild) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
type PassiveCommand struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Matches string `json:"matches"`
|
|
||||||
MatchAgainst string `json:"match_against"`
|
|
||||||
MatchEvent interface{} `json:"match_event"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cmd PassiveCommand) Equals(otherCmd PassiveCommand) bool {
|
|
||||||
return cmd.Name == otherCmd.Name &&
|
|
||||||
cmd.Matches == otherCmd.Matches &&
|
|
||||||
cmd.MatchAgainst == otherCmd.MatchAgainst &&
|
|
||||||
(cmd.MatchEvent != nil && JSONLeftEquals(cmd.MatchEvent, otherCmd.MatchEvent) || otherCmd.MatchEvent == nil)
|
|
||||||
}
|
|
|
@ -1,54 +0,0 @@
|
||||||
// maubot - A plugin-based Matrix bot system written in Go.
|
|
||||||
// Copyright (C) 2018 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io/ioutil"
|
|
||||||
|
|
||||||
"gopkg.in/yaml.v2"
|
|
||||||
"maubot.xyz/database"
|
|
||||||
)
|
|
||||||
|
|
||||||
type MainConfig struct {
|
|
||||||
Logging LogConfig `yaml:"logging"`
|
|
||||||
Database database.Database `yaml:"database"`
|
|
||||||
Server ServerConfig `yaml:"server"`
|
|
||||||
PluginDirs []string `yaml:"plugin_directories"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (config *MainConfig) Load(path string) error {
|
|
||||||
data, err := ioutil.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return yaml.Unmarshal(data, config)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (config *MainConfig) Save(path string) error {
|
|
||||||
data, err := yaml.Marshal(config)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return ioutil.WriteFile(path, data, 0644)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServerConfig struct {
|
|
||||||
Listen string `yaml:"listen"`
|
|
||||||
BasePath string `yaml:"base_path"`
|
|
||||||
}
|
|
|
@ -1,122 +0,0 @@
|
||||||
// maubot - A plugin-based Matrix bot system written in Go.
|
|
||||||
// Copyright (C) 2018 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
|
||||||
"text/template"
|
|
||||||
|
|
||||||
"maunium.net/go/maulogger"
|
|
||||||
)
|
|
||||||
|
|
||||||
// LogConfig contains configs for the logger.
|
|
||||||
type LogConfig struct {
|
|
||||||
Directory string `yaml:"directory"`
|
|
||||||
FileNameFormat string `yaml:"file_name_format"`
|
|
||||||
FileDateFormat string `yaml:"file_date_format"`
|
|
||||||
FileMode uint32 `yaml:"file_mode"`
|
|
||||||
TimestampFormat string `yaml:"timestamp_format"`
|
|
||||||
RawPrintLevel string `yaml:"print_level"`
|
|
||||||
PrintLevel int `yaml:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type umLogConfig LogConfig
|
|
||||||
|
|
||||||
func (lc *LogConfig) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
|
||||||
err := unmarshal((*umLogConfig)(lc))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch strings.ToUpper(lc.RawPrintLevel) {
|
|
||||||
case "DEBUG":
|
|
||||||
lc.PrintLevel = maulogger.LevelDebug.Severity
|
|
||||||
case "INFO":
|
|
||||||
lc.PrintLevel = maulogger.LevelInfo.Severity
|
|
||||||
case "WARN", "WARNING":
|
|
||||||
lc.PrintLevel = maulogger.LevelWarn.Severity
|
|
||||||
case "ERR", "ERROR":
|
|
||||||
lc.PrintLevel = maulogger.LevelError.Severity
|
|
||||||
case "FATAL":
|
|
||||||
lc.PrintLevel = maulogger.LevelFatal.Severity
|
|
||||||
default:
|
|
||||||
return errors.New("invalid print level " + lc.RawPrintLevel)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lc *LogConfig) MarshalYAML() (interface{}, error) {
|
|
||||||
switch {
|
|
||||||
case lc.PrintLevel >= maulogger.LevelFatal.Severity:
|
|
||||||
lc.RawPrintLevel = maulogger.LevelFatal.Name
|
|
||||||
case lc.PrintLevel >= maulogger.LevelError.Severity:
|
|
||||||
lc.RawPrintLevel = maulogger.LevelError.Name
|
|
||||||
case lc.PrintLevel >= maulogger.LevelWarn.Severity:
|
|
||||||
lc.RawPrintLevel = maulogger.LevelWarn.Name
|
|
||||||
case lc.PrintLevel >= maulogger.LevelInfo.Severity:
|
|
||||||
lc.RawPrintLevel = maulogger.LevelInfo.Name
|
|
||||||
default:
|
|
||||||
lc.RawPrintLevel = maulogger.LevelDebug.Name
|
|
||||||
}
|
|
||||||
return lc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateLogConfig creates a basic LogConfig.
|
|
||||||
func CreateLogConfig() LogConfig {
|
|
||||||
return LogConfig{
|
|
||||||
Directory: "./logs",
|
|
||||||
FileNameFormat: "{{.Date}}-{{.Index}}.log",
|
|
||||||
TimestampFormat: "Jan _2, 2006 15:04:05",
|
|
||||||
FileMode: 0600,
|
|
||||||
FileDateFormat: "2006-01-02",
|
|
||||||
PrintLevel: 10,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type FileFormatData struct {
|
|
||||||
Date string
|
|
||||||
Index int
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFileFormat returns a mauLogger-compatible logger file format based on the data in the struct.
|
|
||||||
func (lc LogConfig) GetFileFormat() maulogger.LoggerFileFormat {
|
|
||||||
os.MkdirAll(lc.Directory, 0700)
|
|
||||||
path := filepath.Join(lc.Directory, lc.FileNameFormat)
|
|
||||||
tpl, _ := template.New("fileformat").Parse(path)
|
|
||||||
|
|
||||||
return func(now string, i int) string {
|
|
||||||
var buf strings.Builder
|
|
||||||
tpl.Execute(&buf, FileFormatData{
|
|
||||||
Date: now,
|
|
||||||
Index: i,
|
|
||||||
})
|
|
||||||
return buf.String()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Configure configures a mauLogger instance with the data in this struct.
|
|
||||||
func (lc LogConfig) Configure(log maulogger.Logger) {
|
|
||||||
basicLogger := log.(*maulogger.BasicLogger)
|
|
||||||
basicLogger.FileFormat = lc.GetFileFormat()
|
|
||||||
basicLogger.FileMode = os.FileMode(lc.FileMode)
|
|
||||||
basicLogger.FileTimeFormat = lc.FileDateFormat
|
|
||||||
basicLogger.TimeFormat = lc.TimestampFormat
|
|
||||||
basicLogger.PrintLevel = lc.PrintLevel
|
|
||||||
}
|
|
|
@ -1,161 +0,0 @@
|
||||||
// maubot - A plugin-based Matrix bot system written in Go.
|
|
||||||
// Copyright (C) 2018 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package database
|
|
||||||
|
|
||||||
import (
|
|
||||||
"maubot.xyz"
|
|
||||||
log "maunium.net/go/maulogger"
|
|
||||||
"database/sql"
|
|
||||||
"sort"
|
|
||||||
)
|
|
||||||
|
|
||||||
type MatrixClient struct {
|
|
||||||
db *Database
|
|
||||||
sql *sql.DB
|
|
||||||
|
|
||||||
UserID string `json:"user_id"`
|
|
||||||
Homeserver string `json:"homeserver"`
|
|
||||||
AccessToken string `json:"access_token"`
|
|
||||||
NextBatch string `json:"next_batch"`
|
|
||||||
FilterID string `json:"filter_id"`
|
|
||||||
|
|
||||||
Sync bool `json:"sync"`
|
|
||||||
AutoJoinRooms bool `json:"auto_join_rooms"`
|
|
||||||
DisplayName string `json:"display_name"`
|
|
||||||
AvatarURL string `json:"avatar_url"`
|
|
||||||
|
|
||||||
CommandSpecs map[string]*CommandSpec `json:"command_specs"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type MatrixClientStatic struct {
|
|
||||||
db *Database
|
|
||||||
sql *sql.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mcs *MatrixClientStatic) CreateTable() error {
|
|
||||||
_, err := mcs.sql.Exec(`CREATE TABLE IF NOT EXISTS matrix_client (
|
|
||||||
user_id VARCHAR(255) PRIMARY KEY,
|
|
||||||
homeserver VARCHAR(255) NOT NULL,
|
|
||||||
access_token VARCHAR(255) NOT NULL,
|
|
||||||
next_batch VARCHAR(255) NOT NULL,
|
|
||||||
filter_id VARCHAR(255) NOT NULL,
|
|
||||||
|
|
||||||
sync BOOLEAN NOT NULL,
|
|
||||||
autojoin BOOLEAN NOT NULL,
|
|
||||||
display_name VARCHAR(255) NOT NULL,
|
|
||||||
avatar_url VARCHAR(255) NOT NULL
|
|
||||||
)`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mcs *MatrixClientStatic) Get(userID string) *MatrixClient {
|
|
||||||
row := mcs.sql.QueryRow("SELECT user_id, homeserver, access_token, next_batch, filter_id, sync, autojoin, display_name, avatar_url FROM matrix_client WHERE user_id=?", userID)
|
|
||||||
if row != nil {
|
|
||||||
return mcs.New().Scan(row)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mcs *MatrixClientStatic) GetAll() (clients []*MatrixClient) {
|
|
||||||
rows, err := mcs.sql.Query("SELECT user_id, homeserver, access_token, next_batch, filter_id, sync, autojoin, display_name, avatar_url FROM matrix_client")
|
|
||||||
if err != nil || rows == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
for rows.Next() {
|
|
||||||
clients = append(clients, mcs.New().Scan(rows))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mcs *MatrixClientStatic) New() *MatrixClient {
|
|
||||||
return &MatrixClient{
|
|
||||||
db: mcs.db,
|
|
||||||
sql: mcs.sql,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mxc *MatrixClient) Scan(row Scannable) *MatrixClient {
|
|
||||||
err := row.Scan(&mxc.UserID, &mxc.Homeserver, &mxc.AccessToken, &mxc.NextBatch, &mxc.FilterID, &mxc.Sync, &mxc.AutoJoinRooms, &mxc.DisplayName, &mxc.AvatarURL)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorln("MatrixClient scan failed:", err)
|
|
||||||
return mxc
|
|
||||||
}
|
|
||||||
mxc.LoadCommandSpecs()
|
|
||||||
return mxc
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mxc *MatrixClient) SetCommandSpec(owner string, newSpec *maubot.CommandSpec) bool {
|
|
||||||
spec, ok := mxc.CommandSpecs[owner]
|
|
||||||
if ok && newSpec.Equals(spec.CommandSpec) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if spec == nil {
|
|
||||||
spec = mxc.db.CommandSpec.New()
|
|
||||||
spec.CommandSpec = newSpec
|
|
||||||
spec.Insert()
|
|
||||||
} else {
|
|
||||||
spec.CommandSpec = newSpec
|
|
||||||
spec.Update()
|
|
||||||
}
|
|
||||||
mxc.CommandSpecs[owner] = spec
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mxc *MatrixClient) LoadCommandSpecs() *MatrixClient {
|
|
||||||
specs := mxc.db.CommandSpec.GetAllByClient(mxc.UserID)
|
|
||||||
mxc.CommandSpecs = make(map[string]*CommandSpec)
|
|
||||||
for _, spec := range specs {
|
|
||||||
mxc.CommandSpecs[spec.Owner] = spec
|
|
||||||
}
|
|
||||||
return mxc
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mxc *MatrixClient) CommandSpecIDs() []string {
|
|
||||||
keys := make([]string, len(mxc.CommandSpecs))
|
|
||||||
i := 0
|
|
||||||
for key := range mxc.CommandSpecs {
|
|
||||||
keys[i] = key
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
sort.Strings(keys)
|
|
||||||
return keys
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mxc *MatrixClient) Commands() *maubot.CommandSpec {
|
|
||||||
if len(mxc.CommandSpecs) == 0 {
|
|
||||||
return &maubot.CommandSpec{}
|
|
||||||
}
|
|
||||||
specIDs := mxc.CommandSpecIDs()
|
|
||||||
spec := mxc.CommandSpecs[specIDs[0]].Clone()
|
|
||||||
for _, specID := range specIDs[1:] {
|
|
||||||
spec.Merge(mxc.CommandSpecs[specID].CommandSpec)
|
|
||||||
}
|
|
||||||
return spec
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mxc *MatrixClient) Insert() error {
|
|
||||||
_, err := mxc.sql.Exec("INSERT INTO matrix_client (user_id, homeserver, access_token, next_batch, filter_id, sync, autojoin, display_name, avatar_url) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
|
||||||
mxc.UserID, mxc.Homeserver, mxc.AccessToken, mxc.NextBatch, mxc.FilterID, mxc.Sync, mxc.AutoJoinRooms, mxc.DisplayName, mxc.AvatarURL)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mxc *MatrixClient) Update() error {
|
|
||||||
_, err := mxc.sql.Exec("UPDATE matrix_client SET access_token=?, next_batch=?, filter_id=?, sync=?, autojoin=?, display_name=?, avatar_url=? WHERE user_id=?",
|
|
||||||
mxc.AccessToken, mxc.NextBatch, mxc.FilterID, mxc.Sync, mxc.AutoJoinRooms, mxc.DisplayName, mxc.AvatarURL, mxc.UserID)
|
|
||||||
return err
|
|
||||||
}
|
|
|
@ -1,136 +0,0 @@
|
||||||
// maubot - A plugin-based Matrix bot system written in Go.
|
|
||||||
// Copyright (C) 2018 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package database
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"maubot.xyz"
|
|
||||||
log "maunium.net/go/maulogger"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CommandSpec struct {
|
|
||||||
db *Database
|
|
||||||
sql *sql.DB
|
|
||||||
|
|
||||||
*maubot.CommandSpec
|
|
||||||
Owner string `json:"owner"`
|
|
||||||
Client string `json:"client"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CommandSpecStatic struct {
|
|
||||||
db *Database
|
|
||||||
sql *sql.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
func (css *CommandSpecStatic) CreateTable() error {
|
|
||||||
_, err := css.sql.Exec(`CREATE TABLE IF NOT EXISTS command_spec (
|
|
||||||
owner VARCHAR(255),
|
|
||||||
client VARCHAR(255),
|
|
||||||
spec TEXT,
|
|
||||||
|
|
||||||
PRIMARY KEY (owner, client),
|
|
||||||
FOREIGN KEY (owner) REFERENCES plugin(id)
|
|
||||||
ON DELETE CASCADE ON UPDATE CASCADE,
|
|
||||||
FOREIGN KEY (client) REFERENCES matrix_client(user_id)
|
|
||||||
ON DELETE CASCADE ON UPDATE CASCADE
|
|
||||||
)`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (css *CommandSpecStatic) Get(owner, client string) *CommandSpec {
|
|
||||||
rows, err := css.sql.Query("SELECT * FROM command_spec WHERE owner=? AND client=?", owner, client)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Failed to Get(%s, %s): %v\n", owner, client, err)
|
|
||||||
}
|
|
||||||
return css.New().Scan(rows)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (css *CommandSpecStatic) GetOrCreate(owner, client string) (spec *CommandSpec) {
|
|
||||||
spec = css.Get(owner, client)
|
|
||||||
if spec == nil {
|
|
||||||
spec = css.New()
|
|
||||||
spec.Owner = owner
|
|
||||||
spec.Client = client
|
|
||||||
spec.Insert()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (css *CommandSpecStatic) getAllByQuery(query string, args ...interface{}) (specs []*CommandSpec) {
|
|
||||||
rows, err := css.sql.Query(query, args...)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("Failed to getAllByQuery(%s): %v\n", query, err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
for rows.Next() {
|
|
||||||
specs = append(specs, css.New().Scan(rows))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (css *CommandSpecStatic) GetAllByOwner(owner string) []*CommandSpec {
|
|
||||||
return css.getAllByQuery("SELECT * FROM command_spec WHERE owner=?", owner)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (css *CommandSpecStatic) GetAllByClient(client string) []*CommandSpec {
|
|
||||||
return css.getAllByQuery("SELECT * FROM command_spec WHERE client=?", client)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (css *CommandSpecStatic) New() *CommandSpec {
|
|
||||||
return &CommandSpec{
|
|
||||||
db: css.db,
|
|
||||||
sql: css.sql,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cs *CommandSpec) Scan(row Scannable) *CommandSpec {
|
|
||||||
var spec string
|
|
||||||
err := row.Scan(&cs.Owner, &cs.Client, &spec)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorln("CommandSpec scan failed:", err)
|
|
||||||
return cs
|
|
||||||
}
|
|
||||||
cs.CommandSpec = &maubot.CommandSpec{}
|
|
||||||
err = json.Unmarshal([]byte(spec), cs.CommandSpec)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorln("CommandSpec parse failed:", err)
|
|
||||||
}
|
|
||||||
return cs
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cs *CommandSpec) Insert() error {
|
|
||||||
data, err := json.Marshal(cs.CommandSpec)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = cs.sql.Exec("INSERT INTO command_spec (owner, client, spec) VALUES (?, ?, ?)",
|
|
||||||
cs.Owner, cs.Client, string(data))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cs *CommandSpec) Update() error {
|
|
||||||
data, err := json.Marshal(cs.CommandSpec)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = cs.sql.Exec("UPDATE command_spec SET spec=? WHERE owner=? AND client=?",
|
|
||||||
string(data), cs.Owner, cs.Client)
|
|
||||||
return err
|
|
||||||
}
|
|
|
@ -1,74 +0,0 @@
|
||||||
// maubot - A plugin-based Matrix bot system written in Go.
|
|
||||||
// Copyright (C) 2018 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package database
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
log "maunium.net/go/maulogger"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Scannable interface {
|
|
||||||
Scan(...interface{}) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type Database struct {
|
|
||||||
Type string `yaml:"type"`
|
|
||||||
Name string `yaml:"name"`
|
|
||||||
|
|
||||||
MatrixClient *MatrixClientStatic `yaml:"-"`
|
|
||||||
Plugin *PluginStatic `yaml:"-"`
|
|
||||||
CommandSpec *CommandSpecStatic `yaml:"-"`
|
|
||||||
|
|
||||||
sql *sql.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *Database) Connect() (err error) {
|
|
||||||
db.sql, err = sql.Open(db.Type, db.Name)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
db.MatrixClient = &MatrixClientStatic{db: db, sql: db.sql}
|
|
||||||
db.Plugin = &PluginStatic{db: db, sql: db.sql}
|
|
||||||
db.CommandSpec = &CommandSpecStatic{db: db, sql: db.sql}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *Database) CreateTables() {
|
|
||||||
log.Debugln("Creating database tables")
|
|
||||||
|
|
||||||
err := db.MatrixClient.CreateTable()
|
|
||||||
if err != nil {
|
|
||||||
log.Errorln("Failed to create matrix_client table:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = db.Plugin.CreateTable()
|
|
||||||
if err != nil {
|
|
||||||
log.Errorln("Failed to create plugin table:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = db.CommandSpec.CreateTable()
|
|
||||||
if err != nil {
|
|
||||||
log.Errorln("Failed to create command_spec table:", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (db *Database) SQL() *sql.DB {
|
|
||||||
return db.sql
|
|
||||||
}
|
|
|
@ -1,105 +0,0 @@
|
||||||
// maubot - A plugin-based Matrix bot system written in Go.
|
|
||||||
// Copyright (C) 2018 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package database
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
|
|
||||||
log "maunium.net/go/maulogger"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Plugin struct {
|
|
||||||
db *Database
|
|
||||||
sql *sql.DB
|
|
||||||
|
|
||||||
ID string `json:"id"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
Enabled bool `json:"enabled"`
|
|
||||||
UserID string `json:"user_id"`
|
|
||||||
//User *MatrixClient `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type PluginStatic struct {
|
|
||||||
db *Database
|
|
||||||
sql *sql.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ps *PluginStatic) CreateTable() error {
|
|
||||||
_, err := ps.sql.Exec(`CREATE TABLE IF NOT EXISTS plugin (
|
|
||||||
id VARCHAR(255) PRIMARY KEY,
|
|
||||||
type VARCHAR(255) NOT NULL,
|
|
||||||
enabled BOOLEAN NOT NULL,
|
|
||||||
|
|
||||||
user_id VARCHAR(255) NOT NULL,
|
|
||||||
|
|
||||||
FOREIGN KEY (user_id) REFERENCES matrix_client(user_id)
|
|
||||||
ON DELETE RESTRICT ON UPDATE CASCADE
|
|
||||||
)`)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ps *PluginStatic) Get(id string) *Plugin {
|
|
||||||
row := ps.sql.QueryRow("SELECT * FROM plugin WHERE id=?", id)
|
|
||||||
if row != nil {
|
|
||||||
return ps.New().Scan(row)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ps *PluginStatic) GetAll() (plugins []*Plugin) {
|
|
||||||
rows, err := ps.sql.Query("SELECT * FROM plugin")
|
|
||||||
if err != nil || rows == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
defer rows.Close()
|
|
||||||
for rows.Next() {
|
|
||||||
plugins = append(plugins, ps.New().Scan(rows))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ps *PluginStatic) New() *Plugin {
|
|
||||||
return &Plugin{
|
|
||||||
db: ps.db,
|
|
||||||
sql: ps.sql,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*func (p *Plugin) LoadUser() *Plugin {
|
|
||||||
p.User = p.db.MatrixClient.Get(p.UserID)
|
|
||||||
return p
|
|
||||||
}*/
|
|
||||||
|
|
||||||
func (p *Plugin) Scan(row Scannable) *Plugin {
|
|
||||||
err := row.Scan(&p.ID, &p.Type, &p.Enabled, &p.UserID)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorln("Plugin scan failed:", err)
|
|
||||||
}
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Plugin) Insert() error {
|
|
||||||
_, err := p.sql.Exec("INSERT INTO plugin (id, type, enabled, user_id) VALUES (?, ?, ?, ?)",
|
|
||||||
p.ID, p.Type, p.Enabled, p.UserID)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Plugin) Update() error {
|
|
||||||
_, err := p.sql.Exec("UPDATE plugin SET enabled=? WHERE id=?",
|
|
||||||
p.Enabled, p.ID)
|
|
||||||
return err
|
|
||||||
}
|
|
|
@ -1,19 +1,37 @@
|
||||||
database:
|
database: sqlite:///maubot.db
|
||||||
type: sqlite3
|
|
||||||
name: maubot.db
|
|
||||||
|
|
||||||
logging:
|
|
||||||
directory: ./logs
|
|
||||||
file_mode: 0600
|
|
||||||
print_level: DEBUG
|
|
||||||
file_name_format: "{{.Date}}-{{.Index}}.log"
|
|
||||||
file_date_format: 2006-01-02
|
|
||||||
timestamp_format: Jan _2, 2006 15:04:05
|
|
||||||
|
|
||||||
# If multiple directories have a plugin with the same name, the first directory is used.
|
# If multiple directories have a plugin with the same name, the first directory is used.
|
||||||
plugin_directories:
|
plugin_directories:
|
||||||
- ./plugins
|
- ./plugins
|
||||||
|
|
||||||
server:
|
server:
|
||||||
listen: 0.0.0.0:29316
|
listen: 0.0.0.0:29316
|
||||||
base_path: /_matrix/maubot
|
base_path: /_matrix/maubot
|
||||||
|
|
||||||
|
# Python logging configuration.
|
||||||
|
#
|
||||||
|
# See section 16.7.2 of the Python documentation for more info:
|
||||||
|
# https://docs.python.org/3.6/library/logging.config.html#configuration-dictionary-schema
|
||||||
|
logging:
|
||||||
|
version: 1
|
||||||
|
formatters:
|
||||||
|
precise:
|
||||||
|
format: "[%(asctime)s] [%(levelname)s@%(name)s] %(message)s"
|
||||||
|
handlers:
|
||||||
|
file:
|
||||||
|
class: logging.handlers.RotatingFileHandler
|
||||||
|
formatter: precise
|
||||||
|
filename: ./maubot.log
|
||||||
|
maxBytes: 10485760
|
||||||
|
backupCount: 10
|
||||||
|
console:
|
||||||
|
class: logging.StreamHandler
|
||||||
|
formatter: precise
|
||||||
|
loggers:
|
||||||
|
maubot:
|
||||||
|
level: DEBUG
|
||||||
|
aiohttp:
|
||||||
|
level: INFO
|
||||||
|
root:
|
||||||
|
level: DEBUG
|
||||||
|
handlers: [file, console]
|
41
logging.go
41
logging.go
|
@ -1,41 +0,0 @@
|
||||||
// maubot - A plugin-based Matrix bot system written in Go.
|
|
||||||
// Copyright (C) 2018 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package maubot
|
|
||||||
|
|
||||||
type Logger interface {
|
|
||||||
Write(p []byte) (n int, err error)
|
|
||||||
Debug(parts ...interface{})
|
|
||||||
Debugln(parts ...interface{})
|
|
||||||
Debugf(message string, args ...interface{})
|
|
||||||
Debugfln(message string, args ...interface{})
|
|
||||||
Info(parts ...interface{})
|
|
||||||
Infoln(parts ...interface{})
|
|
||||||
Infof(message string, args ...interface{})
|
|
||||||
Infofln(message string, args ...interface{})
|
|
||||||
Warn(parts ...interface{})
|
|
||||||
Warnln(parts ...interface{})
|
|
||||||
Warnf(message string, args ...interface{})
|
|
||||||
Warnfln(message string, args ...interface{})
|
|
||||||
Error(parts ...interface{})
|
|
||||||
Errorln(parts ...interface{})
|
|
||||||
Errorf(message string, args ...interface{})
|
|
||||||
Errorfln(message string, args ...interface{})
|
|
||||||
Fatal(parts ...interface{})
|
|
||||||
Fatalln(parts ...interface{})
|
|
||||||
Fatalf(message string, args ...interface{})
|
|
||||||
Fatalfln(message string, args ...interface{})
|
|
||||||
}
|
|
125
matrix.go
125
matrix.go
|
@ -1,125 +0,0 @@
|
||||||
// maubot - A plugin-based Matrix bot system written in Go.
|
|
||||||
// Copyright (C) 2018 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package maubot
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
|
|
||||||
"maunium.net/go/gomatrix"
|
|
||||||
)
|
|
||||||
|
|
||||||
type EventHandler func(*Event) EventHandlerResult
|
|
||||||
type EventHandlerResult int
|
|
||||||
type CommandHandlerResult = EventHandlerResult
|
|
||||||
|
|
||||||
const (
|
|
||||||
Continue EventHandlerResult = iota
|
|
||||||
StopEventPropagation
|
|
||||||
StopCommandPropagation CommandHandlerResult = iota
|
|
||||||
)
|
|
||||||
|
|
||||||
type GomatrixClient interface {
|
|
||||||
//d <method> = disabled
|
|
||||||
//r <method> = replaced
|
|
||||||
|
|
||||||
BanUser(roomID string, req *gomatrix.ReqBanUser) (resp *gomatrix.RespBanUser, err error)
|
|
||||||
//d BuildBaseURL(urlPath ...string) string
|
|
||||||
//d BuildURL(urlPath ...string) string
|
|
||||||
//d BuildURLWithQuery(urlPath []string, urlQuery map[string]string) string
|
|
||||||
//d ClearCredentials()
|
|
||||||
//d CreateFilter(filter json.RawMessage) (resp *gomatrix.RespCreateFilter, err error)
|
|
||||||
CreateRoom(req *gomatrix.ReqCreateRoom) (resp *gomatrix.RespCreateRoom, err error)
|
|
||||||
Download(mxcURL string) (io.ReadCloser, error)
|
|
||||||
DownloadBytes(mxcURL string) ([]byte, error)
|
|
||||||
ForgetRoom(roomID string) (resp *gomatrix.RespForgetRoom, err error)
|
|
||||||
GetAvatarURL() (url string, err error)
|
|
||||||
GetDisplayName(mxid string) (resp *gomatrix.RespUserDisplayName, err error)
|
|
||||||
//r GetEvent(roomID, eventID string) (resp *gomatrix.Event, err error)
|
|
||||||
GetOwnDisplayName() (resp *gomatrix.RespUserDisplayName, err error)
|
|
||||||
InviteUser(roomID string, req *gomatrix.ReqInviteUser) (resp *gomatrix.RespInviteUser, err error)
|
|
||||||
InviteUserByThirdParty(roomID string, req *gomatrix.ReqInvite3PID) (resp *gomatrix.RespInviteUser, err error)
|
|
||||||
//r JoinRoom(roomIDorAlias, serverName string, content interface{}) (resp *gomatrix.RespJoinRoom, err error)
|
|
||||||
JoinedMembers(roomID string) (resp *gomatrix.RespJoinedMembers, err error)
|
|
||||||
JoinedRooms() (resp *gomatrix.RespJoinedRooms, err error)
|
|
||||||
KickUser(roomID string, req *gomatrix.ReqKickUser) (resp *gomatrix.RespKickUser, err error)
|
|
||||||
LeaveRoom(roomID string) (resp *gomatrix.RespLeaveRoom, err error)
|
|
||||||
//d Login(req *gomatrix.ReqLogin) (resp *gomatrix.RespLogin, err error)
|
|
||||||
//d Logout() (resp *gomatrix.RespLogout, err error)
|
|
||||||
MakeRequest(method string, httpURL string, reqBody interface{}, resBody interface{}) ([]byte, error)
|
|
||||||
MarkRead(roomID, eventID string) (err error)
|
|
||||||
Messages(roomID, from, to string, dir rune, limit int) (resp *gomatrix.RespMessages, err error)
|
|
||||||
RedactEvent(roomID, eventID string, req *gomatrix.ReqRedact) (resp *gomatrix.RespSendEvent, err error)
|
|
||||||
//d Register(req *gomatrix.ReqRegister) (*gomatrix.RespRegister, *gomatrix.RespUserInteractive, error)
|
|
||||||
//d RegisterDummy(req *gomatrix.ReqRegister) (*gomatrix.RespRegister, error)
|
|
||||||
//d RegisterGuest(req *gomatrix.ReqRegister) (*gomatrix.RespRegister, *gomatrix.RespUserInteractive, error)
|
|
||||||
SendImage(roomID, body, url string) (*gomatrix.RespSendEvent, error)
|
|
||||||
//SendMassagedMessageEvent(roomID string, eventType gomatrix.EventType, contentJSON interface{}, ts int64) (resp *gomatrix.RespSendEvent, err error)
|
|
||||||
//SendMassagedStateEvent(roomID string, eventType gomatrix.EventType, stateKey string, contentJSON interface{}, ts int64) (resp *gomatrix.RespSendEvent, err error)
|
|
||||||
//r SendMessageEvent(roomID string, eventType gomatrix.EventType, contentJSON interface{}) (resp *gomatrix.RespSendEvent, err error)
|
|
||||||
SendNotice(roomID, text string) (*gomatrix.RespSendEvent, error)
|
|
||||||
SendStateEvent(roomID string, eventType gomatrix.EventType, stateKey string, contentJSON interface{}) (resp *gomatrix.RespSendEvent, err error)
|
|
||||||
SendText(roomID, text string) (*gomatrix.RespSendEvent, error)
|
|
||||||
SendVideo(roomID, body, url string) (*gomatrix.RespSendEvent, error)
|
|
||||||
SetAvatarURL(url string) (err error)
|
|
||||||
SetCredentials(userID, accessToken string)
|
|
||||||
SetDisplayName(displayName string) (err error)
|
|
||||||
SetPresence(status string) (err error)
|
|
||||||
StateEvent(roomID string, eventType gomatrix.EventType, stateKey string, outContent interface{}) (err error)
|
|
||||||
//d StopSync()
|
|
||||||
//d Sync() error
|
|
||||||
//d SyncRequest(timeout int, since, filterID string, fullState bool, setPresence string) (resp *gomatrix.RespSync, err error)
|
|
||||||
TurnServer() (resp *gomatrix.RespTurnServer, err error)
|
|
||||||
UnbanUser(roomID string, req *gomatrix.ReqUnbanUser) (resp *gomatrix.RespUnbanUser, err error)
|
|
||||||
Upload(content io.Reader, contentType string, contentLength int64) (*gomatrix.RespMediaUpload, error)
|
|
||||||
UploadBytes(data []byte, contentType string) (*gomatrix.RespMediaUpload, error)
|
|
||||||
UploadLink(link string) (*gomatrix.RespMediaUpload, error)
|
|
||||||
UserTyping(roomID string, typing bool, timeout int64) (resp *gomatrix.RespTyping, err error)
|
|
||||||
Versions() (resp *gomatrix.RespVersions, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type MBMatrixClient interface {
|
|
||||||
AddEventHandler(gomatrix.EventType, EventHandler)
|
|
||||||
AddCommandHandler(string, CommandHandler)
|
|
||||||
SetCommandSpec(*CommandSpec)
|
|
||||||
|
|
||||||
GetEvent(roomID, eventID string) *Event
|
|
||||||
JoinRoom(roomIDOrAlias string) (resp *gomatrix.RespJoinRoom, err error)
|
|
||||||
SendMessage(roomID, text string) (eventID string, err error)
|
|
||||||
SendMessagef(roomID, text string, args ...interface{}) (eventID string, err error)
|
|
||||||
SendContent(roomID string, content gomatrix.Content) (eventID string, err error)
|
|
||||||
SendMessageEvent(roomID string, evtType gomatrix.EventType, content interface{}) (eventID string, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type MatrixClient interface {
|
|
||||||
GomatrixClient
|
|
||||||
MBMatrixClient
|
|
||||||
}
|
|
||||||
|
|
||||||
type EventFuncs interface {
|
|
||||||
MarkRead() error
|
|
||||||
Reply(string) (string, error)
|
|
||||||
ReplyContent(gomatrix.Content) (string, error)
|
|
||||||
SendMessage(string) (string, error)
|
|
||||||
SendMessagef(string, ...interface{}) (string, error)
|
|
||||||
SendContent(gomatrix.Content) (string, error)
|
|
||||||
SendMessageEvent(evtType gomatrix.EventType, content interface{}) (eventID string, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Event struct {
|
|
||||||
EventFuncs
|
|
||||||
*gomatrix.Event
|
|
||||||
}
|
|
|
@ -1,208 +0,0 @@
|
||||||
// maubot - A plugin-based Matrix bot system written in Go.
|
|
||||||
// Copyright (C) 2018 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package matrix
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"maunium.net/go/gomatrix"
|
|
||||||
log "maunium.net/go/maulogger"
|
|
||||||
|
|
||||||
"maubot.xyz"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ParsedCommand struct {
|
|
||||||
Name string
|
|
||||||
IsPassive bool
|
|
||||||
Arguments []string
|
|
||||||
StartsWith string
|
|
||||||
Matches *regexp.Regexp
|
|
||||||
MatchAgainst string
|
|
||||||
MatchesEvent interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pc *ParsedCommand) parseCommandSyntax(command maubot.Command) error {
|
|
||||||
regexBuilder := &strings.Builder{}
|
|
||||||
swBuilder := &strings.Builder{}
|
|
||||||
argumentEncountered := false
|
|
||||||
|
|
||||||
regexBuilder.WriteString("^!")
|
|
||||||
swBuilder.WriteRune('!')
|
|
||||||
words := strings.Split(command.Syntax, " ")
|
|
||||||
for i, word := range words {
|
|
||||||
argument, ok := command.Arguments[word]
|
|
||||||
// TODO enable $ check?
|
|
||||||
if ok && len(word) > 0 /*&& word[0] == '$'*/ {
|
|
||||||
argumentEncountered = true
|
|
||||||
regex := argument.Matches
|
|
||||||
if !argument.Required {
|
|
||||||
regex = fmt.Sprintf("(?:%s)?", regex)
|
|
||||||
} else {
|
|
||||||
regex = fmt.Sprintf("(%s)", regex)
|
|
||||||
}
|
|
||||||
pc.Arguments = append(pc.Arguments, word)
|
|
||||||
regexBuilder.WriteString(regex)
|
|
||||||
} else {
|
|
||||||
if !argumentEncountered {
|
|
||||||
swBuilder.WriteString(word)
|
|
||||||
}
|
|
||||||
regexBuilder.WriteString(regexp.QuoteMeta(word))
|
|
||||||
}
|
|
||||||
|
|
||||||
if i < len(words)-1 {
|
|
||||||
if !argumentEncountered {
|
|
||||||
swBuilder.WriteRune(' ')
|
|
||||||
}
|
|
||||||
regexBuilder.WriteRune(' ')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
regexBuilder.WriteRune('$')
|
|
||||||
|
|
||||||
var err error
|
|
||||||
pc.StartsWith = swBuilder.String()
|
|
||||||
// Trim the extra space at the end added in the parse loop
|
|
||||||
pc.StartsWith = pc.StartsWith[:len(pc.StartsWith)-1]
|
|
||||||
pc.Matches, err = regexp.Compile(regexBuilder.String())
|
|
||||||
pc.MatchAgainst = "body"
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pc *ParsedCommand) parsePassiveCommandSyntax(command maubot.PassiveCommand) error {
|
|
||||||
pc.MatchAgainst = command.MatchAgainst
|
|
||||||
var err error
|
|
||||||
pc.Matches, err = regexp.Compile(command.Matches)
|
|
||||||
pc.MatchesEvent = command.MatchEvent
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseSpec(spec *maubot.CommandSpec) (commands []*ParsedCommand) {
|
|
||||||
for _, command := range spec.Commands {
|
|
||||||
parsing := &ParsedCommand{
|
|
||||||
Name: command.Syntax,
|
|
||||||
IsPassive: false,
|
|
||||||
}
|
|
||||||
err := parsing.parseCommandSyntax(command)
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Failed to parse regex of command %s: %v\n", command.Syntax, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
commands = append(commands, parsing)
|
|
||||||
}
|
|
||||||
for _, command := range spec.PassiveCommands {
|
|
||||||
parsing := &ParsedCommand{
|
|
||||||
Name: command.Name,
|
|
||||||
IsPassive: true,
|
|
||||||
}
|
|
||||||
err := parsing.parsePassiveCommandSyntax(command)
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Failed to parse regex of passive command %s: %v\n", command.Name, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
commands = append(commands, parsing)
|
|
||||||
}
|
|
||||||
return commands
|
|
||||||
}
|
|
||||||
|
|
||||||
func deepGet(from map[string]interface{}, path string) interface{} {
|
|
||||||
for {
|
|
||||||
dotIndex := strings.IndexRune(path, '.')
|
|
||||||
if dotIndex == -1 {
|
|
||||||
return from[path]
|
|
||||||
}
|
|
||||||
|
|
||||||
var key string
|
|
||||||
key, path = path[:dotIndex], path[dotIndex+1:]
|
|
||||||
var ok bool
|
|
||||||
from, ok = from[key].(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pc *ParsedCommand) MatchActive(evt *gomatrix.Event) bool {
|
|
||||||
if !strings.HasPrefix(evt.Content.Body, pc.StartsWith) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
match := pc.Matches.FindStringSubmatch(evt.Content.Body)
|
|
||||||
if match == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// First element is whole content
|
|
||||||
match = match[1:]
|
|
||||||
|
|
||||||
command := &gomatrix.MatchedCommand{
|
|
||||||
Arguments: make(map[string]string),
|
|
||||||
}
|
|
||||||
for i, value := range match {
|
|
||||||
if i >= len(pc.Arguments) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
key := pc.Arguments[i]
|
|
||||||
command.Arguments[key] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
command.Matched = pc.Name
|
|
||||||
// TODO add evt.Content.Command.Target?
|
|
||||||
evt.Content.Command = command
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pc *ParsedCommand) MatchPassive(evt *gomatrix.Event) bool {
|
|
||||||
matchAgainst := evt.Content.Body
|
|
||||||
switch pc.MatchAgainst {
|
|
||||||
case maubot.MatchAgainstBody:
|
|
||||||
matchAgainst = evt.Content.Body
|
|
||||||
case "formatted_body":
|
|
||||||
matchAgainst = evt.Content.FormattedBody
|
|
||||||
default:
|
|
||||||
matchAgainstDirect, ok := deepGet(evt.Content.Raw, pc.MatchAgainst).(string)
|
|
||||||
if ok {
|
|
||||||
matchAgainst = matchAgainstDirect
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if pc.MatchesEvent != nil && !maubot.JSONLeftEquals(pc.MatchesEvent, evt) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
matches := pc.Matches.FindAllStringSubmatch(matchAgainst, -1)
|
|
||||||
if matches == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if evt.Unsigned.PassiveCommand == nil {
|
|
||||||
evt.Unsigned.PassiveCommand = make(map[string]*gomatrix.MatchedPassiveCommand)
|
|
||||||
}
|
|
||||||
evt.Unsigned.PassiveCommand[pc.Name] = &gomatrix.MatchedPassiveCommand{
|
|
||||||
Captured: matches,
|
|
||||||
}
|
|
||||||
//evt.Unsigned.PassiveCommand.Matched = pc.Name
|
|
||||||
//evt.Unsigned.PassiveCommand.Captured = matches
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pc *ParsedCommand) Match(evt *gomatrix.Event) bool {
|
|
||||||
if pc.IsPassive {
|
|
||||||
return pc.MatchPassive(evt)
|
|
||||||
} else {
|
|
||||||
return pc.MatchActive(evt)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,73 +0,0 @@
|
||||||
// maubot - A plugin-based Matrix bot system written in Go.
|
|
||||||
// Copyright (C) 2018 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package matrix
|
|
||||||
|
|
||||||
import (
|
|
||||||
"maubot.xyz"
|
|
||||||
"maunium.net/go/gomatrix"
|
|
||||||
"maunium.net/go/gomatrix/format"
|
|
||||||
)
|
|
||||||
|
|
||||||
type EventFuncsImpl struct {
|
|
||||||
*gomatrix.Event
|
|
||||||
Client *Client
|
|
||||||
}
|
|
||||||
|
|
||||||
func (client *Client) ParseEvent(mxEvent *gomatrix.Event) *maubot.Event {
|
|
||||||
if mxEvent == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
mxEvent.Content.RemoveReplyFallback()
|
|
||||||
return &maubot.Event{
|
|
||||||
EventFuncs: &EventFuncsImpl{
|
|
||||||
Event: mxEvent,
|
|
||||||
Client: client,
|
|
||||||
},
|
|
||||||
Event: mxEvent,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (evt *EventFuncsImpl) MarkRead() error {
|
|
||||||
return evt.Client.MarkRead(evt.RoomID, evt.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (evt *EventFuncsImpl) Reply(text string) (string, error) {
|
|
||||||
content := format.RenderMarkdown(text)
|
|
||||||
content.MsgType = gomatrix.MsgNotice
|
|
||||||
return evt.ReplyContent(content)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (evt *EventFuncsImpl) ReplyContent(content gomatrix.Content) (string, error) {
|
|
||||||
content.SetReply(evt.Event)
|
|
||||||
return evt.SendContent(content)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (evt *EventFuncsImpl) SendMessage(text string) (string, error) {
|
|
||||||
return evt.Client.SendMessage(evt.RoomID, text)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (evt *EventFuncsImpl) SendMessagef(text string, args ...interface{}) (string, error) {
|
|
||||||
return evt.Client.SendMessagef(evt.RoomID, text, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (evt *EventFuncsImpl) SendContent(content gomatrix.Content) (string, error) {
|
|
||||||
return evt.Client.SendContent(evt.RoomID, content)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (evt *EventFuncsImpl) SendMessageEvent(evtType gomatrix.EventType, content interface{}) (eventID string, err error) {
|
|
||||||
return evt.Client.SendMessageEvent(evt.RoomID, evtType, content)
|
|
||||||
}
|
|
|
@ -1,229 +0,0 @@
|
||||||
// maubot - A plugin-based Matrix bot system written in Go.
|
|
||||||
// Copyright (C) 2018 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package matrix
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"golang.org/x/net/html"
|
|
||||||
)
|
|
||||||
|
|
||||||
var matrixToURL = regexp.MustCompile("^(?:https?://)?(?:www\\.)?matrix\\.to/#/([#@!].*)")
|
|
||||||
|
|
||||||
type htmlParser struct{}
|
|
||||||
|
|
||||||
type taggedString struct {
|
|
||||||
string
|
|
||||||
tag string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (parser *htmlParser) getAttribute(node *html.Node, attribute string) string {
|
|
||||||
for _, attr := range node.Attr {
|
|
||||||
if attr.Key == attribute {
|
|
||||||
return attr.Val
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func digits(num int) int {
|
|
||||||
return int(math.Floor(math.Log10(float64(num))) + 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (parser *htmlParser) listToString(node *html.Node, stripLinebreak bool) string {
|
|
||||||
ordered := node.Data == "ol"
|
|
||||||
taggedChildren := parser.nodeToTaggedStrings(node.FirstChild, stripLinebreak)
|
|
||||||
counter := 1
|
|
||||||
indentLength := 0
|
|
||||||
if ordered {
|
|
||||||
start := parser.getAttribute(node, "start")
|
|
||||||
if len(start) > 0 {
|
|
||||||
counter, _ = strconv.Atoi(start)
|
|
||||||
}
|
|
||||||
|
|
||||||
longestIndex := (counter - 1) + len(taggedChildren)
|
|
||||||
indentLength = digits(longestIndex)
|
|
||||||
}
|
|
||||||
indent := strings.Repeat(" ", indentLength+2)
|
|
||||||
var children []string
|
|
||||||
for _, child := range taggedChildren {
|
|
||||||
if child.tag != "li" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
var prefix string
|
|
||||||
if ordered {
|
|
||||||
indexPadding := indentLength - digits(counter)
|
|
||||||
prefix = fmt.Sprintf("%d. %s", counter, strings.Repeat(" ", indexPadding))
|
|
||||||
} else {
|
|
||||||
prefix = "● "
|
|
||||||
}
|
|
||||||
str := prefix + child.string
|
|
||||||
counter++
|
|
||||||
parts := strings.Split(str, "\n")
|
|
||||||
for i, part := range parts[1:] {
|
|
||||||
parts[i+1] = indent + part
|
|
||||||
}
|
|
||||||
str = strings.Join(parts, "\n")
|
|
||||||
children = append(children, str)
|
|
||||||
}
|
|
||||||
return strings.Join(children, "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (parser *htmlParser) basicFormatToString(node *html.Node, stripLinebreak bool) string {
|
|
||||||
str := parser.nodeToTagAwareString(node.FirstChild, stripLinebreak)
|
|
||||||
switch node.Data {
|
|
||||||
case "b", "strong":
|
|
||||||
return fmt.Sprintf("**%s**", str)
|
|
||||||
case "i", "em":
|
|
||||||
return fmt.Sprintf("_%s_", str)
|
|
||||||
case "s", "del":
|
|
||||||
return fmt.Sprintf("~~%s~~", str)
|
|
||||||
}
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
|
|
||||||
func (parser *htmlParser) headerToString(node *html.Node, stripLinebreak bool) string {
|
|
||||||
children := parser.nodeToStrings(node.FirstChild, stripLinebreak)
|
|
||||||
length := int(node.Data[1] - '0')
|
|
||||||
prefix := strings.Repeat("#", length) + " "
|
|
||||||
return prefix + strings.Join(children, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (parser *htmlParser) blockquoteToString(node *html.Node, stripLinebreak bool) string {
|
|
||||||
str := parser.nodeToTagAwareString(node.FirstChild, stripLinebreak)
|
|
||||||
childrenArr := strings.Split(strings.TrimSpace(str), "\n")
|
|
||||||
for index, child := range childrenArr {
|
|
||||||
childrenArr[index] = "> " + child
|
|
||||||
}
|
|
||||||
return strings.Join(childrenArr, "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (parser *htmlParser) linkToString(node *html.Node, stripLinebreak bool) string {
|
|
||||||
str := parser.nodeToTagAwareString(node.FirstChild, stripLinebreak)
|
|
||||||
href := parser.getAttribute(node, "href")
|
|
||||||
if len(href) == 0 {
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
match := matrixToURL.FindStringSubmatch(href)
|
|
||||||
if len(match) == 2 {
|
|
||||||
// pillTarget := match[1]
|
|
||||||
// if pillTarget[0] == '@' {
|
|
||||||
// if member := parser.room.GetMember(pillTarget); member != nil {
|
|
||||||
// return member.DisplayName
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return pillTarget
|
|
||||||
return str
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s (%s)", str, href)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (parser *htmlParser) tagToString(node *html.Node, stripLinebreak bool) string {
|
|
||||||
switch node.Data {
|
|
||||||
case "blockquote":
|
|
||||||
return parser.blockquoteToString(node, stripLinebreak)
|
|
||||||
case "ol", "ul":
|
|
||||||
return parser.listToString(node, stripLinebreak)
|
|
||||||
case "h1", "h2", "h3", "h4", "h5", "h6":
|
|
||||||
return parser.headerToString(node, stripLinebreak)
|
|
||||||
case "br":
|
|
||||||
return "\n"
|
|
||||||
case "b", "strong", "i", "em", "s", "del", "u", "ins":
|
|
||||||
return parser.basicFormatToString(node, stripLinebreak)
|
|
||||||
case "a":
|
|
||||||
return parser.linkToString(node, stripLinebreak)
|
|
||||||
case "p":
|
|
||||||
return parser.nodeToTagAwareString(node.FirstChild, stripLinebreak) + "\n"
|
|
||||||
case "pre":
|
|
||||||
return parser.nodeToString(node.FirstChild, false)
|
|
||||||
default:
|
|
||||||
return parser.nodeToTagAwareString(node.FirstChild, stripLinebreak)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (parser *htmlParser) singleNodeToString(node *html.Node, stripLinebreak bool) taggedString {
|
|
||||||
switch node.Type {
|
|
||||||
case html.TextNode:
|
|
||||||
if stripLinebreak {
|
|
||||||
node.Data = strings.Replace(node.Data, "\n", "", -1)
|
|
||||||
}
|
|
||||||
return taggedString{node.Data, "text"}
|
|
||||||
case html.ElementNode:
|
|
||||||
return taggedString{parser.tagToString(node, stripLinebreak), node.Data}
|
|
||||||
case html.DocumentNode:
|
|
||||||
return taggedString{parser.nodeToTagAwareString(node.FirstChild, stripLinebreak), "html"}
|
|
||||||
default:
|
|
||||||
return taggedString{"", "unknown"}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (parser *htmlParser) nodeToTaggedStrings(node *html.Node, stripLinebreak bool) (strs []taggedString) {
|
|
||||||
for ; node != nil; node = node.NextSibling {
|
|
||||||
strs = append(strs, parser.singleNodeToString(node, stripLinebreak))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var BlockTags = []string{"p", "h1", "h2", "h3", "h4", "h5", "h6", "ol", "ul", "pre", "blockquote", "div", "hr", "table"}
|
|
||||||
|
|
||||||
func (parser *htmlParser) isBlockTag(tag string) bool {
|
|
||||||
for _, blockTag := range BlockTags {
|
|
||||||
if tag == blockTag {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (parser *htmlParser) nodeToTagAwareString(node *html.Node, stripLinebreak bool) string {
|
|
||||||
strs := parser.nodeToTaggedStrings(node, stripLinebreak)
|
|
||||||
var output strings.Builder
|
|
||||||
for _, str := range strs {
|
|
||||||
tstr := str.string
|
|
||||||
if parser.isBlockTag(str.tag) {
|
|
||||||
tstr = fmt.Sprintf("\n%s\n", tstr)
|
|
||||||
}
|
|
||||||
output.WriteString(tstr)
|
|
||||||
}
|
|
||||||
return strings.TrimSpace(output.String())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (parser *htmlParser) nodeToStrings(node *html.Node, stripLinebreak bool) (strs []string) {
|
|
||||||
for ; node != nil; node = node.NextSibling {
|
|
||||||
strs = append(strs, parser.singleNodeToString(node, stripLinebreak).string)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (parser *htmlParser) nodeToString(node *html.Node, stripLinebreak bool) string {
|
|
||||||
return strings.Join(parser.nodeToStrings(node, stripLinebreak), "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (parser *htmlParser) Parse(htmlData string) string {
|
|
||||||
node, _ := html.Parse(strings.NewReader(htmlData))
|
|
||||||
return parser.nodeToTagAwareString(node, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
func HTMLToText(html string) string {
|
|
||||||
html = strings.Replace(html, "\t", " ", -1)
|
|
||||||
str := (&htmlParser{}).Parse(html)
|
|
||||||
return str
|
|
||||||
}
|
|
191
matrix/matrix.go
191
matrix/matrix.go
|
@ -1,191 +0,0 @@
|
||||||
// maubot - A plugin-based Matrix bot system written in Go.
|
|
||||||
// Copyright (C) 2018 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package matrix
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"maubot.xyz"
|
|
||||||
"maubot.xyz/database"
|
|
||||||
"maunium.net/go/gomatrix"
|
|
||||||
"maunium.net/go/gomatrix/format"
|
|
||||||
log "maunium.net/go/maulogger"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Client struct {
|
|
||||||
*gomatrix.Client
|
|
||||||
syncer *MaubotSyncer
|
|
||||||
handlers map[string][]maubot.CommandHandler
|
|
||||||
commands []*ParsedCommand
|
|
||||||
DB *database.MatrixClient
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewClient(db *database.MatrixClient) (*Client, error) {
|
|
||||||
mxClient, err := gomatrix.NewClient(db.Homeserver, db.UserID, db.AccessToken)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
client := &Client{
|
|
||||||
Client: mxClient,
|
|
||||||
handlers: make(map[string][]maubot.CommandHandler),
|
|
||||||
commands: ParseSpec(db.Commands()),
|
|
||||||
DB: db,
|
|
||||||
}
|
|
||||||
|
|
||||||
client.syncer = NewMaubotSyncer(client, client.Store)
|
|
||||||
client.Client.Syncer = client.syncer
|
|
||||||
|
|
||||||
client.AddEventHandler(gomatrix.StateMember, client.onJoin)
|
|
||||||
client.AddEventHandler(gomatrix.EventMessage, client.onMessage)
|
|
||||||
|
|
||||||
return client, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (client *Client) Proxy(owner string) *ClientProxy {
|
|
||||||
return &ClientProxy{
|
|
||||||
hiddenClient: client,
|
|
||||||
owner: owner,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (client *Client) AddEventHandler(evt gomatrix.EventType, handler maubot.EventHandler) {
|
|
||||||
client.syncer.OnEventType(evt, func(evt *maubot.Event) maubot.EventHandlerResult {
|
|
||||||
if evt.Sender == client.UserID {
|
|
||||||
return maubot.StopEventPropagation
|
|
||||||
}
|
|
||||||
return handler(evt)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (client *Client) AddCommandHandler(owner, evt string, handler maubot.CommandHandler) {
|
|
||||||
log.Debugln("Registering command handler for event", evt, "by", owner)
|
|
||||||
list, ok := client.handlers[evt]
|
|
||||||
if !ok {
|
|
||||||
list = []maubot.CommandHandler{handler}
|
|
||||||
} else {
|
|
||||||
list = append(list, handler)
|
|
||||||
}
|
|
||||||
client.handlers[evt] = list
|
|
||||||
}
|
|
||||||
|
|
||||||
func (client *Client) SetCommandSpec(owner string, spec *maubot.CommandSpec) {
|
|
||||||
log.Debugln("Registering command spec for", owner, "on", client.UserID)
|
|
||||||
changed := client.DB.SetCommandSpec(owner, spec)
|
|
||||||
if changed {
|
|
||||||
client.commands = ParseSpec(client.DB.Commands())
|
|
||||||
log.Debugln("Command spec of", owner, "on", client.UserID, "updated.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (client *Client) GetEvent(roomID, eventID string) *maubot.Event {
|
|
||||||
evt, err := client.Client.GetEvent(roomID, eventID)
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("Failed to get event %s @ %s: %v\n", eventID, roomID, err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return client.ParseEvent(evt)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (client *Client) TriggerCommand(command *ParsedCommand, evt *maubot.Event) maubot.CommandHandlerResult {
|
|
||||||
handlers, ok := client.handlers[command.Name]
|
|
||||||
if !ok {
|
|
||||||
log.Warnf("Command `%s` triggered by %s doesn't have any handlers.\n", command.Name, evt.Sender)
|
|
||||||
return maubot.Continue
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("Command `%s` on client %s triggered by %s\n", command.Name, client.UserID, evt.Sender)
|
|
||||||
for _, handler := range handlers {
|
|
||||||
result := handler(evt)
|
|
||||||
if result == maubot.StopCommandPropagation {
|
|
||||||
break
|
|
||||||
} else if result != maubot.Continue {
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return maubot.Continue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (client *Client) onMessage(evt *maubot.Event) maubot.EventHandlerResult {
|
|
||||||
for _, command := range client.commands {
|
|
||||||
if command.Match(evt.Event) {
|
|
||||||
return client.TriggerCommand(command, evt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return maubot.Continue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (client *Client) onJoin(evt *maubot.Event) maubot.EventHandlerResult {
|
|
||||||
if client.DB.AutoJoinRooms && evt.GetStateKey() == client.DB.UserID && evt.Content.Membership == "invite" {
|
|
||||||
client.JoinRoom(evt.RoomID)
|
|
||||||
return maubot.StopEventPropagation
|
|
||||||
}
|
|
||||||
return maubot.Continue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (client *Client) JoinRoom(roomID string) (resp *gomatrix.RespJoinRoom, err error) {
|
|
||||||
return client.Client.JoinRoom(roomID, "", nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (client *Client) SendMessage(roomID, text string) (string, error) {
|
|
||||||
content := format.RenderMarkdown(text)
|
|
||||||
content.MsgType = gomatrix.MsgNotice
|
|
||||||
return client.SendContent(roomID, content)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (client *Client) SendMessagef(roomID, text string, args ...interface{}) (string, error) {
|
|
||||||
content := format.RenderMarkdown(fmt.Sprintf(text, args...))
|
|
||||||
content.MsgType = gomatrix.MsgNotice
|
|
||||||
return client.SendContent(roomID, content)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (client *Client) SendContent(roomID string, content gomatrix.Content) (string, error) {
|
|
||||||
return client.SendMessageEvent(roomID, gomatrix.EventMessage, content)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (client *Client) SendMessageEvent(roomID string, evtType gomatrix.EventType, content interface{}) (string, error) {
|
|
||||||
resp, err := client.Client.SendMessageEvent(roomID, evtType, content)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return resp.EventID, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (client *Client) Sync() {
|
|
||||||
go func() {
|
|
||||||
err := client.Client.Sync()
|
|
||||||
if err != nil {
|
|
||||||
log.Errorln("Sync() in client", client.UserID, "errored:", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
type hiddenClient = Client
|
|
||||||
|
|
||||||
type ClientProxy struct {
|
|
||||||
*hiddenClient
|
|
||||||
owner string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cp *ClientProxy) AddCommandHandler(evt string, handler maubot.CommandHandler) {
|
|
||||||
cp.hiddenClient.AddCommandHandler(cp.owner, evt, handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cp *ClientProxy) SetCommandSpec(spec *maubot.CommandSpec) {
|
|
||||||
cp.hiddenClient.SetCommandSpec(cp.owner, spec)
|
|
||||||
}
|
|
147
matrix/sync.go
147
matrix/sync.go
|
@ -1,147 +0,0 @@
|
||||||
package matrix
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"runtime/debug"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"maubot.xyz"
|
|
||||||
"maunium.net/go/gomatrix"
|
|
||||||
)
|
|
||||||
|
|
||||||
type MaubotSyncer struct {
|
|
||||||
Client *Client
|
|
||||||
Store gomatrix.Storer
|
|
||||||
listeners map[gomatrix.EventType][]maubot.EventHandler
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDefaultSyncer returns an instantiated DefaultSyncer
|
|
||||||
func NewMaubotSyncer(client *Client, store gomatrix.Storer) *MaubotSyncer {
|
|
||||||
return &MaubotSyncer{
|
|
||||||
Client: client,
|
|
||||||
Store: store,
|
|
||||||
listeners: make(map[gomatrix.EventType][]maubot.EventHandler),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ProcessResponse processes the /sync response in a way suitable for bots. "Suitable for bots" means a stream of
|
|
||||||
// unrepeating events. Returns a fatal error if a listener panics.
|
|
||||||
func (s *MaubotSyncer) ProcessResponse(res *gomatrix.RespSync, since string) (err error) {
|
|
||||||
if !s.shouldProcessResponse(res, since) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
err = fmt.Errorf("ProcessResponse panicked! userID=%s since=%s panic=%s\n%s", s.Client.UserID, since, r, debug.Stack())
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
for roomID, roomData := range res.Rooms.Join {
|
|
||||||
room := s.getOrCreateRoom(roomID)
|
|
||||||
for _, event := range roomData.State.Events {
|
|
||||||
event.RoomID = roomID
|
|
||||||
room.UpdateState(event)
|
|
||||||
s.notifyListeners(event)
|
|
||||||
}
|
|
||||||
for _, event := range roomData.Timeline.Events {
|
|
||||||
event.RoomID = roomID
|
|
||||||
s.notifyListeners(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for roomID, roomData := range res.Rooms.Invite {
|
|
||||||
room := s.getOrCreateRoom(roomID)
|
|
||||||
for _, event := range roomData.State.Events {
|
|
||||||
event.RoomID = roomID
|
|
||||||
room.UpdateState(event)
|
|
||||||
s.notifyListeners(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for roomID, roomData := range res.Rooms.Leave {
|
|
||||||
room := s.getOrCreateRoom(roomID)
|
|
||||||
for _, event := range roomData.Timeline.Events {
|
|
||||||
if event.StateKey != nil {
|
|
||||||
event.RoomID = roomID
|
|
||||||
room.UpdateState(event)
|
|
||||||
s.notifyListeners(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnEventType allows callers to be notified when there are new events for the given event type.
|
|
||||||
// There are no duplicate checks.
|
|
||||||
func (s *MaubotSyncer) OnEventType(eventType gomatrix.EventType, callback maubot.EventHandler) {
|
|
||||||
_, exists := s.listeners[eventType]
|
|
||||||
if !exists {
|
|
||||||
s.listeners[eventType] = []maubot.EventHandler{}
|
|
||||||
}
|
|
||||||
s.listeners[eventType] = append(s.listeners[eventType], callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
// shouldProcessResponse returns true if the response should be processed. May modify the response to remove
|
|
||||||
// stuff that shouldn't be processed.
|
|
||||||
func (s *MaubotSyncer) shouldProcessResponse(resp *gomatrix.RespSync, since string) bool {
|
|
||||||
if since == "" {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
// This is a horrible hack because /sync will return the most recent messages for a room
|
|
||||||
// as soon as you /join it. We do NOT want to process those events in that particular room
|
|
||||||
// because they may have already been processed (if you toggle the bot in/out of the room).
|
|
||||||
//
|
|
||||||
// Work around this by inspecting each room's timeline and seeing if an m.room.member event for us
|
|
||||||
// exists and is "join" and then discard processing that room entirely if so.
|
|
||||||
// TODO: We probably want to process messages from after the last join event in the timeline.
|
|
||||||
for roomID, roomData := range resp.Rooms.Join {
|
|
||||||
for i := len(roomData.Timeline.Events) - 1; i >= 0; i-- {
|
|
||||||
evt := roomData.Timeline.Events[i]
|
|
||||||
if evt.Type == gomatrix.StateMember && evt.GetStateKey() == s.Client.UserID {
|
|
||||||
if evt.Content.Membership == gomatrix.MembershipJoin {
|
|
||||||
_, ok := resp.Rooms.Join[roomID]
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
delete(resp.Rooms.Join, roomID) // don't re-process messages
|
|
||||||
delete(resp.Rooms.Invite, roomID) // don't re-process invites
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// getOrCreateRoom must only be called by the Sync() goroutine which calls ProcessResponse()
|
|
||||||
func (s *MaubotSyncer) getOrCreateRoom(roomID string) *gomatrix.Room {
|
|
||||||
room := s.Store.LoadRoom(roomID)
|
|
||||||
if room == nil {
|
|
||||||
room = gomatrix.NewRoom(roomID)
|
|
||||||
s.Store.SaveRoom(room)
|
|
||||||
}
|
|
||||||
return room
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *MaubotSyncer) notifyListeners(mxEvent *gomatrix.Event) {
|
|
||||||
event := s.Client.ParseEvent(mxEvent)
|
|
||||||
listeners, exists := s.listeners[event.Type]
|
|
||||||
if !exists {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, fn := range listeners {
|
|
||||||
if fn(event) == maubot.StopEventPropagation {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// OnFailedSync always returns a 10 second wait period between failed /syncs, never a fatal error.
|
|
||||||
func (s *MaubotSyncer) OnFailedSync(res *gomatrix.RespSync, err error) (time.Duration, error) {
|
|
||||||
return 10 * time.Second, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFilterJSON returns a filter with a timeline limit of 50.
|
|
||||||
func (s *MaubotSyncer) GetFilterJSON(userID string) json.RawMessage {
|
|
||||||
return json.RawMessage(`{"room":{"timeline":{"limit":50}}}`)
|
|
||||||
}
|
|
2
maubot/__init__.py
Normal file
2
maubot/__init__.py
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
__version__ = "0.1.0+dev"
|
||||||
|
__author__ = "Tulir Asokan <tulir@maunium.net>"
|
31
plugin.go
31
plugin.go
|
@ -1,31 +0,0 @@
|
||||||
// maubot - A plugin-based Matrix bot system written in Go.
|
|
||||||
// Copyright (C) 2018 Tulir Asokan
|
|
||||||
//
|
|
||||||
// This program is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Affero General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// This program is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Affero General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Affero General Public License
|
|
||||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package maubot
|
|
||||||
|
|
||||||
type Plugin interface {
|
|
||||||
Start()
|
|
||||||
Stop()
|
|
||||||
}
|
|
||||||
|
|
||||||
type PluginCreatorFunc func(client MatrixClient, logger Logger) Plugin
|
|
||||||
|
|
||||||
type PluginCreator struct {
|
|
||||||
Create PluginCreatorFunc
|
|
||||||
Name string
|
|
||||||
Version string
|
|
||||||
Path string
|
|
||||||
}
|
|
42
setup.py
Normal file
42
setup.py
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
import setuptools
|
||||||
|
import maubot
|
||||||
|
|
||||||
|
setuptools.setup(
|
||||||
|
name="maubot",
|
||||||
|
version=maubot.__version__,
|
||||||
|
url="https://github.com/maubot/maubot",
|
||||||
|
|
||||||
|
author="Tulir Asokan",
|
||||||
|
author_email="tulir@maunium.net",
|
||||||
|
|
||||||
|
description="A plugin-based Matrix bot system.",
|
||||||
|
long_description=open("README.md").read(),
|
||||||
|
long_description_content_type="text/markdown",
|
||||||
|
|
||||||
|
packages=setuptools.find_packages(),
|
||||||
|
|
||||||
|
install_requires=[
|
||||||
|
"aiohttp>=3.0.1,<4",
|
||||||
|
"SQLAlchemy>=1.2.3,<2",
|
||||||
|
"Markdown>=2.6.11,<3",
|
||||||
|
"ruamel.yaml>=0.15.35,<0.16",
|
||||||
|
],
|
||||||
|
|
||||||
|
classifiers=[
|
||||||
|
"Development Status :: 3 :: Alpha",
|
||||||
|
"License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)",
|
||||||
|
"Topic :: Communications :: Chat",
|
||||||
|
"Framework :: AsyncIO",
|
||||||
|
"Programming Language :: Python",
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Programming Language :: Python :: 3.6",
|
||||||
|
"Programming Language :: Python :: 3.7",
|
||||||
|
],
|
||||||
|
entry_points="""
|
||||||
|
[console_scripts]
|
||||||
|
maubot=maubot.__main__:main
|
||||||
|
""",
|
||||||
|
data_files=[
|
||||||
|
(".", ["example-config.yaml"]),
|
||||||
|
],
|
||||||
|
)
|
19
vendor/github.com/gorilla/context/.travis.yml
generated
vendored
19
vendor/github.com/gorilla/context/.travis.yml
generated
vendored
|
@ -1,19 +0,0 @@
|
||||||
language: go
|
|
||||||
sudo: false
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- go: 1.3
|
|
||||||
- go: 1.4
|
|
||||||
- go: 1.5
|
|
||||||
- go: 1.6
|
|
||||||
- go: 1.7
|
|
||||||
- go: tip
|
|
||||||
allow_failures:
|
|
||||||
- go: tip
|
|
||||||
|
|
||||||
script:
|
|
||||||
- go get -t -v ./...
|
|
||||||
- diff -u <(echo -n) <(gofmt -d .)
|
|
||||||
- go vet $(go list ./... | grep -v /vendor/)
|
|
||||||
- go test -v -race ./...
|
|
27
vendor/github.com/gorilla/context/LICENSE
generated
vendored
27
vendor/github.com/gorilla/context/LICENSE
generated
vendored
|
@ -1,27 +0,0 @@
|
||||||
Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above
|
|
||||||
copyright notice, this list of conditions and the following disclaimer
|
|
||||||
in the documentation and/or other materials provided with the
|
|
||||||
distribution.
|
|
||||||
* Neither the name of Google Inc. nor the names of its
|
|
||||||
contributors may be used to endorse or promote products derived from
|
|
||||||
this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
10
vendor/github.com/gorilla/context/README.md
generated
vendored
10
vendor/github.com/gorilla/context/README.md
generated
vendored
|
@ -1,10 +0,0 @@
|
||||||
context
|
|
||||||
=======
|
|
||||||
[![Build Status](https://travis-ci.org/gorilla/context.png?branch=master)](https://travis-ci.org/gorilla/context)
|
|
||||||
|
|
||||||
gorilla/context is a general purpose registry for global request variables.
|
|
||||||
|
|
||||||
> Note: gorilla/context, having been born well before `context.Context` existed, does not play well
|
|
||||||
> with the shallow copying of the request that [`http.Request.WithContext`](https://golang.org/pkg/net/http/#Request.WithContext) (added to net/http Go 1.7 onwards) performs. You should either use *just* gorilla/context, or moving forward, the new `http.Request.Context()`.
|
|
||||||
|
|
||||||
Read the full documentation here: http://www.gorillatoolkit.org/pkg/context
|
|
143
vendor/github.com/gorilla/context/context.go
generated
vendored
143
vendor/github.com/gorilla/context/context.go
generated
vendored
|
@ -1,143 +0,0 @@
|
||||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package context
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
mutex sync.RWMutex
|
|
||||||
data = make(map[*http.Request]map[interface{}]interface{})
|
|
||||||
datat = make(map[*http.Request]int64)
|
|
||||||
)
|
|
||||||
|
|
||||||
// Set stores a value for a given key in a given request.
|
|
||||||
func Set(r *http.Request, key, val interface{}) {
|
|
||||||
mutex.Lock()
|
|
||||||
if data[r] == nil {
|
|
||||||
data[r] = make(map[interface{}]interface{})
|
|
||||||
datat[r] = time.Now().Unix()
|
|
||||||
}
|
|
||||||
data[r][key] = val
|
|
||||||
mutex.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns a value stored for a given key in a given request.
|
|
||||||
func Get(r *http.Request, key interface{}) interface{} {
|
|
||||||
mutex.RLock()
|
|
||||||
if ctx := data[r]; ctx != nil {
|
|
||||||
value := ctx[key]
|
|
||||||
mutex.RUnlock()
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
mutex.RUnlock()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOk returns stored value and presence state like multi-value return of map access.
|
|
||||||
func GetOk(r *http.Request, key interface{}) (interface{}, bool) {
|
|
||||||
mutex.RLock()
|
|
||||||
if _, ok := data[r]; ok {
|
|
||||||
value, ok := data[r][key]
|
|
||||||
mutex.RUnlock()
|
|
||||||
return value, ok
|
|
||||||
}
|
|
||||||
mutex.RUnlock()
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAll returns all stored values for the request as a map. Nil is returned for invalid requests.
|
|
||||||
func GetAll(r *http.Request) map[interface{}]interface{} {
|
|
||||||
mutex.RLock()
|
|
||||||
if context, ok := data[r]; ok {
|
|
||||||
result := make(map[interface{}]interface{}, len(context))
|
|
||||||
for k, v := range context {
|
|
||||||
result[k] = v
|
|
||||||
}
|
|
||||||
mutex.RUnlock()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
mutex.RUnlock()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAllOk returns all stored values for the request as a map and a boolean value that indicates if
|
|
||||||
// the request was registered.
|
|
||||||
func GetAllOk(r *http.Request) (map[interface{}]interface{}, bool) {
|
|
||||||
mutex.RLock()
|
|
||||||
context, ok := data[r]
|
|
||||||
result := make(map[interface{}]interface{}, len(context))
|
|
||||||
for k, v := range context {
|
|
||||||
result[k] = v
|
|
||||||
}
|
|
||||||
mutex.RUnlock()
|
|
||||||
return result, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete removes a value stored for a given key in a given request.
|
|
||||||
func Delete(r *http.Request, key interface{}) {
|
|
||||||
mutex.Lock()
|
|
||||||
if data[r] != nil {
|
|
||||||
delete(data[r], key)
|
|
||||||
}
|
|
||||||
mutex.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear removes all values stored for a given request.
|
|
||||||
//
|
|
||||||
// This is usually called by a handler wrapper to clean up request
|
|
||||||
// variables at the end of a request lifetime. See ClearHandler().
|
|
||||||
func Clear(r *http.Request) {
|
|
||||||
mutex.Lock()
|
|
||||||
clear(r)
|
|
||||||
mutex.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear is Clear without the lock.
|
|
||||||
func clear(r *http.Request) {
|
|
||||||
delete(data, r)
|
|
||||||
delete(datat, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Purge removes request data stored for longer than maxAge, in seconds.
|
|
||||||
// It returns the amount of requests removed.
|
|
||||||
//
|
|
||||||
// If maxAge <= 0, all request data is removed.
|
|
||||||
//
|
|
||||||
// This is only used for sanity check: in case context cleaning was not
|
|
||||||
// properly set some request data can be kept forever, consuming an increasing
|
|
||||||
// amount of memory. In case this is detected, Purge() must be called
|
|
||||||
// periodically until the problem is fixed.
|
|
||||||
func Purge(maxAge int) int {
|
|
||||||
mutex.Lock()
|
|
||||||
count := 0
|
|
||||||
if maxAge <= 0 {
|
|
||||||
count = len(data)
|
|
||||||
data = make(map[*http.Request]map[interface{}]interface{})
|
|
||||||
datat = make(map[*http.Request]int64)
|
|
||||||
} else {
|
|
||||||
min := time.Now().Unix() - int64(maxAge)
|
|
||||||
for r := range data {
|
|
||||||
if datat[r] < min {
|
|
||||||
clear(r)
|
|
||||||
count++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mutex.Unlock()
|
|
||||||
return count
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClearHandler wraps an http.Handler and clears request values at the end
|
|
||||||
// of a request lifetime.
|
|
||||||
func ClearHandler(h http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
defer Clear(r)
|
|
||||||
h.ServeHTTP(w, r)
|
|
||||||
})
|
|
||||||
}
|
|
88
vendor/github.com/gorilla/context/doc.go
generated
vendored
88
vendor/github.com/gorilla/context/doc.go
generated
vendored
|
@ -1,88 +0,0 @@
|
||||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
/*
|
|
||||||
Package context stores values shared during a request lifetime.
|
|
||||||
|
|
||||||
Note: gorilla/context, having been born well before `context.Context` existed,
|
|
||||||
does not play well > with the shallow copying of the request that
|
|
||||||
[`http.Request.WithContext`](https://golang.org/pkg/net/http/#Request.WithContext)
|
|
||||||
(added to net/http Go 1.7 onwards) performs. You should either use *just*
|
|
||||||
gorilla/context, or moving forward, the new `http.Request.Context()`.
|
|
||||||
|
|
||||||
For example, a router can set variables extracted from the URL and later
|
|
||||||
application handlers can access those values, or it can be used to store
|
|
||||||
sessions values to be saved at the end of a request. There are several
|
|
||||||
others common uses.
|
|
||||||
|
|
||||||
The idea was posted by Brad Fitzpatrick to the go-nuts mailing list:
|
|
||||||
|
|
||||||
http://groups.google.com/group/golang-nuts/msg/e2d679d303aa5d53
|
|
||||||
|
|
||||||
Here's the basic usage: first define the keys that you will need. The key
|
|
||||||
type is interface{} so a key can be of any type that supports equality.
|
|
||||||
Here we define a key using a custom int type to avoid name collisions:
|
|
||||||
|
|
||||||
package foo
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/gorilla/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
type key int
|
|
||||||
|
|
||||||
const MyKey key = 0
|
|
||||||
|
|
||||||
Then set a variable. Variables are bound to an http.Request object, so you
|
|
||||||
need a request instance to set a value:
|
|
||||||
|
|
||||||
context.Set(r, MyKey, "bar")
|
|
||||||
|
|
||||||
The application can later access the variable using the same key you provided:
|
|
||||||
|
|
||||||
func MyHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// val is "bar".
|
|
||||||
val := context.Get(r, foo.MyKey)
|
|
||||||
|
|
||||||
// returns ("bar", true)
|
|
||||||
val, ok := context.GetOk(r, foo.MyKey)
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
And that's all about the basic usage. We discuss some other ideas below.
|
|
||||||
|
|
||||||
Any type can be stored in the context. To enforce a given type, make the key
|
|
||||||
private and wrap Get() and Set() to accept and return values of a specific
|
|
||||||
type:
|
|
||||||
|
|
||||||
type key int
|
|
||||||
|
|
||||||
const mykey key = 0
|
|
||||||
|
|
||||||
// GetMyKey returns a value for this package from the request values.
|
|
||||||
func GetMyKey(r *http.Request) SomeType {
|
|
||||||
if rv := context.Get(r, mykey); rv != nil {
|
|
||||||
return rv.(SomeType)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetMyKey sets a value for this package in the request values.
|
|
||||||
func SetMyKey(r *http.Request, val SomeType) {
|
|
||||||
context.Set(r, mykey, val)
|
|
||||||
}
|
|
||||||
|
|
||||||
Variables must be cleared at the end of a request, to remove all values
|
|
||||||
that were stored. This can be done in an http.Handler, after a request was
|
|
||||||
served. Just call Clear() passing the request:
|
|
||||||
|
|
||||||
context.Clear(r)
|
|
||||||
|
|
||||||
...or use ClearHandler(), which conveniently wraps an http.Handler to clear
|
|
||||||
variables at the end of a request lifetime.
|
|
||||||
|
|
||||||
The Routers from the packages gorilla/mux and gorilla/pat call Clear()
|
|
||||||
so if you are using either of them you don't need to clear the context manually.
|
|
||||||
*/
|
|
||||||
package context
|
|
23
vendor/github.com/gorilla/mux/.travis.yml
generated
vendored
23
vendor/github.com/gorilla/mux/.travis.yml
generated
vendored
|
@ -1,23 +0,0 @@
|
||||||
language: go
|
|
||||||
sudo: false
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
include:
|
|
||||||
- go: 1.5.x
|
|
||||||
- go: 1.6.x
|
|
||||||
- go: 1.7.x
|
|
||||||
- go: 1.8.x
|
|
||||||
- go: 1.9.x
|
|
||||||
- go: 1.10.x
|
|
||||||
- go: tip
|
|
||||||
allow_failures:
|
|
||||||
- go: tip
|
|
||||||
|
|
||||||
install:
|
|
||||||
- # Skip
|
|
||||||
|
|
||||||
script:
|
|
||||||
- go get -t -v ./...
|
|
||||||
- diff -u <(echo -n) <(gofmt -d .)
|
|
||||||
- go tool vet .
|
|
||||||
- go test -v -race ./...
|
|
11
vendor/github.com/gorilla/mux/ISSUE_TEMPLATE.md
generated
vendored
11
vendor/github.com/gorilla/mux/ISSUE_TEMPLATE.md
generated
vendored
|
@ -1,11 +0,0 @@
|
||||||
**What version of Go are you running?** (Paste the output of `go version`)
|
|
||||||
|
|
||||||
|
|
||||||
**What version of gorilla/mux are you at?** (Paste the output of `git rev-parse HEAD` inside `$GOPATH/src/github.com/gorilla/mux`)
|
|
||||||
|
|
||||||
|
|
||||||
**Describe your problem** (and what you have tried so far)
|
|
||||||
|
|
||||||
|
|
||||||
**Paste a minimal, runnable, reproduction of your issue below** (use backticks to format it)
|
|
||||||
|
|
27
vendor/github.com/gorilla/mux/LICENSE
generated
vendored
27
vendor/github.com/gorilla/mux/LICENSE
generated
vendored
|
@ -1,27 +0,0 @@
|
||||||
Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above
|
|
||||||
copyright notice, this list of conditions and the following disclaimer
|
|
||||||
in the documentation and/or other materials provided with the
|
|
||||||
distribution.
|
|
||||||
* Neither the name of Google Inc. nor the names of its
|
|
||||||
contributors may be used to endorse or promote products derived from
|
|
||||||
this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
649
vendor/github.com/gorilla/mux/README.md
generated
vendored
649
vendor/github.com/gorilla/mux/README.md
generated
vendored
|
@ -1,649 +0,0 @@
|
||||||
# gorilla/mux
|
|
||||||
|
|
||||||
[![GoDoc](https://godoc.org/github.com/gorilla/mux?status.svg)](https://godoc.org/github.com/gorilla/mux)
|
|
||||||
[![Build Status](https://travis-ci.org/gorilla/mux.svg?branch=master)](https://travis-ci.org/gorilla/mux)
|
|
||||||
[![Sourcegraph](https://sourcegraph.com/github.com/gorilla/mux/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/mux?badge)
|
|
||||||
|
|
||||||
![Gorilla Logo](http://www.gorillatoolkit.org/static/images/gorilla-icon-64.png)
|
|
||||||
|
|
||||||
http://www.gorillatoolkit.org/pkg/mux
|
|
||||||
|
|
||||||
Package `gorilla/mux` implements a request router and dispatcher for matching incoming requests to
|
|
||||||
their respective handler.
|
|
||||||
|
|
||||||
The name mux stands for "HTTP request multiplexer". Like the standard `http.ServeMux`, `mux.Router` matches incoming requests against a list of registered routes and calls a handler for the route that matches the URL or other conditions. The main features are:
|
|
||||||
|
|
||||||
* It implements the `http.Handler` interface so it is compatible with the standard `http.ServeMux`.
|
|
||||||
* Requests can be matched based on URL host, path, path prefix, schemes, header and query values, HTTP methods or using custom matchers.
|
|
||||||
* URL hosts, paths and query values can have variables with an optional regular expression.
|
|
||||||
* Registered URLs can be built, or "reversed", which helps maintaining references to resources.
|
|
||||||
* Routes can be used as subrouters: nested routes are only tested if the parent route matches. This is useful to define groups of routes that share common conditions like a host, a path prefix or other repeated attributes. As a bonus, this optimizes request matching.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
* [Install](#install)
|
|
||||||
* [Examples](#examples)
|
|
||||||
* [Matching Routes](#matching-routes)
|
|
||||||
* [Static Files](#static-files)
|
|
||||||
* [Registered URLs](#registered-urls)
|
|
||||||
* [Walking Routes](#walking-routes)
|
|
||||||
* [Graceful Shutdown](#graceful-shutdown)
|
|
||||||
* [Middleware](#middleware)
|
|
||||||
* [Testing Handlers](#testing-handlers)
|
|
||||||
* [Full Example](#full-example)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Install
|
|
||||||
|
|
||||||
With a [correctly configured](https://golang.org/doc/install#testing) Go toolchain:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
go get -u github.com/gorilla/mux
|
|
||||||
```
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
Let's start registering a couple of URL paths and handlers:
|
|
||||||
|
|
||||||
```go
|
|
||||||
func main() {
|
|
||||||
r := mux.NewRouter()
|
|
||||||
r.HandleFunc("/", HomeHandler)
|
|
||||||
r.HandleFunc("/products", ProductsHandler)
|
|
||||||
r.HandleFunc("/articles", ArticlesHandler)
|
|
||||||
http.Handle("/", r)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Here we register three routes mapping URL paths to handlers. This is equivalent to how `http.HandleFunc()` works: if an incoming request URL matches one of the paths, the corresponding handler is called passing (`http.ResponseWriter`, `*http.Request`) as parameters.
|
|
||||||
|
|
||||||
Paths can have variables. They are defined using the format `{name}` or `{name:pattern}`. If a regular expression pattern is not defined, the matched variable will be anything until the next slash. For example:
|
|
||||||
|
|
||||||
```go
|
|
||||||
r := mux.NewRouter()
|
|
||||||
r.HandleFunc("/products/{key}", ProductHandler)
|
|
||||||
r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
|
|
||||||
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
|
|
||||||
```
|
|
||||||
|
|
||||||
The names are used to create a map of route variables which can be retrieved calling `mux.Vars()`:
|
|
||||||
|
|
||||||
```go
|
|
||||||
func ArticlesCategoryHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
vars := mux.Vars(r)
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
fmt.Fprintf(w, "Category: %v\n", vars["category"])
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
And this is all you need to know about the basic usage. More advanced options are explained below.
|
|
||||||
|
|
||||||
### Matching Routes
|
|
||||||
|
|
||||||
Routes can also be restricted to a domain or subdomain. Just define a host pattern to be matched. They can also have variables:
|
|
||||||
|
|
||||||
```go
|
|
||||||
r := mux.NewRouter()
|
|
||||||
// Only matches if domain is "www.example.com".
|
|
||||||
r.Host("www.example.com")
|
|
||||||
// Matches a dynamic subdomain.
|
|
||||||
r.Host("{subdomain:[a-z]+}.domain.com")
|
|
||||||
```
|
|
||||||
|
|
||||||
There are several other matchers that can be added. To match path prefixes:
|
|
||||||
|
|
||||||
```go
|
|
||||||
r.PathPrefix("/products/")
|
|
||||||
```
|
|
||||||
|
|
||||||
...or HTTP methods:
|
|
||||||
|
|
||||||
```go
|
|
||||||
r.Methods("GET", "POST")
|
|
||||||
```
|
|
||||||
|
|
||||||
...or URL schemes:
|
|
||||||
|
|
||||||
```go
|
|
||||||
r.Schemes("https")
|
|
||||||
```
|
|
||||||
|
|
||||||
...or header values:
|
|
||||||
|
|
||||||
```go
|
|
||||||
r.Headers("X-Requested-With", "XMLHttpRequest")
|
|
||||||
```
|
|
||||||
|
|
||||||
...or query values:
|
|
||||||
|
|
||||||
```go
|
|
||||||
r.Queries("key", "value")
|
|
||||||
```
|
|
||||||
|
|
||||||
...or to use a custom matcher function:
|
|
||||||
|
|
||||||
```go
|
|
||||||
r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool {
|
|
||||||
return r.ProtoMajor == 0
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
...and finally, it is possible to combine several matchers in a single route:
|
|
||||||
|
|
||||||
```go
|
|
||||||
r.HandleFunc("/products", ProductsHandler).
|
|
||||||
Host("www.example.com").
|
|
||||||
Methods("GET").
|
|
||||||
Schemes("http")
|
|
||||||
```
|
|
||||||
|
|
||||||
Routes are tested in the order they were added to the router. If two routes match, the first one wins:
|
|
||||||
|
|
||||||
```go
|
|
||||||
r := mux.NewRouter()
|
|
||||||
r.HandleFunc("/specific", specificHandler)
|
|
||||||
r.PathPrefix("/").Handler(catchAllHandler)
|
|
||||||
```
|
|
||||||
|
|
||||||
Setting the same matching conditions again and again can be boring, so we have a way to group several routes that share the same requirements. We call it "subrouting".
|
|
||||||
|
|
||||||
For example, let's say we have several URLs that should only match when the host is `www.example.com`. Create a route for that host and get a "subrouter" from it:
|
|
||||||
|
|
||||||
```go
|
|
||||||
r := mux.NewRouter()
|
|
||||||
s := r.Host("www.example.com").Subrouter()
|
|
||||||
```
|
|
||||||
|
|
||||||
Then register routes in the subrouter:
|
|
||||||
|
|
||||||
```go
|
|
||||||
s.HandleFunc("/products/", ProductsHandler)
|
|
||||||
s.HandleFunc("/products/{key}", ProductHandler)
|
|
||||||
s.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
|
|
||||||
```
|
|
||||||
|
|
||||||
The three URL paths we registered above will only be tested if the domain is `www.example.com`, because the subrouter is tested first. This is not only convenient, but also optimizes request matching. You can create subrouters combining any attribute matchers accepted by a route.
|
|
||||||
|
|
||||||
Subrouters can be used to create domain or path "namespaces": you define subrouters in a central place and then parts of the app can register its paths relatively to a given subrouter.
|
|
||||||
|
|
||||||
There's one more thing about subroutes. When a subrouter has a path prefix, the inner routes use it as base for their paths:
|
|
||||||
|
|
||||||
```go
|
|
||||||
r := mux.NewRouter()
|
|
||||||
s := r.PathPrefix("/products").Subrouter()
|
|
||||||
// "/products/"
|
|
||||||
s.HandleFunc("/", ProductsHandler)
|
|
||||||
// "/products/{key}/"
|
|
||||||
s.HandleFunc("/{key}/", ProductHandler)
|
|
||||||
// "/products/{key}/details"
|
|
||||||
s.HandleFunc("/{key}/details", ProductDetailsHandler)
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### Static Files
|
|
||||||
|
|
||||||
Note that the path provided to `PathPrefix()` represents a "wildcard": calling
|
|
||||||
`PathPrefix("/static/").Handler(...)` means that the handler will be passed any
|
|
||||||
request that matches "/static/\*". This makes it easy to serve static files with mux:
|
|
||||||
|
|
||||||
```go
|
|
||||||
func main() {
|
|
||||||
var dir string
|
|
||||||
|
|
||||||
flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir")
|
|
||||||
flag.Parse()
|
|
||||||
r := mux.NewRouter()
|
|
||||||
|
|
||||||
// This will serve files under http://localhost:8000/static/<filename>
|
|
||||||
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir))))
|
|
||||||
|
|
||||||
srv := &http.Server{
|
|
||||||
Handler: r,
|
|
||||||
Addr: "127.0.0.1:8000",
|
|
||||||
// Good practice: enforce timeouts for servers you create!
|
|
||||||
WriteTimeout: 15 * time.Second,
|
|
||||||
ReadTimeout: 15 * time.Second,
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Fatal(srv.ListenAndServe())
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Registered URLs
|
|
||||||
|
|
||||||
Now let's see how to build registered URLs.
|
|
||||||
|
|
||||||
Routes can be named. All routes that define a name can have their URLs built, or "reversed". We define a name calling `Name()` on a route. For example:
|
|
||||||
|
|
||||||
```go
|
|
||||||
r := mux.NewRouter()
|
|
||||||
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
|
|
||||||
Name("article")
|
|
||||||
```
|
|
||||||
|
|
||||||
To build a URL, get the route and call the `URL()` method, passing a sequence of key/value pairs for the route variables. For the previous route, we would do:
|
|
||||||
|
|
||||||
```go
|
|
||||||
url, err := r.Get("article").URL("category", "technology", "id", "42")
|
|
||||||
```
|
|
||||||
|
|
||||||
...and the result will be a `url.URL` with the following path:
|
|
||||||
|
|
||||||
```
|
|
||||||
"/articles/technology/42"
|
|
||||||
```
|
|
||||||
|
|
||||||
This also works for host and query value variables:
|
|
||||||
|
|
||||||
```go
|
|
||||||
r := mux.NewRouter()
|
|
||||||
r.Host("{subdomain}.domain.com").
|
|
||||||
Path("/articles/{category}/{id:[0-9]+}").
|
|
||||||
Queries("filter", "{filter}").
|
|
||||||
HandlerFunc(ArticleHandler).
|
|
||||||
Name("article")
|
|
||||||
|
|
||||||
// url.String() will be "http://news.domain.com/articles/technology/42?filter=gorilla"
|
|
||||||
url, err := r.Get("article").URL("subdomain", "news",
|
|
||||||
"category", "technology",
|
|
||||||
"id", "42",
|
|
||||||
"filter", "gorilla")
|
|
||||||
```
|
|
||||||
|
|
||||||
All variables defined in the route are required, and their values must conform to the corresponding patterns. These requirements guarantee that a generated URL will always match a registered route -- the only exception is for explicitly defined "build-only" routes which never match.
|
|
||||||
|
|
||||||
Regex support also exists for matching Headers within a route. For example, we could do:
|
|
||||||
|
|
||||||
```go
|
|
||||||
r.HeadersRegexp("Content-Type", "application/(text|json)")
|
|
||||||
```
|
|
||||||
|
|
||||||
...and the route will match both requests with a Content-Type of `application/json` as well as `application/text`
|
|
||||||
|
|
||||||
There's also a way to build only the URL host or path for a route: use the methods `URLHost()` or `URLPath()` instead. For the previous route, we would do:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// "http://news.domain.com/"
|
|
||||||
host, err := r.Get("article").URLHost("subdomain", "news")
|
|
||||||
|
|
||||||
// "/articles/technology/42"
|
|
||||||
path, err := r.Get("article").URLPath("category", "technology", "id", "42")
|
|
||||||
```
|
|
||||||
|
|
||||||
And if you use subrouters, host and path defined separately can be built as well:
|
|
||||||
|
|
||||||
```go
|
|
||||||
r := mux.NewRouter()
|
|
||||||
s := r.Host("{subdomain}.domain.com").Subrouter()
|
|
||||||
s.Path("/articles/{category}/{id:[0-9]+}").
|
|
||||||
HandlerFunc(ArticleHandler).
|
|
||||||
Name("article")
|
|
||||||
|
|
||||||
// "http://news.domain.com/articles/technology/42"
|
|
||||||
url, err := r.Get("article").URL("subdomain", "news",
|
|
||||||
"category", "technology",
|
|
||||||
"id", "42")
|
|
||||||
```
|
|
||||||
|
|
||||||
### Walking Routes
|
|
||||||
|
|
||||||
The `Walk` function on `mux.Router` can be used to visit all of the routes that are registered on a router. For example,
|
|
||||||
the following prints all of the registered routes:
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
)
|
|
||||||
|
|
||||||
func handler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
r := mux.NewRouter()
|
|
||||||
r.HandleFunc("/", handler)
|
|
||||||
r.HandleFunc("/products", handler).Methods("POST")
|
|
||||||
r.HandleFunc("/articles", handler).Methods("GET")
|
|
||||||
r.HandleFunc("/articles/{id}", handler).Methods("GET", "PUT")
|
|
||||||
r.HandleFunc("/authors", handler).Queries("surname", "{surname}")
|
|
||||||
err := r.Walk(func(route *mux.Route, router *mux.Router, ancestors []*mux.Route) error {
|
|
||||||
pathTemplate, err := route.GetPathTemplate()
|
|
||||||
if err == nil {
|
|
||||||
fmt.Println("ROUTE:", pathTemplate)
|
|
||||||
}
|
|
||||||
pathRegexp, err := route.GetPathRegexp()
|
|
||||||
if err == nil {
|
|
||||||
fmt.Println("Path regexp:", pathRegexp)
|
|
||||||
}
|
|
||||||
queriesTemplates, err := route.GetQueriesTemplates()
|
|
||||||
if err == nil {
|
|
||||||
fmt.Println("Queries templates:", strings.Join(queriesTemplates, ","))
|
|
||||||
}
|
|
||||||
queriesRegexps, err := route.GetQueriesRegexp()
|
|
||||||
if err == nil {
|
|
||||||
fmt.Println("Queries regexps:", strings.Join(queriesRegexps, ","))
|
|
||||||
}
|
|
||||||
methods, err := route.GetMethods()
|
|
||||||
if err == nil {
|
|
||||||
fmt.Println("Methods:", strings.Join(methods, ","))
|
|
||||||
}
|
|
||||||
fmt.Println()
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Handle("/", r)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Graceful Shutdown
|
|
||||||
|
|
||||||
Go 1.8 introduced the ability to [gracefully shutdown](https://golang.org/doc/go1.8#http_shutdown) a `*http.Server`. Here's how to do that alongside `mux`:
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"flag"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
var wait time.Duration
|
|
||||||
flag.DurationVar(&wait, "graceful-timeout", time.Second * 15, "the duration for which the server gracefully wait for existing connections to finish - e.g. 15s or 1m")
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
r := mux.NewRouter()
|
|
||||||
// Add your routes as needed
|
|
||||||
|
|
||||||
srv := &http.Server{
|
|
||||||
Addr: "0.0.0.0:8080",
|
|
||||||
// Good practice to set timeouts to avoid Slowloris attacks.
|
|
||||||
WriteTimeout: time.Second * 15,
|
|
||||||
ReadTimeout: time.Second * 15,
|
|
||||||
IdleTimeout: time.Second * 60,
|
|
||||||
Handler: r, // Pass our instance of gorilla/mux in.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run our server in a goroutine so that it doesn't block.
|
|
||||||
go func() {
|
|
||||||
if err := srv.ListenAndServe(); err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
c := make(chan os.Signal, 1)
|
|
||||||
// We'll accept graceful shutdowns when quit via SIGINT (Ctrl+C)
|
|
||||||
// SIGKILL, SIGQUIT or SIGTERM (Ctrl+/) will not be caught.
|
|
||||||
signal.Notify(c, os.Interrupt)
|
|
||||||
|
|
||||||
// Block until we receive our signal.
|
|
||||||
<-c
|
|
||||||
|
|
||||||
// Create a deadline to wait for.
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), wait)
|
|
||||||
defer cancel()
|
|
||||||
// Doesn't block if no connections, but will otherwise wait
|
|
||||||
// until the timeout deadline.
|
|
||||||
srv.Shutdown(ctx)
|
|
||||||
// Optionally, you could run srv.Shutdown in a goroutine and block on
|
|
||||||
// <-ctx.Done() if your application should wait for other services
|
|
||||||
// to finalize based on context cancellation.
|
|
||||||
log.Println("shutting down")
|
|
||||||
os.Exit(0)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Middleware
|
|
||||||
|
|
||||||
Mux supports the addition of middlewares to a [Router](https://godoc.org/github.com/gorilla/mux#Router), which are executed in the order they are added if a match is found, including its subrouters.
|
|
||||||
Middlewares are (typically) small pieces of code which take one request, do something with it, and pass it down to another middleware or the final handler. Some common use cases for middleware are request logging, header manipulation, or `ResponseWriter` hijacking.
|
|
||||||
|
|
||||||
Mux middlewares are defined using the de facto standard type:
|
|
||||||
|
|
||||||
```go
|
|
||||||
type MiddlewareFunc func(http.Handler) http.Handler
|
|
||||||
```
|
|
||||||
|
|
||||||
Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed to it, and then calls the handler passed as parameter to the MiddlewareFunc. This takes advantage of closures being able access variables from the context where they are created, while retaining the signature enforced by the receivers.
|
|
||||||
|
|
||||||
A very basic middleware which logs the URI of the request being handled could be written as:
|
|
||||||
|
|
||||||
```go
|
|
||||||
func loggingMiddleware(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// Do stuff here
|
|
||||||
log.Println(r.RequestURI)
|
|
||||||
// Call the next handler, which can be another middleware in the chain, or the final handler.
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Middlewares can be added to a router using `Router.Use()`:
|
|
||||||
|
|
||||||
```go
|
|
||||||
r := mux.NewRouter()
|
|
||||||
r.HandleFunc("/", handler)
|
|
||||||
r.Use(loggingMiddleware)
|
|
||||||
```
|
|
||||||
|
|
||||||
A more complex authentication middleware, which maps session token to users, could be written as:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// Define our struct
|
|
||||||
type authenticationMiddleware struct {
|
|
||||||
tokenUsers map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize it somewhere
|
|
||||||
func (amw *authenticationMiddleware) Populate() {
|
|
||||||
amw.tokenUsers["00000000"] = "user0"
|
|
||||||
amw.tokenUsers["aaaaaaaa"] = "userA"
|
|
||||||
amw.tokenUsers["05f717e5"] = "randomUser"
|
|
||||||
amw.tokenUsers["deadbeef"] = "user0"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Middleware function, which will be called for each request
|
|
||||||
func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
token := r.Header.Get("X-Session-Token")
|
|
||||||
|
|
||||||
if user, found := amw.tokenUsers[token]; found {
|
|
||||||
// We found the token in our map
|
|
||||||
log.Printf("Authenticated user %s\n", user)
|
|
||||||
// Pass down the request to the next middleware (or final handler)
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
} else {
|
|
||||||
// Write an error and stop the handler chain
|
|
||||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
```go
|
|
||||||
r := mux.NewRouter()
|
|
||||||
r.HandleFunc("/", handler)
|
|
||||||
|
|
||||||
amw := authenticationMiddleware{}
|
|
||||||
amw.Populate()
|
|
||||||
|
|
||||||
r.Use(amw.Middleware)
|
|
||||||
```
|
|
||||||
|
|
||||||
Note: The handler chain will be stopped if your middleware doesn't call `next.ServeHTTP()` with the corresponding parameters. This can be used to abort a request if the middleware writer wants to. Middlewares _should_ write to `ResponseWriter` if they _are_ going to terminate the request, and they _should not_ write to `ResponseWriter` if they _are not_ going to terminate it.
|
|
||||||
|
|
||||||
### Testing Handlers
|
|
||||||
|
|
||||||
Testing handlers in a Go web application is straightforward, and _mux_ doesn't complicate this any further. Given two files: `endpoints.go` and `endpoints_test.go`, here's how we'd test an application using _mux_.
|
|
||||||
|
|
||||||
First, our simple HTTP handler:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// endpoints.go
|
|
||||||
package main
|
|
||||||
|
|
||||||
func HealthCheckHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// A very simple health check.
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
// In the future we could report back on the status of our DB, or our cache
|
|
||||||
// (e.g. Redis) by performing a simple PING, and include them in the response.
|
|
||||||
io.WriteString(w, `{"alive": true}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
r := mux.NewRouter()
|
|
||||||
r.HandleFunc("/health", HealthCheckHandler)
|
|
||||||
|
|
||||||
log.Fatal(http.ListenAndServe("localhost:8080", r))
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Our test code:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// endpoints_test.go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestHealthCheckHandler(t *testing.T) {
|
|
||||||
// Create a request to pass to our handler. We don't have any query parameters for now, so we'll
|
|
||||||
// pass 'nil' as the third parameter.
|
|
||||||
req, err := http.NewRequest("GET", "/health", nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We create a ResponseRecorder (which satisfies http.ResponseWriter) to record the response.
|
|
||||||
rr := httptest.NewRecorder()
|
|
||||||
handler := http.HandlerFunc(HealthCheckHandler)
|
|
||||||
|
|
||||||
// Our handlers satisfy http.Handler, so we can call their ServeHTTP method
|
|
||||||
// directly and pass in our Request and ResponseRecorder.
|
|
||||||
handler.ServeHTTP(rr, req)
|
|
||||||
|
|
||||||
// Check the status code is what we expect.
|
|
||||||
if status := rr.Code; status != http.StatusOK {
|
|
||||||
t.Errorf("handler returned wrong status code: got %v want %v",
|
|
||||||
status, http.StatusOK)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check the response body is what we expect.
|
|
||||||
expected := `{"alive": true}`
|
|
||||||
if rr.Body.String() != expected {
|
|
||||||
t.Errorf("handler returned unexpected body: got %v want %v",
|
|
||||||
rr.Body.String(), expected)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
In the case that our routes have [variables](#examples), we can pass those in the request. We could write
|
|
||||||
[table-driven tests](https://dave.cheney.net/2013/06/09/writing-table-driven-tests-in-go) to test multiple
|
|
||||||
possible route variables as needed.
|
|
||||||
|
|
||||||
```go
|
|
||||||
// endpoints.go
|
|
||||||
func main() {
|
|
||||||
r := mux.NewRouter()
|
|
||||||
// A route with a route variable:
|
|
||||||
r.HandleFunc("/metrics/{type}", MetricsHandler)
|
|
||||||
|
|
||||||
log.Fatal(http.ListenAndServe("localhost:8080", r))
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Our test file, with a table-driven test of `routeVariables`:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// endpoints_test.go
|
|
||||||
func TestMetricsHandler(t *testing.T) {
|
|
||||||
tt := []struct{
|
|
||||||
routeVariable string
|
|
||||||
shouldPass bool
|
|
||||||
}{
|
|
||||||
{"goroutines", true},
|
|
||||||
{"heap", true},
|
|
||||||
{"counters", true},
|
|
||||||
{"queries", true},
|
|
||||||
{"adhadaeqm3k", false},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range tt {
|
|
||||||
path := fmt.Sprintf("/metrics/%s", tc.routeVariable)
|
|
||||||
req, err := http.NewRequest("GET", path, nil)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
rr := httptest.NewRecorder()
|
|
||||||
|
|
||||||
// Need to create a router that we can pass the request through so that the vars will be added to the context
|
|
||||||
router := mux.NewRouter()
|
|
||||||
router.HandleFunc("/metrics/{type}", MetricsHandler)
|
|
||||||
router.ServeHTTP(rr, req)
|
|
||||||
|
|
||||||
// In this case, our MetricsHandler returns a non-200 response
|
|
||||||
// for a route variable it doesn't know about.
|
|
||||||
if rr.Code == http.StatusOK && !tc.shouldPass {
|
|
||||||
t.Errorf("handler should have failed on routeVariable %s: got %v want %v",
|
|
||||||
tc.routeVariable, rr.Code, http.StatusOK)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Full Example
|
|
||||||
|
|
||||||
Here's a complete, runnable example of a small `mux` based server:
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"log"
|
|
||||||
"github.com/gorilla/mux"
|
|
||||||
)
|
|
||||||
|
|
||||||
func YourHandler(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Write([]byte("Gorilla!\n"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
r := mux.NewRouter()
|
|
||||||
// Routes consist of a path and a handler function.
|
|
||||||
r.HandleFunc("/", YourHandler)
|
|
||||||
|
|
||||||
// Bind to a port and pass our router in
|
|
||||||
log.Fatal(http.ListenAndServe(":8000", r))
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
BSD licensed. See the LICENSE file for details.
|
|
26
vendor/github.com/gorilla/mux/context_gorilla.go
generated
vendored
26
vendor/github.com/gorilla/mux/context_gorilla.go
generated
vendored
|
@ -1,26 +0,0 @@
|
||||||
// +build !go1.7
|
|
||||||
|
|
||||||
package mux
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/gorilla/context"
|
|
||||||
)
|
|
||||||
|
|
||||||
func contextGet(r *http.Request, key interface{}) interface{} {
|
|
||||||
return context.Get(r, key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func contextSet(r *http.Request, key, val interface{}) *http.Request {
|
|
||||||
if val == nil {
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
context.Set(r, key, val)
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
func contextClear(r *http.Request) {
|
|
||||||
context.Clear(r)
|
|
||||||
}
|
|
24
vendor/github.com/gorilla/mux/context_native.go
generated
vendored
24
vendor/github.com/gorilla/mux/context_native.go
generated
vendored
|
@ -1,24 +0,0 @@
|
||||||
// +build go1.7
|
|
||||||
|
|
||||||
package mux
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
func contextGet(r *http.Request, key interface{}) interface{} {
|
|
||||||
return r.Context().Value(key)
|
|
||||||
}
|
|
||||||
|
|
||||||
func contextSet(r *http.Request, key, val interface{}) *http.Request {
|
|
||||||
if val == nil {
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
return r.WithContext(context.WithValue(r.Context(), key, val))
|
|
||||||
}
|
|
||||||
|
|
||||||
func contextClear(r *http.Request) {
|
|
||||||
return
|
|
||||||
}
|
|
306
vendor/github.com/gorilla/mux/doc.go
generated
vendored
306
vendor/github.com/gorilla/mux/doc.go
generated
vendored
|
@ -1,306 +0,0 @@
|
||||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
/*
|
|
||||||
Package mux implements a request router and dispatcher.
|
|
||||||
|
|
||||||
The name mux stands for "HTTP request multiplexer". Like the standard
|
|
||||||
http.ServeMux, mux.Router matches incoming requests against a list of
|
|
||||||
registered routes and calls a handler for the route that matches the URL
|
|
||||||
or other conditions. The main features are:
|
|
||||||
|
|
||||||
* Requests can be matched based on URL host, path, path prefix, schemes,
|
|
||||||
header and query values, HTTP methods or using custom matchers.
|
|
||||||
* URL hosts, paths and query values can have variables with an optional
|
|
||||||
regular expression.
|
|
||||||
* Registered URLs can be built, or "reversed", which helps maintaining
|
|
||||||
references to resources.
|
|
||||||
* Routes can be used as subrouters: nested routes are only tested if the
|
|
||||||
parent route matches. This is useful to define groups of routes that
|
|
||||||
share common conditions like a host, a path prefix or other repeated
|
|
||||||
attributes. As a bonus, this optimizes request matching.
|
|
||||||
* It implements the http.Handler interface so it is compatible with the
|
|
||||||
standard http.ServeMux.
|
|
||||||
|
|
||||||
Let's start registering a couple of URL paths and handlers:
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
r := mux.NewRouter()
|
|
||||||
r.HandleFunc("/", HomeHandler)
|
|
||||||
r.HandleFunc("/products", ProductsHandler)
|
|
||||||
r.HandleFunc("/articles", ArticlesHandler)
|
|
||||||
http.Handle("/", r)
|
|
||||||
}
|
|
||||||
|
|
||||||
Here we register three routes mapping URL paths to handlers. This is
|
|
||||||
equivalent to how http.HandleFunc() works: if an incoming request URL matches
|
|
||||||
one of the paths, the corresponding handler is called passing
|
|
||||||
(http.ResponseWriter, *http.Request) as parameters.
|
|
||||||
|
|
||||||
Paths can have variables. They are defined using the format {name} or
|
|
||||||
{name:pattern}. If a regular expression pattern is not defined, the matched
|
|
||||||
variable will be anything until the next slash. For example:
|
|
||||||
|
|
||||||
r := mux.NewRouter()
|
|
||||||
r.HandleFunc("/products/{key}", ProductHandler)
|
|
||||||
r.HandleFunc("/articles/{category}/", ArticlesCategoryHandler)
|
|
||||||
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler)
|
|
||||||
|
|
||||||
Groups can be used inside patterns, as long as they are non-capturing (?:re). For example:
|
|
||||||
|
|
||||||
r.HandleFunc("/articles/{category}/{sort:(?:asc|desc|new)}", ArticlesCategoryHandler)
|
|
||||||
|
|
||||||
The names are used to create a map of route variables which can be retrieved
|
|
||||||
calling mux.Vars():
|
|
||||||
|
|
||||||
vars := mux.Vars(request)
|
|
||||||
category := vars["category"]
|
|
||||||
|
|
||||||
Note that if any capturing groups are present, mux will panic() during parsing. To prevent
|
|
||||||
this, convert any capturing groups to non-capturing, e.g. change "/{sort:(asc|desc)}" to
|
|
||||||
"/{sort:(?:asc|desc)}". This is a change from prior versions which behaved unpredictably
|
|
||||||
when capturing groups were present.
|
|
||||||
|
|
||||||
And this is all you need to know about the basic usage. More advanced options
|
|
||||||
are explained below.
|
|
||||||
|
|
||||||
Routes can also be restricted to a domain or subdomain. Just define a host
|
|
||||||
pattern to be matched. They can also have variables:
|
|
||||||
|
|
||||||
r := mux.NewRouter()
|
|
||||||
// Only matches if domain is "www.example.com".
|
|
||||||
r.Host("www.example.com")
|
|
||||||
// Matches a dynamic subdomain.
|
|
||||||
r.Host("{subdomain:[a-z]+}.domain.com")
|
|
||||||
|
|
||||||
There are several other matchers that can be added. To match path prefixes:
|
|
||||||
|
|
||||||
r.PathPrefix("/products/")
|
|
||||||
|
|
||||||
...or HTTP methods:
|
|
||||||
|
|
||||||
r.Methods("GET", "POST")
|
|
||||||
|
|
||||||
...or URL schemes:
|
|
||||||
|
|
||||||
r.Schemes("https")
|
|
||||||
|
|
||||||
...or header values:
|
|
||||||
|
|
||||||
r.Headers("X-Requested-With", "XMLHttpRequest")
|
|
||||||
|
|
||||||
...or query values:
|
|
||||||
|
|
||||||
r.Queries("key", "value")
|
|
||||||
|
|
||||||
...or to use a custom matcher function:
|
|
||||||
|
|
||||||
r.MatcherFunc(func(r *http.Request, rm *RouteMatch) bool {
|
|
||||||
return r.ProtoMajor == 0
|
|
||||||
})
|
|
||||||
|
|
||||||
...and finally, it is possible to combine several matchers in a single route:
|
|
||||||
|
|
||||||
r.HandleFunc("/products", ProductsHandler).
|
|
||||||
Host("www.example.com").
|
|
||||||
Methods("GET").
|
|
||||||
Schemes("http")
|
|
||||||
|
|
||||||
Setting the same matching conditions again and again can be boring, so we have
|
|
||||||
a way to group several routes that share the same requirements.
|
|
||||||
We call it "subrouting".
|
|
||||||
|
|
||||||
For example, let's say we have several URLs that should only match when the
|
|
||||||
host is "www.example.com". Create a route for that host and get a "subrouter"
|
|
||||||
from it:
|
|
||||||
|
|
||||||
r := mux.NewRouter()
|
|
||||||
s := r.Host("www.example.com").Subrouter()
|
|
||||||
|
|
||||||
Then register routes in the subrouter:
|
|
||||||
|
|
||||||
s.HandleFunc("/products/", ProductsHandler)
|
|
||||||
s.HandleFunc("/products/{key}", ProductHandler)
|
|
||||||
s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
|
|
||||||
|
|
||||||
The three URL paths we registered above will only be tested if the domain is
|
|
||||||
"www.example.com", because the subrouter is tested first. This is not
|
|
||||||
only convenient, but also optimizes request matching. You can create
|
|
||||||
subrouters combining any attribute matchers accepted by a route.
|
|
||||||
|
|
||||||
Subrouters can be used to create domain or path "namespaces": you define
|
|
||||||
subrouters in a central place and then parts of the app can register its
|
|
||||||
paths relatively to a given subrouter.
|
|
||||||
|
|
||||||
There's one more thing about subroutes. When a subrouter has a path prefix,
|
|
||||||
the inner routes use it as base for their paths:
|
|
||||||
|
|
||||||
r := mux.NewRouter()
|
|
||||||
s := r.PathPrefix("/products").Subrouter()
|
|
||||||
// "/products/"
|
|
||||||
s.HandleFunc("/", ProductsHandler)
|
|
||||||
// "/products/{key}/"
|
|
||||||
s.HandleFunc("/{key}/", ProductHandler)
|
|
||||||
// "/products/{key}/details"
|
|
||||||
s.HandleFunc("/{key}/details", ProductDetailsHandler)
|
|
||||||
|
|
||||||
Note that the path provided to PathPrefix() represents a "wildcard": calling
|
|
||||||
PathPrefix("/static/").Handler(...) means that the handler will be passed any
|
|
||||||
request that matches "/static/*". This makes it easy to serve static files with mux:
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
var dir string
|
|
||||||
|
|
||||||
flag.StringVar(&dir, "dir", ".", "the directory to serve files from. Defaults to the current dir")
|
|
||||||
flag.Parse()
|
|
||||||
r := mux.NewRouter()
|
|
||||||
|
|
||||||
// This will serve files under http://localhost:8000/static/<filename>
|
|
||||||
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir(dir))))
|
|
||||||
|
|
||||||
srv := &http.Server{
|
|
||||||
Handler: r,
|
|
||||||
Addr: "127.0.0.1:8000",
|
|
||||||
// Good practice: enforce timeouts for servers you create!
|
|
||||||
WriteTimeout: 15 * time.Second,
|
|
||||||
ReadTimeout: 15 * time.Second,
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Fatal(srv.ListenAndServe())
|
|
||||||
}
|
|
||||||
|
|
||||||
Now let's see how to build registered URLs.
|
|
||||||
|
|
||||||
Routes can be named. All routes that define a name can have their URLs built,
|
|
||||||
or "reversed". We define a name calling Name() on a route. For example:
|
|
||||||
|
|
||||||
r := mux.NewRouter()
|
|
||||||
r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
|
|
||||||
Name("article")
|
|
||||||
|
|
||||||
To build a URL, get the route and call the URL() method, passing a sequence of
|
|
||||||
key/value pairs for the route variables. For the previous route, we would do:
|
|
||||||
|
|
||||||
url, err := r.Get("article").URL("category", "technology", "id", "42")
|
|
||||||
|
|
||||||
...and the result will be a url.URL with the following path:
|
|
||||||
|
|
||||||
"/articles/technology/42"
|
|
||||||
|
|
||||||
This also works for host and query value variables:
|
|
||||||
|
|
||||||
r := mux.NewRouter()
|
|
||||||
r.Host("{subdomain}.domain.com").
|
|
||||||
Path("/articles/{category}/{id:[0-9]+}").
|
|
||||||
Queries("filter", "{filter}").
|
|
||||||
HandlerFunc(ArticleHandler).
|
|
||||||
Name("article")
|
|
||||||
|
|
||||||
// url.String() will be "http://news.domain.com/articles/technology/42?filter=gorilla"
|
|
||||||
url, err := r.Get("article").URL("subdomain", "news",
|
|
||||||
"category", "technology",
|
|
||||||
"id", "42",
|
|
||||||
"filter", "gorilla")
|
|
||||||
|
|
||||||
All variables defined in the route are required, and their values must
|
|
||||||
conform to the corresponding patterns. These requirements guarantee that a
|
|
||||||
generated URL will always match a registered route -- the only exception is
|
|
||||||
for explicitly defined "build-only" routes which never match.
|
|
||||||
|
|
||||||
Regex support also exists for matching Headers within a route. For example, we could do:
|
|
||||||
|
|
||||||
r.HeadersRegexp("Content-Type", "application/(text|json)")
|
|
||||||
|
|
||||||
...and the route will match both requests with a Content-Type of `application/json` as well as
|
|
||||||
`application/text`
|
|
||||||
|
|
||||||
There's also a way to build only the URL host or path for a route:
|
|
||||||
use the methods URLHost() or URLPath() instead. For the previous route,
|
|
||||||
we would do:
|
|
||||||
|
|
||||||
// "http://news.domain.com/"
|
|
||||||
host, err := r.Get("article").URLHost("subdomain", "news")
|
|
||||||
|
|
||||||
// "/articles/technology/42"
|
|
||||||
path, err := r.Get("article").URLPath("category", "technology", "id", "42")
|
|
||||||
|
|
||||||
And if you use subrouters, host and path defined separately can be built
|
|
||||||
as well:
|
|
||||||
|
|
||||||
r := mux.NewRouter()
|
|
||||||
s := r.Host("{subdomain}.domain.com").Subrouter()
|
|
||||||
s.Path("/articles/{category}/{id:[0-9]+}").
|
|
||||||
HandlerFunc(ArticleHandler).
|
|
||||||
Name("article")
|
|
||||||
|
|
||||||
// "http://news.domain.com/articles/technology/42"
|
|
||||||
url, err := r.Get("article").URL("subdomain", "news",
|
|
||||||
"category", "technology",
|
|
||||||
"id", "42")
|
|
||||||
|
|
||||||
Mux supports the addition of middlewares to a Router, which are executed in the order they are added if a match is found, including its subrouters. Middlewares are (typically) small pieces of code which take one request, do something with it, and pass it down to another middleware or the final handler. Some common use cases for middleware are request logging, header manipulation, or ResponseWriter hijacking.
|
|
||||||
|
|
||||||
type MiddlewareFunc func(http.Handler) http.Handler
|
|
||||||
|
|
||||||
Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed to it, and then calls the handler passed as parameter to the MiddlewareFunc (closures can access variables from the context where they are created).
|
|
||||||
|
|
||||||
A very basic middleware which logs the URI of the request being handled could be written as:
|
|
||||||
|
|
||||||
func simpleMw(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// Do stuff here
|
|
||||||
log.Println(r.RequestURI)
|
|
||||||
// Call the next handler, which can be another middleware in the chain, or the final handler.
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
Middlewares can be added to a router using `Router.Use()`:
|
|
||||||
|
|
||||||
r := mux.NewRouter()
|
|
||||||
r.HandleFunc("/", handler)
|
|
||||||
r.Use(simpleMw)
|
|
||||||
|
|
||||||
A more complex authentication middleware, which maps session token to users, could be written as:
|
|
||||||
|
|
||||||
// Define our struct
|
|
||||||
type authenticationMiddleware struct {
|
|
||||||
tokenUsers map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize it somewhere
|
|
||||||
func (amw *authenticationMiddleware) Populate() {
|
|
||||||
amw.tokenUsers["00000000"] = "user0"
|
|
||||||
amw.tokenUsers["aaaaaaaa"] = "userA"
|
|
||||||
amw.tokenUsers["05f717e5"] = "randomUser"
|
|
||||||
amw.tokenUsers["deadbeef"] = "user0"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Middleware function, which will be called for each request
|
|
||||||
func (amw *authenticationMiddleware) Middleware(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
token := r.Header.Get("X-Session-Token")
|
|
||||||
|
|
||||||
if user, found := amw.tokenUsers[token]; found {
|
|
||||||
// We found the token in our map
|
|
||||||
log.Printf("Authenticated user %s\n", user)
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
} else {
|
|
||||||
http.Error(w, "Forbidden", http.StatusForbidden)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
r := mux.NewRouter()
|
|
||||||
r.HandleFunc("/", handler)
|
|
||||||
|
|
||||||
amw := authenticationMiddleware{}
|
|
||||||
amw.Populate()
|
|
||||||
|
|
||||||
r.Use(amw.Middleware)
|
|
||||||
|
|
||||||
Note: The handler chain will be stopped if your middleware doesn't call `next.ServeHTTP()` with the corresponding parameters. This can be used to abort a request if the middleware writer wants to.
|
|
||||||
|
|
||||||
*/
|
|
||||||
package mux
|
|
72
vendor/github.com/gorilla/mux/middleware.go
generated
vendored
72
vendor/github.com/gorilla/mux/middleware.go
generated
vendored
|
@ -1,72 +0,0 @@
|
||||||
package mux
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MiddlewareFunc is a function which receives an http.Handler and returns another http.Handler.
|
|
||||||
// Typically, the returned handler is a closure which does something with the http.ResponseWriter and http.Request passed
|
|
||||||
// to it, and then calls the handler passed as parameter to the MiddlewareFunc.
|
|
||||||
type MiddlewareFunc func(http.Handler) http.Handler
|
|
||||||
|
|
||||||
// middleware interface is anything which implements a MiddlewareFunc named Middleware.
|
|
||||||
type middleware interface {
|
|
||||||
Middleware(handler http.Handler) http.Handler
|
|
||||||
}
|
|
||||||
|
|
||||||
// Middleware allows MiddlewareFunc to implement the middleware interface.
|
|
||||||
func (mw MiddlewareFunc) Middleware(handler http.Handler) http.Handler {
|
|
||||||
return mw(handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use appends a MiddlewareFunc to the chain. Middleware can be used to intercept or otherwise modify requests and/or responses, and are executed in the order that they are applied to the Router.
|
|
||||||
func (r *Router) Use(mwf ...MiddlewareFunc) {
|
|
||||||
for _, fn := range mwf {
|
|
||||||
r.middlewares = append(r.middlewares, fn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// useInterface appends a middleware to the chain. Middleware can be used to intercept or otherwise modify requests and/or responses, and are executed in the order that they are applied to the Router.
|
|
||||||
func (r *Router) useInterface(mw middleware) {
|
|
||||||
r.middlewares = append(r.middlewares, mw)
|
|
||||||
}
|
|
||||||
|
|
||||||
// CORSMethodMiddleware sets the Access-Control-Allow-Methods response header
|
|
||||||
// on a request, by matching routes based only on paths. It also handles
|
|
||||||
// OPTIONS requests, by settings Access-Control-Allow-Methods, and then
|
|
||||||
// returning without calling the next http handler.
|
|
||||||
func CORSMethodMiddleware(r *Router) MiddlewareFunc {
|
|
||||||
return func(next http.Handler) http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
|
||||||
var allMethods []string
|
|
||||||
|
|
||||||
err := r.Walk(func(route *Route, _ *Router, _ []*Route) error {
|
|
||||||
for _, m := range route.matchers {
|
|
||||||
if _, ok := m.(*routeRegexp); ok {
|
|
||||||
if m.Match(req, &RouteMatch{}) {
|
|
||||||
methods, err := route.GetMethods()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
allMethods = append(allMethods, methods...)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
w.Header().Set("Access-Control-Allow-Methods", strings.Join(append(allMethods, "OPTIONS"), ","))
|
|
||||||
|
|
||||||
if req.Method == "OPTIONS" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
next.ServeHTTP(w, req)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
588
vendor/github.com/gorilla/mux/mux.go
generated
vendored
588
vendor/github.com/gorilla/mux/mux.go
generated
vendored
|
@ -1,588 +0,0 @@
|
||||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package mux
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"path"
|
|
||||||
"regexp"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
// ErrMethodMismatch is returned when the method in the request does not match
|
|
||||||
// the method defined against the route.
|
|
||||||
ErrMethodMismatch = errors.New("method is not allowed")
|
|
||||||
// ErrNotFound is returned when no route match is found.
|
|
||||||
ErrNotFound = errors.New("no matching route was found")
|
|
||||||
)
|
|
||||||
|
|
||||||
// NewRouter returns a new router instance.
|
|
||||||
func NewRouter() *Router {
|
|
||||||
return &Router{namedRoutes: make(map[string]*Route), KeepContext: false}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Router registers routes to be matched and dispatches a handler.
|
|
||||||
//
|
|
||||||
// It implements the http.Handler interface, so it can be registered to serve
|
|
||||||
// requests:
|
|
||||||
//
|
|
||||||
// var router = mux.NewRouter()
|
|
||||||
//
|
|
||||||
// func main() {
|
|
||||||
// http.Handle("/", router)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// Or, for Google App Engine, register it in a init() function:
|
|
||||||
//
|
|
||||||
// func init() {
|
|
||||||
// http.Handle("/", router)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// This will send all incoming requests to the router.
|
|
||||||
type Router struct {
|
|
||||||
// Configurable Handler to be used when no route matches.
|
|
||||||
NotFoundHandler http.Handler
|
|
||||||
|
|
||||||
// Configurable Handler to be used when the request method does not match the route.
|
|
||||||
MethodNotAllowedHandler http.Handler
|
|
||||||
|
|
||||||
// Parent route, if this is a subrouter.
|
|
||||||
parent parentRoute
|
|
||||||
// Routes to be matched, in order.
|
|
||||||
routes []*Route
|
|
||||||
// Routes by name for URL building.
|
|
||||||
namedRoutes map[string]*Route
|
|
||||||
// See Router.StrictSlash(). This defines the flag for new routes.
|
|
||||||
strictSlash bool
|
|
||||||
// See Router.SkipClean(). This defines the flag for new routes.
|
|
||||||
skipClean bool
|
|
||||||
// If true, do not clear the request context after handling the request.
|
|
||||||
// This has no effect when go1.7+ is used, since the context is stored
|
|
||||||
// on the request itself.
|
|
||||||
KeepContext bool
|
|
||||||
// see Router.UseEncodedPath(). This defines a flag for all routes.
|
|
||||||
useEncodedPath bool
|
|
||||||
// Slice of middlewares to be called after a match is found
|
|
||||||
middlewares []middleware
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match attempts to match the given request against the router's registered routes.
|
|
||||||
//
|
|
||||||
// If the request matches a route of this router or one of its subrouters the Route,
|
|
||||||
// Handler, and Vars fields of the the match argument are filled and this function
|
|
||||||
// returns true.
|
|
||||||
//
|
|
||||||
// If the request does not match any of this router's or its subrouters' routes
|
|
||||||
// then this function returns false. If available, a reason for the match failure
|
|
||||||
// will be filled in the match argument's MatchErr field. If the match failure type
|
|
||||||
// (eg: not found) has a registered handler, the handler is assigned to the Handler
|
|
||||||
// field of the match argument.
|
|
||||||
func (r *Router) Match(req *http.Request, match *RouteMatch) bool {
|
|
||||||
for _, route := range r.routes {
|
|
||||||
if route.Match(req, match) {
|
|
||||||
// Build middleware chain if no error was found
|
|
||||||
if match.MatchErr == nil {
|
|
||||||
for i := len(r.middlewares) - 1; i >= 0; i-- {
|
|
||||||
match.Handler = r.middlewares[i].Middleware(match.Handler)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if match.MatchErr == ErrMethodMismatch {
|
|
||||||
if r.MethodNotAllowedHandler != nil {
|
|
||||||
match.Handler = r.MethodNotAllowedHandler
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Closest match for a router (includes sub-routers)
|
|
||||||
if r.NotFoundHandler != nil {
|
|
||||||
match.Handler = r.NotFoundHandler
|
|
||||||
match.MatchErr = ErrNotFound
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
match.MatchErr = ErrNotFound
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// ServeHTTP dispatches the handler registered in the matched route.
|
|
||||||
//
|
|
||||||
// When there is a match, the route variables can be retrieved calling
|
|
||||||
// mux.Vars(request).
|
|
||||||
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
|
||||||
if !r.skipClean {
|
|
||||||
path := req.URL.Path
|
|
||||||
if r.useEncodedPath {
|
|
||||||
path = req.URL.EscapedPath()
|
|
||||||
}
|
|
||||||
// Clean path to canonical form and redirect.
|
|
||||||
if p := cleanPath(path); p != path {
|
|
||||||
|
|
||||||
// Added 3 lines (Philip Schlump) - It was dropping the query string and #whatever from query.
|
|
||||||
// This matches with fix in go 1.2 r.c. 4 for same problem. Go Issue:
|
|
||||||
// http://code.google.com/p/go/issues/detail?id=5252
|
|
||||||
url := *req.URL
|
|
||||||
url.Path = p
|
|
||||||
p = url.String()
|
|
||||||
|
|
||||||
w.Header().Set("Location", p)
|
|
||||||
w.WriteHeader(http.StatusMovedPermanently)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var match RouteMatch
|
|
||||||
var handler http.Handler
|
|
||||||
if r.Match(req, &match) {
|
|
||||||
handler = match.Handler
|
|
||||||
req = setVars(req, match.Vars)
|
|
||||||
req = setCurrentRoute(req, match.Route)
|
|
||||||
}
|
|
||||||
|
|
||||||
if handler == nil && match.MatchErr == ErrMethodMismatch {
|
|
||||||
handler = methodNotAllowedHandler()
|
|
||||||
}
|
|
||||||
|
|
||||||
if handler == nil {
|
|
||||||
handler = http.NotFoundHandler()
|
|
||||||
}
|
|
||||||
|
|
||||||
if !r.KeepContext {
|
|
||||||
defer contextClear(req)
|
|
||||||
}
|
|
||||||
|
|
||||||
handler.ServeHTTP(w, req)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns a route registered with the given name.
|
|
||||||
func (r *Router) Get(name string) *Route {
|
|
||||||
return r.getNamedRoutes()[name]
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRoute returns a route registered with the given name. This method
|
|
||||||
// was renamed to Get() and remains here for backwards compatibility.
|
|
||||||
func (r *Router) GetRoute(name string) *Route {
|
|
||||||
return r.getNamedRoutes()[name]
|
|
||||||
}
|
|
||||||
|
|
||||||
// StrictSlash defines the trailing slash behavior for new routes. The initial
|
|
||||||
// value is false.
|
|
||||||
//
|
|
||||||
// When true, if the route path is "/path/", accessing "/path" will perform a redirect
|
|
||||||
// to the former and vice versa. In other words, your application will always
|
|
||||||
// see the path as specified in the route.
|
|
||||||
//
|
|
||||||
// When false, if the route path is "/path", accessing "/path/" will not match
|
|
||||||
// this route and vice versa.
|
|
||||||
//
|
|
||||||
// The re-direct is a HTTP 301 (Moved Permanently). Note that when this is set for
|
|
||||||
// routes with a non-idempotent method (e.g. POST, PUT), the subsequent re-directed
|
|
||||||
// request will be made as a GET by most clients. Use middleware or client settings
|
|
||||||
// to modify this behaviour as needed.
|
|
||||||
//
|
|
||||||
// Special case: when a route sets a path prefix using the PathPrefix() method,
|
|
||||||
// strict slash is ignored for that route because the redirect behavior can't
|
|
||||||
// be determined from a prefix alone. However, any subrouters created from that
|
|
||||||
// route inherit the original StrictSlash setting.
|
|
||||||
func (r *Router) StrictSlash(value bool) *Router {
|
|
||||||
r.strictSlash = value
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// SkipClean defines the path cleaning behaviour for new routes. The initial
|
|
||||||
// value is false. Users should be careful about which routes are not cleaned
|
|
||||||
//
|
|
||||||
// When true, if the route path is "/path//to", it will remain with the double
|
|
||||||
// slash. This is helpful if you have a route like: /fetch/http://xkcd.com/534/
|
|
||||||
//
|
|
||||||
// When false, the path will be cleaned, so /fetch/http://xkcd.com/534/ will
|
|
||||||
// become /fetch/http/xkcd.com/534
|
|
||||||
func (r *Router) SkipClean(value bool) *Router {
|
|
||||||
r.skipClean = value
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// UseEncodedPath tells the router to match the encoded original path
|
|
||||||
// to the routes.
|
|
||||||
// For eg. "/path/foo%2Fbar/to" will match the path "/path/{var}/to".
|
|
||||||
//
|
|
||||||
// If not called, the router will match the unencoded path to the routes.
|
|
||||||
// For eg. "/path/foo%2Fbar/to" will match the path "/path/foo/bar/to"
|
|
||||||
func (r *Router) UseEncodedPath() *Router {
|
|
||||||
r.useEncodedPath = true
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// parentRoute
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
func (r *Router) getBuildScheme() string {
|
|
||||||
if r.parent != nil {
|
|
||||||
return r.parent.getBuildScheme()
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// getNamedRoutes returns the map where named routes are registered.
|
|
||||||
func (r *Router) getNamedRoutes() map[string]*Route {
|
|
||||||
if r.namedRoutes == nil {
|
|
||||||
if r.parent != nil {
|
|
||||||
r.namedRoutes = r.parent.getNamedRoutes()
|
|
||||||
} else {
|
|
||||||
r.namedRoutes = make(map[string]*Route)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return r.namedRoutes
|
|
||||||
}
|
|
||||||
|
|
||||||
// getRegexpGroup returns regexp definitions from the parent route, if any.
|
|
||||||
func (r *Router) getRegexpGroup() *routeRegexpGroup {
|
|
||||||
if r.parent != nil {
|
|
||||||
return r.parent.getRegexpGroup()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Router) buildVars(m map[string]string) map[string]string {
|
|
||||||
if r.parent != nil {
|
|
||||||
m = r.parent.buildVars(m)
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// Route factories
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// NewRoute registers an empty route.
|
|
||||||
func (r *Router) NewRoute() *Route {
|
|
||||||
route := &Route{parent: r, strictSlash: r.strictSlash, skipClean: r.skipClean, useEncodedPath: r.useEncodedPath}
|
|
||||||
r.routes = append(r.routes, route)
|
|
||||||
return route
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle registers a new route with a matcher for the URL path.
|
|
||||||
// See Route.Path() and Route.Handler().
|
|
||||||
func (r *Router) Handle(path string, handler http.Handler) *Route {
|
|
||||||
return r.NewRoute().Path(path).Handler(handler)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandleFunc registers a new route with a matcher for the URL path.
|
|
||||||
// See Route.Path() and Route.HandlerFunc().
|
|
||||||
func (r *Router) HandleFunc(path string, f func(http.ResponseWriter,
|
|
||||||
*http.Request)) *Route {
|
|
||||||
return r.NewRoute().Path(path).HandlerFunc(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Headers registers a new route with a matcher for request header values.
|
|
||||||
// See Route.Headers().
|
|
||||||
func (r *Router) Headers(pairs ...string) *Route {
|
|
||||||
return r.NewRoute().Headers(pairs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Host registers a new route with a matcher for the URL host.
|
|
||||||
// See Route.Host().
|
|
||||||
func (r *Router) Host(tpl string) *Route {
|
|
||||||
return r.NewRoute().Host(tpl)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MatcherFunc registers a new route with a custom matcher function.
|
|
||||||
// See Route.MatcherFunc().
|
|
||||||
func (r *Router) MatcherFunc(f MatcherFunc) *Route {
|
|
||||||
return r.NewRoute().MatcherFunc(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Methods registers a new route with a matcher for HTTP methods.
|
|
||||||
// See Route.Methods().
|
|
||||||
func (r *Router) Methods(methods ...string) *Route {
|
|
||||||
return r.NewRoute().Methods(methods...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Path registers a new route with a matcher for the URL path.
|
|
||||||
// See Route.Path().
|
|
||||||
func (r *Router) Path(tpl string) *Route {
|
|
||||||
return r.NewRoute().Path(tpl)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PathPrefix registers a new route with a matcher for the URL path prefix.
|
|
||||||
// See Route.PathPrefix().
|
|
||||||
func (r *Router) PathPrefix(tpl string) *Route {
|
|
||||||
return r.NewRoute().PathPrefix(tpl)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Queries registers a new route with a matcher for URL query values.
|
|
||||||
// See Route.Queries().
|
|
||||||
func (r *Router) Queries(pairs ...string) *Route {
|
|
||||||
return r.NewRoute().Queries(pairs...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Schemes registers a new route with a matcher for URL schemes.
|
|
||||||
// See Route.Schemes().
|
|
||||||
func (r *Router) Schemes(schemes ...string) *Route {
|
|
||||||
return r.NewRoute().Schemes(schemes...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// BuildVarsFunc registers a new route with a custom function for modifying
|
|
||||||
// route variables before building a URL.
|
|
||||||
func (r *Router) BuildVarsFunc(f BuildVarsFunc) *Route {
|
|
||||||
return r.NewRoute().BuildVarsFunc(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Walk walks the router and all its sub-routers, calling walkFn for each route
|
|
||||||
// in the tree. The routes are walked in the order they were added. Sub-routers
|
|
||||||
// are explored depth-first.
|
|
||||||
func (r *Router) Walk(walkFn WalkFunc) error {
|
|
||||||
return r.walk(walkFn, []*Route{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// SkipRouter is used as a return value from WalkFuncs to indicate that the
|
|
||||||
// router that walk is about to descend down to should be skipped.
|
|
||||||
var SkipRouter = errors.New("skip this router")
|
|
||||||
|
|
||||||
// WalkFunc is the type of the function called for each route visited by Walk.
|
|
||||||
// At every invocation, it is given the current route, and the current router,
|
|
||||||
// and a list of ancestor routes that lead to the current route.
|
|
||||||
type WalkFunc func(route *Route, router *Router, ancestors []*Route) error
|
|
||||||
|
|
||||||
func (r *Router) walk(walkFn WalkFunc, ancestors []*Route) error {
|
|
||||||
for _, t := range r.routes {
|
|
||||||
err := walkFn(t, r, ancestors)
|
|
||||||
if err == SkipRouter {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, sr := range t.matchers {
|
|
||||||
if h, ok := sr.(*Router); ok {
|
|
||||||
ancestors = append(ancestors, t)
|
|
||||||
err := h.walk(walkFn, ancestors)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ancestors = ancestors[:len(ancestors)-1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if h, ok := t.handler.(*Router); ok {
|
|
||||||
ancestors = append(ancestors, t)
|
|
||||||
err := h.walk(walkFn, ancestors)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ancestors = ancestors[:len(ancestors)-1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// Context
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// RouteMatch stores information about a matched route.
|
|
||||||
type RouteMatch struct {
|
|
||||||
Route *Route
|
|
||||||
Handler http.Handler
|
|
||||||
Vars map[string]string
|
|
||||||
|
|
||||||
// MatchErr is set to appropriate matching error
|
|
||||||
// It is set to ErrMethodMismatch if there is a mismatch in
|
|
||||||
// the request method and route method
|
|
||||||
MatchErr error
|
|
||||||
}
|
|
||||||
|
|
||||||
type contextKey int
|
|
||||||
|
|
||||||
const (
|
|
||||||
varsKey contextKey = iota
|
|
||||||
routeKey
|
|
||||||
)
|
|
||||||
|
|
||||||
// Vars returns the route variables for the current request, if any.
|
|
||||||
func Vars(r *http.Request) map[string]string {
|
|
||||||
if rv := contextGet(r, varsKey); rv != nil {
|
|
||||||
return rv.(map[string]string)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CurrentRoute returns the matched route for the current request, if any.
|
|
||||||
// This only works when called inside the handler of the matched route
|
|
||||||
// because the matched route is stored in the request context which is cleared
|
|
||||||
// after the handler returns, unless the KeepContext option is set on the
|
|
||||||
// Router.
|
|
||||||
func CurrentRoute(r *http.Request) *Route {
|
|
||||||
if rv := contextGet(r, routeKey); rv != nil {
|
|
||||||
return rv.(*Route)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func setVars(r *http.Request, val interface{}) *http.Request {
|
|
||||||
return contextSet(r, varsKey, val)
|
|
||||||
}
|
|
||||||
|
|
||||||
func setCurrentRoute(r *http.Request, val interface{}) *http.Request {
|
|
||||||
return contextSet(r, routeKey, val)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// Helpers
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// cleanPath returns the canonical path for p, eliminating . and .. elements.
|
|
||||||
// Borrowed from the net/http package.
|
|
||||||
func cleanPath(p string) string {
|
|
||||||
if p == "" {
|
|
||||||
return "/"
|
|
||||||
}
|
|
||||||
if p[0] != '/' {
|
|
||||||
p = "/" + p
|
|
||||||
}
|
|
||||||
np := path.Clean(p)
|
|
||||||
// path.Clean removes trailing slash except for root;
|
|
||||||
// put the trailing slash back if necessary.
|
|
||||||
if p[len(p)-1] == '/' && np != "/" {
|
|
||||||
np += "/"
|
|
||||||
}
|
|
||||||
|
|
||||||
return np
|
|
||||||
}
|
|
||||||
|
|
||||||
// uniqueVars returns an error if two slices contain duplicated strings.
|
|
||||||
func uniqueVars(s1, s2 []string) error {
|
|
||||||
for _, v1 := range s1 {
|
|
||||||
for _, v2 := range s2 {
|
|
||||||
if v1 == v2 {
|
|
||||||
return fmt.Errorf("mux: duplicated route variable %q", v2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkPairs returns the count of strings passed in, and an error if
|
|
||||||
// the count is not an even number.
|
|
||||||
func checkPairs(pairs ...string) (int, error) {
|
|
||||||
length := len(pairs)
|
|
||||||
if length%2 != 0 {
|
|
||||||
return length, fmt.Errorf(
|
|
||||||
"mux: number of parameters must be multiple of 2, got %v", pairs)
|
|
||||||
}
|
|
||||||
return length, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// mapFromPairsToString converts variadic string parameters to a
|
|
||||||
// string to string map.
|
|
||||||
func mapFromPairsToString(pairs ...string) (map[string]string, error) {
|
|
||||||
length, err := checkPairs(pairs...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
m := make(map[string]string, length/2)
|
|
||||||
for i := 0; i < length; i += 2 {
|
|
||||||
m[pairs[i]] = pairs[i+1]
|
|
||||||
}
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// mapFromPairsToRegex converts variadic string parameters to a
|
|
||||||
// string to regex map.
|
|
||||||
func mapFromPairsToRegex(pairs ...string) (map[string]*regexp.Regexp, error) {
|
|
||||||
length, err := checkPairs(pairs...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
m := make(map[string]*regexp.Regexp, length/2)
|
|
||||||
for i := 0; i < length; i += 2 {
|
|
||||||
regex, err := regexp.Compile(pairs[i+1])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
m[pairs[i]] = regex
|
|
||||||
}
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// matchInArray returns true if the given string value is in the array.
|
|
||||||
func matchInArray(arr []string, value string) bool {
|
|
||||||
for _, v := range arr {
|
|
||||||
if v == value {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// matchMapWithString returns true if the given key/value pairs exist in a given map.
|
|
||||||
func matchMapWithString(toCheck map[string]string, toMatch map[string][]string, canonicalKey bool) bool {
|
|
||||||
for k, v := range toCheck {
|
|
||||||
// Check if key exists.
|
|
||||||
if canonicalKey {
|
|
||||||
k = http.CanonicalHeaderKey(k)
|
|
||||||
}
|
|
||||||
if values := toMatch[k]; values == nil {
|
|
||||||
return false
|
|
||||||
} else if v != "" {
|
|
||||||
// If value was defined as an empty string we only check that the
|
|
||||||
// key exists. Otherwise we also check for equality.
|
|
||||||
valueExists := false
|
|
||||||
for _, value := range values {
|
|
||||||
if v == value {
|
|
||||||
valueExists = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !valueExists {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// matchMapWithRegex returns true if the given key/value pairs exist in a given map compiled against
|
|
||||||
// the given regex
|
|
||||||
func matchMapWithRegex(toCheck map[string]*regexp.Regexp, toMatch map[string][]string, canonicalKey bool) bool {
|
|
||||||
for k, v := range toCheck {
|
|
||||||
// Check if key exists.
|
|
||||||
if canonicalKey {
|
|
||||||
k = http.CanonicalHeaderKey(k)
|
|
||||||
}
|
|
||||||
if values := toMatch[k]; values == nil {
|
|
||||||
return false
|
|
||||||
} else if v != nil {
|
|
||||||
// If value was defined as an empty string we only check that the
|
|
||||||
// key exists. Otherwise we also check for equality.
|
|
||||||
valueExists := false
|
|
||||||
for _, value := range values {
|
|
||||||
if v.MatchString(value) {
|
|
||||||
valueExists = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !valueExists {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// methodNotAllowed replies to the request with an HTTP status code 405.
|
|
||||||
func methodNotAllowed(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
||||||
}
|
|
||||||
|
|
||||||
// methodNotAllowedHandler returns a simple request handler
|
|
||||||
// that replies to each request with a status code 405.
|
|
||||||
func methodNotAllowedHandler() http.Handler { return http.HandlerFunc(methodNotAllowed) }
|
|
332
vendor/github.com/gorilla/mux/regexp.go
generated
vendored
332
vendor/github.com/gorilla/mux/regexp.go
generated
vendored
|
@ -1,332 +0,0 @@
|
||||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package mux
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type routeRegexpOptions struct {
|
|
||||||
strictSlash bool
|
|
||||||
useEncodedPath bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type regexpType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
regexpTypePath regexpType = 0
|
|
||||||
regexpTypeHost regexpType = 1
|
|
||||||
regexpTypePrefix regexpType = 2
|
|
||||||
regexpTypeQuery regexpType = 3
|
|
||||||
)
|
|
||||||
|
|
||||||
// newRouteRegexp parses a route template and returns a routeRegexp,
|
|
||||||
// used to match a host, a path or a query string.
|
|
||||||
//
|
|
||||||
// It will extract named variables, assemble a regexp to be matched, create
|
|
||||||
// a "reverse" template to build URLs and compile regexps to validate variable
|
|
||||||
// values used in URL building.
|
|
||||||
//
|
|
||||||
// Previously we accepted only Python-like identifiers for variable
|
|
||||||
// names ([a-zA-Z_][a-zA-Z0-9_]*), but currently the only restriction is that
|
|
||||||
// name and pattern can't be empty, and names can't contain a colon.
|
|
||||||
func newRouteRegexp(tpl string, typ regexpType, options routeRegexpOptions) (*routeRegexp, error) {
|
|
||||||
// Check if it is well-formed.
|
|
||||||
idxs, errBraces := braceIndices(tpl)
|
|
||||||
if errBraces != nil {
|
|
||||||
return nil, errBraces
|
|
||||||
}
|
|
||||||
// Backup the original.
|
|
||||||
template := tpl
|
|
||||||
// Now let's parse it.
|
|
||||||
defaultPattern := "[^/]+"
|
|
||||||
if typ == regexpTypeQuery {
|
|
||||||
defaultPattern = ".*"
|
|
||||||
} else if typ == regexpTypeHost {
|
|
||||||
defaultPattern = "[^.]+"
|
|
||||||
}
|
|
||||||
// Only match strict slash if not matching
|
|
||||||
if typ != regexpTypePath {
|
|
||||||
options.strictSlash = false
|
|
||||||
}
|
|
||||||
// Set a flag for strictSlash.
|
|
||||||
endSlash := false
|
|
||||||
if options.strictSlash && strings.HasSuffix(tpl, "/") {
|
|
||||||
tpl = tpl[:len(tpl)-1]
|
|
||||||
endSlash = true
|
|
||||||
}
|
|
||||||
varsN := make([]string, len(idxs)/2)
|
|
||||||
varsR := make([]*regexp.Regexp, len(idxs)/2)
|
|
||||||
pattern := bytes.NewBufferString("")
|
|
||||||
pattern.WriteByte('^')
|
|
||||||
reverse := bytes.NewBufferString("")
|
|
||||||
var end int
|
|
||||||
var err error
|
|
||||||
for i := 0; i < len(idxs); i += 2 {
|
|
||||||
// Set all values we are interested in.
|
|
||||||
raw := tpl[end:idxs[i]]
|
|
||||||
end = idxs[i+1]
|
|
||||||
parts := strings.SplitN(tpl[idxs[i]+1:end-1], ":", 2)
|
|
||||||
name := parts[0]
|
|
||||||
patt := defaultPattern
|
|
||||||
if len(parts) == 2 {
|
|
||||||
patt = parts[1]
|
|
||||||
}
|
|
||||||
// Name or pattern can't be empty.
|
|
||||||
if name == "" || patt == "" {
|
|
||||||
return nil, fmt.Errorf("mux: missing name or pattern in %q",
|
|
||||||
tpl[idxs[i]:end])
|
|
||||||
}
|
|
||||||
// Build the regexp pattern.
|
|
||||||
fmt.Fprintf(pattern, "%s(?P<%s>%s)", regexp.QuoteMeta(raw), varGroupName(i/2), patt)
|
|
||||||
|
|
||||||
// Build the reverse template.
|
|
||||||
fmt.Fprintf(reverse, "%s%%s", raw)
|
|
||||||
|
|
||||||
// Append variable name and compiled pattern.
|
|
||||||
varsN[i/2] = name
|
|
||||||
varsR[i/2], err = regexp.Compile(fmt.Sprintf("^%s$", patt))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Add the remaining.
|
|
||||||
raw := tpl[end:]
|
|
||||||
pattern.WriteString(regexp.QuoteMeta(raw))
|
|
||||||
if options.strictSlash {
|
|
||||||
pattern.WriteString("[/]?")
|
|
||||||
}
|
|
||||||
if typ == regexpTypeQuery {
|
|
||||||
// Add the default pattern if the query value is empty
|
|
||||||
if queryVal := strings.SplitN(template, "=", 2)[1]; queryVal == "" {
|
|
||||||
pattern.WriteString(defaultPattern)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if typ != regexpTypePrefix {
|
|
||||||
pattern.WriteByte('$')
|
|
||||||
}
|
|
||||||
reverse.WriteString(raw)
|
|
||||||
if endSlash {
|
|
||||||
reverse.WriteByte('/')
|
|
||||||
}
|
|
||||||
// Compile full regexp.
|
|
||||||
reg, errCompile := regexp.Compile(pattern.String())
|
|
||||||
if errCompile != nil {
|
|
||||||
return nil, errCompile
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for capturing groups which used to work in older versions
|
|
||||||
if reg.NumSubexp() != len(idxs)/2 {
|
|
||||||
panic(fmt.Sprintf("route %s contains capture groups in its regexp. ", template) +
|
|
||||||
"Only non-capturing groups are accepted: e.g. (?:pattern) instead of (pattern)")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Done!
|
|
||||||
return &routeRegexp{
|
|
||||||
template: template,
|
|
||||||
regexpType: typ,
|
|
||||||
options: options,
|
|
||||||
regexp: reg,
|
|
||||||
reverse: reverse.String(),
|
|
||||||
varsN: varsN,
|
|
||||||
varsR: varsR,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// routeRegexp stores a regexp to match a host or path and information to
|
|
||||||
// collect and validate route variables.
|
|
||||||
type routeRegexp struct {
|
|
||||||
// The unmodified template.
|
|
||||||
template string
|
|
||||||
// The type of match
|
|
||||||
regexpType regexpType
|
|
||||||
// Options for matching
|
|
||||||
options routeRegexpOptions
|
|
||||||
// Expanded regexp.
|
|
||||||
regexp *regexp.Regexp
|
|
||||||
// Reverse template.
|
|
||||||
reverse string
|
|
||||||
// Variable names.
|
|
||||||
varsN []string
|
|
||||||
// Variable regexps (validators).
|
|
||||||
varsR []*regexp.Regexp
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match matches the regexp against the URL host or path.
|
|
||||||
func (r *routeRegexp) Match(req *http.Request, match *RouteMatch) bool {
|
|
||||||
if r.regexpType != regexpTypeHost {
|
|
||||||
if r.regexpType == regexpTypeQuery {
|
|
||||||
return r.matchQueryString(req)
|
|
||||||
}
|
|
||||||
path := req.URL.Path
|
|
||||||
if r.options.useEncodedPath {
|
|
||||||
path = req.URL.EscapedPath()
|
|
||||||
}
|
|
||||||
return r.regexp.MatchString(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r.regexp.MatchString(getHost(req))
|
|
||||||
}
|
|
||||||
|
|
||||||
// url builds a URL part using the given values.
|
|
||||||
func (r *routeRegexp) url(values map[string]string) (string, error) {
|
|
||||||
urlValues := make([]interface{}, len(r.varsN))
|
|
||||||
for k, v := range r.varsN {
|
|
||||||
value, ok := values[v]
|
|
||||||
if !ok {
|
|
||||||
return "", fmt.Errorf("mux: missing route variable %q", v)
|
|
||||||
}
|
|
||||||
if r.regexpType == regexpTypeQuery {
|
|
||||||
value = url.QueryEscape(value)
|
|
||||||
}
|
|
||||||
urlValues[k] = value
|
|
||||||
}
|
|
||||||
rv := fmt.Sprintf(r.reverse, urlValues...)
|
|
||||||
if !r.regexp.MatchString(rv) {
|
|
||||||
// The URL is checked against the full regexp, instead of checking
|
|
||||||
// individual variables. This is faster but to provide a good error
|
|
||||||
// message, we check individual regexps if the URL doesn't match.
|
|
||||||
for k, v := range r.varsN {
|
|
||||||
if !r.varsR[k].MatchString(values[v]) {
|
|
||||||
return "", fmt.Errorf(
|
|
||||||
"mux: variable %q doesn't match, expected %q", values[v],
|
|
||||||
r.varsR[k].String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return rv, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getURLQuery returns a single query parameter from a request URL.
|
|
||||||
// For a URL with foo=bar&baz=ding, we return only the relevant key
|
|
||||||
// value pair for the routeRegexp.
|
|
||||||
func (r *routeRegexp) getURLQuery(req *http.Request) string {
|
|
||||||
if r.regexpType != regexpTypeQuery {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
templateKey := strings.SplitN(r.template, "=", 2)[0]
|
|
||||||
for key, vals := range req.URL.Query() {
|
|
||||||
if key == templateKey && len(vals) > 0 {
|
|
||||||
return key + "=" + vals[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *routeRegexp) matchQueryString(req *http.Request) bool {
|
|
||||||
return r.regexp.MatchString(r.getURLQuery(req))
|
|
||||||
}
|
|
||||||
|
|
||||||
// braceIndices returns the first level curly brace indices from a string.
|
|
||||||
// It returns an error in case of unbalanced braces.
|
|
||||||
func braceIndices(s string) ([]int, error) {
|
|
||||||
var level, idx int
|
|
||||||
var idxs []int
|
|
||||||
for i := 0; i < len(s); i++ {
|
|
||||||
switch s[i] {
|
|
||||||
case '{':
|
|
||||||
if level++; level == 1 {
|
|
||||||
idx = i
|
|
||||||
}
|
|
||||||
case '}':
|
|
||||||
if level--; level == 0 {
|
|
||||||
idxs = append(idxs, idx, i+1)
|
|
||||||
} else if level < 0 {
|
|
||||||
return nil, fmt.Errorf("mux: unbalanced braces in %q", s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if level != 0 {
|
|
||||||
return nil, fmt.Errorf("mux: unbalanced braces in %q", s)
|
|
||||||
}
|
|
||||||
return idxs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// varGroupName builds a capturing group name for the indexed variable.
|
|
||||||
func varGroupName(idx int) string {
|
|
||||||
return "v" + strconv.Itoa(idx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// routeRegexpGroup
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// routeRegexpGroup groups the route matchers that carry variables.
|
|
||||||
type routeRegexpGroup struct {
|
|
||||||
host *routeRegexp
|
|
||||||
path *routeRegexp
|
|
||||||
queries []*routeRegexp
|
|
||||||
}
|
|
||||||
|
|
||||||
// setMatch extracts the variables from the URL once a route matches.
|
|
||||||
func (v *routeRegexpGroup) setMatch(req *http.Request, m *RouteMatch, r *Route) {
|
|
||||||
// Store host variables.
|
|
||||||
if v.host != nil {
|
|
||||||
host := getHost(req)
|
|
||||||
matches := v.host.regexp.FindStringSubmatchIndex(host)
|
|
||||||
if len(matches) > 0 {
|
|
||||||
extractVars(host, matches, v.host.varsN, m.Vars)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
path := req.URL.Path
|
|
||||||
if r.useEncodedPath {
|
|
||||||
path = req.URL.EscapedPath()
|
|
||||||
}
|
|
||||||
// Store path variables.
|
|
||||||
if v.path != nil {
|
|
||||||
matches := v.path.regexp.FindStringSubmatchIndex(path)
|
|
||||||
if len(matches) > 0 {
|
|
||||||
extractVars(path, matches, v.path.varsN, m.Vars)
|
|
||||||
// Check if we should redirect.
|
|
||||||
if v.path.options.strictSlash {
|
|
||||||
p1 := strings.HasSuffix(path, "/")
|
|
||||||
p2 := strings.HasSuffix(v.path.template, "/")
|
|
||||||
if p1 != p2 {
|
|
||||||
u, _ := url.Parse(req.URL.String())
|
|
||||||
if p1 {
|
|
||||||
u.Path = u.Path[:len(u.Path)-1]
|
|
||||||
} else {
|
|
||||||
u.Path += "/"
|
|
||||||
}
|
|
||||||
m.Handler = http.RedirectHandler(u.String(), 301)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Store query string variables.
|
|
||||||
for _, q := range v.queries {
|
|
||||||
queryURL := q.getURLQuery(req)
|
|
||||||
matches := q.regexp.FindStringSubmatchIndex(queryURL)
|
|
||||||
if len(matches) > 0 {
|
|
||||||
extractVars(queryURL, matches, q.varsN, m.Vars)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// getHost tries its best to return the request host.
|
|
||||||
func getHost(r *http.Request) string {
|
|
||||||
if r.URL.IsAbs() {
|
|
||||||
return r.URL.Host
|
|
||||||
}
|
|
||||||
host := r.Host
|
|
||||||
// Slice off any port information.
|
|
||||||
if i := strings.Index(host, ":"); i != -1 {
|
|
||||||
host = host[:i]
|
|
||||||
}
|
|
||||||
return host
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func extractVars(input string, matches []int, names []string, output map[string]string) {
|
|
||||||
for i, name := range names {
|
|
||||||
output[name] = input[matches[2*i+2]:matches[2*i+3]]
|
|
||||||
}
|
|
||||||
}
|
|
763
vendor/github.com/gorilla/mux/route.go
generated
vendored
763
vendor/github.com/gorilla/mux/route.go
generated
vendored
|
@ -1,763 +0,0 @@
|
||||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package mux
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Route stores information to match a request and build URLs.
|
|
||||||
type Route struct {
|
|
||||||
// Parent where the route was registered (a Router).
|
|
||||||
parent parentRoute
|
|
||||||
// Request handler for the route.
|
|
||||||
handler http.Handler
|
|
||||||
// List of matchers.
|
|
||||||
matchers []matcher
|
|
||||||
// Manager for the variables from host and path.
|
|
||||||
regexp *routeRegexpGroup
|
|
||||||
// If true, when the path pattern is "/path/", accessing "/path" will
|
|
||||||
// redirect to the former and vice versa.
|
|
||||||
strictSlash bool
|
|
||||||
// If true, when the path pattern is "/path//to", accessing "/path//to"
|
|
||||||
// will not redirect
|
|
||||||
skipClean bool
|
|
||||||
// If true, "/path/foo%2Fbar/to" will match the path "/path/{var}/to"
|
|
||||||
useEncodedPath bool
|
|
||||||
// The scheme used when building URLs.
|
|
||||||
buildScheme string
|
|
||||||
// If true, this route never matches: it is only used to build URLs.
|
|
||||||
buildOnly bool
|
|
||||||
// The name used to build URLs.
|
|
||||||
name string
|
|
||||||
// Error resulted from building a route.
|
|
||||||
err error
|
|
||||||
|
|
||||||
buildVarsFunc BuildVarsFunc
|
|
||||||
}
|
|
||||||
|
|
||||||
// SkipClean reports whether path cleaning is enabled for this route via
|
|
||||||
// Router.SkipClean.
|
|
||||||
func (r *Route) SkipClean() bool {
|
|
||||||
return r.skipClean
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match matches the route against the request.
|
|
||||||
func (r *Route) Match(req *http.Request, match *RouteMatch) bool {
|
|
||||||
if r.buildOnly || r.err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var matchErr error
|
|
||||||
|
|
||||||
// Match everything.
|
|
||||||
for _, m := range r.matchers {
|
|
||||||
if matched := m.Match(req, match); !matched {
|
|
||||||
if _, ok := m.(methodMatcher); ok {
|
|
||||||
matchErr = ErrMethodMismatch
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
matchErr = nil
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if matchErr != nil {
|
|
||||||
match.MatchErr = matchErr
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if match.MatchErr == ErrMethodMismatch {
|
|
||||||
// We found a route which matches request method, clear MatchErr
|
|
||||||
match.MatchErr = nil
|
|
||||||
// Then override the mis-matched handler
|
|
||||||
match.Handler = r.handler
|
|
||||||
}
|
|
||||||
|
|
||||||
// Yay, we have a match. Let's collect some info about it.
|
|
||||||
if match.Route == nil {
|
|
||||||
match.Route = r
|
|
||||||
}
|
|
||||||
if match.Handler == nil {
|
|
||||||
match.Handler = r.handler
|
|
||||||
}
|
|
||||||
if match.Vars == nil {
|
|
||||||
match.Vars = make(map[string]string)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set variables.
|
|
||||||
if r.regexp != nil {
|
|
||||||
r.regexp.setMatch(req, match, r)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// Route attributes
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// GetError returns an error resulted from building the route, if any.
|
|
||||||
func (r *Route) GetError() error {
|
|
||||||
return r.err
|
|
||||||
}
|
|
||||||
|
|
||||||
// BuildOnly sets the route to never match: it is only used to build URLs.
|
|
||||||
func (r *Route) BuildOnly() *Route {
|
|
||||||
r.buildOnly = true
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handler --------------------------------------------------------------------
|
|
||||||
|
|
||||||
// Handler sets a handler for the route.
|
|
||||||
func (r *Route) Handler(handler http.Handler) *Route {
|
|
||||||
if r.err == nil {
|
|
||||||
r.handler = handler
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// HandlerFunc sets a handler function for the route.
|
|
||||||
func (r *Route) HandlerFunc(f func(http.ResponseWriter, *http.Request)) *Route {
|
|
||||||
return r.Handler(http.HandlerFunc(f))
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHandler returns the handler for the route, if any.
|
|
||||||
func (r *Route) GetHandler() http.Handler {
|
|
||||||
return r.handler
|
|
||||||
}
|
|
||||||
|
|
||||||
// Name -----------------------------------------------------------------------
|
|
||||||
|
|
||||||
// Name sets the name for the route, used to build URLs.
|
|
||||||
// If the name was registered already it will be overwritten.
|
|
||||||
func (r *Route) Name(name string) *Route {
|
|
||||||
if r.name != "" {
|
|
||||||
r.err = fmt.Errorf("mux: route already has name %q, can't set %q",
|
|
||||||
r.name, name)
|
|
||||||
}
|
|
||||||
if r.err == nil {
|
|
||||||
r.name = name
|
|
||||||
r.getNamedRoutes()[name] = r
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetName returns the name for the route, if any.
|
|
||||||
func (r *Route) GetName() string {
|
|
||||||
return r.name
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// Matchers
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// matcher types try to match a request.
|
|
||||||
type matcher interface {
|
|
||||||
Match(*http.Request, *RouteMatch) bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// addMatcher adds a matcher to the route.
|
|
||||||
func (r *Route) addMatcher(m matcher) *Route {
|
|
||||||
if r.err == nil {
|
|
||||||
r.matchers = append(r.matchers, m)
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// addRegexpMatcher adds a host or path matcher and builder to a route.
|
|
||||||
func (r *Route) addRegexpMatcher(tpl string, typ regexpType) error {
|
|
||||||
if r.err != nil {
|
|
||||||
return r.err
|
|
||||||
}
|
|
||||||
r.regexp = r.getRegexpGroup()
|
|
||||||
if typ == regexpTypePath || typ == regexpTypePrefix {
|
|
||||||
if len(tpl) > 0 && tpl[0] != '/' {
|
|
||||||
return fmt.Errorf("mux: path must start with a slash, got %q", tpl)
|
|
||||||
}
|
|
||||||
if r.regexp.path != nil {
|
|
||||||
tpl = strings.TrimRight(r.regexp.path.template, "/") + tpl
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rr, err := newRouteRegexp(tpl, typ, routeRegexpOptions{
|
|
||||||
strictSlash: r.strictSlash,
|
|
||||||
useEncodedPath: r.useEncodedPath,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, q := range r.regexp.queries {
|
|
||||||
if err = uniqueVars(rr.varsN, q.varsN); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if typ == regexpTypeHost {
|
|
||||||
if r.regexp.path != nil {
|
|
||||||
if err = uniqueVars(rr.varsN, r.regexp.path.varsN); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
r.regexp.host = rr
|
|
||||||
} else {
|
|
||||||
if r.regexp.host != nil {
|
|
||||||
if err = uniqueVars(rr.varsN, r.regexp.host.varsN); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if typ == regexpTypeQuery {
|
|
||||||
r.regexp.queries = append(r.regexp.queries, rr)
|
|
||||||
} else {
|
|
||||||
r.regexp.path = rr
|
|
||||||
}
|
|
||||||
}
|
|
||||||
r.addMatcher(rr)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Headers --------------------------------------------------------------------
|
|
||||||
|
|
||||||
// headerMatcher matches the request against header values.
|
|
||||||
type headerMatcher map[string]string
|
|
||||||
|
|
||||||
func (m headerMatcher) Match(r *http.Request, match *RouteMatch) bool {
|
|
||||||
return matchMapWithString(m, r.Header, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Headers adds a matcher for request header values.
|
|
||||||
// It accepts a sequence of key/value pairs to be matched. For example:
|
|
||||||
//
|
|
||||||
// r := mux.NewRouter()
|
|
||||||
// r.Headers("Content-Type", "application/json",
|
|
||||||
// "X-Requested-With", "XMLHttpRequest")
|
|
||||||
//
|
|
||||||
// The above route will only match if both request header values match.
|
|
||||||
// If the value is an empty string, it will match any value if the key is set.
|
|
||||||
func (r *Route) Headers(pairs ...string) *Route {
|
|
||||||
if r.err == nil {
|
|
||||||
var headers map[string]string
|
|
||||||
headers, r.err = mapFromPairsToString(pairs...)
|
|
||||||
return r.addMatcher(headerMatcher(headers))
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// headerRegexMatcher matches the request against the route given a regex for the header
|
|
||||||
type headerRegexMatcher map[string]*regexp.Regexp
|
|
||||||
|
|
||||||
func (m headerRegexMatcher) Match(r *http.Request, match *RouteMatch) bool {
|
|
||||||
return matchMapWithRegex(m, r.Header, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// HeadersRegexp accepts a sequence of key/value pairs, where the value has regex
|
|
||||||
// support. For example:
|
|
||||||
//
|
|
||||||
// r := mux.NewRouter()
|
|
||||||
// r.HeadersRegexp("Content-Type", "application/(text|json)",
|
|
||||||
// "X-Requested-With", "XMLHttpRequest")
|
|
||||||
//
|
|
||||||
// The above route will only match if both the request header matches both regular expressions.
|
|
||||||
// If the value is an empty string, it will match any value if the key is set.
|
|
||||||
// Use the start and end of string anchors (^ and $) to match an exact value.
|
|
||||||
func (r *Route) HeadersRegexp(pairs ...string) *Route {
|
|
||||||
if r.err == nil {
|
|
||||||
var headers map[string]*regexp.Regexp
|
|
||||||
headers, r.err = mapFromPairsToRegex(pairs...)
|
|
||||||
return r.addMatcher(headerRegexMatcher(headers))
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// Host -----------------------------------------------------------------------
|
|
||||||
|
|
||||||
// Host adds a matcher for the URL host.
|
|
||||||
// It accepts a template with zero or more URL variables enclosed by {}.
|
|
||||||
// Variables can define an optional regexp pattern to be matched:
|
|
||||||
//
|
|
||||||
// - {name} matches anything until the next dot.
|
|
||||||
//
|
|
||||||
// - {name:pattern} matches the given regexp pattern.
|
|
||||||
//
|
|
||||||
// For example:
|
|
||||||
//
|
|
||||||
// r := mux.NewRouter()
|
|
||||||
// r.Host("www.example.com")
|
|
||||||
// r.Host("{subdomain}.domain.com")
|
|
||||||
// r.Host("{subdomain:[a-z]+}.domain.com")
|
|
||||||
//
|
|
||||||
// Variable names must be unique in a given route. They can be retrieved
|
|
||||||
// calling mux.Vars(request).
|
|
||||||
func (r *Route) Host(tpl string) *Route {
|
|
||||||
r.err = r.addRegexpMatcher(tpl, regexpTypeHost)
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// MatcherFunc ----------------------------------------------------------------
|
|
||||||
|
|
||||||
// MatcherFunc is the function signature used by custom matchers.
|
|
||||||
type MatcherFunc func(*http.Request, *RouteMatch) bool
|
|
||||||
|
|
||||||
// Match returns the match for a given request.
|
|
||||||
func (m MatcherFunc) Match(r *http.Request, match *RouteMatch) bool {
|
|
||||||
return m(r, match)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MatcherFunc adds a custom function to be used as request matcher.
|
|
||||||
func (r *Route) MatcherFunc(f MatcherFunc) *Route {
|
|
||||||
return r.addMatcher(f)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Methods --------------------------------------------------------------------
|
|
||||||
|
|
||||||
// methodMatcher matches the request against HTTP methods.
|
|
||||||
type methodMatcher []string
|
|
||||||
|
|
||||||
func (m methodMatcher) Match(r *http.Request, match *RouteMatch) bool {
|
|
||||||
return matchInArray(m, r.Method)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Methods adds a matcher for HTTP methods.
|
|
||||||
// It accepts a sequence of one or more methods to be matched, e.g.:
|
|
||||||
// "GET", "POST", "PUT".
|
|
||||||
func (r *Route) Methods(methods ...string) *Route {
|
|
||||||
for k, v := range methods {
|
|
||||||
methods[k] = strings.ToUpper(v)
|
|
||||||
}
|
|
||||||
return r.addMatcher(methodMatcher(methods))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Path -----------------------------------------------------------------------
|
|
||||||
|
|
||||||
// Path adds a matcher for the URL path.
|
|
||||||
// It accepts a template with zero or more URL variables enclosed by {}. The
|
|
||||||
// template must start with a "/".
|
|
||||||
// Variables can define an optional regexp pattern to be matched:
|
|
||||||
//
|
|
||||||
// - {name} matches anything until the next slash.
|
|
||||||
//
|
|
||||||
// - {name:pattern} matches the given regexp pattern.
|
|
||||||
//
|
|
||||||
// For example:
|
|
||||||
//
|
|
||||||
// r := mux.NewRouter()
|
|
||||||
// r.Path("/products/").Handler(ProductsHandler)
|
|
||||||
// r.Path("/products/{key}").Handler(ProductsHandler)
|
|
||||||
// r.Path("/articles/{category}/{id:[0-9]+}").
|
|
||||||
// Handler(ArticleHandler)
|
|
||||||
//
|
|
||||||
// Variable names must be unique in a given route. They can be retrieved
|
|
||||||
// calling mux.Vars(request).
|
|
||||||
func (r *Route) Path(tpl string) *Route {
|
|
||||||
r.err = r.addRegexpMatcher(tpl, regexpTypePath)
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// PathPrefix -----------------------------------------------------------------
|
|
||||||
|
|
||||||
// PathPrefix adds a matcher for the URL path prefix. This matches if the given
|
|
||||||
// template is a prefix of the full URL path. See Route.Path() for details on
|
|
||||||
// the tpl argument.
|
|
||||||
//
|
|
||||||
// Note that it does not treat slashes specially ("/foobar/" will be matched by
|
|
||||||
// the prefix "/foo") so you may want to use a trailing slash here.
|
|
||||||
//
|
|
||||||
// Also note that the setting of Router.StrictSlash() has no effect on routes
|
|
||||||
// with a PathPrefix matcher.
|
|
||||||
func (r *Route) PathPrefix(tpl string) *Route {
|
|
||||||
r.err = r.addRegexpMatcher(tpl, regexpTypePrefix)
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// Query ----------------------------------------------------------------------
|
|
||||||
|
|
||||||
// Queries adds a matcher for URL query values.
|
|
||||||
// It accepts a sequence of key/value pairs. Values may define variables.
|
|
||||||
// For example:
|
|
||||||
//
|
|
||||||
// r := mux.NewRouter()
|
|
||||||
// r.Queries("foo", "bar", "id", "{id:[0-9]+}")
|
|
||||||
//
|
|
||||||
// The above route will only match if the URL contains the defined queries
|
|
||||||
// values, e.g.: ?foo=bar&id=42.
|
|
||||||
//
|
|
||||||
// It the value is an empty string, it will match any value if the key is set.
|
|
||||||
//
|
|
||||||
// Variables can define an optional regexp pattern to be matched:
|
|
||||||
//
|
|
||||||
// - {name} matches anything until the next slash.
|
|
||||||
//
|
|
||||||
// - {name:pattern} matches the given regexp pattern.
|
|
||||||
func (r *Route) Queries(pairs ...string) *Route {
|
|
||||||
length := len(pairs)
|
|
||||||
if length%2 != 0 {
|
|
||||||
r.err = fmt.Errorf(
|
|
||||||
"mux: number of parameters must be multiple of 2, got %v", pairs)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
for i := 0; i < length; i += 2 {
|
|
||||||
if r.err = r.addRegexpMatcher(pairs[i]+"="+pairs[i+1], regexpTypeQuery); r.err != nil {
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// Schemes --------------------------------------------------------------------
|
|
||||||
|
|
||||||
// schemeMatcher matches the request against URL schemes.
|
|
||||||
type schemeMatcher []string
|
|
||||||
|
|
||||||
func (m schemeMatcher) Match(r *http.Request, match *RouteMatch) bool {
|
|
||||||
return matchInArray(m, r.URL.Scheme)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Schemes adds a matcher for URL schemes.
|
|
||||||
// It accepts a sequence of schemes to be matched, e.g.: "http", "https".
|
|
||||||
func (r *Route) Schemes(schemes ...string) *Route {
|
|
||||||
for k, v := range schemes {
|
|
||||||
schemes[k] = strings.ToLower(v)
|
|
||||||
}
|
|
||||||
if r.buildScheme == "" && len(schemes) > 0 {
|
|
||||||
r.buildScheme = schemes[0]
|
|
||||||
}
|
|
||||||
return r.addMatcher(schemeMatcher(schemes))
|
|
||||||
}
|
|
||||||
|
|
||||||
// BuildVarsFunc --------------------------------------------------------------
|
|
||||||
|
|
||||||
// BuildVarsFunc is the function signature used by custom build variable
|
|
||||||
// functions (which can modify route variables before a route's URL is built).
|
|
||||||
type BuildVarsFunc func(map[string]string) map[string]string
|
|
||||||
|
|
||||||
// BuildVarsFunc adds a custom function to be used to modify build variables
|
|
||||||
// before a route's URL is built.
|
|
||||||
func (r *Route) BuildVarsFunc(f BuildVarsFunc) *Route {
|
|
||||||
r.buildVarsFunc = f
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subrouter ------------------------------------------------------------------
|
|
||||||
|
|
||||||
// Subrouter creates a subrouter for the route.
|
|
||||||
//
|
|
||||||
// It will test the inner routes only if the parent route matched. For example:
|
|
||||||
//
|
|
||||||
// r := mux.NewRouter()
|
|
||||||
// s := r.Host("www.example.com").Subrouter()
|
|
||||||
// s.HandleFunc("/products/", ProductsHandler)
|
|
||||||
// s.HandleFunc("/products/{key}", ProductHandler)
|
|
||||||
// s.HandleFunc("/articles/{category}/{id:[0-9]+}"), ArticleHandler)
|
|
||||||
//
|
|
||||||
// Here, the routes registered in the subrouter won't be tested if the host
|
|
||||||
// doesn't match.
|
|
||||||
func (r *Route) Subrouter() *Router {
|
|
||||||
router := &Router{parent: r, strictSlash: r.strictSlash}
|
|
||||||
r.addMatcher(router)
|
|
||||||
return router
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// URL building
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// URL builds a URL for the route.
|
|
||||||
//
|
|
||||||
// It accepts a sequence of key/value pairs for the route variables. For
|
|
||||||
// example, given this route:
|
|
||||||
//
|
|
||||||
// r := mux.NewRouter()
|
|
||||||
// r.HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
|
|
||||||
// Name("article")
|
|
||||||
//
|
|
||||||
// ...a URL for it can be built using:
|
|
||||||
//
|
|
||||||
// url, err := r.Get("article").URL("category", "technology", "id", "42")
|
|
||||||
//
|
|
||||||
// ...which will return an url.URL with the following path:
|
|
||||||
//
|
|
||||||
// "/articles/technology/42"
|
|
||||||
//
|
|
||||||
// This also works for host variables:
|
|
||||||
//
|
|
||||||
// r := mux.NewRouter()
|
|
||||||
// r.Host("{subdomain}.domain.com").
|
|
||||||
// HandleFunc("/articles/{category}/{id:[0-9]+}", ArticleHandler).
|
|
||||||
// Name("article")
|
|
||||||
//
|
|
||||||
// // url.String() will be "http://news.domain.com/articles/technology/42"
|
|
||||||
// url, err := r.Get("article").URL("subdomain", "news",
|
|
||||||
// "category", "technology",
|
|
||||||
// "id", "42")
|
|
||||||
//
|
|
||||||
// All variables defined in the route are required, and their values must
|
|
||||||
// conform to the corresponding patterns.
|
|
||||||
func (r *Route) URL(pairs ...string) (*url.URL, error) {
|
|
||||||
if r.err != nil {
|
|
||||||
return nil, r.err
|
|
||||||
}
|
|
||||||
if r.regexp == nil {
|
|
||||||
return nil, errors.New("mux: route doesn't have a host or path")
|
|
||||||
}
|
|
||||||
values, err := r.prepareVars(pairs...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var scheme, host, path string
|
|
||||||
queries := make([]string, 0, len(r.regexp.queries))
|
|
||||||
if r.regexp.host != nil {
|
|
||||||
if host, err = r.regexp.host.url(values); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
scheme = "http"
|
|
||||||
if s := r.getBuildScheme(); s != "" {
|
|
||||||
scheme = s
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if r.regexp.path != nil {
|
|
||||||
if path, err = r.regexp.path.url(values); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, q := range r.regexp.queries {
|
|
||||||
var query string
|
|
||||||
if query, err = q.url(values); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
queries = append(queries, query)
|
|
||||||
}
|
|
||||||
return &url.URL{
|
|
||||||
Scheme: scheme,
|
|
||||||
Host: host,
|
|
||||||
Path: path,
|
|
||||||
RawQuery: strings.Join(queries, "&"),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// URLHost builds the host part of the URL for a route. See Route.URL().
|
|
||||||
//
|
|
||||||
// The route must have a host defined.
|
|
||||||
func (r *Route) URLHost(pairs ...string) (*url.URL, error) {
|
|
||||||
if r.err != nil {
|
|
||||||
return nil, r.err
|
|
||||||
}
|
|
||||||
if r.regexp == nil || r.regexp.host == nil {
|
|
||||||
return nil, errors.New("mux: route doesn't have a host")
|
|
||||||
}
|
|
||||||
values, err := r.prepareVars(pairs...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
host, err := r.regexp.host.url(values)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
u := &url.URL{
|
|
||||||
Scheme: "http",
|
|
||||||
Host: host,
|
|
||||||
}
|
|
||||||
if s := r.getBuildScheme(); s != "" {
|
|
||||||
u.Scheme = s
|
|
||||||
}
|
|
||||||
return u, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// URLPath builds the path part of the URL for a route. See Route.URL().
|
|
||||||
//
|
|
||||||
// The route must have a path defined.
|
|
||||||
func (r *Route) URLPath(pairs ...string) (*url.URL, error) {
|
|
||||||
if r.err != nil {
|
|
||||||
return nil, r.err
|
|
||||||
}
|
|
||||||
if r.regexp == nil || r.regexp.path == nil {
|
|
||||||
return nil, errors.New("mux: route doesn't have a path")
|
|
||||||
}
|
|
||||||
values, err := r.prepareVars(pairs...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
path, err := r.regexp.path.url(values)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &url.URL{
|
|
||||||
Path: path,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPathTemplate returns the template used to build the
|
|
||||||
// route match.
|
|
||||||
// This is useful for building simple REST API documentation and for instrumentation
|
|
||||||
// against third-party services.
|
|
||||||
// An error will be returned if the route does not define a path.
|
|
||||||
func (r *Route) GetPathTemplate() (string, error) {
|
|
||||||
if r.err != nil {
|
|
||||||
return "", r.err
|
|
||||||
}
|
|
||||||
if r.regexp == nil || r.regexp.path == nil {
|
|
||||||
return "", errors.New("mux: route doesn't have a path")
|
|
||||||
}
|
|
||||||
return r.regexp.path.template, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPathRegexp returns the expanded regular expression used to match route path.
|
|
||||||
// This is useful for building simple REST API documentation and for instrumentation
|
|
||||||
// against third-party services.
|
|
||||||
// An error will be returned if the route does not define a path.
|
|
||||||
func (r *Route) GetPathRegexp() (string, error) {
|
|
||||||
if r.err != nil {
|
|
||||||
return "", r.err
|
|
||||||
}
|
|
||||||
if r.regexp == nil || r.regexp.path == nil {
|
|
||||||
return "", errors.New("mux: route does not have a path")
|
|
||||||
}
|
|
||||||
return r.regexp.path.regexp.String(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetQueriesRegexp returns the expanded regular expressions used to match the
|
|
||||||
// route queries.
|
|
||||||
// This is useful for building simple REST API documentation and for instrumentation
|
|
||||||
// against third-party services.
|
|
||||||
// An error will be returned if the route does not have queries.
|
|
||||||
func (r *Route) GetQueriesRegexp() ([]string, error) {
|
|
||||||
if r.err != nil {
|
|
||||||
return nil, r.err
|
|
||||||
}
|
|
||||||
if r.regexp == nil || r.regexp.queries == nil {
|
|
||||||
return nil, errors.New("mux: route doesn't have queries")
|
|
||||||
}
|
|
||||||
var queries []string
|
|
||||||
for _, query := range r.regexp.queries {
|
|
||||||
queries = append(queries, query.regexp.String())
|
|
||||||
}
|
|
||||||
return queries, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetQueriesTemplates returns the templates used to build the
|
|
||||||
// query matching.
|
|
||||||
// This is useful for building simple REST API documentation and for instrumentation
|
|
||||||
// against third-party services.
|
|
||||||
// An error will be returned if the route does not define queries.
|
|
||||||
func (r *Route) GetQueriesTemplates() ([]string, error) {
|
|
||||||
if r.err != nil {
|
|
||||||
return nil, r.err
|
|
||||||
}
|
|
||||||
if r.regexp == nil || r.regexp.queries == nil {
|
|
||||||
return nil, errors.New("mux: route doesn't have queries")
|
|
||||||
}
|
|
||||||
var queries []string
|
|
||||||
for _, query := range r.regexp.queries {
|
|
||||||
queries = append(queries, query.template)
|
|
||||||
}
|
|
||||||
return queries, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetMethods returns the methods the route matches against
|
|
||||||
// This is useful for building simple REST API documentation and for instrumentation
|
|
||||||
// against third-party services.
|
|
||||||
// An error will be returned if route does not have methods.
|
|
||||||
func (r *Route) GetMethods() ([]string, error) {
|
|
||||||
if r.err != nil {
|
|
||||||
return nil, r.err
|
|
||||||
}
|
|
||||||
for _, m := range r.matchers {
|
|
||||||
if methods, ok := m.(methodMatcher); ok {
|
|
||||||
return []string(methods), nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, errors.New("mux: route doesn't have methods")
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHostTemplate returns the template used to build the
|
|
||||||
// route match.
|
|
||||||
// This is useful for building simple REST API documentation and for instrumentation
|
|
||||||
// against third-party services.
|
|
||||||
// An error will be returned if the route does not define a host.
|
|
||||||
func (r *Route) GetHostTemplate() (string, error) {
|
|
||||||
if r.err != nil {
|
|
||||||
return "", r.err
|
|
||||||
}
|
|
||||||
if r.regexp == nil || r.regexp.host == nil {
|
|
||||||
return "", errors.New("mux: route doesn't have a host")
|
|
||||||
}
|
|
||||||
return r.regexp.host.template, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// prepareVars converts the route variable pairs into a map. If the route has a
|
|
||||||
// BuildVarsFunc, it is invoked.
|
|
||||||
func (r *Route) prepareVars(pairs ...string) (map[string]string, error) {
|
|
||||||
m, err := mapFromPairsToString(pairs...)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return r.buildVars(m), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Route) buildVars(m map[string]string) map[string]string {
|
|
||||||
if r.parent != nil {
|
|
||||||
m = r.parent.buildVars(m)
|
|
||||||
}
|
|
||||||
if r.buildVarsFunc != nil {
|
|
||||||
m = r.buildVarsFunc(m)
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
// parentRoute
|
|
||||||
// ----------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// parentRoute allows routes to know about parent host and path definitions.
|
|
||||||
type parentRoute interface {
|
|
||||||
getBuildScheme() string
|
|
||||||
getNamedRoutes() map[string]*Route
|
|
||||||
getRegexpGroup() *routeRegexpGroup
|
|
||||||
buildVars(map[string]string) map[string]string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Route) getBuildScheme() string {
|
|
||||||
if r.buildScheme != "" {
|
|
||||||
return r.buildScheme
|
|
||||||
}
|
|
||||||
if r.parent != nil {
|
|
||||||
return r.parent.getBuildScheme()
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// getNamedRoutes returns the map where named routes are registered.
|
|
||||||
func (r *Route) getNamedRoutes() map[string]*Route {
|
|
||||||
if r.parent == nil {
|
|
||||||
// During tests router is not always set.
|
|
||||||
r.parent = NewRouter()
|
|
||||||
}
|
|
||||||
return r.parent.getNamedRoutes()
|
|
||||||
}
|
|
||||||
|
|
||||||
// getRegexpGroup returns regexp definitions from this route.
|
|
||||||
func (r *Route) getRegexpGroup() *routeRegexpGroup {
|
|
||||||
if r.regexp == nil {
|
|
||||||
if r.parent == nil {
|
|
||||||
// During tests router is not always set.
|
|
||||||
r.parent = NewRouter()
|
|
||||||
}
|
|
||||||
regexp := r.parent.getRegexpGroup()
|
|
||||||
if regexp == nil {
|
|
||||||
r.regexp = new(routeRegexpGroup)
|
|
||||||
} else {
|
|
||||||
// Copy.
|
|
||||||
r.regexp = &routeRegexpGroup{
|
|
||||||
host: regexp.host,
|
|
||||||
path: regexp.path,
|
|
||||||
queries: regexp.queries,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return r.regexp
|
|
||||||
}
|
|
19
vendor/github.com/gorilla/mux/test_helpers.go
generated
vendored
19
vendor/github.com/gorilla/mux/test_helpers.go
generated
vendored
|
@ -1,19 +0,0 @@
|
||||||
// Copyright 2012 The Gorilla Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package mux
|
|
||||||
|
|
||||||
import "net/http"
|
|
||||||
|
|
||||||
// SetURLVars sets the URL variables for the given request, to be accessed via
|
|
||||||
// mux.Vars for testing route behaviour. Arguments are not modified, a shallow
|
|
||||||
// copy is returned.
|
|
||||||
//
|
|
||||||
// This API should only be used for testing purposes; it provides a way to
|
|
||||||
// inject variables into the request context. Alternatively, URL variables
|
|
||||||
// can be set by making a route that captures the required variables,
|
|
||||||
// starting a server and sending the request to that server.
|
|
||||||
func SetURLVars(r *http.Request, val map[string]string) *http.Request {
|
|
||||||
return setVars(r, val)
|
|
||||||
}
|
|
14
vendor/github.com/mattn/go-sqlite3/.gitignore
generated
vendored
14
vendor/github.com/mattn/go-sqlite3/.gitignore
generated
vendored
|
@ -1,14 +0,0 @@
|
||||||
*.db
|
|
||||||
*.exe
|
|
||||||
*.dll
|
|
||||||
*.o
|
|
||||||
|
|
||||||
# VSCode
|
|
||||||
.vscode
|
|
||||||
|
|
||||||
# Exclude from upgrade
|
|
||||||
upgrade/*.c
|
|
||||||
upgrade/*.h
|
|
||||||
|
|
||||||
# Exclude upgrade binary
|
|
||||||
upgrade/upgrade
|
|
41
vendor/github.com/mattn/go-sqlite3/.travis.yml
generated
vendored
41
vendor/github.com/mattn/go-sqlite3/.travis.yml
generated
vendored
|
@ -1,41 +0,0 @@
|
||||||
language: go
|
|
||||||
|
|
||||||
os:
|
|
||||||
- linux
|
|
||||||
- osx
|
|
||||||
|
|
||||||
addons:
|
|
||||||
apt:
|
|
||||||
update: true
|
|
||||||
|
|
||||||
env:
|
|
||||||
matrix:
|
|
||||||
- GOTAGS=
|
|
||||||
- GOTAGS=libsqlite3
|
|
||||||
- GOTAGS="sqlite_allow_uri_authority sqlite_app_armor sqlite_foreign_keys sqlite_fts5 sqlite_icu sqlite_introspect sqlite_json sqlite_secure_delete sqlite_see sqlite_stat4 sqlite_trace sqlite_userauth sqlite_vacuum_incr sqlite_vtable"
|
|
||||||
- GOTAGS=sqlite_vacuum_full
|
|
||||||
|
|
||||||
go:
|
|
||||||
- 1.9.x
|
|
||||||
- 1.10.x
|
|
||||||
|
|
||||||
before_install:
|
|
||||||
- |
|
|
||||||
if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
|
|
||||||
brew update
|
|
||||||
brew upgrade icu4c
|
|
||||||
fi
|
|
||||||
- |
|
|
||||||
go get github.com/smartystreets/goconvey
|
|
||||||
if [[ "${GOOS}" != "windows" ]]; then
|
|
||||||
go get github.com/mattn/goveralls
|
|
||||||
go get golang.org/x/tools/cmd/cover
|
|
||||||
fi
|
|
||||||
|
|
||||||
script:
|
|
||||||
- GOOS=$(go env GOOS) GOARCH=$(go env GOARCH) go build -v -tags "${GOTAGS}" .
|
|
||||||
- |
|
|
||||||
if [[ "${GOOS}" != "windows" ]]; then
|
|
||||||
$HOME/gopath/bin/goveralls -repotoken 3qJVUE0iQwqnCbmNcDsjYu1nh4J4KIFXx
|
|
||||||
go test -race -v . -tags "${GOTAGS}"
|
|
||||||
fi
|
|
21
vendor/github.com/mattn/go-sqlite3/LICENSE
generated
vendored
21
vendor/github.com/mattn/go-sqlite3/LICENSE
generated
vendored
|
@ -1,21 +0,0 @@
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2014 Yasuhiro Matsumoto
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
518
vendor/github.com/mattn/go-sqlite3/README.md
generated
vendored
518
vendor/github.com/mattn/go-sqlite3/README.md
generated
vendored
|
@ -1,518 +0,0 @@
|
||||||
go-sqlite3
|
|
||||||
==========
|
|
||||||
|
|
||||||
[![GoDoc Reference](https://godoc.org/github.com/mattn/go-sqlite3?status.svg)](http://godoc.org/github.com/mattn/go-sqlite3)
|
|
||||||
[![Build Status](https://travis-ci.org/mattn/go-sqlite3.svg?branch=master)](https://travis-ci.org/mattn/go-sqlite3)
|
|
||||||
[![Coverage Status](https://coveralls.io/repos/mattn/go-sqlite3/badge.svg?branch=master)](https://coveralls.io/r/mattn/go-sqlite3?branch=master)
|
|
||||||
[![Go Report Card](https://goreportcard.com/badge/github.com/mattn/go-sqlite3)](https://goreportcard.com/report/github.com/mattn/go-sqlite3)
|
|
||||||
|
|
||||||
# Description
|
|
||||||
|
|
||||||
sqlite3 driver conforming to the built-in database/sql interface
|
|
||||||
|
|
||||||
Supported Golang version:
|
|
||||||
- 1.9.x
|
|
||||||
- 1.10.x
|
|
||||||
|
|
||||||
[This package follows the official Golang Release Policy.](https://golang.org/doc/devel/release.html#policy)
|
|
||||||
|
|
||||||
### Overview
|
|
||||||
|
|
||||||
- [Installation](#installation)
|
|
||||||
- [API Reference](#api-reference)
|
|
||||||
- [Connection String](#connection-string)
|
|
||||||
- [Features](#features)
|
|
||||||
- [Compilation](#compilation)
|
|
||||||
- [Android](#android)
|
|
||||||
- [ARM](#arm)
|
|
||||||
- [Cross Compile](#cross-compile)
|
|
||||||
- [Google Cloud Platform](#google-cloud-platform)
|
|
||||||
- [Linux](#linux)
|
|
||||||
- [Alpine](#alpine)
|
|
||||||
- [Fedora](#fedora)
|
|
||||||
- [Ubuntu](#ubuntu)
|
|
||||||
- [Mac OSX](#mac-osx)
|
|
||||||
- [Windows](#windows)
|
|
||||||
- [Errors](#errors)
|
|
||||||
- [User Authentication](#user-authentication)
|
|
||||||
- [Compile](#compile)
|
|
||||||
- [Usage](#usage)
|
|
||||||
- [Extensions](#extensions)
|
|
||||||
- [Spatialite](#spatialite)
|
|
||||||
- [FAQ](#faq)
|
|
||||||
- [License](#license)
|
|
||||||
|
|
||||||
# Installation
|
|
||||||
|
|
||||||
This package can be installed with the go get command:
|
|
||||||
|
|
||||||
go get github.com/mattn/go-sqlite3
|
|
||||||
|
|
||||||
_go-sqlite3_ is *cgo* package.
|
|
||||||
If you want to build your app using go-sqlite3, you need gcc.
|
|
||||||
However, after you have built and installed _go-sqlite3_ with `go install github.com/mattn/go-sqlite3` (which requires gcc), you can build your app without relying on gcc in future.
|
|
||||||
|
|
||||||
***Important: because this is a `CGO` enabled package you are required to set the environment variable `CGO_ENABLED=1` and have a `gcc` compile present within your path.***
|
|
||||||
|
|
||||||
# API Reference
|
|
||||||
|
|
||||||
API documentation can be found here: http://godoc.org/github.com/mattn/go-sqlite3
|
|
||||||
|
|
||||||
Examples can be found under the [examples](./_example) directory
|
|
||||||
|
|
||||||
# Connection String
|
|
||||||
|
|
||||||
When creating a new SQLite database or connection to an existing one, with the file name additional options can be given.
|
|
||||||
This is also known as a DSN string. (Data Source Name).
|
|
||||||
|
|
||||||
Options are append after the filename of the SQLite database.
|
|
||||||
The database filename and options are seperated by an `?` (Question Mark).
|
|
||||||
|
|
||||||
This also applies when using an in-memory database instead of a file.
|
|
||||||
|
|
||||||
Options can be given using the following format: `KEYWORD=VALUE` and multiple options can be combined with the `&` ampersand.
|
|
||||||
|
|
||||||
This library supports dsn options of SQLite itself and provides additional options.
|
|
||||||
|
|
||||||
Boolean values can be one of:
|
|
||||||
* `0` `no` `false` `off`
|
|
||||||
* `1` `yes` `true` `on`
|
|
||||||
|
|
||||||
| Name | Key | Value(s) | Description |
|
|
||||||
|------|-----|----------|-------------|
|
|
||||||
| UA - Create | `_auth` | - | Create User Authentication, for more information see [User Authentication](#user-authentication) |
|
|
||||||
| UA - Username | `_auth_user` | `string` | Username for User Authentication, for more information see [User Authentication](#user-authentication) |
|
|
||||||
| UA - Password | `_auth_pass` | `string` | Password for User Authentication, for more information see [User Authentication](#user-authentication) |
|
|
||||||
| UA - Crypt | `_auth_crypt` | <ul><li>SHA1</li><li>SSHA1</li><li>SHA256</li><li>SSHA256</li><li>SHA384</li><li>SSHA384</li><li>SHA512</li><li>SSHA512</li></ul> | Password encoder to use for User Authentication, for more information see [User Authentication](#user-authentication) |
|
|
||||||
| UA - Salt | `_auth_salt` | `string` | Salt to use if the configure password encoder requires a salt, for User Authentication, for more information see [User Authentication](#user-authentication) |
|
|
||||||
| Auto Vacuum | `_auto_vacuum` \| `_vacuum` | <ul><li>`0` \| `none`</li><li>`1` \| `full`</li><li>`2` \| `incremental`</li></ul> | For more information see [PRAGMA auto_vacuum](https://www.sqlite.org/pragma.html#pragma_auto_vacuum) |
|
|
||||||
| Busy Timeout | `_busy_timeout` \| `_timeout` | `int` | Specify value for sqlite3_busy_timeout. For more information see [PRAGMA busy_timeout](https://www.sqlite.org/pragma.html#pragma_busy_timeout) |
|
|
||||||
| Case Sensitive LIKE | `_case_sensitive_like` \| `_cslike` | `boolean` | For more information see [PRAGMA case_sensitive_like](https://www.sqlite.org/pragma.html#pragma_case_sensitive_like) |
|
|
||||||
| Defer Foreign Keys | `_defer_foreign_keys` \| `_defer_fk` | `boolean` | For more information see [PRAGMA defer_foreign_keys](https://www.sqlite.org/pragma.html#pragma_defer_foreign_keys) |
|
|
||||||
| Foreign Keys | `_foreign_keys` \| `_fk` | `boolean` | For more information see [PRAGMA foreign_keys](https://www.sqlite.org/pragma.html#pragma_foreign_keys) |
|
|
||||||
| Ignore CHECK Constraints | `_ignore_check_constraints` | `boolean` | For more information see [PRAGMA ignore_check_constraints](https://www.sqlite.org/pragma.html#pragma_ignore_check_constraints) |
|
|
||||||
| Immutable | `immutable` | `boolean` | For more information see [Immutable](https://www.sqlite.org/c3ref/open.html) |
|
|
||||||
| Journal Mode | `_journal_mode` \| `_journal` | <ul><li>DELETE</li><li>TRUNCATE</li><li>PERSIST</li><li>MEMORY</li><li>WAL</li><li>OFF</li></ul> | For more information see [PRAGMA journal_mode](https://www.sqlite.org/pragma.html#pragma_journal_mode) |
|
|
||||||
| Locking Mode | `_locking_mode` \| `_locking` | <ul><li>NORMAL</li><li>EXCLUSIVE</li></ul> | For more information see [PRAGMA locking_mode](https://www.sqlite.org/pragma.html#pragma_locking_mode) |
|
|
||||||
| Mode | `mode` | <ul><li>ro</li><li>rw</li><li>rwc</li><li>memory</li></ul> | Access Mode of the database. For more information see [SQLite Open](https://www.sqlite.org/c3ref/open.html) |
|
|
||||||
| Mutex Locking | `_mutex` | <ul><li>no</li><li>full</li></ul> | Specify mutex mode. |
|
|
||||||
| Query Only | `_query_only` | `boolean` | For more information see [PRAGMA query_only](https://www.sqlite.org/pragma.html#pragma_query_only) |
|
|
||||||
| Recursive Triggers | `_recursive_triggers` \| `_rt` | `boolean` | For more information see [PRAGMA recursive_triggers](https://www.sqlite.org/pragma.html#pragma_recursive_triggers) |
|
|
||||||
| Secure Delete | `_secure_delete` | `boolean` \| `FAST` | For more information see [PRAGMA secure_delete](https://www.sqlite.org/pragma.html#pragma_secure_delete) |
|
|
||||||
| Shared-Cache Mode | `cache` | <ul><li>shared</li><li>private</li></ul> | Set cache mode for more information see [sqlite.org](https://www.sqlite.org/sharedcache.html) |
|
|
||||||
| Synchronous | `_synchronous` \| `_sync` | <ul><li>0 \| OFF</li><li>1 \| NORMAL</li><li>2 \| FULL</li><li>3 \| EXTRA</li></ul> | For more information see [PRAGMA synchronous](https://www.sqlite.org/pragma.html#pragma_synchronous) |
|
|
||||||
| Time Zone Location | `_loc` | auto | Specify location of time format. |
|
|
||||||
| Transaction Lock | `_txlock` | <ul><li>immediate</li><li>deferred</li><li>exclusive</li></ul> | Specify locking behavior for transactions. |
|
|
||||||
| Writable Schema | `_writable_schema` | `Boolean` | When this pragma is on, the SQLITE_MASTER tables in which database can be changed using ordinary UPDATE, INSERT, and DELETE statements. Warning: misuse of this pragma can easily result in a corrupt database file. |
|
|
||||||
|
|
||||||
## DSN Examples
|
|
||||||
|
|
||||||
```
|
|
||||||
file:test.db?cache=shared&mode=memory
|
|
||||||
```
|
|
||||||
|
|
||||||
# Features
|
|
||||||
|
|
||||||
This package allows additional configuration of features available within SQLite3 to be enabled or disabled by golang build constraints also known as build `tags`.
|
|
||||||
|
|
||||||
[Click here for more information about build tags / constraints.](https://golang.org/pkg/go/build/#hdr-Build_Constraints)
|
|
||||||
|
|
||||||
### Usage
|
|
||||||
|
|
||||||
If you wish to build this library with additional extensions / features.
|
|
||||||
Use the following command.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go build --tags "<FEATURE>"
|
|
||||||
```
|
|
||||||
|
|
||||||
For available features see the extension list.
|
|
||||||
When using multiple build tags, all the different tags should be space delimted.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go build --tags "icu json1 fts5 secure_delete"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Feature / Extension List
|
|
||||||
|
|
||||||
| Extension | Build Tag | Description |
|
|
||||||
|-----------|-----------|-------------|
|
|
||||||
| Additional Statistics | sqlite_stat4 | This option adds additional logic to the ANALYZE command and to the query planner that can help SQLite to chose a better query plan under certain situations. The ANALYZE command is enhanced to collect histogram data from all columns of every index and store that data in the sqlite_stat4 table.<br><br>The query planner will then use the histogram data to help it make better index choices. The downside of this compile-time option is that it violates the query planner stability guarantee making it more difficult to ensure consistent performance in mass-produced applications.<br><br>SQLITE_ENABLE_STAT4 is an enhancement of SQLITE_ENABLE_STAT3. STAT3 only recorded histogram data for the left-most column of each index whereas the STAT4 enhancement records histogram data from all columns of each index.<br><br>The SQLITE_ENABLE_STAT3 compile-time option is a no-op and is ignored if the SQLITE_ENABLE_STAT4 compile-time option is used |
|
|
||||||
| Allow URI Authority | sqlite_allow_uri_authority | URI filenames normally throws an error if the authority section is not either empty or "localhost".<br><br>However, if SQLite is compiled with the SQLITE_ALLOW_URI_AUTHORITY compile-time option, then the URI is converted into a Uniform Naming Convention (UNC) filename and passed down to the underlying operating system that way |
|
|
||||||
| App Armor | sqlite_app_armor | When defined, this C-preprocessor macro activates extra code that attempts to detect misuse of the SQLite API, such as passing in NULL pointers to required parameters or using objects after they have been destroyed. <br><br>App Armor is not available under `Windows`. |
|
|
||||||
| Disable Load Extensions | sqlite_omit_load_extension | Loading of external extensions is enabled by default.<br><br>To disable extension loading add the build tag `sqlite_omit_load_extension`. |
|
|
||||||
| Foreign Keys | sqlite_foreign_keys | This macro determines whether enforcement of foreign key constraints is enabled or disabled by default for new database connections.<br><br>Each database connection can always turn enforcement of foreign key constraints on and off and run-time using the foreign_keys pragma.<br><br>Enforcement of foreign key constraints is normally off by default, but if this compile-time parameter is set to 1, enforcement of foreign key constraints will be on by default |
|
|
||||||
| Full Auto Vacuum | sqlite_vacuum_full | Set the default auto vacuum to full |
|
|
||||||
| Incremental Auto Vacuum | sqlite_vacuum_incr | Set the default auto vacuum to incremental |
|
|
||||||
| Full Text Search Engine | sqlite_fts5 | When this option is defined in the amalgamation, versions 5 of the full-text search engine (fts5) is added to the build automatically |
|
|
||||||
| International Components for Unicode | sqlite_icu | This option causes the International Components for Unicode or "ICU" extension to SQLite to be added to the build |
|
|
||||||
| Introspect PRAGMAS | sqlite_introspect | This option adds some extra PRAGMA statements. <ul><li>PRAGMA function_list</li><li>PRAGMA module_list</li><li>PRAGMA pragma_list</li></ul> |
|
|
||||||
| JSON SQL Functions | sqlite_json | When this option is defined in the amalgamation, the JSON SQL functions are added to the build automatically |
|
|
||||||
| Secure Delete | sqlite_secure_delete | This compile-time option changes the default setting of the secure_delete pragma.<br><br>When this option is not used, secure_delete defaults to off. When this option is present, secure_delete defaults to on.<br><br>The secure_delete setting causes deleted content to be overwritten with zeros. There is a small performance penalty since additional I/O must occur.<br><br>On the other hand, secure_delete can prevent fragments of sensitive information from lingering in unused parts of the database file after it has been deleted. See the documentation on the secure_delete pragma for additional information |
|
|
||||||
| Secure Delete (FAST) | sqlite_secure_delete_fast | For more information see [PRAGMA secure_delete](https://www.sqlite.org/pragma.html#pragma_secure_delete) |
|
|
||||||
| Tracing / Debug | sqlite_trace | Activate trace functions |
|
|
||||||
| User Authentication | sqlite_userauth | SQLite User Authentication see [User Authentication](#user-authentication) for more information. |
|
|
||||||
|
|
||||||
# Compilation
|
|
||||||
|
|
||||||
This package requires `CGO_ENABLED=1` ennvironment variable if not set by default, and the presence of the `gcc` compiler.
|
|
||||||
|
|
||||||
If you need to add additional CFLAGS or LDFLAGS to the build command, and do not want to modify this package. Then this can be achieved by using the `CGO_CFLAGS` and `CGO_LDFLAGS` environment variables.
|
|
||||||
|
|
||||||
## Android
|
|
||||||
|
|
||||||
This package can be compiled for android.
|
|
||||||
Compile with:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go build --tags "android"
|
|
||||||
```
|
|
||||||
|
|
||||||
For more information see [#201](https://github.com/mattn/go-sqlite3/issues/201)
|
|
||||||
|
|
||||||
# ARM
|
|
||||||
|
|
||||||
To compile for `ARM` use the following environment.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
env CC=arm-linux-gnueabihf-gcc CXX=arm-linux-gnueabihf-g++ \
|
|
||||||
CGO_ENABLED=1 GOOS=linux GOARCH=arm GOARM=7 \
|
|
||||||
go build -v
|
|
||||||
```
|
|
||||||
|
|
||||||
Additional information:
|
|
||||||
- [#242](https://github.com/mattn/go-sqlite3/issues/242)
|
|
||||||
- [#504](https://github.com/mattn/go-sqlite3/issues/504)
|
|
||||||
|
|
||||||
# Cross Compile
|
|
||||||
|
|
||||||
This library can be cross-compiled.
|
|
||||||
|
|
||||||
In some cases you are required to the `CC` environment variable with the cross compiler.
|
|
||||||
|
|
||||||
Additional information:
|
|
||||||
- [#491](https://github.com/mattn/go-sqlite3/issues/491)
|
|
||||||
- [#560](https://github.com/mattn/go-sqlite3/issues/560)
|
|
||||||
|
|
||||||
# Google Cloud Platform
|
|
||||||
|
|
||||||
Building on GCP is not possible because `Google Cloud Platform does not allow `gcc` to be executed.
|
|
||||||
|
|
||||||
Please work only with compiled final binaries.
|
|
||||||
|
|
||||||
## Linux
|
|
||||||
|
|
||||||
To compile this package on Linux you must install the development tools for your linux distribution.
|
|
||||||
|
|
||||||
To compile under linux use the build tag `linux`.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go build --tags "linux"
|
|
||||||
```
|
|
||||||
|
|
||||||
If you wish to link directly to libsqlite3 then you can use the `libsqlite3` build tag.
|
|
||||||
|
|
||||||
```
|
|
||||||
go build --tags "libsqlite3 linux"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Alpine
|
|
||||||
|
|
||||||
When building in an `alpine` container run the following command before building.
|
|
||||||
|
|
||||||
```
|
|
||||||
apk add --update gcc musl-dev
|
|
||||||
```
|
|
||||||
|
|
||||||
### Fedora
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo yum groupinstall "Development Tools" "Development Libraries"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Ubuntu
|
|
||||||
|
|
||||||
```bash
|
|
||||||
sudo apt-get install build-essential
|
|
||||||
```
|
|
||||||
|
|
||||||
## Mac OSX
|
|
||||||
|
|
||||||
OSX should have all the tools present to compile this package, if not install XCode this will add all the developers tools.
|
|
||||||
|
|
||||||
Required dependency
|
|
||||||
|
|
||||||
```bash
|
|
||||||
brew install sqlite3
|
|
||||||
```
|
|
||||||
|
|
||||||
For OSX there is an additional package install which is required if you whish to build the `icu` extension.
|
|
||||||
|
|
||||||
This additional package can be installed with `homebrew`.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
brew upgrade icu4c
|
|
||||||
```
|
|
||||||
|
|
||||||
To compile for Mac OSX.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go build --tags "darwin"
|
|
||||||
```
|
|
||||||
|
|
||||||
If you wish to link directly to libsqlite3 then you can use the `libsqlite3` build tag.
|
|
||||||
|
|
||||||
```
|
|
||||||
go build --tags "libsqlite3 darwin"
|
|
||||||
```
|
|
||||||
|
|
||||||
Additional information:
|
|
||||||
- [#206](https://github.com/mattn/go-sqlite3/issues/206)
|
|
||||||
- [#404](https://github.com/mattn/go-sqlite3/issues/404)
|
|
||||||
|
|
||||||
## Windows
|
|
||||||
|
|
||||||
To compile this package on Windows OS you must have the `gcc` compiler installed.
|
|
||||||
|
|
||||||
1) Install a Windows `gcc` toolchain.
|
|
||||||
2) Add the `bin` folders to the Windows path if the installer did not do this by default.
|
|
||||||
3) Open a terminal for the TDM-GCC toolchain, can be found in the Windows Start menu.
|
|
||||||
4) Navigate to your project folder and run the `go build ...` command for this package.
|
|
||||||
|
|
||||||
For example the TDM-GCC Toolchain can be found [here](ttps://sourceforge.net/projects/tdm-gcc/).
|
|
||||||
|
|
||||||
## Errors
|
|
||||||
|
|
||||||
- Compile error: `can not be used when making a shared object; recompile with -fPIC`
|
|
||||||
|
|
||||||
When receiving a compile time error referencing recompile with `-FPIC` then you
|
|
||||||
are probably using a hardend system.
|
|
||||||
|
|
||||||
You can copile the library on a hardend system with the following command.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go build -ldflags '-extldflags=-fno-PIC'
|
|
||||||
```
|
|
||||||
|
|
||||||
More details see [#120](https://github.com/mattn/go-sqlite3/issues/120)
|
|
||||||
|
|
||||||
- Can't build go-sqlite3 on windows 64bit.
|
|
||||||
|
|
||||||
> Probably, you are using go 1.0, go1.0 has a problem when it comes to compiling/linking on windows 64bit.
|
|
||||||
> See: [#27](https://github.com/mattn/go-sqlite3/issues/27)
|
|
||||||
|
|
||||||
- `go get github.com/mattn/go-sqlite3` throws compilation error.
|
|
||||||
|
|
||||||
`gcc` throws: `internal compiler error`
|
|
||||||
|
|
||||||
Remove the download repository from your disk and try re-install with:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go install github.com/mattn/go-sqlite3
|
|
||||||
```
|
|
||||||
|
|
||||||
# User Authentication
|
|
||||||
|
|
||||||
This package supports the SQLite User Authentication module.
|
|
||||||
|
|
||||||
## Compile
|
|
||||||
|
|
||||||
To use the User authentication module the package has to be compiled with the tag `sqlite_userauth`. See [Features](#features).
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### Create protected database
|
|
||||||
|
|
||||||
To create a database protected by user authentication provide the following argument to the connection string `_auth`.
|
|
||||||
This will enable user authentication within the database. This option however requires two additional arguments:
|
|
||||||
|
|
||||||
- `_auth_user`
|
|
||||||
- `_auth_pass`
|
|
||||||
|
|
||||||
When `_auth` is present on the connection string user authentication will be enabled and the provided user will be created
|
|
||||||
as an `admin` user. After initial creation, the parameter `_auth` has no effect anymore and can be omitted from the connection string.
|
|
||||||
|
|
||||||
Example connection string:
|
|
||||||
|
|
||||||
Create an user authentication database with user `admin` and password `admin`.
|
|
||||||
|
|
||||||
`file:test.s3db?_auth&_auth_user=admin&_auth_pass=admin`
|
|
||||||
|
|
||||||
Create an user authentication database with user `admin` and password `admin` and use `SHA1` for the password encoding.
|
|
||||||
|
|
||||||
`file:test.s3db?_auth&_auth_user=admin&_auth_pass=admin&_auth_crypt=sha1`
|
|
||||||
|
|
||||||
### Password Encoding
|
|
||||||
|
|
||||||
The passwords within the user authentication module of SQLite are encoded with the SQLite function `sqlite_cryp`.
|
|
||||||
This function uses a ceasar-cypher which is quite insecure.
|
|
||||||
This library provides several additional password encoders which can be configured through the connection string.
|
|
||||||
|
|
||||||
The password cypher can be configured with the key `_auth_crypt`. And if the configured password encoder also requires an
|
|
||||||
salt this can be configured with `_auth_salt`.
|
|
||||||
|
|
||||||
#### Available Encoders
|
|
||||||
|
|
||||||
- SHA1
|
|
||||||
- SSHA1 (Salted SHA1)
|
|
||||||
- SHA256
|
|
||||||
- SSHA256 (salted SHA256)
|
|
||||||
- SHA384
|
|
||||||
- SSHA384 (salted SHA384)
|
|
||||||
- SHA512
|
|
||||||
- SSHA512 (salted SHA512)
|
|
||||||
|
|
||||||
### Restrictions
|
|
||||||
|
|
||||||
Operations on the database regarding to user management can only be preformed by an administrator user.
|
|
||||||
|
|
||||||
### Support
|
|
||||||
|
|
||||||
The user authentication supports two kinds of users
|
|
||||||
|
|
||||||
- administrators
|
|
||||||
- regular users
|
|
||||||
|
|
||||||
### User Management
|
|
||||||
|
|
||||||
User management can be done by directly using the `*SQLiteConn` or by SQL.
|
|
||||||
|
|
||||||
#### SQL
|
|
||||||
|
|
||||||
The following sql functions are available for user management.
|
|
||||||
|
|
||||||
| Function | Arguments | Description |
|
|
||||||
|----------|-----------|-------------|
|
|
||||||
| `authenticate` | username `string`, password `string` | Will authenticate an user, this is done by the connection; and should not be used manually. |
|
|
||||||
| `auth_user_add` | username `string`, password `string`, admin `int` | This function will add an user to the database.<br>if the database is not protected by user authentication it will enable it. Argument `admin` is an integer identifying if the added user should be an administrator. Only Administrators can add administrators. |
|
|
||||||
| `auth_user_change` | username `string`, password `string`, admin `int` | Function to modify an user. Users can change their own password, but only an administrator can change the administrator flag. |
|
|
||||||
| `authUserDelete` | username `string` | Delete an user from the database. Can only be used by an administrator. The current logged in administrator cannot be deleted. This is to make sure their is always an administrator remaining. |
|
|
||||||
|
|
||||||
These functions will return an integer.
|
|
||||||
|
|
||||||
- 0 (SQLITE_OK)
|
|
||||||
- 23 (SQLITE_AUTH) Failed to perform due to authentication or insufficient privileges
|
|
||||||
|
|
||||||
##### Examples
|
|
||||||
|
|
||||||
```sql
|
|
||||||
// Autheticate user
|
|
||||||
// Create Admin User
|
|
||||||
SELECT auth_user_add('admin2', 'admin2', 1);
|
|
||||||
|
|
||||||
// Change password for user
|
|
||||||
SELECT auth_user_change('user', 'userpassword', 0);
|
|
||||||
|
|
||||||
// Delete user
|
|
||||||
SELECT user_delete('user');
|
|
||||||
```
|
|
||||||
|
|
||||||
#### *SQLiteConn
|
|
||||||
|
|
||||||
The following functions are available for User authentication from the `*SQLiteConn`.
|
|
||||||
|
|
||||||
| Function | Description |
|
|
||||||
|----------|-------------|
|
|
||||||
| `Authenticate(username, password string) error` | Authenticate user |
|
|
||||||
| `AuthUserAdd(username, password string, admin bool) error` | Add user |
|
|
||||||
| `AuthUserChange(username, password string, admin bool) error` | Modify user |
|
|
||||||
| `AuthUserDelete(username string) error` | Delete user |
|
|
||||||
|
|
||||||
### Attached database
|
|
||||||
|
|
||||||
When using attached databases. SQLite will use the authentication from the `main` database for the attached database(s).
|
|
||||||
|
|
||||||
# Extensions
|
|
||||||
|
|
||||||
If you want your own extension to be listed here or you want to add a reference to an extension; please submit an Issue for this.
|
|
||||||
|
|
||||||
## Spatialite
|
|
||||||
|
|
||||||
Spatialite is available as an extension to SQLite, and can be used in combination with this repository.
|
|
||||||
For an example see [shaxbee/go-spatialite](https://github.com/shaxbee/go-spatialite).
|
|
||||||
|
|
||||||
# FAQ
|
|
||||||
|
|
||||||
- Getting insert error while query is opened.
|
|
||||||
|
|
||||||
> You can pass some arguments into the connection string, for example, a URI.
|
|
||||||
> See: [#39](https://github.com/mattn/go-sqlite3/issues/39)
|
|
||||||
|
|
||||||
- Do you want to cross compile? mingw on Linux or Mac?
|
|
||||||
|
|
||||||
> See: [#106](https://github.com/mattn/go-sqlite3/issues/106)
|
|
||||||
> See also: http://www.limitlessfx.com/cross-compile-golang-app-for-windows-from-linux.html
|
|
||||||
|
|
||||||
- Want to get time.Time with current locale
|
|
||||||
|
|
||||||
Use `_loc=auto` in SQLite3 filename schema like `file:foo.db?_loc=auto`.
|
|
||||||
|
|
||||||
- Can I use this in multiple routines concurrently?
|
|
||||||
|
|
||||||
Yes for readonly. But, No for writable. See [#50](https://github.com/mattn/go-sqlite3/issues/50), [#51](https://github.com/mattn/go-sqlite3/issues/51), [#209](https://github.com/mattn/go-sqlite3/issues/209), [#274](https://github.com/mattn/go-sqlite3/issues/274).
|
|
||||||
|
|
||||||
- Why I'm getting `no such table` error?
|
|
||||||
|
|
||||||
Why is it racy if I use a `sql.Open("sqlite3", ":memory:")` database?
|
|
||||||
|
|
||||||
Each connection to :memory: opens a brand new in-memory sql database, so if
|
|
||||||
the stdlib's sql engine happens to open another connection and you've only
|
|
||||||
specified ":memory:", that connection will see a brand new database. A
|
|
||||||
workaround is to use "file::memory:?mode=memory&cache=shared". Every
|
|
||||||
connection to this string will point to the same in-memory database.
|
|
||||||
|
|
||||||
For more information see
|
|
||||||
* [#204](https://github.com/mattn/go-sqlite3/issues/204)
|
|
||||||
* [#511](https://github.com/mattn/go-sqlite3/issues/511)
|
|
||||||
|
|
||||||
- Reading from database with large amount of goroutines fails on OSX.
|
|
||||||
|
|
||||||
OS X limits OS-wide to not have more than 1000 files open simultaneously by default.
|
|
||||||
|
|
||||||
For more information see [#289](https://github.com/mattn/go-sqlite3/issues/289)
|
|
||||||
|
|
||||||
- Trying to execure a `.` (dot) command throws an error.
|
|
||||||
|
|
||||||
Error: `Error: near ".": syntax error`
|
|
||||||
Dot command are part of SQLite3 CLI not of this library.
|
|
||||||
|
|
||||||
You need to implement the feature or call the sqlite3 cli.
|
|
||||||
|
|
||||||
More infomation see [#305](https://github.com/mattn/go-sqlite3/issues/305)
|
|
||||||
|
|
||||||
- Error: `database is locked`
|
|
||||||
|
|
||||||
When you get an database is locked. Please use the following options.
|
|
||||||
|
|
||||||
Add to DSN: `cache=shared`
|
|
||||||
|
|
||||||
Example:
|
|
||||||
```go
|
|
||||||
db, err := sql.Open("sqlite3", "file:locked.sqlite?cache=shared")
|
|
||||||
```
|
|
||||||
|
|
||||||
Second please set the database connections of the SQL package to 1.
|
|
||||||
|
|
||||||
```go
|
|
||||||
db.SetMaxOpenConn(1)
|
|
||||||
```
|
|
||||||
|
|
||||||
More information see [#209](https://github.com/mattn/go-sqlite3/issues/209)
|
|
||||||
|
|
||||||
# License
|
|
||||||
|
|
||||||
MIT: http://mattn.mit-license.org/2018
|
|
||||||
|
|
||||||
sqlite3-binding.c, sqlite3-binding.h, sqlite3ext.h
|
|
||||||
|
|
||||||
The -binding suffix was added to avoid build failures under gccgo.
|
|
||||||
|
|
||||||
In this repository, those files are an amalgamation of code that was copied from SQLite3. The license of that code is the same as the license of SQLite3.
|
|
||||||
|
|
||||||
# Author
|
|
||||||
|
|
||||||
Yasuhiro Matsumoto (a.k.a mattn)
|
|
||||||
|
|
||||||
G.J.R. Timmer
|
|
85
vendor/github.com/mattn/go-sqlite3/backup.go
generated
vendored
85
vendor/github.com/mattn/go-sqlite3/backup.go
generated
vendored
|
@ -1,85 +0,0 @@
|
||||||
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
/*
|
|
||||||
#ifndef USE_LIBSQLITE3
|
|
||||||
#include <sqlite3-binding.h>
|
|
||||||
#else
|
|
||||||
#include <sqlite3.h>
|
|
||||||
#endif
|
|
||||||
#include <stdlib.h>
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
import (
|
|
||||||
"runtime"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// SQLiteBackup implement interface of Backup.
|
|
||||||
type SQLiteBackup struct {
|
|
||||||
b *C.sqlite3_backup
|
|
||||||
}
|
|
||||||
|
|
||||||
// Backup make backup from src to dest.
|
|
||||||
func (c *SQLiteConn) Backup(dest string, conn *SQLiteConn, src string) (*SQLiteBackup, error) {
|
|
||||||
destptr := C.CString(dest)
|
|
||||||
defer C.free(unsafe.Pointer(destptr))
|
|
||||||
srcptr := C.CString(src)
|
|
||||||
defer C.free(unsafe.Pointer(srcptr))
|
|
||||||
|
|
||||||
if b := C.sqlite3_backup_init(c.db, destptr, conn.db, srcptr); b != nil {
|
|
||||||
bb := &SQLiteBackup{b: b}
|
|
||||||
runtime.SetFinalizer(bb, (*SQLiteBackup).Finish)
|
|
||||||
return bb, nil
|
|
||||||
}
|
|
||||||
return nil, c.lastError()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step to backs up for one step. Calls the underlying `sqlite3_backup_step`
|
|
||||||
// function. This function returns a boolean indicating if the backup is done
|
|
||||||
// and an error signalling any other error. Done is returned if the underlying
|
|
||||||
// C function returns SQLITE_DONE (Code 101)
|
|
||||||
func (b *SQLiteBackup) Step(p int) (bool, error) {
|
|
||||||
ret := C.sqlite3_backup_step(b.b, C.int(p))
|
|
||||||
if ret == C.SQLITE_DONE {
|
|
||||||
return true, nil
|
|
||||||
} else if ret != 0 && ret != C.SQLITE_LOCKED && ret != C.SQLITE_BUSY {
|
|
||||||
return false, Error{Code: ErrNo(ret)}
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remaining return whether have the rest for backup.
|
|
||||||
func (b *SQLiteBackup) Remaining() int {
|
|
||||||
return int(C.sqlite3_backup_remaining(b.b))
|
|
||||||
}
|
|
||||||
|
|
||||||
// PageCount return count of pages.
|
|
||||||
func (b *SQLiteBackup) PageCount() int {
|
|
||||||
return int(C.sqlite3_backup_pagecount(b.b))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finish close backup.
|
|
||||||
func (b *SQLiteBackup) Finish() error {
|
|
||||||
return b.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close close backup.
|
|
||||||
func (b *SQLiteBackup) Close() error {
|
|
||||||
ret := C.sqlite3_backup_finish(b.b)
|
|
||||||
|
|
||||||
// sqlite3_backup_finish() never fails, it just returns the
|
|
||||||
// error code from previous operations, so clean up before
|
|
||||||
// checking and returning an error
|
|
||||||
b.b = nil
|
|
||||||
runtime.SetFinalizer(b, nil)
|
|
||||||
|
|
||||||
if ret != 0 {
|
|
||||||
return Error{Code: ErrNo(ret)}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
374
vendor/github.com/mattn/go-sqlite3/callback.go
generated
vendored
374
vendor/github.com/mattn/go-sqlite3/callback.go
generated
vendored
|
@ -1,374 +0,0 @@
|
||||||
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
// You can't export a Go function to C and have definitions in the C
|
|
||||||
// preamble in the same file, so we have to have callbackTrampoline in
|
|
||||||
// its own file. Because we need a separate file anyway, the support
|
|
||||||
// code for SQLite custom functions is in here.
|
|
||||||
|
|
||||||
/*
|
|
||||||
#ifndef USE_LIBSQLITE3
|
|
||||||
#include <sqlite3-binding.h>
|
|
||||||
#else
|
|
||||||
#include <sqlite3.h>
|
|
||||||
#endif
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
void _sqlite3_result_text(sqlite3_context* ctx, const char* s);
|
|
||||||
void _sqlite3_result_blob(sqlite3_context* ctx, const void* b, int l);
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"reflect"
|
|
||||||
"sync"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
//export callbackTrampoline
|
|
||||||
func callbackTrampoline(ctx *C.sqlite3_context, argc int, argv **C.sqlite3_value) {
|
|
||||||
args := (*[(math.MaxInt32 - 1) / unsafe.Sizeof((*C.sqlite3_value)(nil))]*C.sqlite3_value)(unsafe.Pointer(argv))[:argc:argc]
|
|
||||||
fi := lookupHandle(uintptr(C.sqlite3_user_data(ctx))).(*functionInfo)
|
|
||||||
fi.Call(ctx, args)
|
|
||||||
}
|
|
||||||
|
|
||||||
//export stepTrampoline
|
|
||||||
func stepTrampoline(ctx *C.sqlite3_context, argc C.int, argv **C.sqlite3_value) {
|
|
||||||
args := (*[(math.MaxInt32 - 1) / unsafe.Sizeof((*C.sqlite3_value)(nil))]*C.sqlite3_value)(unsafe.Pointer(argv))[:int(argc):int(argc)]
|
|
||||||
ai := lookupHandle(uintptr(C.sqlite3_user_data(ctx))).(*aggInfo)
|
|
||||||
ai.Step(ctx, args)
|
|
||||||
}
|
|
||||||
|
|
||||||
//export doneTrampoline
|
|
||||||
func doneTrampoline(ctx *C.sqlite3_context) {
|
|
||||||
handle := uintptr(C.sqlite3_user_data(ctx))
|
|
||||||
ai := lookupHandle(handle).(*aggInfo)
|
|
||||||
ai.Done(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
//export compareTrampoline
|
|
||||||
func compareTrampoline(handlePtr uintptr, la C.int, a *C.char, lb C.int, b *C.char) C.int {
|
|
||||||
cmp := lookupHandle(handlePtr).(func(string, string) int)
|
|
||||||
return C.int(cmp(C.GoStringN(a, la), C.GoStringN(b, lb)))
|
|
||||||
}
|
|
||||||
|
|
||||||
//export commitHookTrampoline
|
|
||||||
func commitHookTrampoline(handle uintptr) int {
|
|
||||||
callback := lookupHandle(handle).(func() int)
|
|
||||||
return callback()
|
|
||||||
}
|
|
||||||
|
|
||||||
//export rollbackHookTrampoline
|
|
||||||
func rollbackHookTrampoline(handle uintptr) {
|
|
||||||
callback := lookupHandle(handle).(func())
|
|
||||||
callback()
|
|
||||||
}
|
|
||||||
|
|
||||||
//export updateHookTrampoline
|
|
||||||
func updateHookTrampoline(handle uintptr, op int, db *C.char, table *C.char, rowid int64) {
|
|
||||||
callback := lookupHandle(handle).(func(int, string, string, int64))
|
|
||||||
callback(op, C.GoString(db), C.GoString(table), rowid)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use handles to avoid passing Go pointers to C.
|
|
||||||
|
|
||||||
type handleVal struct {
|
|
||||||
db *SQLiteConn
|
|
||||||
val interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
var handleLock sync.Mutex
|
|
||||||
var handleVals = make(map[uintptr]handleVal)
|
|
||||||
var handleIndex uintptr = 100
|
|
||||||
|
|
||||||
func newHandle(db *SQLiteConn, v interface{}) uintptr {
|
|
||||||
handleLock.Lock()
|
|
||||||
defer handleLock.Unlock()
|
|
||||||
i := handleIndex
|
|
||||||
handleIndex++
|
|
||||||
handleVals[i] = handleVal{db, v}
|
|
||||||
return i
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupHandle(handle uintptr) interface{} {
|
|
||||||
handleLock.Lock()
|
|
||||||
defer handleLock.Unlock()
|
|
||||||
r, ok := handleVals[handle]
|
|
||||||
if !ok {
|
|
||||||
if handle >= 100 && handle < handleIndex {
|
|
||||||
panic("deleted handle")
|
|
||||||
} else {
|
|
||||||
panic("invalid handle")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return r.val
|
|
||||||
}
|
|
||||||
|
|
||||||
func deleteHandles(db *SQLiteConn) {
|
|
||||||
handleLock.Lock()
|
|
||||||
defer handleLock.Unlock()
|
|
||||||
for handle, val := range handleVals {
|
|
||||||
if val.db == db {
|
|
||||||
delete(handleVals, handle)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is only here so that tests can refer to it.
|
|
||||||
type callbackArgRaw C.sqlite3_value
|
|
||||||
|
|
||||||
type callbackArgConverter func(*C.sqlite3_value) (reflect.Value, error)
|
|
||||||
|
|
||||||
type callbackArgCast struct {
|
|
||||||
f callbackArgConverter
|
|
||||||
typ reflect.Type
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c callbackArgCast) Run(v *C.sqlite3_value) (reflect.Value, error) {
|
|
||||||
val, err := c.f(v)
|
|
||||||
if err != nil {
|
|
||||||
return reflect.Value{}, err
|
|
||||||
}
|
|
||||||
if !val.Type().ConvertibleTo(c.typ) {
|
|
||||||
return reflect.Value{}, fmt.Errorf("cannot convert %s to %s", val.Type(), c.typ)
|
|
||||||
}
|
|
||||||
return val.Convert(c.typ), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func callbackArgInt64(v *C.sqlite3_value) (reflect.Value, error) {
|
|
||||||
if C.sqlite3_value_type(v) != C.SQLITE_INTEGER {
|
|
||||||
return reflect.Value{}, fmt.Errorf("argument must be an INTEGER")
|
|
||||||
}
|
|
||||||
return reflect.ValueOf(int64(C.sqlite3_value_int64(v))), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func callbackArgBool(v *C.sqlite3_value) (reflect.Value, error) {
|
|
||||||
if C.sqlite3_value_type(v) != C.SQLITE_INTEGER {
|
|
||||||
return reflect.Value{}, fmt.Errorf("argument must be an INTEGER")
|
|
||||||
}
|
|
||||||
i := int64(C.sqlite3_value_int64(v))
|
|
||||||
val := false
|
|
||||||
if i != 0 {
|
|
||||||
val = true
|
|
||||||
}
|
|
||||||
return reflect.ValueOf(val), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func callbackArgFloat64(v *C.sqlite3_value) (reflect.Value, error) {
|
|
||||||
if C.sqlite3_value_type(v) != C.SQLITE_FLOAT {
|
|
||||||
return reflect.Value{}, fmt.Errorf("argument must be a FLOAT")
|
|
||||||
}
|
|
||||||
return reflect.ValueOf(float64(C.sqlite3_value_double(v))), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func callbackArgBytes(v *C.sqlite3_value) (reflect.Value, error) {
|
|
||||||
switch C.sqlite3_value_type(v) {
|
|
||||||
case C.SQLITE_BLOB:
|
|
||||||
l := C.sqlite3_value_bytes(v)
|
|
||||||
p := C.sqlite3_value_blob(v)
|
|
||||||
return reflect.ValueOf(C.GoBytes(p, l)), nil
|
|
||||||
case C.SQLITE_TEXT:
|
|
||||||
l := C.sqlite3_value_bytes(v)
|
|
||||||
c := unsafe.Pointer(C.sqlite3_value_text(v))
|
|
||||||
return reflect.ValueOf(C.GoBytes(c, l)), nil
|
|
||||||
default:
|
|
||||||
return reflect.Value{}, fmt.Errorf("argument must be BLOB or TEXT")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func callbackArgString(v *C.sqlite3_value) (reflect.Value, error) {
|
|
||||||
switch C.sqlite3_value_type(v) {
|
|
||||||
case C.SQLITE_BLOB:
|
|
||||||
l := C.sqlite3_value_bytes(v)
|
|
||||||
p := (*C.char)(C.sqlite3_value_blob(v))
|
|
||||||
return reflect.ValueOf(C.GoStringN(p, l)), nil
|
|
||||||
case C.SQLITE_TEXT:
|
|
||||||
c := (*C.char)(unsafe.Pointer(C.sqlite3_value_text(v)))
|
|
||||||
return reflect.ValueOf(C.GoString(c)), nil
|
|
||||||
default:
|
|
||||||
return reflect.Value{}, fmt.Errorf("argument must be BLOB or TEXT")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func callbackArgGeneric(v *C.sqlite3_value) (reflect.Value, error) {
|
|
||||||
switch C.sqlite3_value_type(v) {
|
|
||||||
case C.SQLITE_INTEGER:
|
|
||||||
return callbackArgInt64(v)
|
|
||||||
case C.SQLITE_FLOAT:
|
|
||||||
return callbackArgFloat64(v)
|
|
||||||
case C.SQLITE_TEXT:
|
|
||||||
return callbackArgString(v)
|
|
||||||
case C.SQLITE_BLOB:
|
|
||||||
return callbackArgBytes(v)
|
|
||||||
case C.SQLITE_NULL:
|
|
||||||
// Interpret NULL as a nil byte slice.
|
|
||||||
var ret []byte
|
|
||||||
return reflect.ValueOf(ret), nil
|
|
||||||
default:
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func callbackArg(typ reflect.Type) (callbackArgConverter, error) {
|
|
||||||
switch typ.Kind() {
|
|
||||||
case reflect.Interface:
|
|
||||||
if typ.NumMethod() != 0 {
|
|
||||||
return nil, errors.New("the only supported interface type is interface{}")
|
|
||||||
}
|
|
||||||
return callbackArgGeneric, nil
|
|
||||||
case reflect.Slice:
|
|
||||||
if typ.Elem().Kind() != reflect.Uint8 {
|
|
||||||
return nil, errors.New("the only supported slice type is []byte")
|
|
||||||
}
|
|
||||||
return callbackArgBytes, nil
|
|
||||||
case reflect.String:
|
|
||||||
return callbackArgString, nil
|
|
||||||
case reflect.Bool:
|
|
||||||
return callbackArgBool, nil
|
|
||||||
case reflect.Int64:
|
|
||||||
return callbackArgInt64, nil
|
|
||||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Int, reflect.Uint:
|
|
||||||
c := callbackArgCast{callbackArgInt64, typ}
|
|
||||||
return c.Run, nil
|
|
||||||
case reflect.Float64:
|
|
||||||
return callbackArgFloat64, nil
|
|
||||||
case reflect.Float32:
|
|
||||||
c := callbackArgCast{callbackArgFloat64, typ}
|
|
||||||
return c.Run, nil
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("don't know how to convert to %s", typ)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func callbackConvertArgs(argv []*C.sqlite3_value, converters []callbackArgConverter, variadic callbackArgConverter) ([]reflect.Value, error) {
|
|
||||||
var args []reflect.Value
|
|
||||||
|
|
||||||
if len(argv) < len(converters) {
|
|
||||||
return nil, fmt.Errorf("function requires at least %d arguments", len(converters))
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, arg := range argv[:len(converters)] {
|
|
||||||
v, err := converters[i](arg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
args = append(args, v)
|
|
||||||
}
|
|
||||||
|
|
||||||
if variadic != nil {
|
|
||||||
for _, arg := range argv[len(converters):] {
|
|
||||||
v, err := variadic(arg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
args = append(args, v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return args, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type callbackRetConverter func(*C.sqlite3_context, reflect.Value) error
|
|
||||||
|
|
||||||
func callbackRetInteger(ctx *C.sqlite3_context, v reflect.Value) error {
|
|
||||||
switch v.Type().Kind() {
|
|
||||||
case reflect.Int64:
|
|
||||||
case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Int, reflect.Uint:
|
|
||||||
v = v.Convert(reflect.TypeOf(int64(0)))
|
|
||||||
case reflect.Bool:
|
|
||||||
b := v.Interface().(bool)
|
|
||||||
if b {
|
|
||||||
v = reflect.ValueOf(int64(1))
|
|
||||||
} else {
|
|
||||||
v = reflect.ValueOf(int64(0))
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("cannot convert %s to INTEGER", v.Type())
|
|
||||||
}
|
|
||||||
|
|
||||||
C.sqlite3_result_int64(ctx, C.sqlite3_int64(v.Interface().(int64)))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func callbackRetFloat(ctx *C.sqlite3_context, v reflect.Value) error {
|
|
||||||
switch v.Type().Kind() {
|
|
||||||
case reflect.Float64:
|
|
||||||
case reflect.Float32:
|
|
||||||
v = v.Convert(reflect.TypeOf(float64(0)))
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("cannot convert %s to FLOAT", v.Type())
|
|
||||||
}
|
|
||||||
|
|
||||||
C.sqlite3_result_double(ctx, C.double(v.Interface().(float64)))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func callbackRetBlob(ctx *C.sqlite3_context, v reflect.Value) error {
|
|
||||||
if v.Type().Kind() != reflect.Slice || v.Type().Elem().Kind() != reflect.Uint8 {
|
|
||||||
return fmt.Errorf("cannot convert %s to BLOB", v.Type())
|
|
||||||
}
|
|
||||||
i := v.Interface()
|
|
||||||
if i == nil || len(i.([]byte)) == 0 {
|
|
||||||
C.sqlite3_result_null(ctx)
|
|
||||||
} else {
|
|
||||||
bs := i.([]byte)
|
|
||||||
C._sqlite3_result_blob(ctx, unsafe.Pointer(&bs[0]), C.int(len(bs)))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func callbackRetText(ctx *C.sqlite3_context, v reflect.Value) error {
|
|
||||||
if v.Type().Kind() != reflect.String {
|
|
||||||
return fmt.Errorf("cannot convert %s to TEXT", v.Type())
|
|
||||||
}
|
|
||||||
C._sqlite3_result_text(ctx, C.CString(v.Interface().(string)))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func callbackRetNil(ctx *C.sqlite3_context, v reflect.Value) error {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func callbackRet(typ reflect.Type) (callbackRetConverter, error) {
|
|
||||||
switch typ.Kind() {
|
|
||||||
case reflect.Interface:
|
|
||||||
errorInterface := reflect.TypeOf((*error)(nil)).Elem()
|
|
||||||
if typ.Implements(errorInterface) {
|
|
||||||
return callbackRetNil, nil
|
|
||||||
}
|
|
||||||
fallthrough
|
|
||||||
case reflect.Slice:
|
|
||||||
if typ.Elem().Kind() != reflect.Uint8 {
|
|
||||||
return nil, errors.New("the only supported slice type is []byte")
|
|
||||||
}
|
|
||||||
return callbackRetBlob, nil
|
|
||||||
case reflect.String:
|
|
||||||
return callbackRetText, nil
|
|
||||||
case reflect.Bool, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Int, reflect.Uint:
|
|
||||||
return callbackRetInteger, nil
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
return callbackRetFloat, nil
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("don't know how to convert to %s", typ)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func callbackError(ctx *C.sqlite3_context, err error) {
|
|
||||||
cstr := C.CString(err.Error())
|
|
||||||
defer C.free(unsafe.Pointer(cstr))
|
|
||||||
C.sqlite3_result_error(ctx, cstr, -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test support code. Tests are not allowed to import "C", so we can't
|
|
||||||
// declare any functions that use C.sqlite3_value.
|
|
||||||
func callbackSyntheticForTests(v reflect.Value, err error) callbackArgConverter {
|
|
||||||
return func(*C.sqlite3_value) (reflect.Value, error) {
|
|
||||||
return v, err
|
|
||||||
}
|
|
||||||
}
|
|
112
vendor/github.com/mattn/go-sqlite3/doc.go
generated
vendored
112
vendor/github.com/mattn/go-sqlite3/doc.go
generated
vendored
|
@ -1,112 +0,0 @@
|
||||||
/*
|
|
||||||
Package sqlite3 provides interface to SQLite3 databases.
|
|
||||||
|
|
||||||
This works as a driver for database/sql.
|
|
||||||
|
|
||||||
Installation
|
|
||||||
|
|
||||||
go get github.com/mattn/go-sqlite3
|
|
||||||
|
|
||||||
Supported Types
|
|
||||||
|
|
||||||
Currently, go-sqlite3 supports the following data types.
|
|
||||||
|
|
||||||
+------------------------------+
|
|
||||||
|go | sqlite3 |
|
|
||||||
|----------|-------------------|
|
|
||||||
|nil | null |
|
|
||||||
|int | integer |
|
|
||||||
|int64 | integer |
|
|
||||||
|float64 | float |
|
|
||||||
|bool | integer |
|
|
||||||
|[]byte | blob |
|
|
||||||
|string | text |
|
|
||||||
|time.Time | timestamp/datetime|
|
|
||||||
+------------------------------+
|
|
||||||
|
|
||||||
SQLite3 Extension
|
|
||||||
|
|
||||||
You can write your own extension module for sqlite3. For example, below is an
|
|
||||||
extension for a Regexp matcher operation.
|
|
||||||
|
|
||||||
#include <pcre.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <sqlite3ext.h>
|
|
||||||
|
|
||||||
SQLITE_EXTENSION_INIT1
|
|
||||||
static void regexp_func(sqlite3_context *context, int argc, sqlite3_value **argv) {
|
|
||||||
if (argc >= 2) {
|
|
||||||
const char *target = (const char *)sqlite3_value_text(argv[1]);
|
|
||||||
const char *pattern = (const char *)sqlite3_value_text(argv[0]);
|
|
||||||
const char* errstr = NULL;
|
|
||||||
int erroff = 0;
|
|
||||||
int vec[500];
|
|
||||||
int n, rc;
|
|
||||||
pcre* re = pcre_compile(pattern, 0, &errstr, &erroff, NULL);
|
|
||||||
rc = pcre_exec(re, NULL, target, strlen(target), 0, 0, vec, 500);
|
|
||||||
if (rc <= 0) {
|
|
||||||
sqlite3_result_error(context, errstr, 0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
sqlite3_result_int(context, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
__declspec(dllexport)
|
|
||||||
#endif
|
|
||||||
int sqlite3_extension_init(sqlite3 *db, char **errmsg,
|
|
||||||
const sqlite3_api_routines *api) {
|
|
||||||
SQLITE_EXTENSION_INIT2(api);
|
|
||||||
return sqlite3_create_function(db, "regexp", 2, SQLITE_UTF8,
|
|
||||||
(void*)db, regexp_func, NULL, NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
It needs to be built as a so/dll shared library. And you need to register
|
|
||||||
the extension module like below.
|
|
||||||
|
|
||||||
sql.Register("sqlite3_with_extensions",
|
|
||||||
&sqlite3.SQLiteDriver{
|
|
||||||
Extensions: []string{
|
|
||||||
"sqlite3_mod_regexp",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
Then, you can use this extension.
|
|
||||||
|
|
||||||
rows, err := db.Query("select text from mytable where name regexp '^golang'")
|
|
||||||
|
|
||||||
Connection Hook
|
|
||||||
|
|
||||||
You can hook and inject your code when the connection is established. database/sql
|
|
||||||
doesn't provide a way to get native go-sqlite3 interfaces. So if you want,
|
|
||||||
you need to set ConnectHook and get the SQLiteConn.
|
|
||||||
|
|
||||||
sql.Register("sqlite3_with_hook_example",
|
|
||||||
&sqlite3.SQLiteDriver{
|
|
||||||
ConnectHook: func(conn *sqlite3.SQLiteConn) error {
|
|
||||||
sqlite3conn = append(sqlite3conn, conn)
|
|
||||||
return nil
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
Go SQlite3 Extensions
|
|
||||||
|
|
||||||
If you want to register Go functions as SQLite extension functions,
|
|
||||||
call RegisterFunction from ConnectHook.
|
|
||||||
|
|
||||||
regex = func(re, s string) (bool, error) {
|
|
||||||
return regexp.MatchString(re, s)
|
|
||||||
}
|
|
||||||
sql.Register("sqlite3_with_go_func",
|
|
||||||
&sqlite3.SQLiteDriver{
|
|
||||||
ConnectHook: func(conn *sqlite3.SQLiteConn) error {
|
|
||||||
return conn.RegisterFunc("regexp", regex, true)
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
See the documentation of RegisterFunc for more details.
|
|
||||||
|
|
||||||
*/
|
|
||||||
package sqlite3
|
|
135
vendor/github.com/mattn/go-sqlite3/error.go
generated
vendored
135
vendor/github.com/mattn/go-sqlite3/error.go
generated
vendored
|
@ -1,135 +0,0 @@
|
||||||
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
// ErrNo inherit errno.
|
|
||||||
type ErrNo int
|
|
||||||
|
|
||||||
// ErrNoMask is mask code.
|
|
||||||
const ErrNoMask C.int = 0xff
|
|
||||||
|
|
||||||
// ErrNoExtended is extended errno.
|
|
||||||
type ErrNoExtended int
|
|
||||||
|
|
||||||
// Error implement sqlite error code.
|
|
||||||
type Error struct {
|
|
||||||
Code ErrNo /* The error code returned by SQLite */
|
|
||||||
ExtendedCode ErrNoExtended /* The extended error code returned by SQLite */
|
|
||||||
err string /* The error string returned by sqlite3_errmsg(),
|
|
||||||
this usually contains more specific details. */
|
|
||||||
}
|
|
||||||
|
|
||||||
// result codes from http://www.sqlite.org/c3ref/c_abort.html
|
|
||||||
var (
|
|
||||||
ErrError = ErrNo(1) /* SQL error or missing database */
|
|
||||||
ErrInternal = ErrNo(2) /* Internal logic error in SQLite */
|
|
||||||
ErrPerm = ErrNo(3) /* Access permission denied */
|
|
||||||
ErrAbort = ErrNo(4) /* Callback routine requested an abort */
|
|
||||||
ErrBusy = ErrNo(5) /* The database file is locked */
|
|
||||||
ErrLocked = ErrNo(6) /* A table in the database is locked */
|
|
||||||
ErrNomem = ErrNo(7) /* A malloc() failed */
|
|
||||||
ErrReadonly = ErrNo(8) /* Attempt to write a readonly database */
|
|
||||||
ErrInterrupt = ErrNo(9) /* Operation terminated by sqlite3_interrupt() */
|
|
||||||
ErrIoErr = ErrNo(10) /* Some kind of disk I/O error occurred */
|
|
||||||
ErrCorrupt = ErrNo(11) /* The database disk image is malformed */
|
|
||||||
ErrNotFound = ErrNo(12) /* Unknown opcode in sqlite3_file_control() */
|
|
||||||
ErrFull = ErrNo(13) /* Insertion failed because database is full */
|
|
||||||
ErrCantOpen = ErrNo(14) /* Unable to open the database file */
|
|
||||||
ErrProtocol = ErrNo(15) /* Database lock protocol error */
|
|
||||||
ErrEmpty = ErrNo(16) /* Database is empty */
|
|
||||||
ErrSchema = ErrNo(17) /* The database schema changed */
|
|
||||||
ErrTooBig = ErrNo(18) /* String or BLOB exceeds size limit */
|
|
||||||
ErrConstraint = ErrNo(19) /* Abort due to constraint violation */
|
|
||||||
ErrMismatch = ErrNo(20) /* Data type mismatch */
|
|
||||||
ErrMisuse = ErrNo(21) /* Library used incorrectly */
|
|
||||||
ErrNoLFS = ErrNo(22) /* Uses OS features not supported on host */
|
|
||||||
ErrAuth = ErrNo(23) /* Authorization denied */
|
|
||||||
ErrFormat = ErrNo(24) /* Auxiliary database format error */
|
|
||||||
ErrRange = ErrNo(25) /* 2nd parameter to sqlite3_bind out of range */
|
|
||||||
ErrNotADB = ErrNo(26) /* File opened that is not a database file */
|
|
||||||
ErrNotice = ErrNo(27) /* Notifications from sqlite3_log() */
|
|
||||||
ErrWarning = ErrNo(28) /* Warnings from sqlite3_log() */
|
|
||||||
)
|
|
||||||
|
|
||||||
// Error return error message from errno.
|
|
||||||
func (err ErrNo) Error() string {
|
|
||||||
return Error{Code: err}.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extend return extended errno.
|
|
||||||
func (err ErrNo) Extend(by int) ErrNoExtended {
|
|
||||||
return ErrNoExtended(int(err) | (by << 8))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error return error message that is extended code.
|
|
||||||
func (err ErrNoExtended) Error() string {
|
|
||||||
return Error{Code: ErrNo(C.int(err) & ErrNoMask), ExtendedCode: err}.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (err Error) Error() string {
|
|
||||||
if err.err != "" {
|
|
||||||
return err.err
|
|
||||||
}
|
|
||||||
return errorString(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// result codes from http://www.sqlite.org/c3ref/c_abort_rollback.html
|
|
||||||
var (
|
|
||||||
ErrIoErrRead = ErrIoErr.Extend(1)
|
|
||||||
ErrIoErrShortRead = ErrIoErr.Extend(2)
|
|
||||||
ErrIoErrWrite = ErrIoErr.Extend(3)
|
|
||||||
ErrIoErrFsync = ErrIoErr.Extend(4)
|
|
||||||
ErrIoErrDirFsync = ErrIoErr.Extend(5)
|
|
||||||
ErrIoErrTruncate = ErrIoErr.Extend(6)
|
|
||||||
ErrIoErrFstat = ErrIoErr.Extend(7)
|
|
||||||
ErrIoErrUnlock = ErrIoErr.Extend(8)
|
|
||||||
ErrIoErrRDlock = ErrIoErr.Extend(9)
|
|
||||||
ErrIoErrDelete = ErrIoErr.Extend(10)
|
|
||||||
ErrIoErrBlocked = ErrIoErr.Extend(11)
|
|
||||||
ErrIoErrNoMem = ErrIoErr.Extend(12)
|
|
||||||
ErrIoErrAccess = ErrIoErr.Extend(13)
|
|
||||||
ErrIoErrCheckReservedLock = ErrIoErr.Extend(14)
|
|
||||||
ErrIoErrLock = ErrIoErr.Extend(15)
|
|
||||||
ErrIoErrClose = ErrIoErr.Extend(16)
|
|
||||||
ErrIoErrDirClose = ErrIoErr.Extend(17)
|
|
||||||
ErrIoErrSHMOpen = ErrIoErr.Extend(18)
|
|
||||||
ErrIoErrSHMSize = ErrIoErr.Extend(19)
|
|
||||||
ErrIoErrSHMLock = ErrIoErr.Extend(20)
|
|
||||||
ErrIoErrSHMMap = ErrIoErr.Extend(21)
|
|
||||||
ErrIoErrSeek = ErrIoErr.Extend(22)
|
|
||||||
ErrIoErrDeleteNoent = ErrIoErr.Extend(23)
|
|
||||||
ErrIoErrMMap = ErrIoErr.Extend(24)
|
|
||||||
ErrIoErrGetTempPath = ErrIoErr.Extend(25)
|
|
||||||
ErrIoErrConvPath = ErrIoErr.Extend(26)
|
|
||||||
ErrLockedSharedCache = ErrLocked.Extend(1)
|
|
||||||
ErrBusyRecovery = ErrBusy.Extend(1)
|
|
||||||
ErrBusySnapshot = ErrBusy.Extend(2)
|
|
||||||
ErrCantOpenNoTempDir = ErrCantOpen.Extend(1)
|
|
||||||
ErrCantOpenIsDir = ErrCantOpen.Extend(2)
|
|
||||||
ErrCantOpenFullPath = ErrCantOpen.Extend(3)
|
|
||||||
ErrCantOpenConvPath = ErrCantOpen.Extend(4)
|
|
||||||
ErrCorruptVTab = ErrCorrupt.Extend(1)
|
|
||||||
ErrReadonlyRecovery = ErrReadonly.Extend(1)
|
|
||||||
ErrReadonlyCantLock = ErrReadonly.Extend(2)
|
|
||||||
ErrReadonlyRollback = ErrReadonly.Extend(3)
|
|
||||||
ErrReadonlyDbMoved = ErrReadonly.Extend(4)
|
|
||||||
ErrAbortRollback = ErrAbort.Extend(2)
|
|
||||||
ErrConstraintCheck = ErrConstraint.Extend(1)
|
|
||||||
ErrConstraintCommitHook = ErrConstraint.Extend(2)
|
|
||||||
ErrConstraintForeignKey = ErrConstraint.Extend(3)
|
|
||||||
ErrConstraintFunction = ErrConstraint.Extend(4)
|
|
||||||
ErrConstraintNotNull = ErrConstraint.Extend(5)
|
|
||||||
ErrConstraintPrimaryKey = ErrConstraint.Extend(6)
|
|
||||||
ErrConstraintTrigger = ErrConstraint.Extend(7)
|
|
||||||
ErrConstraintUnique = ErrConstraint.Extend(8)
|
|
||||||
ErrConstraintVTab = ErrConstraint.Extend(9)
|
|
||||||
ErrConstraintRowID = ErrConstraint.Extend(10)
|
|
||||||
ErrNoticeRecoverWAL = ErrNotice.Extend(1)
|
|
||||||
ErrNoticeRecoverRollback = ErrNotice.Extend(2)
|
|
||||||
ErrWarningAutoIndex = ErrWarning.Extend(1)
|
|
||||||
)
|
|
212224
vendor/github.com/mattn/go-sqlite3/sqlite3-binding.c
generated
vendored
212224
vendor/github.com/mattn/go-sqlite3/sqlite3-binding.c
generated
vendored
File diff suppressed because it is too large
Load diff
11535
vendor/github.com/mattn/go-sqlite3/sqlite3-binding.h
generated
vendored
11535
vendor/github.com/mattn/go-sqlite3/sqlite3-binding.h
generated
vendored
File diff suppressed because it is too large
Load diff
1979
vendor/github.com/mattn/go-sqlite3/sqlite3.go
generated
vendored
1979
vendor/github.com/mattn/go-sqlite3/sqlite3.go
generated
vendored
File diff suppressed because it is too large
Load diff
103
vendor/github.com/mattn/go-sqlite3/sqlite3_context.go
generated
vendored
103
vendor/github.com/mattn/go-sqlite3/sqlite3_context.go
generated
vendored
|
@ -1,103 +0,0 @@
|
||||||
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
/*
|
|
||||||
|
|
||||||
#ifndef USE_LIBSQLITE3
|
|
||||||
#include <sqlite3-binding.h>
|
|
||||||
#else
|
|
||||||
#include <sqlite3.h>
|
|
||||||
#endif
|
|
||||||
#include <stdlib.h>
|
|
||||||
// These wrappers are necessary because SQLITE_TRANSIENT
|
|
||||||
// is a pointer constant, and cgo doesn't translate them correctly.
|
|
||||||
|
|
||||||
static inline void my_result_text(sqlite3_context *ctx, char *p, int np) {
|
|
||||||
sqlite3_result_text(ctx, p, np, SQLITE_TRANSIENT);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void my_result_blob(sqlite3_context *ctx, void *p, int np) {
|
|
||||||
sqlite3_result_blob(ctx, p, np, SQLITE_TRANSIENT);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math"
|
|
||||||
"reflect"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
const i64 = unsafe.Sizeof(int(0)) > 4
|
|
||||||
|
|
||||||
// SQLiteContext behave sqlite3_context
|
|
||||||
type SQLiteContext C.sqlite3_context
|
|
||||||
|
|
||||||
// ResultBool sets the result of an SQL function.
|
|
||||||
func (c *SQLiteContext) ResultBool(b bool) {
|
|
||||||
if b {
|
|
||||||
c.ResultInt(1)
|
|
||||||
} else {
|
|
||||||
c.ResultInt(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResultBlob sets the result of an SQL function.
|
|
||||||
// See: sqlite3_result_blob, http://sqlite.org/c3ref/result_blob.html
|
|
||||||
func (c *SQLiteContext) ResultBlob(b []byte) {
|
|
||||||
if i64 && len(b) > math.MaxInt32 {
|
|
||||||
C.sqlite3_result_error_toobig((*C.sqlite3_context)(c))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
var p *byte
|
|
||||||
if len(b) > 0 {
|
|
||||||
p = &b[0]
|
|
||||||
}
|
|
||||||
C.my_result_blob((*C.sqlite3_context)(c), unsafe.Pointer(p), C.int(len(b)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResultDouble sets the result of an SQL function.
|
|
||||||
// See: sqlite3_result_double, http://sqlite.org/c3ref/result_blob.html
|
|
||||||
func (c *SQLiteContext) ResultDouble(d float64) {
|
|
||||||
C.sqlite3_result_double((*C.sqlite3_context)(c), C.double(d))
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResultInt sets the result of an SQL function.
|
|
||||||
// See: sqlite3_result_int, http://sqlite.org/c3ref/result_blob.html
|
|
||||||
func (c *SQLiteContext) ResultInt(i int) {
|
|
||||||
if i64 && (i > math.MaxInt32 || i < math.MinInt32) {
|
|
||||||
C.sqlite3_result_int64((*C.sqlite3_context)(c), C.sqlite3_int64(i))
|
|
||||||
} else {
|
|
||||||
C.sqlite3_result_int((*C.sqlite3_context)(c), C.int(i))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResultInt64 sets the result of an SQL function.
|
|
||||||
// See: sqlite3_result_int64, http://sqlite.org/c3ref/result_blob.html
|
|
||||||
func (c *SQLiteContext) ResultInt64(i int64) {
|
|
||||||
C.sqlite3_result_int64((*C.sqlite3_context)(c), C.sqlite3_int64(i))
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResultNull sets the result of an SQL function.
|
|
||||||
// See: sqlite3_result_null, http://sqlite.org/c3ref/result_blob.html
|
|
||||||
func (c *SQLiteContext) ResultNull() {
|
|
||||||
C.sqlite3_result_null((*C.sqlite3_context)(c))
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResultText sets the result of an SQL function.
|
|
||||||
// See: sqlite3_result_text, http://sqlite.org/c3ref/result_blob.html
|
|
||||||
func (c *SQLiteContext) ResultText(s string) {
|
|
||||||
h := (*reflect.StringHeader)(unsafe.Pointer(&s))
|
|
||||||
cs, l := (*C.char)(unsafe.Pointer(h.Data)), C.int(h.Len)
|
|
||||||
C.my_result_text((*C.sqlite3_context)(c), cs, l)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ResultZeroblob sets the result of an SQL function.
|
|
||||||
// See: sqlite3_result_zeroblob, http://sqlite.org/c3ref/result_blob.html
|
|
||||||
func (c *SQLiteContext) ResultZeroblob(n int) {
|
|
||||||
C.sqlite3_result_zeroblob((*C.sqlite3_context)(c), C.int(n))
|
|
||||||
}
|
|
120
vendor/github.com/mattn/go-sqlite3/sqlite3_func_crypt.go
generated
vendored
120
vendor/github.com/mattn/go-sqlite3/sqlite3_func_crypt.go
generated
vendored
|
@ -1,120 +0,0 @@
|
||||||
// Copyright (C) 2018 G.J.R. Timmer <gjr.timmer@gmail.com>.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/sha1"
|
|
||||||
"crypto/sha256"
|
|
||||||
"crypto/sha512"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This file provides several different implementations for the
|
|
||||||
// default embedded sqlite_crypt function.
|
|
||||||
// This function is uses a ceasar-cypher by default
|
|
||||||
// and is used within the UserAuthentication module to encode
|
|
||||||
// the password.
|
|
||||||
//
|
|
||||||
// The provided functions can be used as an overload to the sqlite_crypt
|
|
||||||
// function through the use of the RegisterFunc on the connection.
|
|
||||||
//
|
|
||||||
// Because the functions can serv a purpose to an end-user
|
|
||||||
// without using the UserAuthentication module
|
|
||||||
// the functions are default compiled in.
|
|
||||||
//
|
|
||||||
// From SQLITE3 - user-auth.txt
|
|
||||||
// The sqlite_user.pw field is encoded by a built-in SQL function
|
|
||||||
// "sqlite_crypt(X,Y)". The two arguments are both BLOBs. The first argument
|
|
||||||
// is the plaintext password supplied to the sqlite3_user_authenticate()
|
|
||||||
// interface. The second argument is the sqlite_user.pw value and is supplied
|
|
||||||
// so that the function can extract the "salt" used by the password encoder.
|
|
||||||
// The result of sqlite_crypt(X,Y) is another blob which is the value that
|
|
||||||
// ends up being stored in sqlite_user.pw. To verify credentials X supplied
|
|
||||||
// by the sqlite3_user_authenticate() routine, SQLite runs:
|
|
||||||
//
|
|
||||||
// sqlite_user.pw == sqlite_crypt(X, sqlite_user.pw)
|
|
||||||
//
|
|
||||||
// To compute an appropriate sqlite_user.pw value from a new or modified
|
|
||||||
// password X, sqlite_crypt(X,NULL) is run. A new random salt is selected
|
|
||||||
// when the second argument is NULL.
|
|
||||||
//
|
|
||||||
// The built-in version of of sqlite_crypt() uses a simple Ceasar-cypher
|
|
||||||
// which prevents passwords from being revealed by searching the raw database
|
|
||||||
// for ASCII text, but is otherwise trivally broken. For better password
|
|
||||||
// security, the database should be encrypted using the SQLite Encryption
|
|
||||||
// Extension or similar technology. Or, the application can use the
|
|
||||||
// sqlite3_create_function() interface to provide an alternative
|
|
||||||
// implementation of sqlite_crypt() that computes a stronger password hash,
|
|
||||||
// perhaps using a cryptographic hash function like SHA1.
|
|
||||||
|
|
||||||
// CryptEncoderSHA1 encodes a password with SHA1
|
|
||||||
func CryptEncoderSHA1(pass []byte, hash interface{}) []byte {
|
|
||||||
h := sha1.Sum(pass)
|
|
||||||
return h[:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// CryptEncoderSSHA1 encodes a password with SHA1 with the
|
|
||||||
// configured salt.
|
|
||||||
func CryptEncoderSSHA1(salt string) func(pass []byte, hash interface{}) []byte {
|
|
||||||
return func(pass []byte, hash interface{}) []byte {
|
|
||||||
s := []byte(salt)
|
|
||||||
p := append(pass, s...)
|
|
||||||
h := sha1.Sum(p)
|
|
||||||
return h[:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CryptEncoderSHA256 encodes a password with SHA256
|
|
||||||
func CryptEncoderSHA256(pass []byte, hash interface{}) []byte {
|
|
||||||
h := sha256.Sum256(pass)
|
|
||||||
return h[:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// CryptEncoderSSHA256 encodes a password with SHA256
|
|
||||||
// with the configured salt
|
|
||||||
func CryptEncoderSSHA256(salt string) func(pass []byte, hash interface{}) []byte {
|
|
||||||
return func(pass []byte, hash interface{}) []byte {
|
|
||||||
s := []byte(salt)
|
|
||||||
p := append(pass, s...)
|
|
||||||
h := sha256.Sum256(p)
|
|
||||||
return h[:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CryptEncoderSHA384 encodes a password with SHA256
|
|
||||||
func CryptEncoderSHA384(pass []byte, hash interface{}) []byte {
|
|
||||||
h := sha512.Sum384(pass)
|
|
||||||
return h[:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// CryptEncoderSSHA384 encodes a password with SHA256
|
|
||||||
// with the configured salt
|
|
||||||
func CryptEncoderSSHA384(salt string) func(pass []byte, hash interface{}) []byte {
|
|
||||||
return func(pass []byte, hash interface{}) []byte {
|
|
||||||
s := []byte(salt)
|
|
||||||
p := append(pass, s...)
|
|
||||||
h := sha512.Sum384(p)
|
|
||||||
return h[:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// CryptEncoderSHA512 encodes a password with SHA256
|
|
||||||
func CryptEncoderSHA512(pass []byte, hash interface{}) []byte {
|
|
||||||
h := sha512.Sum512(pass)
|
|
||||||
return h[:]
|
|
||||||
}
|
|
||||||
|
|
||||||
// CryptEncoderSSHA512 encodes a password with SHA256
|
|
||||||
// with the configured salt
|
|
||||||
func CryptEncoderSSHA512(salt string) func(pass []byte, hash interface{}) []byte {
|
|
||||||
return func(pass []byte, hash interface{}) []byte {
|
|
||||||
s := []byte(salt)
|
|
||||||
p := append(pass, s...)
|
|
||||||
h := sha512.Sum512(p)
|
|
||||||
return h[:]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// EOF
|
|
70
vendor/github.com/mattn/go-sqlite3/sqlite3_go18.go
generated
vendored
70
vendor/github.com/mattn/go-sqlite3/sqlite3_go18.go
generated
vendored
|
@ -1,70 +0,0 @@
|
||||||
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build cgo
|
|
||||||
// +build go1.8
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql/driver"
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"context"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Ping implement Pinger.
|
|
||||||
func (c *SQLiteConn) Ping(ctx context.Context) error {
|
|
||||||
if c.db == nil {
|
|
||||||
return errors.New("Connection was closed")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryContext implement QueryerContext.
|
|
||||||
func (c *SQLiteConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) {
|
|
||||||
list := make([]namedValue, len(args))
|
|
||||||
for i, nv := range args {
|
|
||||||
list[i] = namedValue(nv)
|
|
||||||
}
|
|
||||||
return c.query(ctx, query, list)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExecContext implement ExecerContext.
|
|
||||||
func (c *SQLiteConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) {
|
|
||||||
list := make([]namedValue, len(args))
|
|
||||||
for i, nv := range args {
|
|
||||||
list[i] = namedValue(nv)
|
|
||||||
}
|
|
||||||
return c.exec(ctx, query, list)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrepareContext implement ConnPrepareContext.
|
|
||||||
func (c *SQLiteConn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) {
|
|
||||||
return c.prepare(ctx, query)
|
|
||||||
}
|
|
||||||
|
|
||||||
// BeginTx implement ConnBeginTx.
|
|
||||||
func (c *SQLiteConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
|
|
||||||
return c.begin(ctx)
|
|
||||||
}
|
|
||||||
|
|
||||||
// QueryContext implement QueryerContext.
|
|
||||||
func (s *SQLiteStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) {
|
|
||||||
list := make([]namedValue, len(args))
|
|
||||||
for i, nv := range args {
|
|
||||||
list[i] = namedValue(nv)
|
|
||||||
}
|
|
||||||
return s.query(ctx, list)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExecContext implement ExecerContext.
|
|
||||||
func (s *SQLiteStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) {
|
|
||||||
list := make([]namedValue, len(args))
|
|
||||||
for i, nv := range args {
|
|
||||||
list[i] = namedValue(nv)
|
|
||||||
}
|
|
||||||
return s.exec(ctx, list)
|
|
||||||
}
|
|
17
vendor/github.com/mattn/go-sqlite3/sqlite3_libsqlite3.go
generated
vendored
17
vendor/github.com/mattn/go-sqlite3/sqlite3_libsqlite3.go
generated
vendored
|
@ -1,17 +0,0 @@
|
||||||
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build libsqlite3
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo CFLAGS: -DUSE_LIBSQLITE3
|
|
||||||
#cgo linux LDFLAGS: -lsqlite3
|
|
||||||
#cgo darwin LDFLAGS: -L/usr/local/opt/sqlite/lib -lsqlite3
|
|
||||||
#cgo openbsd LDFLAGS: -lsqlite3
|
|
||||||
#cgo solaris LDFLAGS: -lsqlite3
|
|
||||||
*/
|
|
||||||
import "C"
|
|
70
vendor/github.com/mattn/go-sqlite3/sqlite3_load_extension.go
generated
vendored
70
vendor/github.com/mattn/go-sqlite3/sqlite3_load_extension.go
generated
vendored
|
@ -1,70 +0,0 @@
|
||||||
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build !sqlite_omit_load_extension
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
/*
|
|
||||||
#ifndef USE_LIBSQLITE3
|
|
||||||
#include <sqlite3-binding.h>
|
|
||||||
#else
|
|
||||||
#include <sqlite3.h>
|
|
||||||
#endif
|
|
||||||
#include <stdlib.h>
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *SQLiteConn) loadExtensions(extensions []string) error {
|
|
||||||
rv := C.sqlite3_enable_load_extension(c.db, 1)
|
|
||||||
if rv != C.SQLITE_OK {
|
|
||||||
return errors.New(C.GoString(C.sqlite3_errmsg(c.db)))
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, extension := range extensions {
|
|
||||||
cext := C.CString(extension)
|
|
||||||
defer C.free(unsafe.Pointer(cext))
|
|
||||||
rv = C.sqlite3_load_extension(c.db, cext, nil, nil)
|
|
||||||
if rv != C.SQLITE_OK {
|
|
||||||
C.sqlite3_enable_load_extension(c.db, 0)
|
|
||||||
return errors.New(C.GoString(C.sqlite3_errmsg(c.db)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rv = C.sqlite3_enable_load_extension(c.db, 0)
|
|
||||||
if rv != C.SQLITE_OK {
|
|
||||||
return errors.New(C.GoString(C.sqlite3_errmsg(c.db)))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadExtension load the sqlite3 extension.
|
|
||||||
func (c *SQLiteConn) LoadExtension(lib string, entry string) error {
|
|
||||||
rv := C.sqlite3_enable_load_extension(c.db, 1)
|
|
||||||
if rv != C.SQLITE_OK {
|
|
||||||
return errors.New(C.GoString(C.sqlite3_errmsg(c.db)))
|
|
||||||
}
|
|
||||||
|
|
||||||
clib := C.CString(lib)
|
|
||||||
defer C.free(unsafe.Pointer(clib))
|
|
||||||
centry := C.CString(entry)
|
|
||||||
defer C.free(unsafe.Pointer(centry))
|
|
||||||
|
|
||||||
rv = C.sqlite3_load_extension(c.db, clib, centry, nil)
|
|
||||||
if rv != C.SQLITE_OK {
|
|
||||||
return errors.New(C.GoString(C.sqlite3_errmsg(c.db)))
|
|
||||||
}
|
|
||||||
|
|
||||||
rv = C.sqlite3_enable_load_extension(c.db, 0)
|
|
||||||
if rv != C.SQLITE_OK {
|
|
||||||
return errors.New(C.GoString(C.sqlite3_errmsg(c.db)))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
24
vendor/github.com/mattn/go-sqlite3/sqlite3_load_extension_omit.go
generated
vendored
24
vendor/github.com/mattn/go-sqlite3/sqlite3_load_extension_omit.go
generated
vendored
|
@ -1,24 +0,0 @@
|
||||||
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build sqlite_omit_load_extension
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo CFLAGS: -DSQLITE_OMIT_LOAD_EXTENSION
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *SQLiteConn) loadExtensions(extensions []string) error {
|
|
||||||
return errors.New("Extensions have been disabled for static builds")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *SQLiteConn) LoadExtension(lib string, entry string) error {
|
|
||||||
return errors.New("Extensions have been disabled for static builds")
|
|
||||||
}
|
|
15
vendor/github.com/mattn/go-sqlite3/sqlite3_opt_allow_uri_authority.go
generated
vendored
15
vendor/github.com/mattn/go-sqlite3/sqlite3_opt_allow_uri_authority.go
generated
vendored
|
@ -1,15 +0,0 @@
|
||||||
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
|
||||||
// Copyright (C) 2018 G.J.R. Timmer <gjr.timmer@gmail.com>.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build sqlite_allow_uri_authority
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo CFLAGS: -DSQLITE_ALLOW_URI_AUTHORITY
|
|
||||||
#cgo LDFLAGS: -lm
|
|
||||||
*/
|
|
||||||
import "C"
|
|
16
vendor/github.com/mattn/go-sqlite3/sqlite3_opt_app_armor.go
generated
vendored
16
vendor/github.com/mattn/go-sqlite3/sqlite3_opt_app_armor.go
generated
vendored
|
@ -1,16 +0,0 @@
|
||||||
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
|
||||||
// Copyright (C) 2018 G.J.R. Timmer <gjr.timmer@gmail.com>.
|
|
||||||
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build !windows
|
|
||||||
// +build sqlite_app_armor
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo CFLAGS: -DSQLITE_ENABLE_API_ARMOR
|
|
||||||
#cgo LDFLAGS: -lm
|
|
||||||
*/
|
|
||||||
import "C"
|
|
15
vendor/github.com/mattn/go-sqlite3/sqlite3_opt_foreign_keys.go
generated
vendored
15
vendor/github.com/mattn/go-sqlite3/sqlite3_opt_foreign_keys.go
generated
vendored
|
@ -1,15 +0,0 @@
|
||||||
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
|
||||||
// Copyright (C) 2018 G.J.R. Timmer <gjr.timmer@gmail.com>.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build sqlite_foreign_keys
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo CFLAGS: -DSQLITE_DEFAULT_FOREIGN_KEYS=1
|
|
||||||
#cgo LDFLAGS: -lm
|
|
||||||
*/
|
|
||||||
import "C"
|
|
14
vendor/github.com/mattn/go-sqlite3/sqlite3_opt_fts5.go
generated
vendored
14
vendor/github.com/mattn/go-sqlite3/sqlite3_opt_fts5.go
generated
vendored
|
@ -1,14 +0,0 @@
|
||||||
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build sqlite_fts5 fts5
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo CFLAGS: -DSQLITE_ENABLE_FTS5
|
|
||||||
#cgo LDFLAGS: -lm
|
|
||||||
*/
|
|
||||||
import "C"
|
|
17
vendor/github.com/mattn/go-sqlite3/sqlite3_opt_icu.go
generated
vendored
17
vendor/github.com/mattn/go-sqlite3/sqlite3_opt_icu.go
generated
vendored
|
@ -1,17 +0,0 @@
|
||||||
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build sqlite_icu icu
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo LDFLAGS: -licuuc -licui18n
|
|
||||||
#cgo CFLAGS: -DSQLITE_ENABLE_ICU
|
|
||||||
#cgo darwin CFLAGS: -I/usr/local/opt/icu4c/include
|
|
||||||
#cgo darwin LDFLAGS: -L/usr/local/opt/icu4c/lib
|
|
||||||
#cgo openbsd LDFLAGS: -lsqlite3
|
|
||||||
*/
|
|
||||||
import "C"
|
|
15
vendor/github.com/mattn/go-sqlite3/sqlite3_opt_introspect.go
generated
vendored
15
vendor/github.com/mattn/go-sqlite3/sqlite3_opt_introspect.go
generated
vendored
|
@ -1,15 +0,0 @@
|
||||||
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
|
||||||
// Copyright (C) 2018 G.J.R. Timmer <gjr.timmer@gmail.com>.
|
|
||||||
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build sqlite_introspect
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo CFLAGS: -DSQLITE_INTROSPECTION_PRAGMAS
|
|
||||||
#cgo LDFLAGS: -lm
|
|
||||||
*/
|
|
||||||
import "C"
|
|
13
vendor/github.com/mattn/go-sqlite3/sqlite3_opt_json1.go
generated
vendored
13
vendor/github.com/mattn/go-sqlite3/sqlite3_opt_json1.go
generated
vendored
|
@ -1,13 +0,0 @@
|
||||||
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build sqlite_json sqlite_json1 json1
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo CFLAGS: -DSQLITE_ENABLE_JSON1
|
|
||||||
*/
|
|
||||||
import "C"
|
|
15
vendor/github.com/mattn/go-sqlite3/sqlite3_opt_secure_delete.go
generated
vendored
15
vendor/github.com/mattn/go-sqlite3/sqlite3_opt_secure_delete.go
generated
vendored
|
@ -1,15 +0,0 @@
|
||||||
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
|
||||||
// Copyright (C) 2018 G.J.R. Timmer <gjr.timmer@gmail.com>.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build sqlite_secure_delete
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo CFLAGS: -DSQLITE_SECURE_DELETE=1
|
|
||||||
#cgo LDFLAGS: -lm
|
|
||||||
*/
|
|
||||||
import "C"
|
|
15
vendor/github.com/mattn/go-sqlite3/sqlite3_opt_secure_delete_fast.go
generated
vendored
15
vendor/github.com/mattn/go-sqlite3/sqlite3_opt_secure_delete_fast.go
generated
vendored
|
@ -1,15 +0,0 @@
|
||||||
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
|
||||||
// Copyright (C) 2018 G.J.R. Timmer <gjr.timmer@gmail.com>.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build sqlite_secure_delete_fast
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo CFLAGS: -DSQLITE_SECURE_DELETE=FAST
|
|
||||||
#cgo LDFLAGS: -lm
|
|
||||||
*/
|
|
||||||
import "C"
|
|
15
vendor/github.com/mattn/go-sqlite3/sqlite3_opt_stat4.go
generated
vendored
15
vendor/github.com/mattn/go-sqlite3/sqlite3_opt_stat4.go
generated
vendored
|
@ -1,15 +0,0 @@
|
||||||
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
|
||||||
// Copyright (C) 2018 G.J.R. Timmer <gjr.timmer@gmail.com>.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build sqlite_stat4
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo CFLAGS: -DSQLITE_ENABLE_STAT4
|
|
||||||
#cgo LDFLAGS: -lm
|
|
||||||
*/
|
|
||||||
import "C"
|
|
289
vendor/github.com/mattn/go-sqlite3/sqlite3_opt_userauth.go
generated
vendored
289
vendor/github.com/mattn/go-sqlite3/sqlite3_opt_userauth.go
generated
vendored
|
@ -1,289 +0,0 @@
|
||||||
// Copyright (C) 2018 G.J.R. Timmer <gjr.timmer@gmail.com>.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build sqlite_userauth
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo CFLAGS: -DSQLITE_USER_AUTHENTICATION
|
|
||||||
#cgo LDFLAGS: -lm
|
|
||||||
#ifndef USE_LIBSQLITE3
|
|
||||||
#include <sqlite3-binding.h>
|
|
||||||
#else
|
|
||||||
#include <sqlite3.h>
|
|
||||||
#endif
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
static int
|
|
||||||
_sqlite3_user_authenticate(sqlite3* db, const char* zUsername, const char* aPW, int nPW)
|
|
||||||
{
|
|
||||||
return sqlite3_user_authenticate(db, zUsername, aPW, nPW);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
_sqlite3_user_add(sqlite3* db, const char* zUsername, const char* aPW, int nPW, int isAdmin)
|
|
||||||
{
|
|
||||||
return sqlite3_user_add(db, zUsername, aPW, nPW, isAdmin);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
_sqlite3_user_change(sqlite3* db, const char* zUsername, const char* aPW, int nPW, int isAdmin)
|
|
||||||
{
|
|
||||||
return sqlite3_user_change(db, zUsername, aPW, nPW, isAdmin);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
_sqlite3_user_delete(sqlite3* db, const char* zUsername)
|
|
||||||
{
|
|
||||||
return sqlite3_user_delete(db, zUsername);
|
|
||||||
}
|
|
||||||
|
|
||||||
static int
|
|
||||||
_sqlite3_auth_enabled(sqlite3* db)
|
|
||||||
{
|
|
||||||
int exists = -1;
|
|
||||||
|
|
||||||
sqlite3_stmt *stmt;
|
|
||||||
sqlite3_prepare_v2(db, "select count(type) from sqlite_master WHERE type='table' and name='sqlite_user';", -1, &stmt, NULL);
|
|
||||||
|
|
||||||
while ( sqlite3_step(stmt) == SQLITE_ROW) {
|
|
||||||
exists = sqlite3_column_int(stmt, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
sqlite3_finalize(stmt);
|
|
||||||
|
|
||||||
return exists;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
SQLITE_AUTH = C.SQLITE_AUTH
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrUnauthorized = errors.New("SQLITE_AUTH: Unauthorized")
|
|
||||||
ErrAdminRequired = errors.New("SQLITE_AUTH: Unauthorized; Admin Privileges Required")
|
|
||||||
)
|
|
||||||
|
|
||||||
// Authenticate will perform an authentication of the provided username
|
|
||||||
// and password against the database.
|
|
||||||
//
|
|
||||||
// If a database contains the SQLITE_USER table, then the
|
|
||||||
// call to Authenticate must be invoked with an
|
|
||||||
// appropriate username and password prior to enable read and write
|
|
||||||
//access to the database.
|
|
||||||
//
|
|
||||||
// Return SQLITE_OK on success or SQLITE_ERROR if the username/password
|
|
||||||
// combination is incorrect or unknown.
|
|
||||||
//
|
|
||||||
// If the SQLITE_USER table is not present in the database file, then
|
|
||||||
// this interface is a harmless no-op returnning SQLITE_OK.
|
|
||||||
func (c *SQLiteConn) Authenticate(username, password string) error {
|
|
||||||
rv := c.authenticate(username, password)
|
|
||||||
switch rv {
|
|
||||||
case C.SQLITE_ERROR, C.SQLITE_AUTH:
|
|
||||||
return ErrUnauthorized
|
|
||||||
case C.SQLITE_OK:
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
return c.lastError()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// authenticate provides the actual authentication to SQLite.
|
|
||||||
// This is not exported for usage in Go.
|
|
||||||
// It is however exported for usage within SQL by the user.
|
|
||||||
//
|
|
||||||
// Returns:
|
|
||||||
// C.SQLITE_OK (0)
|
|
||||||
// C.SQLITE_ERROR (1)
|
|
||||||
// C.SQLITE_AUTH (23)
|
|
||||||
func (c *SQLiteConn) authenticate(username, password string) int {
|
|
||||||
// Allocate C Variables
|
|
||||||
cuser := C.CString(username)
|
|
||||||
cpass := C.CString(password)
|
|
||||||
|
|
||||||
// Free C Variables
|
|
||||||
defer func() {
|
|
||||||
C.free(unsafe.Pointer(cuser))
|
|
||||||
C.free(unsafe.Pointer(cpass))
|
|
||||||
}()
|
|
||||||
|
|
||||||
return int(C._sqlite3_user_authenticate(c.db, cuser, cpass, C.int(len(password))))
|
|
||||||
}
|
|
||||||
|
|
||||||
// AuthUserAdd can be used (by an admin user only)
|
|
||||||
// to create a new user. When called on a no-authentication-required
|
|
||||||
// database, this routine converts the database into an authentication-
|
|
||||||
// required database, automatically makes the added user an
|
|
||||||
// administrator, and logs in the current connection as that user.
|
|
||||||
// The AuthUserAdd only works for the "main" database, not
|
|
||||||
// for any ATTACH-ed databases. Any call to AuthUserAdd by a
|
|
||||||
// non-admin user results in an error.
|
|
||||||
func (c *SQLiteConn) AuthUserAdd(username, password string, admin bool) error {
|
|
||||||
isAdmin := 0
|
|
||||||
if admin {
|
|
||||||
isAdmin = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
rv := c.authUserAdd(username, password, isAdmin)
|
|
||||||
switch rv {
|
|
||||||
case C.SQLITE_ERROR, C.SQLITE_AUTH:
|
|
||||||
return ErrAdminRequired
|
|
||||||
case C.SQLITE_OK:
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
return c.lastError()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// authUserAdd enables the User Authentication if not enabled.
|
|
||||||
// Otherwise it will add a user.
|
|
||||||
//
|
|
||||||
// When user authentication is already enabled then this function
|
|
||||||
// can only be called by an admin.
|
|
||||||
//
|
|
||||||
// This is not exported for usage in Go.
|
|
||||||
// It is however exported for usage within SQL by the user.
|
|
||||||
//
|
|
||||||
// Returns:
|
|
||||||
// C.SQLITE_OK (0)
|
|
||||||
// C.SQLITE_ERROR (1)
|
|
||||||
// C.SQLITE_AUTH (23)
|
|
||||||
func (c *SQLiteConn) authUserAdd(username, password string, admin int) int {
|
|
||||||
// Allocate C Variables
|
|
||||||
cuser := C.CString(username)
|
|
||||||
cpass := C.CString(password)
|
|
||||||
|
|
||||||
// Free C Variables
|
|
||||||
defer func() {
|
|
||||||
C.free(unsafe.Pointer(cuser))
|
|
||||||
C.free(unsafe.Pointer(cpass))
|
|
||||||
}()
|
|
||||||
|
|
||||||
return int(C._sqlite3_user_add(c.db, cuser, cpass, C.int(len(password)), C.int(admin)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// AuthUserChange can be used to change a users
|
|
||||||
// login credentials or admin privilege. Any user can change their own
|
|
||||||
// login credentials. Only an admin user can change another users login
|
|
||||||
// credentials or admin privilege setting. No user may change their own
|
|
||||||
// admin privilege setting.
|
|
||||||
func (c *SQLiteConn) AuthUserChange(username, password string, admin bool) error {
|
|
||||||
isAdmin := 0
|
|
||||||
if admin {
|
|
||||||
isAdmin = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
rv := c.authUserChange(username, password, isAdmin)
|
|
||||||
switch rv {
|
|
||||||
case C.SQLITE_ERROR, C.SQLITE_AUTH:
|
|
||||||
return ErrAdminRequired
|
|
||||||
case C.SQLITE_OK:
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
return c.lastError()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// authUserChange allows to modify a user.
|
|
||||||
// Users can change their own password.
|
|
||||||
//
|
|
||||||
// Only admins can change passwords for other users
|
|
||||||
// and modify the admin flag.
|
|
||||||
//
|
|
||||||
// The admin flag of the current logged in user cannot be changed.
|
|
||||||
// THis ensures that their is always an admin.
|
|
||||||
//
|
|
||||||
// This is not exported for usage in Go.
|
|
||||||
// It is however exported for usage within SQL by the user.
|
|
||||||
//
|
|
||||||
// Returns:
|
|
||||||
// C.SQLITE_OK (0)
|
|
||||||
// C.SQLITE_ERROR (1)
|
|
||||||
// C.SQLITE_AUTH (23)
|
|
||||||
func (c *SQLiteConn) authUserChange(username, password string, admin int) int {
|
|
||||||
// Allocate C Variables
|
|
||||||
cuser := C.CString(username)
|
|
||||||
cpass := C.CString(password)
|
|
||||||
|
|
||||||
// Free C Variables
|
|
||||||
defer func() {
|
|
||||||
C.free(unsafe.Pointer(cuser))
|
|
||||||
C.free(unsafe.Pointer(cpass))
|
|
||||||
}()
|
|
||||||
|
|
||||||
return int(C._sqlite3_user_change(c.db, cuser, cpass, C.int(len(password)), C.int(admin)))
|
|
||||||
}
|
|
||||||
|
|
||||||
// AuthUserDelete can be used (by an admin user only)
|
|
||||||
// to delete a user. The currently logged-in user cannot be deleted,
|
|
||||||
// which guarantees that there is always an admin user and hence that
|
|
||||||
// the database cannot be converted into a no-authentication-required
|
|
||||||
// database.
|
|
||||||
func (c *SQLiteConn) AuthUserDelete(username string) error {
|
|
||||||
rv := c.authUserDelete(username)
|
|
||||||
switch rv {
|
|
||||||
case C.SQLITE_ERROR, C.SQLITE_AUTH:
|
|
||||||
return ErrAdminRequired
|
|
||||||
case C.SQLITE_OK:
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
return c.lastError()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// authUserDelete can be used to delete a user.
|
|
||||||
//
|
|
||||||
// This function can only be executed by an admin.
|
|
||||||
//
|
|
||||||
// This is not exported for usage in Go.
|
|
||||||
// It is however exported for usage within SQL by the user.
|
|
||||||
//
|
|
||||||
// Returns:
|
|
||||||
// C.SQLITE_OK (0)
|
|
||||||
// C.SQLITE_ERROR (1)
|
|
||||||
// C.SQLITE_AUTH (23)
|
|
||||||
func (c *SQLiteConn) authUserDelete(username string) int {
|
|
||||||
// Allocate C Variables
|
|
||||||
cuser := C.CString(username)
|
|
||||||
|
|
||||||
// Free C Variables
|
|
||||||
defer func() {
|
|
||||||
C.free(unsafe.Pointer(cuser))
|
|
||||||
}()
|
|
||||||
|
|
||||||
return int(C._sqlite3_user_delete(c.db, cuser))
|
|
||||||
}
|
|
||||||
|
|
||||||
// AuthEnabled checks if the database is protected by user authentication
|
|
||||||
func (c *SQLiteConn) AuthEnabled() (exists bool) {
|
|
||||||
rv := c.authEnabled()
|
|
||||||
if rv == 1 {
|
|
||||||
exists = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// authEnabled perform the actual check for user authentication.
|
|
||||||
//
|
|
||||||
// This is not exported for usage in Go.
|
|
||||||
// It is however exported for usage within SQL by the user.
|
|
||||||
//
|
|
||||||
// Returns:
|
|
||||||
// 0 - Disabled
|
|
||||||
// 1 - Enabled
|
|
||||||
func (c *SQLiteConn) authEnabled() int {
|
|
||||||
return int(C._sqlite3_auth_enabled(c.db))
|
|
||||||
}
|
|
||||||
|
|
||||||
// EOF
|
|
152
vendor/github.com/mattn/go-sqlite3/sqlite3_opt_userauth_omit.go
generated
vendored
152
vendor/github.com/mattn/go-sqlite3/sqlite3_opt_userauth_omit.go
generated
vendored
|
@ -1,152 +0,0 @@
|
||||||
// Copyright (C) 2018 G.J.R. Timmer <gjr.timmer@gmail.com>.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build !sqlite_userauth
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
import (
|
|
||||||
"C"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Authenticate will perform an authentication of the provided username
|
|
||||||
// and password against the database.
|
|
||||||
//
|
|
||||||
// If a database contains the SQLITE_USER table, then the
|
|
||||||
// call to Authenticate must be invoked with an
|
|
||||||
// appropriate username and password prior to enable read and write
|
|
||||||
//access to the database.
|
|
||||||
//
|
|
||||||
// Return SQLITE_OK on success or SQLITE_ERROR if the username/password
|
|
||||||
// combination is incorrect or unknown.
|
|
||||||
//
|
|
||||||
// If the SQLITE_USER table is not present in the database file, then
|
|
||||||
// this interface is a harmless no-op returnning SQLITE_OK.
|
|
||||||
func (c *SQLiteConn) Authenticate(username, password string) error {
|
|
||||||
// NOOP
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// authenticate provides the actual authentication to SQLite.
|
|
||||||
// This is not exported for usage in Go.
|
|
||||||
// It is however exported for usage within SQL by the user.
|
|
||||||
//
|
|
||||||
// Returns:
|
|
||||||
// C.SQLITE_OK (0)
|
|
||||||
// C.SQLITE_ERROR (1)
|
|
||||||
// C.SQLITE_AUTH (23)
|
|
||||||
func (c *SQLiteConn) authenticate(username, password string) int {
|
|
||||||
// NOOP
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// AuthUserAdd can be used (by an admin user only)
|
|
||||||
// to create a new user. When called on a no-authentication-required
|
|
||||||
// database, this routine converts the database into an authentication-
|
|
||||||
// required database, automatically makes the added user an
|
|
||||||
// administrator, and logs in the current connection as that user.
|
|
||||||
// The AuthUserAdd only works for the "main" database, not
|
|
||||||
// for any ATTACH-ed databases. Any call to AuthUserAdd by a
|
|
||||||
// non-admin user results in an error.
|
|
||||||
func (c *SQLiteConn) AuthUserAdd(username, password string, admin bool) error {
|
|
||||||
// NOOP
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// authUserAdd enables the User Authentication if not enabled.
|
|
||||||
// Otherwise it will add a user.
|
|
||||||
//
|
|
||||||
// When user authentication is already enabled then this function
|
|
||||||
// can only be called by an admin.
|
|
||||||
//
|
|
||||||
// This is not exported for usage in Go.
|
|
||||||
// It is however exported for usage within SQL by the user.
|
|
||||||
//
|
|
||||||
// Returns:
|
|
||||||
// C.SQLITE_OK (0)
|
|
||||||
// C.SQLITE_ERROR (1)
|
|
||||||
// C.SQLITE_AUTH (23)
|
|
||||||
func (c *SQLiteConn) authUserAdd(username, password string, admin int) int {
|
|
||||||
// NOOP
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// AuthUserChange can be used to change a users
|
|
||||||
// login credentials or admin privilege. Any user can change their own
|
|
||||||
// login credentials. Only an admin user can change another users login
|
|
||||||
// credentials or admin privilege setting. No user may change their own
|
|
||||||
// admin privilege setting.
|
|
||||||
func (c *SQLiteConn) AuthUserChange(username, password string, admin bool) error {
|
|
||||||
// NOOP
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// authUserChange allows to modify a user.
|
|
||||||
// Users can change their own password.
|
|
||||||
//
|
|
||||||
// Only admins can change passwords for other users
|
|
||||||
// and modify the admin flag.
|
|
||||||
//
|
|
||||||
// The admin flag of the current logged in user cannot be changed.
|
|
||||||
// THis ensures that their is always an admin.
|
|
||||||
//
|
|
||||||
// This is not exported for usage in Go.
|
|
||||||
// It is however exported for usage within SQL by the user.
|
|
||||||
//
|
|
||||||
// Returns:
|
|
||||||
// C.SQLITE_OK (0)
|
|
||||||
// C.SQLITE_ERROR (1)
|
|
||||||
// C.SQLITE_AUTH (23)
|
|
||||||
func (c *SQLiteConn) authUserChange(username, password string, admin int) int {
|
|
||||||
// NOOP
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// AuthUserDelete can be used (by an admin user only)
|
|
||||||
// to delete a user. The currently logged-in user cannot be deleted,
|
|
||||||
// which guarantees that there is always an admin user and hence that
|
|
||||||
// the database cannot be converted into a no-authentication-required
|
|
||||||
// database.
|
|
||||||
func (c *SQLiteConn) AuthUserDelete(username string) error {
|
|
||||||
// NOOP
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// authUserDelete can be used to delete a user.
|
|
||||||
//
|
|
||||||
// This function can only be executed by an admin.
|
|
||||||
//
|
|
||||||
// This is not exported for usage in Go.
|
|
||||||
// It is however exported for usage within SQL by the user.
|
|
||||||
//
|
|
||||||
// Returns:
|
|
||||||
// C.SQLITE_OK (0)
|
|
||||||
// C.SQLITE_ERROR (1)
|
|
||||||
// C.SQLITE_AUTH (23)
|
|
||||||
func (c *SQLiteConn) authUserDelete(username string) int {
|
|
||||||
// NOOP
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// AuthEnabled checks if the database is protected by user authentication
|
|
||||||
func (c *SQLiteConn) AuthEnabled() (exists bool) {
|
|
||||||
// NOOP
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// authEnabled perform the actual check for user authentication.
|
|
||||||
//
|
|
||||||
// This is not exported for usage in Go.
|
|
||||||
// It is however exported for usage within SQL by the user.
|
|
||||||
//
|
|
||||||
// Returns:
|
|
||||||
// 0 - Disabled
|
|
||||||
// 1 - Enabled
|
|
||||||
func (c *SQLiteConn) authEnabled() int {
|
|
||||||
// NOOP
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// EOF
|
|
15
vendor/github.com/mattn/go-sqlite3/sqlite3_opt_vacuum_full.go
generated
vendored
15
vendor/github.com/mattn/go-sqlite3/sqlite3_opt_vacuum_full.go
generated
vendored
|
@ -1,15 +0,0 @@
|
||||||
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
|
||||||
// Copyright (C) 2018 G.J.R. Timmer <gjr.timmer@gmail.com>.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build sqlite_vacuum_full
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo CFLAGS: -DSQLITE_DEFAULT_AUTOVACUUM=1
|
|
||||||
#cgo LDFLAGS: -lm
|
|
||||||
*/
|
|
||||||
import "C"
|
|
15
vendor/github.com/mattn/go-sqlite3/sqlite3_opt_vacuum_incr.go
generated
vendored
15
vendor/github.com/mattn/go-sqlite3/sqlite3_opt_vacuum_incr.go
generated
vendored
|
@ -1,15 +0,0 @@
|
||||||
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
|
||||||
// Copyright (C) 2018 G.J.R. Timmer <gjr.timmer@gmail.com>.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build sqlite_vacuum_incr
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo CFLAGS: -DSQLITE_DEFAULT_AUTOVACUUM=2
|
|
||||||
#cgo LDFLAGS: -lm
|
|
||||||
*/
|
|
||||||
import "C"
|
|
650
vendor/github.com/mattn/go-sqlite3/sqlite3_opt_vtable.go
generated
vendored
650
vendor/github.com/mattn/go-sqlite3/sqlite3_opt_vtable.go
generated
vendored
|
@ -1,650 +0,0 @@
|
||||||
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build sqlite_vtable vtable
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo CFLAGS: -std=gnu99
|
|
||||||
#cgo CFLAGS: -DSQLITE_ENABLE_RTREE
|
|
||||||
#cgo CFLAGS: -DSQLITE_THREADSAFE
|
|
||||||
#cgo CFLAGS: -DSQLITE_ENABLE_FTS3
|
|
||||||
#cgo CFLAGS: -DSQLITE_ENABLE_FTS3_PARENTHESIS
|
|
||||||
#cgo CFLAGS: -DSQLITE_ENABLE_FTS4_UNICODE61
|
|
||||||
#cgo CFLAGS: -DSQLITE_TRACE_SIZE_LIMIT=15
|
|
||||||
#cgo CFLAGS: -DSQLITE_ENABLE_COLUMN_METADATA=1
|
|
||||||
#cgo CFLAGS: -Wno-deprecated-declarations
|
|
||||||
|
|
||||||
#ifndef USE_LIBSQLITE3
|
|
||||||
#include <sqlite3-binding.h>
|
|
||||||
#else
|
|
||||||
#include <sqlite3.h>
|
|
||||||
#endif
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include <memory.h>
|
|
||||||
|
|
||||||
static inline char *_sqlite3_mprintf(char *zFormat, char *arg) {
|
|
||||||
return sqlite3_mprintf(zFormat, arg);
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef struct goVTab goVTab;
|
|
||||||
|
|
||||||
struct goVTab {
|
|
||||||
sqlite3_vtab base;
|
|
||||||
void *vTab;
|
|
||||||
};
|
|
||||||
|
|
||||||
uintptr_t goMInit(void *db, void *pAux, int argc, char **argv, char **pzErr, int isCreate);
|
|
||||||
|
|
||||||
static int cXInit(sqlite3 *db, void *pAux, int argc, const char *const*argv, sqlite3_vtab **ppVTab, char **pzErr, int isCreate) {
|
|
||||||
void *vTab = (void *)goMInit(db, pAux, argc, (char**)argv, pzErr, isCreate);
|
|
||||||
if (!vTab || *pzErr) {
|
|
||||||
return SQLITE_ERROR;
|
|
||||||
}
|
|
||||||
goVTab *pvTab = (goVTab *)sqlite3_malloc(sizeof(goVTab));
|
|
||||||
if (!pvTab) {
|
|
||||||
*pzErr = sqlite3_mprintf("%s", "Out of memory");
|
|
||||||
return SQLITE_NOMEM;
|
|
||||||
}
|
|
||||||
memset(pvTab, 0, sizeof(goVTab));
|
|
||||||
pvTab->vTab = vTab;
|
|
||||||
|
|
||||||
*ppVTab = (sqlite3_vtab *)pvTab;
|
|
||||||
*pzErr = 0;
|
|
||||||
return SQLITE_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int cXCreate(sqlite3 *db, void *pAux, int argc, const char *const*argv, sqlite3_vtab **ppVTab, char **pzErr) {
|
|
||||||
return cXInit(db, pAux, argc, argv, ppVTab, pzErr, 1);
|
|
||||||
}
|
|
||||||
static inline int cXConnect(sqlite3 *db, void *pAux, int argc, const char *const*argv, sqlite3_vtab **ppVTab, char **pzErr) {
|
|
||||||
return cXInit(db, pAux, argc, argv, ppVTab, pzErr, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
char* goVBestIndex(void *pVTab, void *icp);
|
|
||||||
|
|
||||||
static inline int cXBestIndex(sqlite3_vtab *pVTab, sqlite3_index_info *info) {
|
|
||||||
char *pzErr = goVBestIndex(((goVTab*)pVTab)->vTab, info);
|
|
||||||
if (pzErr) {
|
|
||||||
if (pVTab->zErrMsg)
|
|
||||||
sqlite3_free(pVTab->zErrMsg);
|
|
||||||
pVTab->zErrMsg = pzErr;
|
|
||||||
return SQLITE_ERROR;
|
|
||||||
}
|
|
||||||
return SQLITE_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
char* goVRelease(void *pVTab, int isDestroy);
|
|
||||||
|
|
||||||
static int cXRelease(sqlite3_vtab *pVTab, int isDestroy) {
|
|
||||||
char *pzErr = goVRelease(((goVTab*)pVTab)->vTab, isDestroy);
|
|
||||||
if (pzErr) {
|
|
||||||
if (pVTab->zErrMsg)
|
|
||||||
sqlite3_free(pVTab->zErrMsg);
|
|
||||||
pVTab->zErrMsg = pzErr;
|
|
||||||
return SQLITE_ERROR;
|
|
||||||
}
|
|
||||||
if (pVTab->zErrMsg)
|
|
||||||
sqlite3_free(pVTab->zErrMsg);
|
|
||||||
sqlite3_free(pVTab);
|
|
||||||
return SQLITE_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline int cXDisconnect(sqlite3_vtab *pVTab) {
|
|
||||||
return cXRelease(pVTab, 0);
|
|
||||||
}
|
|
||||||
static inline int cXDestroy(sqlite3_vtab *pVTab) {
|
|
||||||
return cXRelease(pVTab, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef struct goVTabCursor goVTabCursor;
|
|
||||||
|
|
||||||
struct goVTabCursor {
|
|
||||||
sqlite3_vtab_cursor base;
|
|
||||||
void *vTabCursor;
|
|
||||||
};
|
|
||||||
|
|
||||||
uintptr_t goVOpen(void *pVTab, char **pzErr);
|
|
||||||
|
|
||||||
static int cXOpen(sqlite3_vtab *pVTab, sqlite3_vtab_cursor **ppCursor) {
|
|
||||||
void *vTabCursor = (void *)goVOpen(((goVTab*)pVTab)->vTab, &(pVTab->zErrMsg));
|
|
||||||
goVTabCursor *pCursor = (goVTabCursor *)sqlite3_malloc(sizeof(goVTabCursor));
|
|
||||||
if (!pCursor) {
|
|
||||||
return SQLITE_NOMEM;
|
|
||||||
}
|
|
||||||
memset(pCursor, 0, sizeof(goVTabCursor));
|
|
||||||
pCursor->vTabCursor = vTabCursor;
|
|
||||||
*ppCursor = (sqlite3_vtab_cursor *)pCursor;
|
|
||||||
return SQLITE_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int setErrMsg(sqlite3_vtab_cursor *pCursor, char *pzErr) {
|
|
||||||
if (pCursor->pVtab->zErrMsg)
|
|
||||||
sqlite3_free(pCursor->pVtab->zErrMsg);
|
|
||||||
pCursor->pVtab->zErrMsg = pzErr;
|
|
||||||
return SQLITE_ERROR;
|
|
||||||
}
|
|
||||||
|
|
||||||
char* goVClose(void *pCursor);
|
|
||||||
|
|
||||||
static int cXClose(sqlite3_vtab_cursor *pCursor) {
|
|
||||||
char *pzErr = goVClose(((goVTabCursor*)pCursor)->vTabCursor);
|
|
||||||
if (pzErr) {
|
|
||||||
return setErrMsg(pCursor, pzErr);
|
|
||||||
}
|
|
||||||
sqlite3_free(pCursor);
|
|
||||||
return SQLITE_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
char* goVFilter(void *pCursor, int idxNum, char* idxName, int argc, sqlite3_value **argv);
|
|
||||||
|
|
||||||
static int cXFilter(sqlite3_vtab_cursor *pCursor, int idxNum, const char *idxStr, int argc, sqlite3_value **argv) {
|
|
||||||
char *pzErr = goVFilter(((goVTabCursor*)pCursor)->vTabCursor, idxNum, (char*)idxStr, argc, argv);
|
|
||||||
if (pzErr) {
|
|
||||||
return setErrMsg(pCursor, pzErr);
|
|
||||||
}
|
|
||||||
return SQLITE_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
char* goVNext(void *pCursor);
|
|
||||||
|
|
||||||
static int cXNext(sqlite3_vtab_cursor *pCursor) {
|
|
||||||
char *pzErr = goVNext(((goVTabCursor*)pCursor)->vTabCursor);
|
|
||||||
if (pzErr) {
|
|
||||||
return setErrMsg(pCursor, pzErr);
|
|
||||||
}
|
|
||||||
return SQLITE_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
int goVEof(void *pCursor);
|
|
||||||
|
|
||||||
static inline int cXEof(sqlite3_vtab_cursor *pCursor) {
|
|
||||||
return goVEof(((goVTabCursor*)pCursor)->vTabCursor);
|
|
||||||
}
|
|
||||||
|
|
||||||
char* goVColumn(void *pCursor, void *cp, int col);
|
|
||||||
|
|
||||||
static int cXColumn(sqlite3_vtab_cursor *pCursor, sqlite3_context *ctx, int i) {
|
|
||||||
char *pzErr = goVColumn(((goVTabCursor*)pCursor)->vTabCursor, ctx, i);
|
|
||||||
if (pzErr) {
|
|
||||||
return setErrMsg(pCursor, pzErr);
|
|
||||||
}
|
|
||||||
return SQLITE_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
char* goVRowid(void *pCursor, sqlite3_int64 *pRowid);
|
|
||||||
|
|
||||||
static int cXRowid(sqlite3_vtab_cursor *pCursor, sqlite3_int64 *pRowid) {
|
|
||||||
char *pzErr = goVRowid(((goVTabCursor*)pCursor)->vTabCursor, pRowid);
|
|
||||||
if (pzErr) {
|
|
||||||
return setErrMsg(pCursor, pzErr);
|
|
||||||
}
|
|
||||||
return SQLITE_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
char* goVUpdate(void *pVTab, int argc, sqlite3_value **argv, sqlite3_int64 *pRowid);
|
|
||||||
|
|
||||||
static int cXUpdate(sqlite3_vtab *pVTab, int argc, sqlite3_value **argv, sqlite3_int64 *pRowid) {
|
|
||||||
char *pzErr = goVUpdate(((goVTab*)pVTab)->vTab, argc, argv, pRowid);
|
|
||||||
if (pzErr) {
|
|
||||||
if (pVTab->zErrMsg)
|
|
||||||
sqlite3_free(pVTab->zErrMsg);
|
|
||||||
pVTab->zErrMsg = pzErr;
|
|
||||||
return SQLITE_ERROR;
|
|
||||||
}
|
|
||||||
return SQLITE_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
static sqlite3_module goModule = {
|
|
||||||
0, // iVersion
|
|
||||||
cXCreate, // xCreate - create a table
|
|
||||||
cXConnect, // xConnect - connect to an existing table
|
|
||||||
cXBestIndex, // xBestIndex - Determine search strategy
|
|
||||||
cXDisconnect, // xDisconnect - Disconnect from a table
|
|
||||||
cXDestroy, // xDestroy - Drop a table
|
|
||||||
cXOpen, // xOpen - open a cursor
|
|
||||||
cXClose, // xClose - close a cursor
|
|
||||||
cXFilter, // xFilter - configure scan constraints
|
|
||||||
cXNext, // xNext - advance a cursor
|
|
||||||
cXEof, // xEof
|
|
||||||
cXColumn, // xColumn - read data
|
|
||||||
cXRowid, // xRowid - read data
|
|
||||||
cXUpdate, // xUpdate - write data
|
|
||||||
// Not implemented
|
|
||||||
0, // xBegin - begin transaction
|
|
||||||
0, // xSync - sync transaction
|
|
||||||
0, // xCommit - commit transaction
|
|
||||||
0, // xRollback - rollback transaction
|
|
||||||
0, // xFindFunction - function overloading
|
|
||||||
0, // xRename - rename the table
|
|
||||||
0, // xSavepoint
|
|
||||||
0, // xRelease
|
|
||||||
0 // xRollbackTo
|
|
||||||
};
|
|
||||||
|
|
||||||
void goMDestroy(void*);
|
|
||||||
|
|
||||||
static int _sqlite3_create_module(sqlite3 *db, const char *zName, uintptr_t pClientData) {
|
|
||||||
return sqlite3_create_module_v2(db, zName, &goModule, (void*) pClientData, goMDestroy);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math"
|
|
||||||
"reflect"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
type sqliteModule struct {
|
|
||||||
c *SQLiteConn
|
|
||||||
name string
|
|
||||||
module Module
|
|
||||||
}
|
|
||||||
|
|
||||||
type sqliteVTab struct {
|
|
||||||
module *sqliteModule
|
|
||||||
vTab VTab
|
|
||||||
}
|
|
||||||
|
|
||||||
type sqliteVTabCursor struct {
|
|
||||||
vTab *sqliteVTab
|
|
||||||
vTabCursor VTabCursor
|
|
||||||
}
|
|
||||||
|
|
||||||
// Op is type of operations.
|
|
||||||
type Op uint8
|
|
||||||
|
|
||||||
// Op mean identity of operations.
|
|
||||||
const (
|
|
||||||
OpEQ Op = 2
|
|
||||||
OpGT = 4
|
|
||||||
OpLE = 8
|
|
||||||
OpLT = 16
|
|
||||||
OpGE = 32
|
|
||||||
OpMATCH = 64
|
|
||||||
OpLIKE = 65 /* 3.10.0 and later only */
|
|
||||||
OpGLOB = 66 /* 3.10.0 and later only */
|
|
||||||
OpREGEXP = 67 /* 3.10.0 and later only */
|
|
||||||
OpScanUnique = 1 /* Scan visits at most 1 row */
|
|
||||||
)
|
|
||||||
|
|
||||||
// InfoConstraint give information of constraint.
|
|
||||||
type InfoConstraint struct {
|
|
||||||
Column int
|
|
||||||
Op Op
|
|
||||||
Usable bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// InfoOrderBy give information of order-by.
|
|
||||||
type InfoOrderBy struct {
|
|
||||||
Column int
|
|
||||||
Desc bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func constraints(info *C.sqlite3_index_info) []InfoConstraint {
|
|
||||||
l := info.nConstraint
|
|
||||||
slice := (*[1 << 30]C.struct_sqlite3_index_constraint)(unsafe.Pointer(info.aConstraint))[:l:l]
|
|
||||||
|
|
||||||
cst := make([]InfoConstraint, 0, l)
|
|
||||||
for _, c := range slice {
|
|
||||||
var usable bool
|
|
||||||
if c.usable > 0 {
|
|
||||||
usable = true
|
|
||||||
}
|
|
||||||
cst = append(cst, InfoConstraint{
|
|
||||||
Column: int(c.iColumn),
|
|
||||||
Op: Op(c.op),
|
|
||||||
Usable: usable,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return cst
|
|
||||||
}
|
|
||||||
|
|
||||||
func orderBys(info *C.sqlite3_index_info) []InfoOrderBy {
|
|
||||||
l := info.nOrderBy
|
|
||||||
slice := (*[1 << 30]C.struct_sqlite3_index_orderby)(unsafe.Pointer(info.aOrderBy))[:l:l]
|
|
||||||
|
|
||||||
ob := make([]InfoOrderBy, 0, l)
|
|
||||||
for _, c := range slice {
|
|
||||||
var desc bool
|
|
||||||
if c.desc > 0 {
|
|
||||||
desc = true
|
|
||||||
}
|
|
||||||
ob = append(ob, InfoOrderBy{
|
|
||||||
Column: int(c.iColumn),
|
|
||||||
Desc: desc,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return ob
|
|
||||||
}
|
|
||||||
|
|
||||||
// IndexResult is a Go struct representation of what eventually ends up in the
|
|
||||||
// output fields for `sqlite3_index_info`
|
|
||||||
// See: https://www.sqlite.org/c3ref/index_info.html
|
|
||||||
type IndexResult struct {
|
|
||||||
Used []bool // aConstraintUsage
|
|
||||||
IdxNum int
|
|
||||||
IdxStr string
|
|
||||||
AlreadyOrdered bool // orderByConsumed
|
|
||||||
EstimatedCost float64
|
|
||||||
EstimatedRows float64
|
|
||||||
}
|
|
||||||
|
|
||||||
// mPrintf is a utility wrapper around sqlite3_mprintf
|
|
||||||
func mPrintf(format, arg string) *C.char {
|
|
||||||
cf := C.CString(format)
|
|
||||||
defer C.free(unsafe.Pointer(cf))
|
|
||||||
ca := C.CString(arg)
|
|
||||||
defer C.free(unsafe.Pointer(ca))
|
|
||||||
return C._sqlite3_mprintf(cf, ca)
|
|
||||||
}
|
|
||||||
|
|
||||||
//export goMInit
|
|
||||||
func goMInit(db, pClientData unsafe.Pointer, argc C.int, argv **C.char, pzErr **C.char, isCreate C.int) C.uintptr_t {
|
|
||||||
m := lookupHandle(uintptr(pClientData)).(*sqliteModule)
|
|
||||||
if m.c.db != (*C.sqlite3)(db) {
|
|
||||||
*pzErr = mPrintf("%s", "Inconsistent db handles")
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
args := make([]string, argc)
|
|
||||||
var A []*C.char
|
|
||||||
slice := reflect.SliceHeader{Data: uintptr(unsafe.Pointer(argv)), Len: int(argc), Cap: int(argc)}
|
|
||||||
a := reflect.NewAt(reflect.TypeOf(A), unsafe.Pointer(&slice)).Elem().Interface()
|
|
||||||
for i, s := range a.([]*C.char) {
|
|
||||||
args[i] = C.GoString(s)
|
|
||||||
}
|
|
||||||
var vTab VTab
|
|
||||||
var err error
|
|
||||||
if isCreate == 1 {
|
|
||||||
vTab, err = m.module.Create(m.c, args)
|
|
||||||
} else {
|
|
||||||
vTab, err = m.module.Connect(m.c, args)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
*pzErr = mPrintf("%s", err.Error())
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
vt := sqliteVTab{m, vTab}
|
|
||||||
*pzErr = nil
|
|
||||||
return C.uintptr_t(newHandle(m.c, &vt))
|
|
||||||
}
|
|
||||||
|
|
||||||
//export goVRelease
|
|
||||||
func goVRelease(pVTab unsafe.Pointer, isDestroy C.int) *C.char {
|
|
||||||
vt := lookupHandle(uintptr(pVTab)).(*sqliteVTab)
|
|
||||||
var err error
|
|
||||||
if isDestroy == 1 {
|
|
||||||
err = vt.vTab.Destroy()
|
|
||||||
} else {
|
|
||||||
err = vt.vTab.Disconnect()
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return mPrintf("%s", err.Error())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//export goVOpen
|
|
||||||
func goVOpen(pVTab unsafe.Pointer, pzErr **C.char) C.uintptr_t {
|
|
||||||
vt := lookupHandle(uintptr(pVTab)).(*sqliteVTab)
|
|
||||||
vTabCursor, err := vt.vTab.Open()
|
|
||||||
if err != nil {
|
|
||||||
*pzErr = mPrintf("%s", err.Error())
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
vtc := sqliteVTabCursor{vt, vTabCursor}
|
|
||||||
*pzErr = nil
|
|
||||||
return C.uintptr_t(newHandle(vt.module.c, &vtc))
|
|
||||||
}
|
|
||||||
|
|
||||||
//export goVBestIndex
|
|
||||||
func goVBestIndex(pVTab unsafe.Pointer, icp unsafe.Pointer) *C.char {
|
|
||||||
vt := lookupHandle(uintptr(pVTab)).(*sqliteVTab)
|
|
||||||
info := (*C.sqlite3_index_info)(icp)
|
|
||||||
csts := constraints(info)
|
|
||||||
res, err := vt.vTab.BestIndex(csts, orderBys(info))
|
|
||||||
if err != nil {
|
|
||||||
return mPrintf("%s", err.Error())
|
|
||||||
}
|
|
||||||
if len(res.Used) != len(csts) {
|
|
||||||
return mPrintf("Result.Used != expected value", "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get a pointer to constraint_usage struct so we can update in place.
|
|
||||||
l := info.nConstraint
|
|
||||||
s := (*[1 << 30]C.struct_sqlite3_index_constraint_usage)(unsafe.Pointer(info.aConstraintUsage))[:l:l]
|
|
||||||
index := 1
|
|
||||||
for i := C.int(0); i < info.nConstraint; i++ {
|
|
||||||
if res.Used[i] {
|
|
||||||
s[i].argvIndex = C.int(index)
|
|
||||||
s[i].omit = C.uchar(1)
|
|
||||||
index++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
info.idxNum = C.int(res.IdxNum)
|
|
||||||
idxStr := C.CString(res.IdxStr)
|
|
||||||
defer C.free(unsafe.Pointer(idxStr))
|
|
||||||
info.idxStr = idxStr
|
|
||||||
info.needToFreeIdxStr = C.int(0)
|
|
||||||
if res.AlreadyOrdered {
|
|
||||||
info.orderByConsumed = C.int(1)
|
|
||||||
}
|
|
||||||
info.estimatedCost = C.double(res.EstimatedCost)
|
|
||||||
info.estimatedRows = C.sqlite3_int64(res.EstimatedRows)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//export goVClose
|
|
||||||
func goVClose(pCursor unsafe.Pointer) *C.char {
|
|
||||||
vtc := lookupHandle(uintptr(pCursor)).(*sqliteVTabCursor)
|
|
||||||
err := vtc.vTabCursor.Close()
|
|
||||||
if err != nil {
|
|
||||||
return mPrintf("%s", err.Error())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//export goMDestroy
|
|
||||||
func goMDestroy(pClientData unsafe.Pointer) {
|
|
||||||
m := lookupHandle(uintptr(pClientData)).(*sqliteModule)
|
|
||||||
m.module.DestroyModule()
|
|
||||||
}
|
|
||||||
|
|
||||||
//export goVFilter
|
|
||||||
func goVFilter(pCursor unsafe.Pointer, idxNum C.int, idxName *C.char, argc C.int, argv **C.sqlite3_value) *C.char {
|
|
||||||
vtc := lookupHandle(uintptr(pCursor)).(*sqliteVTabCursor)
|
|
||||||
args := (*[(math.MaxInt32 - 1) / unsafe.Sizeof((*C.sqlite3_value)(nil))]*C.sqlite3_value)(unsafe.Pointer(argv))[:argc:argc]
|
|
||||||
vals := make([]interface{}, 0, argc)
|
|
||||||
for _, v := range args {
|
|
||||||
conv, err := callbackArgGeneric(v)
|
|
||||||
if err != nil {
|
|
||||||
return mPrintf("%s", err.Error())
|
|
||||||
}
|
|
||||||
vals = append(vals, conv.Interface())
|
|
||||||
}
|
|
||||||
err := vtc.vTabCursor.Filter(int(idxNum), C.GoString(idxName), vals)
|
|
||||||
if err != nil {
|
|
||||||
return mPrintf("%s", err.Error())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//export goVNext
|
|
||||||
func goVNext(pCursor unsafe.Pointer) *C.char {
|
|
||||||
vtc := lookupHandle(uintptr(pCursor)).(*sqliteVTabCursor)
|
|
||||||
err := vtc.vTabCursor.Next()
|
|
||||||
if err != nil {
|
|
||||||
return mPrintf("%s", err.Error())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//export goVEof
|
|
||||||
func goVEof(pCursor unsafe.Pointer) C.int {
|
|
||||||
vtc := lookupHandle(uintptr(pCursor)).(*sqliteVTabCursor)
|
|
||||||
err := vtc.vTabCursor.EOF()
|
|
||||||
if err {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
//export goVColumn
|
|
||||||
func goVColumn(pCursor, cp unsafe.Pointer, col C.int) *C.char {
|
|
||||||
vtc := lookupHandle(uintptr(pCursor)).(*sqliteVTabCursor)
|
|
||||||
c := (*SQLiteContext)(cp)
|
|
||||||
err := vtc.vTabCursor.Column(c, int(col))
|
|
||||||
if err != nil {
|
|
||||||
return mPrintf("%s", err.Error())
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//export goVRowid
|
|
||||||
func goVRowid(pCursor unsafe.Pointer, pRowid *C.sqlite3_int64) *C.char {
|
|
||||||
vtc := lookupHandle(uintptr(pCursor)).(*sqliteVTabCursor)
|
|
||||||
rowid, err := vtc.vTabCursor.Rowid()
|
|
||||||
if err != nil {
|
|
||||||
return mPrintf("%s", err.Error())
|
|
||||||
}
|
|
||||||
*pRowid = C.sqlite3_int64(rowid)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//export goVUpdate
|
|
||||||
func goVUpdate(pVTab unsafe.Pointer, argc C.int, argv **C.sqlite3_value, pRowid *C.sqlite3_int64) *C.char {
|
|
||||||
vt := lookupHandle(uintptr(pVTab)).(*sqliteVTab)
|
|
||||||
|
|
||||||
var tname string
|
|
||||||
if n, ok := vt.vTab.(interface {
|
|
||||||
TableName() string
|
|
||||||
}); ok {
|
|
||||||
tname = n.TableName() + " "
|
|
||||||
}
|
|
||||||
|
|
||||||
err := fmt.Errorf("virtual %s table %sis read-only", vt.module.name, tname)
|
|
||||||
if v, ok := vt.vTab.(VTabUpdater); ok {
|
|
||||||
// convert argv
|
|
||||||
args := (*[(math.MaxInt32 - 1) / unsafe.Sizeof((*C.sqlite3_value)(nil))]*C.sqlite3_value)(unsafe.Pointer(argv))[:argc:argc]
|
|
||||||
vals := make([]interface{}, 0, argc)
|
|
||||||
for _, v := range args {
|
|
||||||
conv, err := callbackArgGeneric(v)
|
|
||||||
if err != nil {
|
|
||||||
return mPrintf("%s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// work around for SQLITE_NULL
|
|
||||||
x := conv.Interface()
|
|
||||||
if z, ok := x.([]byte); ok && z == nil {
|
|
||||||
x = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
vals = append(vals, x)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case argc == 1:
|
|
||||||
err = v.Delete(vals[0])
|
|
||||||
|
|
||||||
case argc > 1 && vals[0] == nil:
|
|
||||||
var id int64
|
|
||||||
id, err = v.Insert(vals[1], vals[2:])
|
|
||||||
if err == nil {
|
|
||||||
*pRowid = C.sqlite3_int64(id)
|
|
||||||
}
|
|
||||||
|
|
||||||
case argc > 1:
|
|
||||||
err = v.Update(vals[1], vals[2:])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return mPrintf("%s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Module is a "virtual table module", it defines the implementation of a
|
|
||||||
// virtual tables. See: http://sqlite.org/c3ref/module.html
|
|
||||||
type Module interface {
|
|
||||||
// http://sqlite.org/vtab.html#xcreate
|
|
||||||
Create(c *SQLiteConn, args []string) (VTab, error)
|
|
||||||
// http://sqlite.org/vtab.html#xconnect
|
|
||||||
Connect(c *SQLiteConn, args []string) (VTab, error)
|
|
||||||
// http://sqlite.org/c3ref/create_module.html
|
|
||||||
DestroyModule()
|
|
||||||
}
|
|
||||||
|
|
||||||
// VTab describes a particular instance of the virtual table.
|
|
||||||
// See: http://sqlite.org/c3ref/vtab.html
|
|
||||||
type VTab interface {
|
|
||||||
// http://sqlite.org/vtab.html#xbestindex
|
|
||||||
BestIndex([]InfoConstraint, []InfoOrderBy) (*IndexResult, error)
|
|
||||||
// http://sqlite.org/vtab.html#xdisconnect
|
|
||||||
Disconnect() error
|
|
||||||
// http://sqlite.org/vtab.html#sqlite3_module.xDestroy
|
|
||||||
Destroy() error
|
|
||||||
// http://sqlite.org/vtab.html#xopen
|
|
||||||
Open() (VTabCursor, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// VTabUpdater is a type that allows a VTab to be inserted, updated, or
|
|
||||||
// deleted.
|
|
||||||
// See: https://sqlite.org/vtab.html#xupdate
|
|
||||||
type VTabUpdater interface {
|
|
||||||
Delete(interface{}) error
|
|
||||||
Insert(interface{}, []interface{}) (int64, error)
|
|
||||||
Update(interface{}, []interface{}) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// VTabCursor describes cursors that point into the virtual table and are used
|
|
||||||
// to loop through the virtual table. See: http://sqlite.org/c3ref/vtab_cursor.html
|
|
||||||
type VTabCursor interface {
|
|
||||||
// http://sqlite.org/vtab.html#xclose
|
|
||||||
Close() error
|
|
||||||
// http://sqlite.org/vtab.html#xfilter
|
|
||||||
Filter(idxNum int, idxStr string, vals []interface{}) error
|
|
||||||
// http://sqlite.org/vtab.html#xnext
|
|
||||||
Next() error
|
|
||||||
// http://sqlite.org/vtab.html#xeof
|
|
||||||
EOF() bool
|
|
||||||
// http://sqlite.org/vtab.html#xcolumn
|
|
||||||
Column(c *SQLiteContext, col int) error
|
|
||||||
// http://sqlite.org/vtab.html#xrowid
|
|
||||||
Rowid() (int64, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeclareVTab declares the Schema of a virtual table.
|
|
||||||
// See: http://sqlite.org/c3ref/declare_vtab.html
|
|
||||||
func (c *SQLiteConn) DeclareVTab(sql string) error {
|
|
||||||
zSQL := C.CString(sql)
|
|
||||||
defer C.free(unsafe.Pointer(zSQL))
|
|
||||||
rv := C.sqlite3_declare_vtab(c.db, zSQL)
|
|
||||||
if rv != C.SQLITE_OK {
|
|
||||||
return c.lastError()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateModule registers a virtual table implementation.
|
|
||||||
// See: http://sqlite.org/c3ref/create_module.html
|
|
||||||
func (c *SQLiteConn) CreateModule(moduleName string, module Module) error {
|
|
||||||
mname := C.CString(moduleName)
|
|
||||||
defer C.free(unsafe.Pointer(mname))
|
|
||||||
udm := sqliteModule{c, moduleName, module}
|
|
||||||
rv := C._sqlite3_create_module(c.db, mname, C.uintptr_t(newHandle(c, &udm)))
|
|
||||||
if rv != C.SQLITE_OK {
|
|
||||||
return c.lastError()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
14
vendor/github.com/mattn/go-sqlite3/sqlite3_other.go
generated
vendored
14
vendor/github.com/mattn/go-sqlite3/sqlite3_other.go
generated
vendored
|
@ -1,14 +0,0 @@
|
||||||
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build !windows
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo CFLAGS: -I.
|
|
||||||
#cgo linux LDFLAGS: -ldl
|
|
||||||
*/
|
|
||||||
import "C"
|
|
14
vendor/github.com/mattn/go-sqlite3/sqlite3_solaris.go
generated
vendored
14
vendor/github.com/mattn/go-sqlite3/sqlite3_solaris.go
generated
vendored
|
@ -1,14 +0,0 @@
|
||||||
// Copyright (C) 2018 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build solaris
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo CFLAGS: -D__EXTENSIONS__=1
|
|
||||||
#cgo LDFLAGS: -lc
|
|
||||||
*/
|
|
||||||
import "C"
|
|
288
vendor/github.com/mattn/go-sqlite3/sqlite3_trace.go
generated
vendored
288
vendor/github.com/mattn/go-sqlite3/sqlite3_trace.go
generated
vendored
|
@ -1,288 +0,0 @@
|
||||||
// Copyright (C) 2016 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build sqlite_trace trace
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
/*
|
|
||||||
#ifndef USE_LIBSQLITE3
|
|
||||||
#include <sqlite3-binding.h>
|
|
||||||
#else
|
|
||||||
#include <sqlite3.h>
|
|
||||||
#endif
|
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
int traceCallbackTrampoline(unsigned int traceEventCode, void *ctx, void *p, void *x);
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Trace... constants identify the possible events causing callback invocation.
|
|
||||||
// Values are same as the corresponding SQLite Trace Event Codes.
|
|
||||||
const (
|
|
||||||
TraceStmt = uint32(C.SQLITE_TRACE_STMT)
|
|
||||||
TraceProfile = uint32(C.SQLITE_TRACE_PROFILE)
|
|
||||||
TraceRow = uint32(C.SQLITE_TRACE_ROW)
|
|
||||||
TraceClose = uint32(C.SQLITE_TRACE_CLOSE)
|
|
||||||
)
|
|
||||||
|
|
||||||
type TraceInfo struct {
|
|
||||||
// Pack together the shorter fields, to keep the struct smaller.
|
|
||||||
// On a 64-bit machine there would be padding
|
|
||||||
// between EventCode and ConnHandle; having AutoCommit here is "free":
|
|
||||||
EventCode uint32
|
|
||||||
AutoCommit bool
|
|
||||||
ConnHandle uintptr
|
|
||||||
|
|
||||||
// Usually filled, unless EventCode = TraceClose = SQLITE_TRACE_CLOSE:
|
|
||||||
// identifier for a prepared statement:
|
|
||||||
StmtHandle uintptr
|
|
||||||
|
|
||||||
// Two strings filled when EventCode = TraceStmt = SQLITE_TRACE_STMT:
|
|
||||||
// (1) either the unexpanded SQL text of the prepared statement, or
|
|
||||||
// an SQL comment that indicates the invocation of a trigger;
|
|
||||||
// (2) expanded SQL, if requested and if (1) is not an SQL comment.
|
|
||||||
StmtOrTrigger string
|
|
||||||
ExpandedSQL string // only if requested (TraceConfig.WantExpandedSQL = true)
|
|
||||||
|
|
||||||
// filled when EventCode = TraceProfile = SQLITE_TRACE_PROFILE:
|
|
||||||
// estimated number of nanoseconds that the prepared statement took to run:
|
|
||||||
RunTimeNanosec int64
|
|
||||||
|
|
||||||
DBError Error
|
|
||||||
}
|
|
||||||
|
|
||||||
// TraceUserCallback gives the signature for a trace function
|
|
||||||
// provided by the user (Go application programmer).
|
|
||||||
// SQLite 3.14 documentation (as of September 2, 2016)
|
|
||||||
// for SQL Trace Hook = sqlite3_trace_v2():
|
|
||||||
// The integer return value from the callback is currently ignored,
|
|
||||||
// though this may change in future releases. Callback implementations
|
|
||||||
// should return zero to ensure future compatibility.
|
|
||||||
type TraceUserCallback func(TraceInfo) int
|
|
||||||
|
|
||||||
type TraceConfig struct {
|
|
||||||
Callback TraceUserCallback
|
|
||||||
EventMask uint32
|
|
||||||
WantExpandedSQL bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func fillDBError(dbErr *Error, db *C.sqlite3) {
|
|
||||||
// See SQLiteConn.lastError(), in file 'sqlite3.go' at the time of writing (Sept 5, 2016)
|
|
||||||
dbErr.Code = ErrNo(C.sqlite3_errcode(db))
|
|
||||||
dbErr.ExtendedCode = ErrNoExtended(C.sqlite3_extended_errcode(db))
|
|
||||||
dbErr.err = C.GoString(C.sqlite3_errmsg(db))
|
|
||||||
}
|
|
||||||
|
|
||||||
func fillExpandedSQL(info *TraceInfo, db *C.sqlite3, pStmt unsafe.Pointer) {
|
|
||||||
if pStmt == nil {
|
|
||||||
panic("No SQLite statement pointer in P arg of trace_v2 callback")
|
|
||||||
}
|
|
||||||
|
|
||||||
expSQLiteCStr := C.sqlite3_expanded_sql((*C.sqlite3_stmt)(pStmt))
|
|
||||||
if expSQLiteCStr == nil {
|
|
||||||
fillDBError(&info.DBError, db)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
info.ExpandedSQL = C.GoString(expSQLiteCStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
//export traceCallbackTrampoline
|
|
||||||
func traceCallbackTrampoline(
|
|
||||||
traceEventCode C.uint,
|
|
||||||
// Parameter named 'C' in SQLite docs = Context given at registration:
|
|
||||||
ctx unsafe.Pointer,
|
|
||||||
// Parameter named 'P' in SQLite docs (Primary event data?):
|
|
||||||
p unsafe.Pointer,
|
|
||||||
// Parameter named 'X' in SQLite docs (eXtra event data?):
|
|
||||||
xValue unsafe.Pointer) C.int {
|
|
||||||
|
|
||||||
eventCode := uint32(traceEventCode)
|
|
||||||
|
|
||||||
if ctx == nil {
|
|
||||||
panic(fmt.Sprintf("No context (ev 0x%x)", traceEventCode))
|
|
||||||
}
|
|
||||||
|
|
||||||
contextDB := (*C.sqlite3)(ctx)
|
|
||||||
connHandle := uintptr(ctx)
|
|
||||||
|
|
||||||
var traceConf TraceConfig
|
|
||||||
var found bool
|
|
||||||
if eventCode == TraceClose {
|
|
||||||
// clean up traceMap: 'pop' means get and delete
|
|
||||||
traceConf, found = popTraceMapping(connHandle)
|
|
||||||
} else {
|
|
||||||
traceConf, found = lookupTraceMapping(connHandle)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !found {
|
|
||||||
panic(fmt.Sprintf("Mapping not found for handle 0x%x (ev 0x%x)",
|
|
||||||
connHandle, eventCode))
|
|
||||||
}
|
|
||||||
|
|
||||||
var info TraceInfo
|
|
||||||
|
|
||||||
info.EventCode = eventCode
|
|
||||||
info.AutoCommit = (int(C.sqlite3_get_autocommit(contextDB)) != 0)
|
|
||||||
info.ConnHandle = connHandle
|
|
||||||
|
|
||||||
switch eventCode {
|
|
||||||
case TraceStmt:
|
|
||||||
info.StmtHandle = uintptr(p)
|
|
||||||
|
|
||||||
var xStr string
|
|
||||||
if xValue != nil {
|
|
||||||
xStr = C.GoString((*C.char)(xValue))
|
|
||||||
}
|
|
||||||
info.StmtOrTrigger = xStr
|
|
||||||
if !strings.HasPrefix(xStr, "--") {
|
|
||||||
// Not SQL comment, therefore the current event
|
|
||||||
// is not related to a trigger.
|
|
||||||
// The user might want to receive the expanded SQL;
|
|
||||||
// let's check:
|
|
||||||
if traceConf.WantExpandedSQL {
|
|
||||||
fillExpandedSQL(&info, contextDB, p)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
case TraceProfile:
|
|
||||||
info.StmtHandle = uintptr(p)
|
|
||||||
|
|
||||||
if xValue == nil {
|
|
||||||
panic("NULL pointer in X arg of trace_v2 callback for SQLITE_TRACE_PROFILE event")
|
|
||||||
}
|
|
||||||
|
|
||||||
info.RunTimeNanosec = *(*int64)(xValue)
|
|
||||||
|
|
||||||
// sample the error //TODO: is it safe? is it useful?
|
|
||||||
fillDBError(&info.DBError, contextDB)
|
|
||||||
|
|
||||||
case TraceRow:
|
|
||||||
info.StmtHandle = uintptr(p)
|
|
||||||
|
|
||||||
case TraceClose:
|
|
||||||
handle := uintptr(p)
|
|
||||||
if handle != info.ConnHandle {
|
|
||||||
panic(fmt.Sprintf("Different conn handle 0x%x (expected 0x%x) in SQLITE_TRACE_CLOSE event.",
|
|
||||||
handle, info.ConnHandle))
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Pass unsupported events to the user callback (if configured);
|
|
||||||
// let the user callback decide whether to panic or ignore them.
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do not execute user callback when the event was not requested by user!
|
|
||||||
// Remember that the Close event is always selected when
|
|
||||||
// registering this callback trampoline with SQLite --- for cleanup.
|
|
||||||
// In the future there may be more events forced to "selected" in SQLite
|
|
||||||
// for the driver's needs.
|
|
||||||
if traceConf.EventMask&eventCode == 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
r := 0
|
|
||||||
if traceConf.Callback != nil {
|
|
||||||
r = traceConf.Callback(info)
|
|
||||||
}
|
|
||||||
return C.int(r)
|
|
||||||
}
|
|
||||||
|
|
||||||
type traceMapEntry struct {
|
|
||||||
config TraceConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
var traceMapLock sync.Mutex
|
|
||||||
var traceMap = make(map[uintptr]traceMapEntry)
|
|
||||||
|
|
||||||
func addTraceMapping(connHandle uintptr, traceConf TraceConfig) {
|
|
||||||
traceMapLock.Lock()
|
|
||||||
defer traceMapLock.Unlock()
|
|
||||||
|
|
||||||
oldEntryCopy, found := traceMap[connHandle]
|
|
||||||
if found {
|
|
||||||
panic(fmt.Sprintf("Adding trace config %v: handle 0x%x already registered (%v).",
|
|
||||||
traceConf, connHandle, oldEntryCopy.config))
|
|
||||||
}
|
|
||||||
traceMap[connHandle] = traceMapEntry{config: traceConf}
|
|
||||||
fmt.Printf("Added trace config %v: handle 0x%x.\n", traceConf, connHandle)
|
|
||||||
}
|
|
||||||
|
|
||||||
func lookupTraceMapping(connHandle uintptr) (TraceConfig, bool) {
|
|
||||||
traceMapLock.Lock()
|
|
||||||
defer traceMapLock.Unlock()
|
|
||||||
|
|
||||||
entryCopy, found := traceMap[connHandle]
|
|
||||||
return entryCopy.config, found
|
|
||||||
}
|
|
||||||
|
|
||||||
// 'pop' = get and delete from map before returning the value to the caller
|
|
||||||
func popTraceMapping(connHandle uintptr) (TraceConfig, bool) {
|
|
||||||
traceMapLock.Lock()
|
|
||||||
defer traceMapLock.Unlock()
|
|
||||||
|
|
||||||
entryCopy, found := traceMap[connHandle]
|
|
||||||
if found {
|
|
||||||
delete(traceMap, connHandle)
|
|
||||||
fmt.Printf("Pop handle 0x%x: deleted trace config %v.\n", connHandle, entryCopy.config)
|
|
||||||
}
|
|
||||||
return entryCopy.config, found
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetTrace installs or removes the trace callback for the given database connection.
|
|
||||||
// It's not named 'RegisterTrace' because only one callback can be kept and called.
|
|
||||||
// Calling SetTrace a second time on same database connection
|
|
||||||
// overrides (cancels) any prior callback and all its settings:
|
|
||||||
// event mask, etc.
|
|
||||||
func (c *SQLiteConn) SetTrace(requested *TraceConfig) error {
|
|
||||||
connHandle := uintptr(unsafe.Pointer(c.db))
|
|
||||||
|
|
||||||
_, _ = popTraceMapping(connHandle)
|
|
||||||
|
|
||||||
if requested == nil {
|
|
||||||
// The traceMap entry was deleted already by popTraceMapping():
|
|
||||||
// can disable all events now, no need to watch for TraceClose.
|
|
||||||
err := c.setSQLiteTrace(0)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
reqCopy := *requested
|
|
||||||
|
|
||||||
// Disable potentially expensive operations
|
|
||||||
// if their result will not be used. We are doing this
|
|
||||||
// just in case the caller provided nonsensical input.
|
|
||||||
if reqCopy.EventMask&TraceStmt == 0 {
|
|
||||||
reqCopy.WantExpandedSQL = false
|
|
||||||
}
|
|
||||||
|
|
||||||
addTraceMapping(connHandle, reqCopy)
|
|
||||||
|
|
||||||
// The callback trampoline function does cleanup on Close event,
|
|
||||||
// regardless of the presence or absence of the user callback.
|
|
||||||
// Therefore it needs the Close event to be selected:
|
|
||||||
actualEventMask := uint(reqCopy.EventMask | TraceClose)
|
|
||||||
err := c.setSQLiteTrace(actualEventMask)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *SQLiteConn) setSQLiteTrace(sqliteEventMask uint) error {
|
|
||||||
rv := C.sqlite3_trace_v2(c.db,
|
|
||||||
C.uint(sqliteEventMask),
|
|
||||||
(*[0]byte)(unsafe.Pointer(C.traceCallbackTrampoline)),
|
|
||||||
unsafe.Pointer(c.db)) // Fourth arg is same as first: we are
|
|
||||||
// passing the database connection handle as callback context.
|
|
||||||
|
|
||||||
if rv != C.SQLITE_OK {
|
|
||||||
return c.lastError()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
57
vendor/github.com/mattn/go-sqlite3/sqlite3_type.go
generated
vendored
57
vendor/github.com/mattn/go-sqlite3/sqlite3_type.go
generated
vendored
|
@ -1,57 +0,0 @@
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
/*
|
|
||||||
#ifndef USE_LIBSQLITE3
|
|
||||||
#include <sqlite3-binding.h>
|
|
||||||
#else
|
|
||||||
#include <sqlite3.h>
|
|
||||||
#endif
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ColumnTypeDatabaseTypeName implement RowsColumnTypeDatabaseTypeName.
|
|
||||||
func (rc *SQLiteRows) ColumnTypeDatabaseTypeName(i int) string {
|
|
||||||
return C.GoString(C.sqlite3_column_decltype(rc.s.s, C.int(i)))
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
func (rc *SQLiteRows) ColumnTypeLength(index int) (length int64, ok bool) {
|
|
||||||
return 0, false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (rc *SQLiteRows) ColumnTypePrecisionScale(index int) (precision, scale int64, ok bool) {
|
|
||||||
return 0, 0, false
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
// ColumnTypeNullable implement RowsColumnTypeNullable.
|
|
||||||
func (rc *SQLiteRows) ColumnTypeNullable(i int) (nullable, ok bool) {
|
|
||||||
return true, true
|
|
||||||
}
|
|
||||||
|
|
||||||
// ColumnTypeScanType implement RowsColumnTypeScanType.
|
|
||||||
func (rc *SQLiteRows) ColumnTypeScanType(i int) reflect.Type {
|
|
||||||
switch C.sqlite3_column_type(rc.s.s, C.int(i)) {
|
|
||||||
case C.SQLITE_INTEGER:
|
|
||||||
switch C.GoString(C.sqlite3_column_decltype(rc.s.s, C.int(i))) {
|
|
||||||
case "timestamp", "datetime", "date":
|
|
||||||
return reflect.TypeOf(time.Time{})
|
|
||||||
case "boolean":
|
|
||||||
return reflect.TypeOf(false)
|
|
||||||
}
|
|
||||||
return reflect.TypeOf(int64(0))
|
|
||||||
case C.SQLITE_FLOAT:
|
|
||||||
return reflect.TypeOf(float64(0))
|
|
||||||
case C.SQLITE_BLOB:
|
|
||||||
return reflect.SliceOf(reflect.TypeOf(byte(0)))
|
|
||||||
case C.SQLITE_NULL:
|
|
||||||
return reflect.TypeOf(nil)
|
|
||||||
case C.SQLITE_TEXT:
|
|
||||||
return reflect.TypeOf("")
|
|
||||||
}
|
|
||||||
return reflect.SliceOf(reflect.TypeOf(byte(0)))
|
|
||||||
}
|
|
39
vendor/github.com/mattn/go-sqlite3/sqlite3_usleep_windows.go
generated
vendored
39
vendor/github.com/mattn/go-sqlite3/sqlite3_usleep_windows.go
generated
vendored
|
@ -1,39 +0,0 @@
|
||||||
// Copyright (C) 2018 G.J.R. Timmer <gjr.timmer@gmail.com>.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build cgo
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
// usleep is a function available on *nix based systems.
|
|
||||||
// This function is not present in Windows.
|
|
||||||
// Windows has a sleep function but this works with seconds
|
|
||||||
// and not with microseconds as usleep.
|
|
||||||
//
|
|
||||||
// This code should improve performance on windows because
|
|
||||||
// without the presence of usleep SQLite waits 1 second.
|
|
||||||
//
|
|
||||||
// Source: https://stackoverflow.com/questions/5801813/c-usleep-is-obsolete-workarounds-for-windows-mingw?utm_medium=organic&utm_source=google_rich_qa&utm_campaign=google_rich_qa
|
|
||||||
|
|
||||||
/*
|
|
||||||
#include <windows.h>
|
|
||||||
|
|
||||||
void usleep(__int64 usec)
|
|
||||||
{
|
|
||||||
HANDLE timer;
|
|
||||||
LARGE_INTEGER ft;
|
|
||||||
|
|
||||||
// Convert to 100 nanosecond interval, negative value indicates relative time
|
|
||||||
ft.QuadPart = -(10*usec);
|
|
||||||
|
|
||||||
timer = CreateWaitableTimer(NULL, TRUE, NULL);
|
|
||||||
SetWaitableTimer(timer, &ft, 0, NULL, NULL, 0);
|
|
||||||
WaitForSingleObject(timer, INFINITE);
|
|
||||||
CloseHandle(timer);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
import "C"
|
|
||||||
|
|
||||||
// EOF
|
|
18
vendor/github.com/mattn/go-sqlite3/sqlite3_windows.go
generated
vendored
18
vendor/github.com/mattn/go-sqlite3/sqlite3_windows.go
generated
vendored
|
@ -1,18 +0,0 @@
|
||||||
// Copyright (C) 2014 Yasuhiro Matsumoto <mattn.jp@gmail.com>.
|
|
||||||
//
|
|
||||||
// Use of this source code is governed by an MIT-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build windows
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
/*
|
|
||||||
#cgo CFLAGS: -I.
|
|
||||||
#cgo CFLAGS: -fno-stack-check
|
|
||||||
#cgo CFLAGS: -fno-stack-protector
|
|
||||||
#cgo CFLAGS: -mno-stack-arg-probe
|
|
||||||
#cgo LDFLAGS: -lmingwex -lmingw32
|
|
||||||
#cgo windows,386 CFLAGS: -D_USE_32BIT_TIME_T
|
|
||||||
*/
|
|
||||||
import "C"
|
|
620
vendor/github.com/mattn/go-sqlite3/sqlite3ext.h
generated
vendored
620
vendor/github.com/mattn/go-sqlite3/sqlite3ext.h
generated
vendored
|
@ -1,620 +0,0 @@
|
||||||
#ifndef USE_LIBSQLITE3
|
|
||||||
/*
|
|
||||||
** 2006 June 7
|
|
||||||
**
|
|
||||||
** The author disclaims copyright to this source code. In place of
|
|
||||||
** a legal notice, here is a blessing:
|
|
||||||
**
|
|
||||||
** May you do good and not evil.
|
|
||||||
** May you find forgiveness for yourself and forgive others.
|
|
||||||
** May you share freely, never taking more than you give.
|
|
||||||
**
|
|
||||||
*************************************************************************
|
|
||||||
** This header file defines the SQLite interface for use by
|
|
||||||
** shared libraries that want to be imported as extensions into
|
|
||||||
** an SQLite instance. Shared libraries that intend to be loaded
|
|
||||||
** as extensions by SQLite should #include this file instead of
|
|
||||||
** sqlite3.h.
|
|
||||||
*/
|
|
||||||
#ifndef SQLITE3EXT_H
|
|
||||||
#define SQLITE3EXT_H
|
|
||||||
#include "sqlite3-binding.h"
|
|
||||||
|
|
||||||
/*
|
|
||||||
** The following structure holds pointers to all of the SQLite API
|
|
||||||
** routines.
|
|
||||||
**
|
|
||||||
** WARNING: In order to maintain backwards compatibility, add new
|
|
||||||
** interfaces to the end of this structure only. If you insert new
|
|
||||||
** interfaces in the middle of this structure, then older different
|
|
||||||
** versions of SQLite will not be able to load each other's shared
|
|
||||||
** libraries!
|
|
||||||
*/
|
|
||||||
struct sqlite3_api_routines {
|
|
||||||
void * (*aggregate_context)(sqlite3_context*,int nBytes);
|
|
||||||
int (*aggregate_count)(sqlite3_context*);
|
|
||||||
int (*bind_blob)(sqlite3_stmt*,int,const void*,int n,void(*)(void*));
|
|
||||||
int (*bind_double)(sqlite3_stmt*,int,double);
|
|
||||||
int (*bind_int)(sqlite3_stmt*,int,int);
|
|
||||||
int (*bind_int64)(sqlite3_stmt*,int,sqlite_int64);
|
|
||||||
int (*bind_null)(sqlite3_stmt*,int);
|
|
||||||
int (*bind_parameter_count)(sqlite3_stmt*);
|
|
||||||
int (*bind_parameter_index)(sqlite3_stmt*,const char*zName);
|
|
||||||
const char * (*bind_parameter_name)(sqlite3_stmt*,int);
|
|
||||||
int (*bind_text)(sqlite3_stmt*,int,const char*,int n,void(*)(void*));
|
|
||||||
int (*bind_text16)(sqlite3_stmt*,int,const void*,int,void(*)(void*));
|
|
||||||
int (*bind_value)(sqlite3_stmt*,int,const sqlite3_value*);
|
|
||||||
int (*busy_handler)(sqlite3*,int(*)(void*,int),void*);
|
|
||||||
int (*busy_timeout)(sqlite3*,int ms);
|
|
||||||
int (*changes)(sqlite3*);
|
|
||||||
int (*close)(sqlite3*);
|
|
||||||
int (*collation_needed)(sqlite3*,void*,void(*)(void*,sqlite3*,
|
|
||||||
int eTextRep,const char*));
|
|
||||||
int (*collation_needed16)(sqlite3*,void*,void(*)(void*,sqlite3*,
|
|
||||||
int eTextRep,const void*));
|
|
||||||
const void * (*column_blob)(sqlite3_stmt*,int iCol);
|
|
||||||
int (*column_bytes)(sqlite3_stmt*,int iCol);
|
|
||||||
int (*column_bytes16)(sqlite3_stmt*,int iCol);
|
|
||||||
int (*column_count)(sqlite3_stmt*pStmt);
|
|
||||||
const char * (*column_database_name)(sqlite3_stmt*,int);
|
|
||||||
const void * (*column_database_name16)(sqlite3_stmt*,int);
|
|
||||||
const char * (*column_decltype)(sqlite3_stmt*,int i);
|
|
||||||
const void * (*column_decltype16)(sqlite3_stmt*,int);
|
|
||||||
double (*column_double)(sqlite3_stmt*,int iCol);
|
|
||||||
int (*column_int)(sqlite3_stmt*,int iCol);
|
|
||||||
sqlite_int64 (*column_int64)(sqlite3_stmt*,int iCol);
|
|
||||||
const char * (*column_name)(sqlite3_stmt*,int);
|
|
||||||
const void * (*column_name16)(sqlite3_stmt*,int);
|
|
||||||
const char * (*column_origin_name)(sqlite3_stmt*,int);
|
|
||||||
const void * (*column_origin_name16)(sqlite3_stmt*,int);
|
|
||||||
const char * (*column_table_name)(sqlite3_stmt*,int);
|
|
||||||
const void * (*column_table_name16)(sqlite3_stmt*,int);
|
|
||||||
const unsigned char * (*column_text)(sqlite3_stmt*,int iCol);
|
|
||||||
const void * (*column_text16)(sqlite3_stmt*,int iCol);
|
|
||||||
int (*column_type)(sqlite3_stmt*,int iCol);
|
|
||||||
sqlite3_value* (*column_value)(sqlite3_stmt*,int iCol);
|
|
||||||
void * (*commit_hook)(sqlite3*,int(*)(void*),void*);
|
|
||||||
int (*complete)(const char*sql);
|
|
||||||
int (*complete16)(const void*sql);
|
|
||||||
int (*create_collation)(sqlite3*,const char*,int,void*,
|
|
||||||
int(*)(void*,int,const void*,int,const void*));
|
|
||||||
int (*create_collation16)(sqlite3*,const void*,int,void*,
|
|
||||||
int(*)(void*,int,const void*,int,const void*));
|
|
||||||
int (*create_function)(sqlite3*,const char*,int,int,void*,
|
|
||||||
void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
|
|
||||||
void (*xStep)(sqlite3_context*,int,sqlite3_value**),
|
|
||||||
void (*xFinal)(sqlite3_context*));
|
|
||||||
int (*create_function16)(sqlite3*,const void*,int,int,void*,
|
|
||||||
void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
|
|
||||||
void (*xStep)(sqlite3_context*,int,sqlite3_value**),
|
|
||||||
void (*xFinal)(sqlite3_context*));
|
|
||||||
int (*create_module)(sqlite3*,const char*,const sqlite3_module*,void*);
|
|
||||||
int (*data_count)(sqlite3_stmt*pStmt);
|
|
||||||
sqlite3 * (*db_handle)(sqlite3_stmt*);
|
|
||||||
int (*declare_vtab)(sqlite3*,const char*);
|
|
||||||
int (*enable_shared_cache)(int);
|
|
||||||
int (*errcode)(sqlite3*db);
|
|
||||||
const char * (*errmsg)(sqlite3*);
|
|
||||||
const void * (*errmsg16)(sqlite3*);
|
|
||||||
int (*exec)(sqlite3*,const char*,sqlite3_callback,void*,char**);
|
|
||||||
int (*expired)(sqlite3_stmt*);
|
|
||||||
int (*finalize)(sqlite3_stmt*pStmt);
|
|
||||||
void (*free)(void*);
|
|
||||||
void (*free_table)(char**result);
|
|
||||||
int (*get_autocommit)(sqlite3*);
|
|
||||||
void * (*get_auxdata)(sqlite3_context*,int);
|
|
||||||
int (*get_table)(sqlite3*,const char*,char***,int*,int*,char**);
|
|
||||||
int (*global_recover)(void);
|
|
||||||
void (*interruptx)(sqlite3*);
|
|
||||||
sqlite_int64 (*last_insert_rowid)(sqlite3*);
|
|
||||||
const char * (*libversion)(void);
|
|
||||||
int (*libversion_number)(void);
|
|
||||||
void *(*malloc)(int);
|
|
||||||
char * (*mprintf)(const char*,...);
|
|
||||||
int (*open)(const char*,sqlite3**);
|
|
||||||
int (*open16)(const void*,sqlite3**);
|
|
||||||
int (*prepare)(sqlite3*,const char*,int,sqlite3_stmt**,const char**);
|
|
||||||
int (*prepare16)(sqlite3*,const void*,int,sqlite3_stmt**,const void**);
|
|
||||||
void * (*profile)(sqlite3*,void(*)(void*,const char*,sqlite_uint64),void*);
|
|
||||||
void (*progress_handler)(sqlite3*,int,int(*)(void*),void*);
|
|
||||||
void *(*realloc)(void*,int);
|
|
||||||
int (*reset)(sqlite3_stmt*pStmt);
|
|
||||||
void (*result_blob)(sqlite3_context*,const void*,int,void(*)(void*));
|
|
||||||
void (*result_double)(sqlite3_context*,double);
|
|
||||||
void (*result_error)(sqlite3_context*,const char*,int);
|
|
||||||
void (*result_error16)(sqlite3_context*,const void*,int);
|
|
||||||
void (*result_int)(sqlite3_context*,int);
|
|
||||||
void (*result_int64)(sqlite3_context*,sqlite_int64);
|
|
||||||
void (*result_null)(sqlite3_context*);
|
|
||||||
void (*result_text)(sqlite3_context*,const char*,int,void(*)(void*));
|
|
||||||
void (*result_text16)(sqlite3_context*,const void*,int,void(*)(void*));
|
|
||||||
void (*result_text16be)(sqlite3_context*,const void*,int,void(*)(void*));
|
|
||||||
void (*result_text16le)(sqlite3_context*,const void*,int,void(*)(void*));
|
|
||||||
void (*result_value)(sqlite3_context*,sqlite3_value*);
|
|
||||||
void * (*rollback_hook)(sqlite3*,void(*)(void*),void*);
|
|
||||||
int (*set_authorizer)(sqlite3*,int(*)(void*,int,const char*,const char*,
|
|
||||||
const char*,const char*),void*);
|
|
||||||
void (*set_auxdata)(sqlite3_context*,int,void*,void (*)(void*));
|
|
||||||
char * (*xsnprintf)(int,char*,const char*,...);
|
|
||||||
int (*step)(sqlite3_stmt*);
|
|
||||||
int (*table_column_metadata)(sqlite3*,const char*,const char*,const char*,
|
|
||||||
char const**,char const**,int*,int*,int*);
|
|
||||||
void (*thread_cleanup)(void);
|
|
||||||
int (*total_changes)(sqlite3*);
|
|
||||||
void * (*trace)(sqlite3*,void(*xTrace)(void*,const char*),void*);
|
|
||||||
int (*transfer_bindings)(sqlite3_stmt*,sqlite3_stmt*);
|
|
||||||
void * (*update_hook)(sqlite3*,void(*)(void*,int ,char const*,char const*,
|
|
||||||
sqlite_int64),void*);
|
|
||||||
void * (*user_data)(sqlite3_context*);
|
|
||||||
const void * (*value_blob)(sqlite3_value*);
|
|
||||||
int (*value_bytes)(sqlite3_value*);
|
|
||||||
int (*value_bytes16)(sqlite3_value*);
|
|
||||||
double (*value_double)(sqlite3_value*);
|
|
||||||
int (*value_int)(sqlite3_value*);
|
|
||||||
sqlite_int64 (*value_int64)(sqlite3_value*);
|
|
||||||
int (*value_numeric_type)(sqlite3_value*);
|
|
||||||
const unsigned char * (*value_text)(sqlite3_value*);
|
|
||||||
const void * (*value_text16)(sqlite3_value*);
|
|
||||||
const void * (*value_text16be)(sqlite3_value*);
|
|
||||||
const void * (*value_text16le)(sqlite3_value*);
|
|
||||||
int (*value_type)(sqlite3_value*);
|
|
||||||
char *(*vmprintf)(const char*,va_list);
|
|
||||||
/* Added ??? */
|
|
||||||
int (*overload_function)(sqlite3*, const char *zFuncName, int nArg);
|
|
||||||
/* Added by 3.3.13 */
|
|
||||||
int (*prepare_v2)(sqlite3*,const char*,int,sqlite3_stmt**,const char**);
|
|
||||||
int (*prepare16_v2)(sqlite3*,const void*,int,sqlite3_stmt**,const void**);
|
|
||||||
int (*clear_bindings)(sqlite3_stmt*);
|
|
||||||
/* Added by 3.4.1 */
|
|
||||||
int (*create_module_v2)(sqlite3*,const char*,const sqlite3_module*,void*,
|
|
||||||
void (*xDestroy)(void *));
|
|
||||||
/* Added by 3.5.0 */
|
|
||||||
int (*bind_zeroblob)(sqlite3_stmt*,int,int);
|
|
||||||
int (*blob_bytes)(sqlite3_blob*);
|
|
||||||
int (*blob_close)(sqlite3_blob*);
|
|
||||||
int (*blob_open)(sqlite3*,const char*,const char*,const char*,sqlite3_int64,
|
|
||||||
int,sqlite3_blob**);
|
|
||||||
int (*blob_read)(sqlite3_blob*,void*,int,int);
|
|
||||||
int (*blob_write)(sqlite3_blob*,const void*,int,int);
|
|
||||||
int (*create_collation_v2)(sqlite3*,const char*,int,void*,
|
|
||||||
int(*)(void*,int,const void*,int,const void*),
|
|
||||||
void(*)(void*));
|
|
||||||
int (*file_control)(sqlite3*,const char*,int,void*);
|
|
||||||
sqlite3_int64 (*memory_highwater)(int);
|
|
||||||
sqlite3_int64 (*memory_used)(void);
|
|
||||||
sqlite3_mutex *(*mutex_alloc)(int);
|
|
||||||
void (*mutex_enter)(sqlite3_mutex*);
|
|
||||||
void (*mutex_free)(sqlite3_mutex*);
|
|
||||||
void (*mutex_leave)(sqlite3_mutex*);
|
|
||||||
int (*mutex_try)(sqlite3_mutex*);
|
|
||||||
int (*open_v2)(const char*,sqlite3**,int,const char*);
|
|
||||||
int (*release_memory)(int);
|
|
||||||
void (*result_error_nomem)(sqlite3_context*);
|
|
||||||
void (*result_error_toobig)(sqlite3_context*);
|
|
||||||
int (*sleep)(int);
|
|
||||||
void (*soft_heap_limit)(int);
|
|
||||||
sqlite3_vfs *(*vfs_find)(const char*);
|
|
||||||
int (*vfs_register)(sqlite3_vfs*,int);
|
|
||||||
int (*vfs_unregister)(sqlite3_vfs*);
|
|
||||||
int (*xthreadsafe)(void);
|
|
||||||
void (*result_zeroblob)(sqlite3_context*,int);
|
|
||||||
void (*result_error_code)(sqlite3_context*,int);
|
|
||||||
int (*test_control)(int, ...);
|
|
||||||
void (*randomness)(int,void*);
|
|
||||||
sqlite3 *(*context_db_handle)(sqlite3_context*);
|
|
||||||
int (*extended_result_codes)(sqlite3*,int);
|
|
||||||
int (*limit)(sqlite3*,int,int);
|
|
||||||
sqlite3_stmt *(*next_stmt)(sqlite3*,sqlite3_stmt*);
|
|
||||||
const char *(*sql)(sqlite3_stmt*);
|
|
||||||
int (*status)(int,int*,int*,int);
|
|
||||||
int (*backup_finish)(sqlite3_backup*);
|
|
||||||
sqlite3_backup *(*backup_init)(sqlite3*,const char*,sqlite3*,const char*);
|
|
||||||
int (*backup_pagecount)(sqlite3_backup*);
|
|
||||||
int (*backup_remaining)(sqlite3_backup*);
|
|
||||||
int (*backup_step)(sqlite3_backup*,int);
|
|
||||||
const char *(*compileoption_get)(int);
|
|
||||||
int (*compileoption_used)(const char*);
|
|
||||||
int (*create_function_v2)(sqlite3*,const char*,int,int,void*,
|
|
||||||
void (*xFunc)(sqlite3_context*,int,sqlite3_value**),
|
|
||||||
void (*xStep)(sqlite3_context*,int,sqlite3_value**),
|
|
||||||
void (*xFinal)(sqlite3_context*),
|
|
||||||
void(*xDestroy)(void*));
|
|
||||||
int (*db_config)(sqlite3*,int,...);
|
|
||||||
sqlite3_mutex *(*db_mutex)(sqlite3*);
|
|
||||||
int (*db_status)(sqlite3*,int,int*,int*,int);
|
|
||||||
int (*extended_errcode)(sqlite3*);
|
|
||||||
void (*log)(int,const char*,...);
|
|
||||||
sqlite3_int64 (*soft_heap_limit64)(sqlite3_int64);
|
|
||||||
const char *(*sourceid)(void);
|
|
||||||
int (*stmt_status)(sqlite3_stmt*,int,int);
|
|
||||||
int (*strnicmp)(const char*,const char*,int);
|
|
||||||
int (*unlock_notify)(sqlite3*,void(*)(void**,int),void*);
|
|
||||||
int (*wal_autocheckpoint)(sqlite3*,int);
|
|
||||||
int (*wal_checkpoint)(sqlite3*,const char*);
|
|
||||||
void *(*wal_hook)(sqlite3*,int(*)(void*,sqlite3*,const char*,int),void*);
|
|
||||||
int (*blob_reopen)(sqlite3_blob*,sqlite3_int64);
|
|
||||||
int (*vtab_config)(sqlite3*,int op,...);
|
|
||||||
int (*vtab_on_conflict)(sqlite3*);
|
|
||||||
/* Version 3.7.16 and later */
|
|
||||||
int (*close_v2)(sqlite3*);
|
|
||||||
const char *(*db_filename)(sqlite3*,const char*);
|
|
||||||
int (*db_readonly)(sqlite3*,const char*);
|
|
||||||
int (*db_release_memory)(sqlite3*);
|
|
||||||
const char *(*errstr)(int);
|
|
||||||
int (*stmt_busy)(sqlite3_stmt*);
|
|
||||||
int (*stmt_readonly)(sqlite3_stmt*);
|
|
||||||
int (*stricmp)(const char*,const char*);
|
|
||||||
int (*uri_boolean)(const char*,const char*,int);
|
|
||||||
sqlite3_int64 (*uri_int64)(const char*,const char*,sqlite3_int64);
|
|
||||||
const char *(*uri_parameter)(const char*,const char*);
|
|
||||||
char *(*xvsnprintf)(int,char*,const char*,va_list);
|
|
||||||
int (*wal_checkpoint_v2)(sqlite3*,const char*,int,int*,int*);
|
|
||||||
/* Version 3.8.7 and later */
|
|
||||||
int (*auto_extension)(void(*)(void));
|
|
||||||
int (*bind_blob64)(sqlite3_stmt*,int,const void*,sqlite3_uint64,
|
|
||||||
void(*)(void*));
|
|
||||||
int (*bind_text64)(sqlite3_stmt*,int,const char*,sqlite3_uint64,
|
|
||||||
void(*)(void*),unsigned char);
|
|
||||||
int (*cancel_auto_extension)(void(*)(void));
|
|
||||||
int (*load_extension)(sqlite3*,const char*,const char*,char**);
|
|
||||||
void *(*malloc64)(sqlite3_uint64);
|
|
||||||
sqlite3_uint64 (*msize)(void*);
|
|
||||||
void *(*realloc64)(void*,sqlite3_uint64);
|
|
||||||
void (*reset_auto_extension)(void);
|
|
||||||
void (*result_blob64)(sqlite3_context*,const void*,sqlite3_uint64,
|
|
||||||
void(*)(void*));
|
|
||||||
void (*result_text64)(sqlite3_context*,const char*,sqlite3_uint64,
|
|
||||||
void(*)(void*), unsigned char);
|
|
||||||
int (*strglob)(const char*,const char*);
|
|
||||||
/* Version 3.8.11 and later */
|
|
||||||
sqlite3_value *(*value_dup)(const sqlite3_value*);
|
|
||||||
void (*value_free)(sqlite3_value*);
|
|
||||||
int (*result_zeroblob64)(sqlite3_context*,sqlite3_uint64);
|
|
||||||
int (*bind_zeroblob64)(sqlite3_stmt*, int, sqlite3_uint64);
|
|
||||||
/* Version 3.9.0 and later */
|
|
||||||
unsigned int (*value_subtype)(sqlite3_value*);
|
|
||||||
void (*result_subtype)(sqlite3_context*,unsigned int);
|
|
||||||
/* Version 3.10.0 and later */
|
|
||||||
int (*status64)(int,sqlite3_int64*,sqlite3_int64*,int);
|
|
||||||
int (*strlike)(const char*,const char*,unsigned int);
|
|
||||||
int (*db_cacheflush)(sqlite3*);
|
|
||||||
/* Version 3.12.0 and later */
|
|
||||||
int (*system_errno)(sqlite3*);
|
|
||||||
/* Version 3.14.0 and later */
|
|
||||||
int (*trace_v2)(sqlite3*,unsigned,int(*)(unsigned,void*,void*,void*),void*);
|
|
||||||
char *(*expanded_sql)(sqlite3_stmt*);
|
|
||||||
/* Version 3.18.0 and later */
|
|
||||||
void (*set_last_insert_rowid)(sqlite3*,sqlite3_int64);
|
|
||||||
/* Version 3.20.0 and later */
|
|
||||||
int (*prepare_v3)(sqlite3*,const char*,int,unsigned int,
|
|
||||||
sqlite3_stmt**,const char**);
|
|
||||||
int (*prepare16_v3)(sqlite3*,const void*,int,unsigned int,
|
|
||||||
sqlite3_stmt**,const void**);
|
|
||||||
int (*bind_pointer)(sqlite3_stmt*,int,void*,const char*,void(*)(void*));
|
|
||||||
void (*result_pointer)(sqlite3_context*,void*,const char*,void(*)(void*));
|
|
||||||
void *(*value_pointer)(sqlite3_value*,const char*);
|
|
||||||
int (*vtab_nochange)(sqlite3_context*);
|
|
||||||
int (*value_nochange)(sqlite3_value*);
|
|
||||||
const char *(*vtab_collation)(sqlite3_index_info*,int);
|
|
||||||
/* Version 3.24.0 and later */
|
|
||||||
int (*keyword_count)(void);
|
|
||||||
int (*keyword_name)(int,const char**,int*);
|
|
||||||
int (*keyword_check)(const char*,int);
|
|
||||||
sqlite3_str *(*str_new)(sqlite3*);
|
|
||||||
char *(*str_finish)(sqlite3_str*);
|
|
||||||
void (*str_appendf)(sqlite3_str*, const char *zFormat, ...);
|
|
||||||
void (*str_vappendf)(sqlite3_str*, const char *zFormat, va_list);
|
|
||||||
void (*str_append)(sqlite3_str*, const char *zIn, int N);
|
|
||||||
void (*str_appendall)(sqlite3_str*, const char *zIn);
|
|
||||||
void (*str_appendchar)(sqlite3_str*, int N, char C);
|
|
||||||
void (*str_reset)(sqlite3_str*);
|
|
||||||
int (*str_errcode)(sqlite3_str*);
|
|
||||||
int (*str_length)(sqlite3_str*);
|
|
||||||
char *(*str_value)(sqlite3_str*);
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
|
||||||
** This is the function signature used for all extension entry points. It
|
|
||||||
** is also defined in the file "loadext.c".
|
|
||||||
*/
|
|
||||||
typedef int (*sqlite3_loadext_entry)(
|
|
||||||
sqlite3 *db, /* Handle to the database. */
|
|
||||||
char **pzErrMsg, /* Used to set error string on failure. */
|
|
||||||
const sqlite3_api_routines *pThunk /* Extension API function pointers. */
|
|
||||||
);
|
|
||||||
|
|
||||||
/*
|
|
||||||
** The following macros redefine the API routines so that they are
|
|
||||||
** redirected through the global sqlite3_api structure.
|
|
||||||
**
|
|
||||||
** This header file is also used by the loadext.c source file
|
|
||||||
** (part of the main SQLite library - not an extension) so that
|
|
||||||
** it can get access to the sqlite3_api_routines structure
|
|
||||||
** definition. But the main library does not want to redefine
|
|
||||||
** the API. So the redefinition macros are only valid if the
|
|
||||||
** SQLITE_CORE macros is undefined.
|
|
||||||
*/
|
|
||||||
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
|
|
||||||
#define sqlite3_aggregate_context sqlite3_api->aggregate_context
|
|
||||||
#ifndef SQLITE_OMIT_DEPRECATED
|
|
||||||
#define sqlite3_aggregate_count sqlite3_api->aggregate_count
|
|
||||||
#endif
|
|
||||||
#define sqlite3_bind_blob sqlite3_api->bind_blob
|
|
||||||
#define sqlite3_bind_double sqlite3_api->bind_double
|
|
||||||
#define sqlite3_bind_int sqlite3_api->bind_int
|
|
||||||
#define sqlite3_bind_int64 sqlite3_api->bind_int64
|
|
||||||
#define sqlite3_bind_null sqlite3_api->bind_null
|
|
||||||
#define sqlite3_bind_parameter_count sqlite3_api->bind_parameter_count
|
|
||||||
#define sqlite3_bind_parameter_index sqlite3_api->bind_parameter_index
|
|
||||||
#define sqlite3_bind_parameter_name sqlite3_api->bind_parameter_name
|
|
||||||
#define sqlite3_bind_text sqlite3_api->bind_text
|
|
||||||
#define sqlite3_bind_text16 sqlite3_api->bind_text16
|
|
||||||
#define sqlite3_bind_value sqlite3_api->bind_value
|
|
||||||
#define sqlite3_busy_handler sqlite3_api->busy_handler
|
|
||||||
#define sqlite3_busy_timeout sqlite3_api->busy_timeout
|
|
||||||
#define sqlite3_changes sqlite3_api->changes
|
|
||||||
#define sqlite3_close sqlite3_api->close
|
|
||||||
#define sqlite3_collation_needed sqlite3_api->collation_needed
|
|
||||||
#define sqlite3_collation_needed16 sqlite3_api->collation_needed16
|
|
||||||
#define sqlite3_column_blob sqlite3_api->column_blob
|
|
||||||
#define sqlite3_column_bytes sqlite3_api->column_bytes
|
|
||||||
#define sqlite3_column_bytes16 sqlite3_api->column_bytes16
|
|
||||||
#define sqlite3_column_count sqlite3_api->column_count
|
|
||||||
#define sqlite3_column_database_name sqlite3_api->column_database_name
|
|
||||||
#define sqlite3_column_database_name16 sqlite3_api->column_database_name16
|
|
||||||
#define sqlite3_column_decltype sqlite3_api->column_decltype
|
|
||||||
#define sqlite3_column_decltype16 sqlite3_api->column_decltype16
|
|
||||||
#define sqlite3_column_double sqlite3_api->column_double
|
|
||||||
#define sqlite3_column_int sqlite3_api->column_int
|
|
||||||
#define sqlite3_column_int64 sqlite3_api->column_int64
|
|
||||||
#define sqlite3_column_name sqlite3_api->column_name
|
|
||||||
#define sqlite3_column_name16 sqlite3_api->column_name16
|
|
||||||
#define sqlite3_column_origin_name sqlite3_api->column_origin_name
|
|
||||||
#define sqlite3_column_origin_name16 sqlite3_api->column_origin_name16
|
|
||||||
#define sqlite3_column_table_name sqlite3_api->column_table_name
|
|
||||||
#define sqlite3_column_table_name16 sqlite3_api->column_table_name16
|
|
||||||
#define sqlite3_column_text sqlite3_api->column_text
|
|
||||||
#define sqlite3_column_text16 sqlite3_api->column_text16
|
|
||||||
#define sqlite3_column_type sqlite3_api->column_type
|
|
||||||
#define sqlite3_column_value sqlite3_api->column_value
|
|
||||||
#define sqlite3_commit_hook sqlite3_api->commit_hook
|
|
||||||
#define sqlite3_complete sqlite3_api->complete
|
|
||||||
#define sqlite3_complete16 sqlite3_api->complete16
|
|
||||||
#define sqlite3_create_collation sqlite3_api->create_collation
|
|
||||||
#define sqlite3_create_collation16 sqlite3_api->create_collation16
|
|
||||||
#define sqlite3_create_function sqlite3_api->create_function
|
|
||||||
#define sqlite3_create_function16 sqlite3_api->create_function16
|
|
||||||
#define sqlite3_create_module sqlite3_api->create_module
|
|
||||||
#define sqlite3_create_module_v2 sqlite3_api->create_module_v2
|
|
||||||
#define sqlite3_data_count sqlite3_api->data_count
|
|
||||||
#define sqlite3_db_handle sqlite3_api->db_handle
|
|
||||||
#define sqlite3_declare_vtab sqlite3_api->declare_vtab
|
|
||||||
#define sqlite3_enable_shared_cache sqlite3_api->enable_shared_cache
|
|
||||||
#define sqlite3_errcode sqlite3_api->errcode
|
|
||||||
#define sqlite3_errmsg sqlite3_api->errmsg
|
|
||||||
#define sqlite3_errmsg16 sqlite3_api->errmsg16
|
|
||||||
#define sqlite3_exec sqlite3_api->exec
|
|
||||||
#ifndef SQLITE_OMIT_DEPRECATED
|
|
||||||
#define sqlite3_expired sqlite3_api->expired
|
|
||||||
#endif
|
|
||||||
#define sqlite3_finalize sqlite3_api->finalize
|
|
||||||
#define sqlite3_free sqlite3_api->free
|
|
||||||
#define sqlite3_free_table sqlite3_api->free_table
|
|
||||||
#define sqlite3_get_autocommit sqlite3_api->get_autocommit
|
|
||||||
#define sqlite3_get_auxdata sqlite3_api->get_auxdata
|
|
||||||
#define sqlite3_get_table sqlite3_api->get_table
|
|
||||||
#ifndef SQLITE_OMIT_DEPRECATED
|
|
||||||
#define sqlite3_global_recover sqlite3_api->global_recover
|
|
||||||
#endif
|
|
||||||
#define sqlite3_interrupt sqlite3_api->interruptx
|
|
||||||
#define sqlite3_last_insert_rowid sqlite3_api->last_insert_rowid
|
|
||||||
#define sqlite3_libversion sqlite3_api->libversion
|
|
||||||
#define sqlite3_libversion_number sqlite3_api->libversion_number
|
|
||||||
#define sqlite3_malloc sqlite3_api->malloc
|
|
||||||
#define sqlite3_mprintf sqlite3_api->mprintf
|
|
||||||
#define sqlite3_open sqlite3_api->open
|
|
||||||
#define sqlite3_open16 sqlite3_api->open16
|
|
||||||
#define sqlite3_prepare sqlite3_api->prepare
|
|
||||||
#define sqlite3_prepare16 sqlite3_api->prepare16
|
|
||||||
#define sqlite3_prepare_v2 sqlite3_api->prepare_v2
|
|
||||||
#define sqlite3_prepare16_v2 sqlite3_api->prepare16_v2
|
|
||||||
#define sqlite3_profile sqlite3_api->profile
|
|
||||||
#define sqlite3_progress_handler sqlite3_api->progress_handler
|
|
||||||
#define sqlite3_realloc sqlite3_api->realloc
|
|
||||||
#define sqlite3_reset sqlite3_api->reset
|
|
||||||
#define sqlite3_result_blob sqlite3_api->result_blob
|
|
||||||
#define sqlite3_result_double sqlite3_api->result_double
|
|
||||||
#define sqlite3_result_error sqlite3_api->result_error
|
|
||||||
#define sqlite3_result_error16 sqlite3_api->result_error16
|
|
||||||
#define sqlite3_result_int sqlite3_api->result_int
|
|
||||||
#define sqlite3_result_int64 sqlite3_api->result_int64
|
|
||||||
#define sqlite3_result_null sqlite3_api->result_null
|
|
||||||
#define sqlite3_result_text sqlite3_api->result_text
|
|
||||||
#define sqlite3_result_text16 sqlite3_api->result_text16
|
|
||||||
#define sqlite3_result_text16be sqlite3_api->result_text16be
|
|
||||||
#define sqlite3_result_text16le sqlite3_api->result_text16le
|
|
||||||
#define sqlite3_result_value sqlite3_api->result_value
|
|
||||||
#define sqlite3_rollback_hook sqlite3_api->rollback_hook
|
|
||||||
#define sqlite3_set_authorizer sqlite3_api->set_authorizer
|
|
||||||
#define sqlite3_set_auxdata sqlite3_api->set_auxdata
|
|
||||||
#define sqlite3_snprintf sqlite3_api->xsnprintf
|
|
||||||
#define sqlite3_step sqlite3_api->step
|
|
||||||
#define sqlite3_table_column_metadata sqlite3_api->table_column_metadata
|
|
||||||
#define sqlite3_thread_cleanup sqlite3_api->thread_cleanup
|
|
||||||
#define sqlite3_total_changes sqlite3_api->total_changes
|
|
||||||
#define sqlite3_trace sqlite3_api->trace
|
|
||||||
#ifndef SQLITE_OMIT_DEPRECATED
|
|
||||||
#define sqlite3_transfer_bindings sqlite3_api->transfer_bindings
|
|
||||||
#endif
|
|
||||||
#define sqlite3_update_hook sqlite3_api->update_hook
|
|
||||||
#define sqlite3_user_data sqlite3_api->user_data
|
|
||||||
#define sqlite3_value_blob sqlite3_api->value_blob
|
|
||||||
#define sqlite3_value_bytes sqlite3_api->value_bytes
|
|
||||||
#define sqlite3_value_bytes16 sqlite3_api->value_bytes16
|
|
||||||
#define sqlite3_value_double sqlite3_api->value_double
|
|
||||||
#define sqlite3_value_int sqlite3_api->value_int
|
|
||||||
#define sqlite3_value_int64 sqlite3_api->value_int64
|
|
||||||
#define sqlite3_value_numeric_type sqlite3_api->value_numeric_type
|
|
||||||
#define sqlite3_value_text sqlite3_api->value_text
|
|
||||||
#define sqlite3_value_text16 sqlite3_api->value_text16
|
|
||||||
#define sqlite3_value_text16be sqlite3_api->value_text16be
|
|
||||||
#define sqlite3_value_text16le sqlite3_api->value_text16le
|
|
||||||
#define sqlite3_value_type sqlite3_api->value_type
|
|
||||||
#define sqlite3_vmprintf sqlite3_api->vmprintf
|
|
||||||
#define sqlite3_vsnprintf sqlite3_api->xvsnprintf
|
|
||||||
#define sqlite3_overload_function sqlite3_api->overload_function
|
|
||||||
#define sqlite3_prepare_v2 sqlite3_api->prepare_v2
|
|
||||||
#define sqlite3_prepare16_v2 sqlite3_api->prepare16_v2
|
|
||||||
#define sqlite3_clear_bindings sqlite3_api->clear_bindings
|
|
||||||
#define sqlite3_bind_zeroblob sqlite3_api->bind_zeroblob
|
|
||||||
#define sqlite3_blob_bytes sqlite3_api->blob_bytes
|
|
||||||
#define sqlite3_blob_close sqlite3_api->blob_close
|
|
||||||
#define sqlite3_blob_open sqlite3_api->blob_open
|
|
||||||
#define sqlite3_blob_read sqlite3_api->blob_read
|
|
||||||
#define sqlite3_blob_write sqlite3_api->blob_write
|
|
||||||
#define sqlite3_create_collation_v2 sqlite3_api->create_collation_v2
|
|
||||||
#define sqlite3_file_control sqlite3_api->file_control
|
|
||||||
#define sqlite3_memory_highwater sqlite3_api->memory_highwater
|
|
||||||
#define sqlite3_memory_used sqlite3_api->memory_used
|
|
||||||
#define sqlite3_mutex_alloc sqlite3_api->mutex_alloc
|
|
||||||
#define sqlite3_mutex_enter sqlite3_api->mutex_enter
|
|
||||||
#define sqlite3_mutex_free sqlite3_api->mutex_free
|
|
||||||
#define sqlite3_mutex_leave sqlite3_api->mutex_leave
|
|
||||||
#define sqlite3_mutex_try sqlite3_api->mutex_try
|
|
||||||
#define sqlite3_open_v2 sqlite3_api->open_v2
|
|
||||||
#define sqlite3_release_memory sqlite3_api->release_memory
|
|
||||||
#define sqlite3_result_error_nomem sqlite3_api->result_error_nomem
|
|
||||||
#define sqlite3_result_error_toobig sqlite3_api->result_error_toobig
|
|
||||||
#define sqlite3_sleep sqlite3_api->sleep
|
|
||||||
#define sqlite3_soft_heap_limit sqlite3_api->soft_heap_limit
|
|
||||||
#define sqlite3_vfs_find sqlite3_api->vfs_find
|
|
||||||
#define sqlite3_vfs_register sqlite3_api->vfs_register
|
|
||||||
#define sqlite3_vfs_unregister sqlite3_api->vfs_unregister
|
|
||||||
#define sqlite3_threadsafe sqlite3_api->xthreadsafe
|
|
||||||
#define sqlite3_result_zeroblob sqlite3_api->result_zeroblob
|
|
||||||
#define sqlite3_result_error_code sqlite3_api->result_error_code
|
|
||||||
#define sqlite3_test_control sqlite3_api->test_control
|
|
||||||
#define sqlite3_randomness sqlite3_api->randomness
|
|
||||||
#define sqlite3_context_db_handle sqlite3_api->context_db_handle
|
|
||||||
#define sqlite3_extended_result_codes sqlite3_api->extended_result_codes
|
|
||||||
#define sqlite3_limit sqlite3_api->limit
|
|
||||||
#define sqlite3_next_stmt sqlite3_api->next_stmt
|
|
||||||
#define sqlite3_sql sqlite3_api->sql
|
|
||||||
#define sqlite3_status sqlite3_api->status
|
|
||||||
#define sqlite3_backup_finish sqlite3_api->backup_finish
|
|
||||||
#define sqlite3_backup_init sqlite3_api->backup_init
|
|
||||||
#define sqlite3_backup_pagecount sqlite3_api->backup_pagecount
|
|
||||||
#define sqlite3_backup_remaining sqlite3_api->backup_remaining
|
|
||||||
#define sqlite3_backup_step sqlite3_api->backup_step
|
|
||||||
#define sqlite3_compileoption_get sqlite3_api->compileoption_get
|
|
||||||
#define sqlite3_compileoption_used sqlite3_api->compileoption_used
|
|
||||||
#define sqlite3_create_function_v2 sqlite3_api->create_function_v2
|
|
||||||
#define sqlite3_db_config sqlite3_api->db_config
|
|
||||||
#define sqlite3_db_mutex sqlite3_api->db_mutex
|
|
||||||
#define sqlite3_db_status sqlite3_api->db_status
|
|
||||||
#define sqlite3_extended_errcode sqlite3_api->extended_errcode
|
|
||||||
#define sqlite3_log sqlite3_api->log
|
|
||||||
#define sqlite3_soft_heap_limit64 sqlite3_api->soft_heap_limit64
|
|
||||||
#define sqlite3_sourceid sqlite3_api->sourceid
|
|
||||||
#define sqlite3_stmt_status sqlite3_api->stmt_status
|
|
||||||
#define sqlite3_strnicmp sqlite3_api->strnicmp
|
|
||||||
#define sqlite3_unlock_notify sqlite3_api->unlock_notify
|
|
||||||
#define sqlite3_wal_autocheckpoint sqlite3_api->wal_autocheckpoint
|
|
||||||
#define sqlite3_wal_checkpoint sqlite3_api->wal_checkpoint
|
|
||||||
#define sqlite3_wal_hook sqlite3_api->wal_hook
|
|
||||||
#define sqlite3_blob_reopen sqlite3_api->blob_reopen
|
|
||||||
#define sqlite3_vtab_config sqlite3_api->vtab_config
|
|
||||||
#define sqlite3_vtab_on_conflict sqlite3_api->vtab_on_conflict
|
|
||||||
/* Version 3.7.16 and later */
|
|
||||||
#define sqlite3_close_v2 sqlite3_api->close_v2
|
|
||||||
#define sqlite3_db_filename sqlite3_api->db_filename
|
|
||||||
#define sqlite3_db_readonly sqlite3_api->db_readonly
|
|
||||||
#define sqlite3_db_release_memory sqlite3_api->db_release_memory
|
|
||||||
#define sqlite3_errstr sqlite3_api->errstr
|
|
||||||
#define sqlite3_stmt_busy sqlite3_api->stmt_busy
|
|
||||||
#define sqlite3_stmt_readonly sqlite3_api->stmt_readonly
|
|
||||||
#define sqlite3_stricmp sqlite3_api->stricmp
|
|
||||||
#define sqlite3_uri_boolean sqlite3_api->uri_boolean
|
|
||||||
#define sqlite3_uri_int64 sqlite3_api->uri_int64
|
|
||||||
#define sqlite3_uri_parameter sqlite3_api->uri_parameter
|
|
||||||
#define sqlite3_uri_vsnprintf sqlite3_api->xvsnprintf
|
|
||||||
#define sqlite3_wal_checkpoint_v2 sqlite3_api->wal_checkpoint_v2
|
|
||||||
/* Version 3.8.7 and later */
|
|
||||||
#define sqlite3_auto_extension sqlite3_api->auto_extension
|
|
||||||
#define sqlite3_bind_blob64 sqlite3_api->bind_blob64
|
|
||||||
#define sqlite3_bind_text64 sqlite3_api->bind_text64
|
|
||||||
#define sqlite3_cancel_auto_extension sqlite3_api->cancel_auto_extension
|
|
||||||
#define sqlite3_load_extension sqlite3_api->load_extension
|
|
||||||
#define sqlite3_malloc64 sqlite3_api->malloc64
|
|
||||||
#define sqlite3_msize sqlite3_api->msize
|
|
||||||
#define sqlite3_realloc64 sqlite3_api->realloc64
|
|
||||||
#define sqlite3_reset_auto_extension sqlite3_api->reset_auto_extension
|
|
||||||
#define sqlite3_result_blob64 sqlite3_api->result_blob64
|
|
||||||
#define sqlite3_result_text64 sqlite3_api->result_text64
|
|
||||||
#define sqlite3_strglob sqlite3_api->strglob
|
|
||||||
/* Version 3.8.11 and later */
|
|
||||||
#define sqlite3_value_dup sqlite3_api->value_dup
|
|
||||||
#define sqlite3_value_free sqlite3_api->value_free
|
|
||||||
#define sqlite3_result_zeroblob64 sqlite3_api->result_zeroblob64
|
|
||||||
#define sqlite3_bind_zeroblob64 sqlite3_api->bind_zeroblob64
|
|
||||||
/* Version 3.9.0 and later */
|
|
||||||
#define sqlite3_value_subtype sqlite3_api->value_subtype
|
|
||||||
#define sqlite3_result_subtype sqlite3_api->result_subtype
|
|
||||||
/* Version 3.10.0 and later */
|
|
||||||
#define sqlite3_status64 sqlite3_api->status64
|
|
||||||
#define sqlite3_strlike sqlite3_api->strlike
|
|
||||||
#define sqlite3_db_cacheflush sqlite3_api->db_cacheflush
|
|
||||||
/* Version 3.12.0 and later */
|
|
||||||
#define sqlite3_system_errno sqlite3_api->system_errno
|
|
||||||
/* Version 3.14.0 and later */
|
|
||||||
#define sqlite3_trace_v2 sqlite3_api->trace_v2
|
|
||||||
#define sqlite3_expanded_sql sqlite3_api->expanded_sql
|
|
||||||
/* Version 3.18.0 and later */
|
|
||||||
#define sqlite3_set_last_insert_rowid sqlite3_api->set_last_insert_rowid
|
|
||||||
/* Version 3.20.0 and later */
|
|
||||||
#define sqlite3_prepare_v3 sqlite3_api->prepare_v3
|
|
||||||
#define sqlite3_prepare16_v3 sqlite3_api->prepare16_v3
|
|
||||||
#define sqlite3_bind_pointer sqlite3_api->bind_pointer
|
|
||||||
#define sqlite3_result_pointer sqlite3_api->result_pointer
|
|
||||||
#define sqlite3_value_pointer sqlite3_api->value_pointer
|
|
||||||
/* Version 3.22.0 and later */
|
|
||||||
#define sqlite3_vtab_nochange sqlite3_api->vtab_nochange
|
|
||||||
#define sqlite3_value_nochange sqlite3_api->value_nochange
|
|
||||||
#define sqlite3_vtab_collation sqlite3_api->vtab_collation
|
|
||||||
/* Version 3.24.0 and later */
|
|
||||||
#define sqlite3_keyword_count sqlite3_api->keyword_count
|
|
||||||
#define sqlite3_keyword_name sqlite3_api->keyword_name
|
|
||||||
#define sqlite3_keyword_check sqlite3_api->keyword_check
|
|
||||||
#define sqlite3_str_new sqlite3_api->str_new
|
|
||||||
#define sqlite3_str_finish sqlite3_api->str_finish
|
|
||||||
#define sqlite3_str_appendf sqlite3_api->str_appendf
|
|
||||||
#define sqlite3_str_vappendf sqlite3_api->str_vappendf
|
|
||||||
#define sqlite3_str_append sqlite3_api->str_append
|
|
||||||
#define sqlite3_str_appendall sqlite3_api->str_appendall
|
|
||||||
#define sqlite3_str_appendchar sqlite3_api->str_appendchar
|
|
||||||
#define sqlite3_str_reset sqlite3_api->str_reset
|
|
||||||
#define sqlite3_str_errcode sqlite3_api->str_errcode
|
|
||||||
#define sqlite3_str_length sqlite3_api->str_length
|
|
||||||
#define sqlite3_str_value sqlite3_api->str_value
|
|
||||||
#endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */
|
|
||||||
|
|
||||||
#if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION)
|
|
||||||
/* This case when the file really is being compiled as a loadable
|
|
||||||
** extension */
|
|
||||||
# define SQLITE_EXTENSION_INIT1 const sqlite3_api_routines *sqlite3_api=0;
|
|
||||||
# define SQLITE_EXTENSION_INIT2(v) sqlite3_api=v;
|
|
||||||
# define SQLITE_EXTENSION_INIT3 \
|
|
||||||
extern const sqlite3_api_routines *sqlite3_api;
|
|
||||||
#else
|
|
||||||
/* This case when the file is being statically linked into the
|
|
||||||
** application */
|
|
||||||
# define SQLITE_EXTENSION_INIT1 /*no-op*/
|
|
||||||
# define SQLITE_EXTENSION_INIT2(v) (void)v; /* unused parameter */
|
|
||||||
# define SQLITE_EXTENSION_INIT3 /*no-op*/
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif /* SQLITE3EXT_H */
|
|
||||||
#else // USE_LIBSQLITE3
|
|
||||||
// If users really want to link against the system sqlite3 we
|
|
||||||
// need to make this file a noop.
|
|
||||||
#endif
|
|
21
vendor/github.com/mattn/go-sqlite3/static_mock.go
generated
vendored
21
vendor/github.com/mattn/go-sqlite3/static_mock.go
generated
vendored
|
@ -1,21 +0,0 @@
|
||||||
// +build !cgo
|
|
||||||
|
|
||||||
package sqlite3
|
|
||||||
|
|
||||||
import (
|
|
||||||
"database/sql"
|
|
||||||
"database/sql/driver"
|
|
||||||
"errors"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
sql.Register("sqlite3", &SQLiteDriverMock{})
|
|
||||||
}
|
|
||||||
|
|
||||||
type SQLiteDriverMock struct{}
|
|
||||||
|
|
||||||
var errorMsg = errors.New("Binary was compiled with 'CGO_ENABLED=0', go-sqlite3 requires cgo to work. This is a stub")
|
|
||||||
|
|
||||||
func (SQLiteDriverMock) Open(s string) (driver.Conn, error) {
|
|
||||||
return nil, errorMsg
|
|
||||||
}
|
|
16
vendor/github.com/shurcooL/sanitized_anchor_name/.travis.yml
generated
vendored
16
vendor/github.com/shurcooL/sanitized_anchor_name/.travis.yml
generated
vendored
|
@ -1,16 +0,0 @@
|
||||||
sudo: false
|
|
||||||
language: go
|
|
||||||
go:
|
|
||||||
- 1.x
|
|
||||||
- master
|
|
||||||
matrix:
|
|
||||||
allow_failures:
|
|
||||||
- go: master
|
|
||||||
fast_finish: true
|
|
||||||
install:
|
|
||||||
- # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step).
|
|
||||||
script:
|
|
||||||
- go get -t -v ./...
|
|
||||||
- diff -u <(echo -n) <(gofmt -d -s .)
|
|
||||||
- go tool vet .
|
|
||||||
- go test -v -race ./...
|
|
21
vendor/github.com/shurcooL/sanitized_anchor_name/LICENSE
generated
vendored
21
vendor/github.com/shurcooL/sanitized_anchor_name/LICENSE
generated
vendored
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2015 Dmitri Shuralyov
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
36
vendor/github.com/shurcooL/sanitized_anchor_name/README.md
generated
vendored
36
vendor/github.com/shurcooL/sanitized_anchor_name/README.md
generated
vendored
|
@ -1,36 +0,0 @@
|
||||||
sanitized_anchor_name
|
|
||||||
=====================
|
|
||||||
|
|
||||||
[![Build Status](https://travis-ci.org/shurcooL/sanitized_anchor_name.svg?branch=master)](https://travis-ci.org/shurcooL/sanitized_anchor_name) [![GoDoc](https://godoc.org/github.com/shurcooL/sanitized_anchor_name?status.svg)](https://godoc.org/github.com/shurcooL/sanitized_anchor_name)
|
|
||||||
|
|
||||||
Package sanitized_anchor_name provides a func to create sanitized anchor names.
|
|
||||||
|
|
||||||
Its logic can be reused by multiple packages to create interoperable anchor names
|
|
||||||
and links to those anchors.
|
|
||||||
|
|
||||||
At this time, it does not try to ensure that generated anchor names
|
|
||||||
are unique, that responsibility falls on the caller.
|
|
||||||
|
|
||||||
Installation
|
|
||||||
------------
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go get -u github.com/shurcooL/sanitized_anchor_name
|
|
||||||
```
|
|
||||||
|
|
||||||
Example
|
|
||||||
-------
|
|
||||||
|
|
||||||
```Go
|
|
||||||
anchorName := sanitized_anchor_name.Create("This is a header")
|
|
||||||
|
|
||||||
fmt.Println(anchorName)
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// this-is-a-header
|
|
||||||
```
|
|
||||||
|
|
||||||
License
|
|
||||||
-------
|
|
||||||
|
|
||||||
- [MIT License](LICENSE)
|
|
29
vendor/github.com/shurcooL/sanitized_anchor_name/main.go
generated
vendored
29
vendor/github.com/shurcooL/sanitized_anchor_name/main.go
generated
vendored
|
@ -1,29 +0,0 @@
|
||||||
// Package sanitized_anchor_name provides a func to create sanitized anchor names.
|
|
||||||
//
|
|
||||||
// Its logic can be reused by multiple packages to create interoperable anchor names
|
|
||||||
// and links to those anchors.
|
|
||||||
//
|
|
||||||
// At this time, it does not try to ensure that generated anchor names
|
|
||||||
// are unique, that responsibility falls on the caller.
|
|
||||||
package sanitized_anchor_name // import "github.com/shurcooL/sanitized_anchor_name"
|
|
||||||
|
|
||||||
import "unicode"
|
|
||||||
|
|
||||||
// Create returns a sanitized anchor name for the given text.
|
|
||||||
func Create(text string) string {
|
|
||||||
var anchorName []rune
|
|
||||||
var futureDash = false
|
|
||||||
for _, r := range text {
|
|
||||||
switch {
|
|
||||||
case unicode.IsLetter(r) || unicode.IsNumber(r):
|
|
||||||
if futureDash && len(anchorName) > 0 {
|
|
||||||
anchorName = append(anchorName, '-')
|
|
||||||
}
|
|
||||||
futureDash = false
|
|
||||||
anchorName = append(anchorName, unicode.ToLower(r))
|
|
||||||
default:
|
|
||||||
futureDash = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return string(anchorName)
|
|
||||||
}
|
|
3
vendor/golang.org/x/net/AUTHORS
generated
vendored
3
vendor/golang.org/x/net/AUTHORS
generated
vendored
|
@ -1,3 +0,0 @@
|
||||||
# This source code refers to The Go Authors for copyright purposes.
|
|
||||||
# The master list of authors is in the main Go distribution,
|
|
||||||
# visible at http://tip.golang.org/AUTHORS.
|
|
3
vendor/golang.org/x/net/CONTRIBUTORS
generated
vendored
3
vendor/golang.org/x/net/CONTRIBUTORS
generated
vendored
|
@ -1,3 +0,0 @@
|
||||||
# This source code was written by the Go contributors.
|
|
||||||
# The master list of contributors is in the main Go distribution,
|
|
||||||
# visible at http://tip.golang.org/CONTRIBUTORS.
|
|
27
vendor/golang.org/x/net/LICENSE
generated
vendored
27
vendor/golang.org/x/net/LICENSE
generated
vendored
|
@ -1,27 +0,0 @@
|
||||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above
|
|
||||||
copyright notice, this list of conditions and the following disclaimer
|
|
||||||
in the documentation and/or other materials provided with the
|
|
||||||
distribution.
|
|
||||||
* Neither the name of Google Inc. nor the names of its
|
|
||||||
contributors may be used to endorse or promote products derived from
|
|
||||||
this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
22
vendor/golang.org/x/net/PATENTS
generated
vendored
22
vendor/golang.org/x/net/PATENTS
generated
vendored
|
@ -1,22 +0,0 @@
|
||||||
Additional IP Rights Grant (Patents)
|
|
||||||
|
|
||||||
"This implementation" means the copyrightable works distributed by
|
|
||||||
Google as part of the Go project.
|
|
||||||
|
|
||||||
Google hereby grants to You a perpetual, worldwide, non-exclusive,
|
|
||||||
no-charge, royalty-free, irrevocable (except as stated in this section)
|
|
||||||
patent license to make, have made, use, offer to sell, sell, import,
|
|
||||||
transfer and otherwise run, modify and propagate the contents of this
|
|
||||||
implementation of Go, where such license applies only to those patent
|
|
||||||
claims, both currently owned or controlled by Google and acquired in
|
|
||||||
the future, licensable by Google that are necessarily infringed by this
|
|
||||||
implementation of Go. This grant does not include claims that would be
|
|
||||||
infringed only as a consequence of further modification of this
|
|
||||||
implementation. If you or your agent or exclusive licensee institute or
|
|
||||||
order or agree to the institution of patent litigation against any
|
|
||||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging
|
|
||||||
that this implementation of Go or any code incorporated within this
|
|
||||||
implementation of Go constitutes direct or contributory patent
|
|
||||||
infringement, or inducement of patent infringement, then any patent
|
|
||||||
rights granted to you under this License for this implementation of Go
|
|
||||||
shall terminate as of the date such litigation is filed.
|
|
78
vendor/golang.org/x/net/html/atom/atom.go
generated
vendored
78
vendor/golang.org/x/net/html/atom/atom.go
generated
vendored
|
@ -1,78 +0,0 @@
|
||||||
// Copyright 2012 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// Package atom provides integer codes (also known as atoms) for a fixed set of
|
|
||||||
// frequently occurring HTML strings: tag names and attribute keys such as "p"
|
|
||||||
// and "id".
|
|
||||||
//
|
|
||||||
// Sharing an atom's name between all elements with the same tag can result in
|
|
||||||
// fewer string allocations when tokenizing and parsing HTML. Integer
|
|
||||||
// comparisons are also generally faster than string comparisons.
|
|
||||||
//
|
|
||||||
// The value of an atom's particular code is not guaranteed to stay the same
|
|
||||||
// between versions of this package. Neither is any ordering guaranteed:
|
|
||||||
// whether atom.H1 < atom.H2 may also change. The codes are not guaranteed to
|
|
||||||
// be dense. The only guarantees are that e.g. looking up "div" will yield
|
|
||||||
// atom.Div, calling atom.Div.String will return "div", and atom.Div != 0.
|
|
||||||
package atom // import "golang.org/x/net/html/atom"
|
|
||||||
|
|
||||||
// Atom is an integer code for a string. The zero value maps to "".
|
|
||||||
type Atom uint32
|
|
||||||
|
|
||||||
// String returns the atom's name.
|
|
||||||
func (a Atom) String() string {
|
|
||||||
start := uint32(a >> 8)
|
|
||||||
n := uint32(a & 0xff)
|
|
||||||
if start+n > uint32(len(atomText)) {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return atomText[start : start+n]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a Atom) string() string {
|
|
||||||
return atomText[a>>8 : a>>8+a&0xff]
|
|
||||||
}
|
|
||||||
|
|
||||||
// fnv computes the FNV hash with an arbitrary starting value h.
|
|
||||||
func fnv(h uint32, s []byte) uint32 {
|
|
||||||
for i := range s {
|
|
||||||
h ^= uint32(s[i])
|
|
||||||
h *= 16777619
|
|
||||||
}
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
func match(s string, t []byte) bool {
|
|
||||||
for i, c := range t {
|
|
||||||
if s[i] != c {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lookup returns the atom whose name is s. It returns zero if there is no
|
|
||||||
// such atom. The lookup is case sensitive.
|
|
||||||
func Lookup(s []byte) Atom {
|
|
||||||
if len(s) == 0 || len(s) > maxAtomLen {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
h := fnv(hash0, s)
|
|
||||||
if a := table[h&uint32(len(table)-1)]; int(a&0xff) == len(s) && match(a.string(), s) {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
if a := table[(h>>16)&uint32(len(table)-1)]; int(a&0xff) == len(s) && match(a.string(), s) {
|
|
||||||
return a
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// String returns a string whose contents are equal to s. In that sense, it is
|
|
||||||
// equivalent to string(s) but may be more efficient.
|
|
||||||
func String(s []byte) string {
|
|
||||||
if a := Lookup(s); a != 0 {
|
|
||||||
return a.String()
|
|
||||||
}
|
|
||||||
return string(s)
|
|
||||||
}
|
|
712
vendor/golang.org/x/net/html/atom/gen.go
generated
vendored
712
vendor/golang.org/x/net/html/atom/gen.go
generated
vendored
|
@ -1,712 +0,0 @@
|
||||||
// Copyright 2012 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
// +build ignore
|
|
||||||
|
|
||||||
//go:generate go run gen.go
|
|
||||||
//go:generate go run gen.go -test
|
|
||||||
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"go/format"
|
|
||||||
"io/ioutil"
|
|
||||||
"math/rand"
|
|
||||||
"os"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// identifier converts s to a Go exported identifier.
|
|
||||||
// It converts "div" to "Div" and "accept-charset" to "AcceptCharset".
|
|
||||||
func identifier(s string) string {
|
|
||||||
b := make([]byte, 0, len(s))
|
|
||||||
cap := true
|
|
||||||
for _, c := range s {
|
|
||||||
if c == '-' {
|
|
||||||
cap = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if cap && 'a' <= c && c <= 'z' {
|
|
||||||
c -= 'a' - 'A'
|
|
||||||
}
|
|
||||||
cap = false
|
|
||||||
b = append(b, byte(c))
|
|
||||||
}
|
|
||||||
return string(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
var test = flag.Bool("test", false, "generate table_test.go")
|
|
||||||
|
|
||||||
func genFile(name string, buf *bytes.Buffer) {
|
|
||||||
b, err := format.Source(buf.Bytes())
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
if err := ioutil.WriteFile(name, b, 0644); err != nil {
|
|
||||||
fmt.Fprintln(os.Stderr, err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
var all []string
|
|
||||||
all = append(all, elements...)
|
|
||||||
all = append(all, attributes...)
|
|
||||||
all = append(all, eventHandlers...)
|
|
||||||
all = append(all, extra...)
|
|
||||||
sort.Strings(all)
|
|
||||||
|
|
||||||
// uniq - lists have dups
|
|
||||||
w := 0
|
|
||||||
for _, s := range all {
|
|
||||||
if w == 0 || all[w-1] != s {
|
|
||||||
all[w] = s
|
|
||||||
w++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
all = all[:w]
|
|
||||||
|
|
||||||
if *test {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
fmt.Fprintln(&buf, "// Code generated by go generate gen.go; DO NOT EDIT.\n")
|
|
||||||
fmt.Fprintln(&buf, "//go:generate go run gen.go -test\n")
|
|
||||||
fmt.Fprintln(&buf, "package atom\n")
|
|
||||||
fmt.Fprintln(&buf, "var testAtomList = []string{")
|
|
||||||
for _, s := range all {
|
|
||||||
fmt.Fprintf(&buf, "\t%q,\n", s)
|
|
||||||
}
|
|
||||||
fmt.Fprintln(&buf, "}")
|
|
||||||
|
|
||||||
genFile("table_test.go", &buf)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find hash that minimizes table size.
|
|
||||||
var best *table
|
|
||||||
for i := 0; i < 1000000; i++ {
|
|
||||||
if best != nil && 1<<(best.k-1) < len(all) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
h := rand.Uint32()
|
|
||||||
for k := uint(0); k <= 16; k++ {
|
|
||||||
if best != nil && k >= best.k {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
var t table
|
|
||||||
if t.init(h, k, all) {
|
|
||||||
best = &t
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if best == nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "failed to construct string table\n")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Lay out strings, using overlaps when possible.
|
|
||||||
layout := append([]string{}, all...)
|
|
||||||
|
|
||||||
// Remove strings that are substrings of other strings
|
|
||||||
for changed := true; changed; {
|
|
||||||
changed = false
|
|
||||||
for i, s := range layout {
|
|
||||||
if s == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for j, t := range layout {
|
|
||||||
if i != j && t != "" && strings.Contains(s, t) {
|
|
||||||
changed = true
|
|
||||||
layout[j] = ""
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Join strings where one suffix matches another prefix.
|
|
||||||
for {
|
|
||||||
// Find best i, j, k such that layout[i][len-k:] == layout[j][:k],
|
|
||||||
// maximizing overlap length k.
|
|
||||||
besti := -1
|
|
||||||
bestj := -1
|
|
||||||
bestk := 0
|
|
||||||
for i, s := range layout {
|
|
||||||
if s == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for j, t := range layout {
|
|
||||||
if i == j {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for k := bestk + 1; k <= len(s) && k <= len(t); k++ {
|
|
||||||
if s[len(s)-k:] == t[:k] {
|
|
||||||
besti = i
|
|
||||||
bestj = j
|
|
||||||
bestk = k
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if bestk > 0 {
|
|
||||||
layout[besti] += layout[bestj][bestk:]
|
|
||||||
layout[bestj] = ""
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
text := strings.Join(layout, "")
|
|
||||||
|
|
||||||
atom := map[string]uint32{}
|
|
||||||
for _, s := range all {
|
|
||||||
off := strings.Index(text, s)
|
|
||||||
if off < 0 {
|
|
||||||
panic("lost string " + s)
|
|
||||||
}
|
|
||||||
atom[s] = uint32(off<<8 | len(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
|
||||||
// Generate the Go code.
|
|
||||||
fmt.Fprintln(&buf, "// Code generated by go generate gen.go; DO NOT EDIT.\n")
|
|
||||||
fmt.Fprintln(&buf, "//go:generate go run gen.go\n")
|
|
||||||
fmt.Fprintln(&buf, "package atom\n\nconst (")
|
|
||||||
|
|
||||||
// compute max len
|
|
||||||
maxLen := 0
|
|
||||||
for _, s := range all {
|
|
||||||
if maxLen < len(s) {
|
|
||||||
maxLen = len(s)
|
|
||||||
}
|
|
||||||
fmt.Fprintf(&buf, "\t%s Atom = %#x\n", identifier(s), atom[s])
|
|
||||||
}
|
|
||||||
fmt.Fprintln(&buf, ")\n")
|
|
||||||
|
|
||||||
fmt.Fprintf(&buf, "const hash0 = %#x\n\n", best.h0)
|
|
||||||
fmt.Fprintf(&buf, "const maxAtomLen = %d\n\n", maxLen)
|
|
||||||
|
|
||||||
fmt.Fprintf(&buf, "var table = [1<<%d]Atom{\n", best.k)
|
|
||||||
for i, s := range best.tab {
|
|
||||||
if s == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
fmt.Fprintf(&buf, "\t%#x: %#x, // %s\n", i, atom[s], s)
|
|
||||||
}
|
|
||||||
fmt.Fprintf(&buf, "}\n")
|
|
||||||
datasize := (1 << best.k) * 4
|
|
||||||
|
|
||||||
fmt.Fprintln(&buf, "const atomText =")
|
|
||||||
textsize := len(text)
|
|
||||||
for len(text) > 60 {
|
|
||||||
fmt.Fprintf(&buf, "\t%q +\n", text[:60])
|
|
||||||
text = text[60:]
|
|
||||||
}
|
|
||||||
fmt.Fprintf(&buf, "\t%q\n\n", text)
|
|
||||||
|
|
||||||
genFile("table.go", &buf)
|
|
||||||
|
|
||||||
fmt.Fprintf(os.Stdout, "%d atoms; %d string bytes + %d tables = %d total data\n", len(all), textsize, datasize, textsize+datasize)
|
|
||||||
}
|
|
||||||
|
|
||||||
type byLen []string
|
|
||||||
|
|
||||||
func (x byLen) Less(i, j int) bool { return len(x[i]) > len(x[j]) }
|
|
||||||
func (x byLen) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
|
||||||
func (x byLen) Len() int { return len(x) }
|
|
||||||
|
|
||||||
// fnv computes the FNV hash with an arbitrary starting value h.
|
|
||||||
func fnv(h uint32, s string) uint32 {
|
|
||||||
for i := 0; i < len(s); i++ {
|
|
||||||
h ^= uint32(s[i])
|
|
||||||
h *= 16777619
|
|
||||||
}
|
|
||||||
return h
|
|
||||||
}
|
|
||||||
|
|
||||||
// A table represents an attempt at constructing the lookup table.
|
|
||||||
// The lookup table uses cuckoo hashing, meaning that each string
|
|
||||||
// can be found in one of two positions.
|
|
||||||
type table struct {
|
|
||||||
h0 uint32
|
|
||||||
k uint
|
|
||||||
mask uint32
|
|
||||||
tab []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// hash returns the two hashes for s.
|
|
||||||
func (t *table) hash(s string) (h1, h2 uint32) {
|
|
||||||
h := fnv(t.h0, s)
|
|
||||||
h1 = h & t.mask
|
|
||||||
h2 = (h >> 16) & t.mask
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// init initializes the table with the given parameters.
|
|
||||||
// h0 is the initial hash value,
|
|
||||||
// k is the number of bits of hash value to use, and
|
|
||||||
// x is the list of strings to store in the table.
|
|
||||||
// init returns false if the table cannot be constructed.
|
|
||||||
func (t *table) init(h0 uint32, k uint, x []string) bool {
|
|
||||||
t.h0 = h0
|
|
||||||
t.k = k
|
|
||||||
t.tab = make([]string, 1<<k)
|
|
||||||
t.mask = 1<<k - 1
|
|
||||||
for _, s := range x {
|
|
||||||
if !t.insert(s) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// insert inserts s in the table.
|
|
||||||
func (t *table) insert(s string) bool {
|
|
||||||
h1, h2 := t.hash(s)
|
|
||||||
if t.tab[h1] == "" {
|
|
||||||
t.tab[h1] = s
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if t.tab[h2] == "" {
|
|
||||||
t.tab[h2] = s
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if t.push(h1, 0) {
|
|
||||||
t.tab[h1] = s
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if t.push(h2, 0) {
|
|
||||||
t.tab[h2] = s
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// push attempts to push aside the entry in slot i.
|
|
||||||
func (t *table) push(i uint32, depth int) bool {
|
|
||||||
if depth > len(t.tab) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
s := t.tab[i]
|
|
||||||
h1, h2 := t.hash(s)
|
|
||||||
j := h1 + h2 - i
|
|
||||||
if t.tab[j] != "" && !t.push(j, depth+1) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
t.tab[j] = s
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// The lists of element names and attribute keys were taken from
|
|
||||||
// https://html.spec.whatwg.org/multipage/indices.html#index
|
|
||||||
// as of the "HTML Living Standard - Last Updated 16 April 2018" version.
|
|
||||||
|
|
||||||
// "command", "keygen" and "menuitem" have been removed from the spec,
|
|
||||||
// but are kept here for backwards compatibility.
|
|
||||||
var elements = []string{
|
|
||||||
"a",
|
|
||||||
"abbr",
|
|
||||||
"address",
|
|
||||||
"area",
|
|
||||||
"article",
|
|
||||||
"aside",
|
|
||||||
"audio",
|
|
||||||
"b",
|
|
||||||
"base",
|
|
||||||
"bdi",
|
|
||||||
"bdo",
|
|
||||||
"blockquote",
|
|
||||||
"body",
|
|
||||||
"br",
|
|
||||||
"button",
|
|
||||||
"canvas",
|
|
||||||
"caption",
|
|
||||||
"cite",
|
|
||||||
"code",
|
|
||||||
"col",
|
|
||||||
"colgroup",
|
|
||||||
"command",
|
|
||||||
"data",
|
|
||||||
"datalist",
|
|
||||||
"dd",
|
|
||||||
"del",
|
|
||||||
"details",
|
|
||||||
"dfn",
|
|
||||||
"dialog",
|
|
||||||
"div",
|
|
||||||
"dl",
|
|
||||||
"dt",
|
|
||||||
"em",
|
|
||||||
"embed",
|
|
||||||
"fieldset",
|
|
||||||
"figcaption",
|
|
||||||
"figure",
|
|
||||||
"footer",
|
|
||||||
"form",
|
|
||||||
"h1",
|
|
||||||
"h2",
|
|
||||||
"h3",
|
|
||||||
"h4",
|
|
||||||
"h5",
|
|
||||||
"h6",
|
|
||||||
"head",
|
|
||||||
"header",
|
|
||||||
"hgroup",
|
|
||||||
"hr",
|
|
||||||
"html",
|
|
||||||
"i",
|
|
||||||
"iframe",
|
|
||||||
"img",
|
|
||||||
"input",
|
|
||||||
"ins",
|
|
||||||
"kbd",
|
|
||||||
"keygen",
|
|
||||||
"label",
|
|
||||||
"legend",
|
|
||||||
"li",
|
|
||||||
"link",
|
|
||||||
"main",
|
|
||||||
"map",
|
|
||||||
"mark",
|
|
||||||
"menu",
|
|
||||||
"menuitem",
|
|
||||||
"meta",
|
|
||||||
"meter",
|
|
||||||
"nav",
|
|
||||||
"noscript",
|
|
||||||
"object",
|
|
||||||
"ol",
|
|
||||||
"optgroup",
|
|
||||||
"option",
|
|
||||||
"output",
|
|
||||||
"p",
|
|
||||||
"param",
|
|
||||||
"picture",
|
|
||||||
"pre",
|
|
||||||
"progress",
|
|
||||||
"q",
|
|
||||||
"rp",
|
|
||||||
"rt",
|
|
||||||
"ruby",
|
|
||||||
"s",
|
|
||||||
"samp",
|
|
||||||
"script",
|
|
||||||
"section",
|
|
||||||
"select",
|
|
||||||
"slot",
|
|
||||||
"small",
|
|
||||||
"source",
|
|
||||||
"span",
|
|
||||||
"strong",
|
|
||||||
"style",
|
|
||||||
"sub",
|
|
||||||
"summary",
|
|
||||||
"sup",
|
|
||||||
"table",
|
|
||||||
"tbody",
|
|
||||||
"td",
|
|
||||||
"template",
|
|
||||||
"textarea",
|
|
||||||
"tfoot",
|
|
||||||
"th",
|
|
||||||
"thead",
|
|
||||||
"time",
|
|
||||||
"title",
|
|
||||||
"tr",
|
|
||||||
"track",
|
|
||||||
"u",
|
|
||||||
"ul",
|
|
||||||
"var",
|
|
||||||
"video",
|
|
||||||
"wbr",
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://html.spec.whatwg.org/multipage/indices.html#attributes-3
|
|
||||||
//
|
|
||||||
// "challenge", "command", "contextmenu", "dropzone", "icon", "keytype", "mediagroup",
|
|
||||||
// "radiogroup", "spellcheck", "scoped", "seamless", "sortable" and "sorted" have been removed from the spec,
|
|
||||||
// but are kept here for backwards compatibility.
|
|
||||||
var attributes = []string{
|
|
||||||
"abbr",
|
|
||||||
"accept",
|
|
||||||
"accept-charset",
|
|
||||||
"accesskey",
|
|
||||||
"action",
|
|
||||||
"allowfullscreen",
|
|
||||||
"allowpaymentrequest",
|
|
||||||
"allowusermedia",
|
|
||||||
"alt",
|
|
||||||
"as",
|
|
||||||
"async",
|
|
||||||
"autocomplete",
|
|
||||||
"autofocus",
|
|
||||||
"autoplay",
|
|
||||||
"challenge",
|
|
||||||
"charset",
|
|
||||||
"checked",
|
|
||||||
"cite",
|
|
||||||
"class",
|
|
||||||
"color",
|
|
||||||
"cols",
|
|
||||||
"colspan",
|
|
||||||
"command",
|
|
||||||
"content",
|
|
||||||
"contenteditable",
|
|
||||||
"contextmenu",
|
|
||||||
"controls",
|
|
||||||
"coords",
|
|
||||||
"crossorigin",
|
|
||||||
"data",
|
|
||||||
"datetime",
|
|
||||||
"default",
|
|
||||||
"defer",
|
|
||||||
"dir",
|
|
||||||
"dirname",
|
|
||||||
"disabled",
|
|
||||||
"download",
|
|
||||||
"draggable",
|
|
||||||
"dropzone",
|
|
||||||
"enctype",
|
|
||||||
"for",
|
|
||||||
"form",
|
|
||||||
"formaction",
|
|
||||||
"formenctype",
|
|
||||||
"formmethod",
|
|
||||||
"formnovalidate",
|
|
||||||
"formtarget",
|
|
||||||
"headers",
|
|
||||||
"height",
|
|
||||||
"hidden",
|
|
||||||
"high",
|
|
||||||
"href",
|
|
||||||
"hreflang",
|
|
||||||
"http-equiv",
|
|
||||||
"icon",
|
|
||||||
"id",
|
|
||||||
"inputmode",
|
|
||||||
"integrity",
|
|
||||||
"is",
|
|
||||||
"ismap",
|
|
||||||
"itemid",
|
|
||||||
"itemprop",
|
|
||||||
"itemref",
|
|
||||||
"itemscope",
|
|
||||||
"itemtype",
|
|
||||||
"keytype",
|
|
||||||
"kind",
|
|
||||||
"label",
|
|
||||||
"lang",
|
|
||||||
"list",
|
|
||||||
"loop",
|
|
||||||
"low",
|
|
||||||
"manifest",
|
|
||||||
"max",
|
|
||||||
"maxlength",
|
|
||||||
"media",
|
|
||||||
"mediagroup",
|
|
||||||
"method",
|
|
||||||
"min",
|
|
||||||
"minlength",
|
|
||||||
"multiple",
|
|
||||||
"muted",
|
|
||||||
"name",
|
|
||||||
"nomodule",
|
|
||||||
"nonce",
|
|
||||||
"novalidate",
|
|
||||||
"open",
|
|
||||||
"optimum",
|
|
||||||
"pattern",
|
|
||||||
"ping",
|
|
||||||
"placeholder",
|
|
||||||
"playsinline",
|
|
||||||
"poster",
|
|
||||||
"preload",
|
|
||||||
"radiogroup",
|
|
||||||
"readonly",
|
|
||||||
"referrerpolicy",
|
|
||||||
"rel",
|
|
||||||
"required",
|
|
||||||
"reversed",
|
|
||||||
"rows",
|
|
||||||
"rowspan",
|
|
||||||
"sandbox",
|
|
||||||
"spellcheck",
|
|
||||||
"scope",
|
|
||||||
"scoped",
|
|
||||||
"seamless",
|
|
||||||
"selected",
|
|
||||||
"shape",
|
|
||||||
"size",
|
|
||||||
"sizes",
|
|
||||||
"sortable",
|
|
||||||
"sorted",
|
|
||||||
"slot",
|
|
||||||
"span",
|
|
||||||
"spellcheck",
|
|
||||||
"src",
|
|
||||||
"srcdoc",
|
|
||||||
"srclang",
|
|
||||||
"srcset",
|
|
||||||
"start",
|
|
||||||
"step",
|
|
||||||
"style",
|
|
||||||
"tabindex",
|
|
||||||
"target",
|
|
||||||
"title",
|
|
||||||
"translate",
|
|
||||||
"type",
|
|
||||||
"typemustmatch",
|
|
||||||
"updateviacache",
|
|
||||||
"usemap",
|
|
||||||
"value",
|
|
||||||
"width",
|
|
||||||
"workertype",
|
|
||||||
"wrap",
|
|
||||||
}
|
|
||||||
|
|
||||||
// "onautocomplete", "onautocompleteerror", "onmousewheel",
|
|
||||||
// "onshow" and "onsort" have been removed from the spec,
|
|
||||||
// but are kept here for backwards compatibility.
|
|
||||||
var eventHandlers = []string{
|
|
||||||
"onabort",
|
|
||||||
"onautocomplete",
|
|
||||||
"onautocompleteerror",
|
|
||||||
"onauxclick",
|
|
||||||
"onafterprint",
|
|
||||||
"onbeforeprint",
|
|
||||||
"onbeforeunload",
|
|
||||||
"onblur",
|
|
||||||
"oncancel",
|
|
||||||
"oncanplay",
|
|
||||||
"oncanplaythrough",
|
|
||||||
"onchange",
|
|
||||||
"onclick",
|
|
||||||
"onclose",
|
|
||||||
"oncontextmenu",
|
|
||||||
"oncopy",
|
|
||||||
"oncuechange",
|
|
||||||
"oncut",
|
|
||||||
"ondblclick",
|
|
||||||
"ondrag",
|
|
||||||
"ondragend",
|
|
||||||
"ondragenter",
|
|
||||||
"ondragexit",
|
|
||||||
"ondragleave",
|
|
||||||
"ondragover",
|
|
||||||
"ondragstart",
|
|
||||||
"ondrop",
|
|
||||||
"ondurationchange",
|
|
||||||
"onemptied",
|
|
||||||
"onended",
|
|
||||||
"onerror",
|
|
||||||
"onfocus",
|
|
||||||
"onhashchange",
|
|
||||||
"oninput",
|
|
||||||
"oninvalid",
|
|
||||||
"onkeydown",
|
|
||||||
"onkeypress",
|
|
||||||
"onkeyup",
|
|
||||||
"onlanguagechange",
|
|
||||||
"onload",
|
|
||||||
"onloadeddata",
|
|
||||||
"onloadedmetadata",
|
|
||||||
"onloadend",
|
|
||||||
"onloadstart",
|
|
||||||
"onmessage",
|
|
||||||
"onmessageerror",
|
|
||||||
"onmousedown",
|
|
||||||
"onmouseenter",
|
|
||||||
"onmouseleave",
|
|
||||||
"onmousemove",
|
|
||||||
"onmouseout",
|
|
||||||
"onmouseover",
|
|
||||||
"onmouseup",
|
|
||||||
"onmousewheel",
|
|
||||||
"onwheel",
|
|
||||||
"onoffline",
|
|
||||||
"ononline",
|
|
||||||
"onpagehide",
|
|
||||||
"onpageshow",
|
|
||||||
"onpaste",
|
|
||||||
"onpause",
|
|
||||||
"onplay",
|
|
||||||
"onplaying",
|
|
||||||
"onpopstate",
|
|
||||||
"onprogress",
|
|
||||||
"onratechange",
|
|
||||||
"onreset",
|
|
||||||
"onresize",
|
|
||||||
"onrejectionhandled",
|
|
||||||
"onscroll",
|
|
||||||
"onsecuritypolicyviolation",
|
|
||||||
"onseeked",
|
|
||||||
"onseeking",
|
|
||||||
"onselect",
|
|
||||||
"onshow",
|
|
||||||
"onsort",
|
|
||||||
"onstalled",
|
|
||||||
"onstorage",
|
|
||||||
"onsubmit",
|
|
||||||
"onsuspend",
|
|
||||||
"ontimeupdate",
|
|
||||||
"ontoggle",
|
|
||||||
"onunhandledrejection",
|
|
||||||
"onunload",
|
|
||||||
"onvolumechange",
|
|
||||||
"onwaiting",
|
|
||||||
}
|
|
||||||
|
|
||||||
// extra are ad-hoc values not covered by any of the lists above.
|
|
||||||
var extra = []string{
|
|
||||||
"acronym",
|
|
||||||
"align",
|
|
||||||
"annotation",
|
|
||||||
"annotation-xml",
|
|
||||||
"applet",
|
|
||||||
"basefont",
|
|
||||||
"bgsound",
|
|
||||||
"big",
|
|
||||||
"blink",
|
|
||||||
"center",
|
|
||||||
"color",
|
|
||||||
"desc",
|
|
||||||
"face",
|
|
||||||
"font",
|
|
||||||
"foreignObject", // HTML is case-insensitive, but SVG-embedded-in-HTML is case-sensitive.
|
|
||||||
"foreignobject",
|
|
||||||
"frame",
|
|
||||||
"frameset",
|
|
||||||
"image",
|
|
||||||
"isindex",
|
|
||||||
"listing",
|
|
||||||
"malignmark",
|
|
||||||
"marquee",
|
|
||||||
"math",
|
|
||||||
"mglyph",
|
|
||||||
"mi",
|
|
||||||
"mn",
|
|
||||||
"mo",
|
|
||||||
"ms",
|
|
||||||
"mtext",
|
|
||||||
"nobr",
|
|
||||||
"noembed",
|
|
||||||
"noframes",
|
|
||||||
"plaintext",
|
|
||||||
"prompt",
|
|
||||||
"public",
|
|
||||||
"rb",
|
|
||||||
"rtc",
|
|
||||||
"spacer",
|
|
||||||
"strike",
|
|
||||||
"svg",
|
|
||||||
"system",
|
|
||||||
"tt",
|
|
||||||
"xmp",
|
|
||||||
}
|
|
783
vendor/golang.org/x/net/html/atom/table.go
generated
vendored
783
vendor/golang.org/x/net/html/atom/table.go
generated
vendored
|
@ -1,783 +0,0 @@
|
||||||
// Code generated by go generate gen.go; DO NOT EDIT.
|
|
||||||
|
|
||||||
//go:generate go run gen.go
|
|
||||||
|
|
||||||
package atom
|
|
||||||
|
|
||||||
const (
|
|
||||||
A Atom = 0x1
|
|
||||||
Abbr Atom = 0x4
|
|
||||||
Accept Atom = 0x1a06
|
|
||||||
AcceptCharset Atom = 0x1a0e
|
|
||||||
Accesskey Atom = 0x2c09
|
|
||||||
Acronym Atom = 0xaa07
|
|
||||||
Action Atom = 0x27206
|
|
||||||
Address Atom = 0x6f307
|
|
||||||
Align Atom = 0xb105
|
|
||||||
Allowfullscreen Atom = 0x2080f
|
|
||||||
Allowpaymentrequest Atom = 0xc113
|
|
||||||
Allowusermedia Atom = 0xdd0e
|
|
||||||
Alt Atom = 0xf303
|
|
||||||
Annotation Atom = 0x1c90a
|
|
||||||
AnnotationXml Atom = 0x1c90e
|
|
||||||
Applet Atom = 0x31906
|
|
||||||
Area Atom = 0x35604
|
|
||||||
Article Atom = 0x3fc07
|
|
||||||
As Atom = 0x3c02
|
|
||||||
Aside Atom = 0x10705
|
|
||||||
Async Atom = 0xff05
|
|
||||||
Audio Atom = 0x11505
|
|
||||||
Autocomplete Atom = 0x2780c
|
|
||||||
Autofocus Atom = 0x12109
|
|
||||||
Autoplay Atom = 0x13c08
|
|
||||||
B Atom = 0x101
|
|
||||||
Base Atom = 0x3b04
|
|
||||||
Basefont Atom = 0x3b08
|
|
||||||
Bdi Atom = 0xba03
|
|
||||||
Bdo Atom = 0x14b03
|
|
||||||
Bgsound Atom = 0x15e07
|
|
||||||
Big Atom = 0x17003
|
|
||||||
Blink Atom = 0x17305
|
|
||||||
Blockquote Atom = 0x1870a
|
|
||||||
Body Atom = 0x2804
|
|
||||||
Br Atom = 0x202
|
|
||||||
Button Atom = 0x19106
|
|
||||||
Canvas Atom = 0x10306
|
|
||||||
Caption Atom = 0x23107
|
|
||||||
Center Atom = 0x22006
|
|
||||||
Challenge Atom = 0x29b09
|
|
||||||
Charset Atom = 0x2107
|
|
||||||
Checked Atom = 0x47907
|
|
||||||
Cite Atom = 0x19c04
|
|
||||||
Class Atom = 0x56405
|
|
||||||
Code Atom = 0x5c504
|
|
||||||
Col Atom = 0x1ab03
|
|
||||||
Colgroup Atom = 0x1ab08
|
|
||||||
Color Atom = 0x1bf05
|
|
||||||
Cols Atom = 0x1c404
|
|
||||||
Colspan Atom = 0x1c407
|
|
||||||
Command Atom = 0x1d707
|
|
||||||
Content Atom = 0x58b07
|
|
||||||
Contenteditable Atom = 0x58b0f
|
|
||||||
Contextmenu Atom = 0x3800b
|
|
||||||
Controls Atom = 0x1de08
|
|
||||||
Coords Atom = 0x1ea06
|
|
||||||
Crossorigin Atom = 0x1fb0b
|
|
||||||
Data Atom = 0x4a504
|
|
||||||
Datalist Atom = 0x4a508
|
|
||||||
Datetime Atom = 0x2b808
|
|
||||||
Dd Atom = 0x2d702
|
|
||||||
Default Atom = 0x10a07
|
|
||||||
Defer Atom = 0x5c705
|
|
||||||
Del Atom = 0x45203
|
|
||||||
Desc Atom = 0x56104
|
|
||||||
Details Atom = 0x7207
|
|
||||||
Dfn Atom = 0x8703
|
|
||||||
Dialog Atom = 0xbb06
|
|
||||||
Dir Atom = 0x9303
|
|
||||||
Dirname Atom = 0x9307
|
|
||||||
Disabled Atom = 0x16408
|
|
||||||
Div Atom = 0x16b03
|
|
||||||
Dl Atom = 0x5e602
|
|
||||||
Download Atom = 0x46308
|
|
||||||
Draggable Atom = 0x17a09
|
|
||||||
Dropzone Atom = 0x40508
|
|
||||||
Dt Atom = 0x64b02
|
|
||||||
Em Atom = 0x6e02
|
|
||||||
Embed Atom = 0x6e05
|
|
||||||
Enctype Atom = 0x28d07
|
|
||||||
Face Atom = 0x21e04
|
|
||||||
Fieldset Atom = 0x22608
|
|
||||||
Figcaption Atom = 0x22e0a
|
|
||||||
Figure Atom = 0x24806
|
|
||||||
Font Atom = 0x3f04
|
|
||||||
Footer Atom = 0xf606
|
|
||||||
For Atom = 0x25403
|
|
||||||
ForeignObject Atom = 0x2540d
|
|
||||||
Foreignobject Atom = 0x2610d
|
|
||||||
Form Atom = 0x26e04
|
|
||||||
Formaction Atom = 0x26e0a
|
|
||||||
Formenctype Atom = 0x2890b
|
|
||||||
Formmethod Atom = 0x2a40a
|
|
||||||
Formnovalidate Atom = 0x2ae0e
|
|
||||||
Formtarget Atom = 0x2c00a
|
|
||||||
Frame Atom = 0x8b05
|
|
||||||
Frameset Atom = 0x8b08
|
|
||||||
H1 Atom = 0x15c02
|
|
||||||
H2 Atom = 0x2de02
|
|
||||||
H3 Atom = 0x30d02
|
|
||||||
H4 Atom = 0x34502
|
|
||||||
H5 Atom = 0x34f02
|
|
||||||
H6 Atom = 0x64d02
|
|
||||||
Head Atom = 0x33104
|
|
||||||
Header Atom = 0x33106
|
|
||||||
Headers Atom = 0x33107
|
|
||||||
Height Atom = 0x5206
|
|
||||||
Hgroup Atom = 0x2ca06
|
|
||||||
Hidden Atom = 0x2d506
|
|
||||||
High Atom = 0x2db04
|
|
||||||
Hr Atom = 0x15702
|
|
||||||
Href Atom = 0x2e004
|
|
||||||
Hreflang Atom = 0x2e008
|
|
||||||
Html Atom = 0x5604
|
|
||||||
HttpEquiv Atom = 0x2e80a
|
|
||||||
I Atom = 0x601
|
|
||||||
Icon Atom = 0x58a04
|
|
||||||
Id Atom = 0x10902
|
|
||||||
Iframe Atom = 0x2fc06
|
|
||||||
Image Atom = 0x30205
|
|
||||||
Img Atom = 0x30703
|
|
||||||
Input Atom = 0x44b05
|
|
||||||
Inputmode Atom = 0x44b09
|
|
||||||
Ins Atom = 0x20403
|
|
||||||
Integrity Atom = 0x23f09
|
|
||||||
Is Atom = 0x16502
|
|
||||||
Isindex Atom = 0x30f07
|
|
||||||
Ismap Atom = 0x31605
|
|
||||||
Itemid Atom = 0x38b06
|
|
||||||
Itemprop Atom = 0x19d08
|
|
||||||
Itemref Atom = 0x3cd07
|
|
||||||
Itemscope Atom = 0x67109
|
|
||||||
Itemtype Atom = 0x31f08
|
|
||||||
Kbd Atom = 0xb903
|
|
||||||
Keygen Atom = 0x3206
|
|
||||||
Keytype Atom = 0xd607
|
|
||||||
Kind Atom = 0x17704
|
|
||||||
Label Atom = 0x5905
|
|
||||||
Lang Atom = 0x2e404
|
|
||||||
Legend Atom = 0x18106
|
|
||||||
Li Atom = 0xb202
|
|
||||||
Link Atom = 0x17404
|
|
||||||
List Atom = 0x4a904
|
|
||||||
Listing Atom = 0x4a907
|
|
||||||
Loop Atom = 0x5d04
|
|
||||||
Low Atom = 0xc303
|
|
||||||
Main Atom = 0x1004
|
|
||||||
Malignmark Atom = 0xb00a
|
|
||||||
Manifest Atom = 0x6d708
|
|
||||||
Map Atom = 0x31803
|
|
||||||
Mark Atom = 0xb604
|
|
||||||
Marquee Atom = 0x32707
|
|
||||||
Math Atom = 0x32e04
|
|
||||||
Max Atom = 0x33d03
|
|
||||||
Maxlength Atom = 0x33d09
|
|
||||||
Media Atom = 0xe605
|
|
||||||
Mediagroup Atom = 0xe60a
|
|
||||||
Menu Atom = 0x38704
|
|
||||||
Menuitem Atom = 0x38708
|
|
||||||
Meta Atom = 0x4b804
|
|
||||||
Meter Atom = 0x9805
|
|
||||||
Method Atom = 0x2a806
|
|
||||||
Mglyph Atom = 0x30806
|
|
||||||
Mi Atom = 0x34702
|
|
||||||
Min Atom = 0x34703
|
|
||||||
Minlength Atom = 0x34709
|
|
||||||
Mn Atom = 0x2b102
|
|
||||||
Mo Atom = 0xa402
|
|
||||||
Ms Atom = 0x67402
|
|
||||||
Mtext Atom = 0x35105
|
|
||||||
Multiple Atom = 0x35f08
|
|
||||||
Muted Atom = 0x36705
|
|
||||||
Name Atom = 0x9604
|
|
||||||
Nav Atom = 0x1303
|
|
||||||
Nobr Atom = 0x3704
|
|
||||||
Noembed Atom = 0x6c07
|
|
||||||
Noframes Atom = 0x8908
|
|
||||||
Nomodule Atom = 0xa208
|
|
||||||
Nonce Atom = 0x1a605
|
|
||||||
Noscript Atom = 0x21608
|
|
||||||
Novalidate Atom = 0x2b20a
|
|
||||||
Object Atom = 0x26806
|
|
||||||
Ol Atom = 0x13702
|
|
||||||
Onabort Atom = 0x19507
|
|
||||||
Onafterprint Atom = 0x2360c
|
|
||||||
Onautocomplete Atom = 0x2760e
|
|
||||||
Onautocompleteerror Atom = 0x27613
|
|
||||||
Onauxclick Atom = 0x61f0a
|
|
||||||
Onbeforeprint Atom = 0x69e0d
|
|
||||||
Onbeforeunload Atom = 0x6e70e
|
|
||||||
Onblur Atom = 0x56d06
|
|
||||||
Oncancel Atom = 0x11908
|
|
||||||
Oncanplay Atom = 0x14d09
|
|
||||||
Oncanplaythrough Atom = 0x14d10
|
|
||||||
Onchange Atom = 0x41b08
|
|
||||||
Onclick Atom = 0x2f507
|
|
||||||
Onclose Atom = 0x36c07
|
|
||||||
Oncontextmenu Atom = 0x37e0d
|
|
||||||
Oncopy Atom = 0x39106
|
|
||||||
Oncuechange Atom = 0x3970b
|
|
||||||
Oncut Atom = 0x3a205
|
|
||||||
Ondblclick Atom = 0x3a70a
|
|
||||||
Ondrag Atom = 0x3b106
|
|
||||||
Ondragend Atom = 0x3b109
|
|
||||||
Ondragenter Atom = 0x3ba0b
|
|
||||||
Ondragexit Atom = 0x3c50a
|
|
||||||
Ondragleave Atom = 0x3df0b
|
|
||||||
Ondragover Atom = 0x3ea0a
|
|
||||||
Ondragstart Atom = 0x3f40b
|
|
||||||
Ondrop Atom = 0x40306
|
|
||||||
Ondurationchange Atom = 0x41310
|
|
||||||
Onemptied Atom = 0x40a09
|
|
||||||
Onended Atom = 0x42307
|
|
||||||
Onerror Atom = 0x42a07
|
|
||||||
Onfocus Atom = 0x43107
|
|
||||||
Onhashchange Atom = 0x43d0c
|
|
||||||
Oninput Atom = 0x44907
|
|
||||||
Oninvalid Atom = 0x45509
|
|
||||||
Onkeydown Atom = 0x45e09
|
|
||||||
Onkeypress Atom = 0x46b0a
|
|
||||||
Onkeyup Atom = 0x48007
|
|
||||||
Onlanguagechange Atom = 0x48d10
|
|
||||||
Onload Atom = 0x49d06
|
|
||||||
Onloadeddata Atom = 0x49d0c
|
|
||||||
Onloadedmetadata Atom = 0x4b010
|
|
||||||
Onloadend Atom = 0x4c609
|
|
||||||
Onloadstart Atom = 0x4cf0b
|
|
||||||
Onmessage Atom = 0x4da09
|
|
||||||
Onmessageerror Atom = 0x4da0e
|
|
||||||
Onmousedown Atom = 0x4e80b
|
|
||||||
Onmouseenter Atom = 0x4f30c
|
|
||||||
Onmouseleave Atom = 0x4ff0c
|
|
||||||
Onmousemove Atom = 0x50b0b
|
|
||||||
Onmouseout Atom = 0x5160a
|
|
||||||
Onmouseover Atom = 0x5230b
|
|
||||||
Onmouseup Atom = 0x52e09
|
|
||||||
Onmousewheel Atom = 0x53c0c
|
|
||||||
Onoffline Atom = 0x54809
|
|
||||||
Ononline Atom = 0x55108
|
|
||||||
Onpagehide Atom = 0x5590a
|
|
||||||
Onpageshow Atom = 0x5730a
|
|
||||||
Onpaste Atom = 0x57f07
|
|
||||||
Onpause Atom = 0x59a07
|
|
||||||
Onplay Atom = 0x5a406
|
|
||||||
Onplaying Atom = 0x5a409
|
|
||||||
Onpopstate Atom = 0x5ad0a
|
|
||||||
Onprogress Atom = 0x5b70a
|
|
||||||
Onratechange Atom = 0x5cc0c
|
|
||||||
Onrejectionhandled Atom = 0x5d812
|
|
||||||
Onreset Atom = 0x5ea07
|
|
||||||
Onresize Atom = 0x5f108
|
|
||||||
Onscroll Atom = 0x60008
|
|
||||||
Onsecuritypolicyviolation Atom = 0x60819
|
|
||||||
Onseeked Atom = 0x62908
|
|
||||||
Onseeking Atom = 0x63109
|
|
||||||
Onselect Atom = 0x63a08
|
|
||||||
Onshow Atom = 0x64406
|
|
||||||
Onsort Atom = 0x64f06
|
|
||||||
Onstalled Atom = 0x65909
|
|
||||||
Onstorage Atom = 0x66209
|
|
||||||
Onsubmit Atom = 0x66b08
|
|
||||||
Onsuspend Atom = 0x67b09
|
|
||||||
Ontimeupdate Atom = 0x400c
|
|
||||||
Ontoggle Atom = 0x68408
|
|
||||||
Onunhandledrejection Atom = 0x68c14
|
|
||||||
Onunload Atom = 0x6ab08
|
|
||||||
Onvolumechange Atom = 0x6b30e
|
|
||||||
Onwaiting Atom = 0x6c109
|
|
||||||
Onwheel Atom = 0x6ca07
|
|
||||||
Open Atom = 0x1a304
|
|
||||||
Optgroup Atom = 0x5f08
|
|
||||||
Optimum Atom = 0x6d107
|
|
||||||
Option Atom = 0x6e306
|
|
||||||
Output Atom = 0x51d06
|
|
||||||
P Atom = 0xc01
|
|
||||||
Param Atom = 0xc05
|
|
||||||
Pattern Atom = 0x6607
|
|
||||||
Picture Atom = 0x7b07
|
|
||||||
Ping Atom = 0xef04
|
|
||||||
Placeholder Atom = 0x1310b
|
|
||||||
Plaintext Atom = 0x1b209
|
|
||||||
Playsinline Atom = 0x1400b
|
|
||||||
Poster Atom = 0x2cf06
|
|
||||||
Pre Atom = 0x47003
|
|
||||||
Preload Atom = 0x48607
|
|
||||||
Progress Atom = 0x5b908
|
|
||||||
Prompt Atom = 0x53606
|
|
||||||
Public Atom = 0x58606
|
|
||||||
Q Atom = 0xcf01
|
|
||||||
Radiogroup Atom = 0x30a
|
|
||||||
Rb Atom = 0x3a02
|
|
||||||
Readonly Atom = 0x35708
|
|
||||||
Referrerpolicy Atom = 0x3d10e
|
|
||||||
Rel Atom = 0x48703
|
|
||||||
Required Atom = 0x24c08
|
|
||||||
Reversed Atom = 0x8008
|
|
||||||
Rows Atom = 0x9c04
|
|
||||||
Rowspan Atom = 0x9c07
|
|
||||||
Rp Atom = 0x23c02
|
|
||||||
Rt Atom = 0x19a02
|
|
||||||
Rtc Atom = 0x19a03
|
|
||||||
Ruby Atom = 0xfb04
|
|
||||||
S Atom = 0x2501
|
|
||||||
Samp Atom = 0x7804
|
|
||||||
Sandbox Atom = 0x12907
|
|
||||||
Scope Atom = 0x67505
|
|
||||||
Scoped Atom = 0x67506
|
|
||||||
Script Atom = 0x21806
|
|
||||||
Seamless Atom = 0x37108
|
|
||||||
Section Atom = 0x56807
|
|
||||||
Select Atom = 0x63c06
|
|
||||||
Selected Atom = 0x63c08
|
|
||||||
Shape Atom = 0x1e505
|
|
||||||
Size Atom = 0x5f504
|
|
||||||
Sizes Atom = 0x5f505
|
|
||||||
Slot Atom = 0x1ef04
|
|
||||||
Small Atom = 0x20605
|
|
||||||
Sortable Atom = 0x65108
|
|
||||||
Sorted Atom = 0x33706
|
|
||||||
Source Atom = 0x37806
|
|
||||||
Spacer Atom = 0x43706
|
|
||||||
Span Atom = 0x9f04
|
|
||||||
Spellcheck Atom = 0x4740a
|
|
||||||
Src Atom = 0x5c003
|
|
||||||
Srcdoc Atom = 0x5c006
|
|
||||||
Srclang Atom = 0x5f907
|
|
||||||
Srcset Atom = 0x6f906
|
|
||||||
Start Atom = 0x3fa05
|
|
||||||
Step Atom = 0x58304
|
|
||||||
Strike Atom = 0xd206
|
|
||||||
Strong Atom = 0x6dd06
|
|
||||||
Style Atom = 0x6ff05
|
|
||||||
Sub Atom = 0x66d03
|
|
||||||
Summary Atom = 0x70407
|
|
||||||
Sup Atom = 0x70b03
|
|
||||||
Svg Atom = 0x70e03
|
|
||||||
System Atom = 0x71106
|
|
||||||
Tabindex Atom = 0x4be08
|
|
||||||
Table Atom = 0x59505
|
|
||||||
Target Atom = 0x2c406
|
|
||||||
Tbody Atom = 0x2705
|
|
||||||
Td Atom = 0x9202
|
|
||||||
Template Atom = 0x71408
|
|
||||||
Textarea Atom = 0x35208
|
|
||||||
Tfoot Atom = 0xf505
|
|
||||||
Th Atom = 0x15602
|
|
||||||
Thead Atom = 0x33005
|
|
||||||
Time Atom = 0x4204
|
|
||||||
Title Atom = 0x11005
|
|
||||||
Tr Atom = 0xcc02
|
|
||||||
Track Atom = 0x1ba05
|
|
||||||
Translate Atom = 0x1f209
|
|
||||||
Tt Atom = 0x6802
|
|
||||||
Type Atom = 0xd904
|
|
||||||
Typemustmatch Atom = 0x2900d
|
|
||||||
U Atom = 0xb01
|
|
||||||
Ul Atom = 0xa702
|
|
||||||
Updateviacache Atom = 0x460e
|
|
||||||
Usemap Atom = 0x59e06
|
|
||||||
Value Atom = 0x1505
|
|
||||||
Var Atom = 0x16d03
|
|
||||||
Video Atom = 0x2f105
|
|
||||||
Wbr Atom = 0x57c03
|
|
||||||
Width Atom = 0x64905
|
|
||||||
Workertype Atom = 0x71c0a
|
|
||||||
Wrap Atom = 0x72604
|
|
||||||
Xmp Atom = 0x12f03
|
|
||||||
)
|
|
||||||
|
|
||||||
const hash0 = 0x81cdf10e
|
|
||||||
|
|
||||||
const maxAtomLen = 25
|
|
||||||
|
|
||||||
var table = [1 << 9]Atom{
|
|
||||||
0x1: 0xe60a, // mediagroup
|
|
||||||
0x2: 0x2e404, // lang
|
|
||||||
0x4: 0x2c09, // accesskey
|
|
||||||
0x5: 0x8b08, // frameset
|
|
||||||
0x7: 0x63a08, // onselect
|
|
||||||
0x8: 0x71106, // system
|
|
||||||
0xa: 0x64905, // width
|
|
||||||
0xc: 0x2890b, // formenctype
|
|
||||||
0xd: 0x13702, // ol
|
|
||||||
0xe: 0x3970b, // oncuechange
|
|
||||||
0x10: 0x14b03, // bdo
|
|
||||||
0x11: 0x11505, // audio
|
|
||||||
0x12: 0x17a09, // draggable
|
|
||||||
0x14: 0x2f105, // video
|
|
||||||
0x15: 0x2b102, // mn
|
|
||||||
0x16: 0x38704, // menu
|
|
||||||
0x17: 0x2cf06, // poster
|
|
||||||
0x19: 0xf606, // footer
|
|
||||||
0x1a: 0x2a806, // method
|
|
||||||
0x1b: 0x2b808, // datetime
|
|
||||||
0x1c: 0x19507, // onabort
|
|
||||||
0x1d: 0x460e, // updateviacache
|
|
||||||
0x1e: 0xff05, // async
|
|
||||||
0x1f: 0x49d06, // onload
|
|
||||||
0x21: 0x11908, // oncancel
|
|
||||||
0x22: 0x62908, // onseeked
|
|
||||||
0x23: 0x30205, // image
|
|
||||||
0x24: 0x5d812, // onrejectionhandled
|
|
||||||
0x26: 0x17404, // link
|
|
||||||
0x27: 0x51d06, // output
|
|
||||||
0x28: 0x33104, // head
|
|
||||||
0x29: 0x4ff0c, // onmouseleave
|
|
||||||
0x2a: 0x57f07, // onpaste
|
|
||||||
0x2b: 0x5a409, // onplaying
|
|
||||||
0x2c: 0x1c407, // colspan
|
|
||||||
0x2f: 0x1bf05, // color
|
|
||||||
0x30: 0x5f504, // size
|
|
||||||
0x31: 0x2e80a, // http-equiv
|
|
||||||
0x33: 0x601, // i
|
|
||||||
0x34: 0x5590a, // onpagehide
|
|
||||||
0x35: 0x68c14, // onunhandledrejection
|
|
||||||
0x37: 0x42a07, // onerror
|
|
||||||
0x3a: 0x3b08, // basefont
|
|
||||||
0x3f: 0x1303, // nav
|
|
||||||
0x40: 0x17704, // kind
|
|
||||||
0x41: 0x35708, // readonly
|
|
||||||
0x42: 0x30806, // mglyph
|
|
||||||
0x44: 0xb202, // li
|
|
||||||
0x46: 0x2d506, // hidden
|
|
||||||
0x47: 0x70e03, // svg
|
|
||||||
0x48: 0x58304, // step
|
|
||||||
0x49: 0x23f09, // integrity
|
|
||||||
0x4a: 0x58606, // public
|
|
||||||
0x4c: 0x1ab03, // col
|
|
||||||
0x4d: 0x1870a, // blockquote
|
|
||||||
0x4e: 0x34f02, // h5
|
|
||||||
0x50: 0x5b908, // progress
|
|
||||||
0x51: 0x5f505, // sizes
|
|
||||||
0x52: 0x34502, // h4
|
|
||||||
0x56: 0x33005, // thead
|
|
||||||
0x57: 0xd607, // keytype
|
|
||||||
0x58: 0x5b70a, // onprogress
|
|
||||||
0x59: 0x44b09, // inputmode
|
|
||||||
0x5a: 0x3b109, // ondragend
|
|
||||||
0x5d: 0x3a205, // oncut
|
|
||||||
0x5e: 0x43706, // spacer
|
|
||||||
0x5f: 0x1ab08, // colgroup
|
|
||||||
0x62: 0x16502, // is
|
|
||||||
0x65: 0x3c02, // as
|
|
||||||
0x66: 0x54809, // onoffline
|
|
||||||
0x67: 0x33706, // sorted
|
|
||||||
0x69: 0x48d10, // onlanguagechange
|
|
||||||
0x6c: 0x43d0c, // onhashchange
|
|
||||||
0x6d: 0x9604, // name
|
|
||||||
0x6e: 0xf505, // tfoot
|
|
||||||
0x6f: 0x56104, // desc
|
|
||||||
0x70: 0x33d03, // max
|
|
||||||
0x72: 0x1ea06, // coords
|
|
||||||
0x73: 0x30d02, // h3
|
|
||||||
0x74: 0x6e70e, // onbeforeunload
|
|
||||||
0x75: 0x9c04, // rows
|
|
||||||
0x76: 0x63c06, // select
|
|
||||||
0x77: 0x9805, // meter
|
|
||||||
0x78: 0x38b06, // itemid
|
|
||||||
0x79: 0x53c0c, // onmousewheel
|
|
||||||
0x7a: 0x5c006, // srcdoc
|
|
||||||
0x7d: 0x1ba05, // track
|
|
||||||
0x7f: 0x31f08, // itemtype
|
|
||||||
0x82: 0xa402, // mo
|
|
||||||
0x83: 0x41b08, // onchange
|
|
||||||
0x84: 0x33107, // headers
|
|
||||||
0x85: 0x5cc0c, // onratechange
|
|
||||||
0x86: 0x60819, // onsecuritypolicyviolation
|
|
||||||
0x88: 0x4a508, // datalist
|
|
||||||
0x89: 0x4e80b, // onmousedown
|
|
||||||
0x8a: 0x1ef04, // slot
|
|
||||||
0x8b: 0x4b010, // onloadedmetadata
|
|
||||||
0x8c: 0x1a06, // accept
|
|
||||||
0x8d: 0x26806, // object
|
|
||||||
0x91: 0x6b30e, // onvolumechange
|
|
||||||
0x92: 0x2107, // charset
|
|
||||||
0x93: 0x27613, // onautocompleteerror
|
|
||||||
0x94: 0xc113, // allowpaymentrequest
|
|
||||||
0x95: 0x2804, // body
|
|
||||||
0x96: 0x10a07, // default
|
|
||||||
0x97: 0x63c08, // selected
|
|
||||||
0x98: 0x21e04, // face
|
|
||||||
0x99: 0x1e505, // shape
|
|
||||||
0x9b: 0x68408, // ontoggle
|
|
||||||
0x9e: 0x64b02, // dt
|
|
||||||
0x9f: 0xb604, // mark
|
|
||||||
0xa1: 0xb01, // u
|
|
||||||
0xa4: 0x6ab08, // onunload
|
|
||||||
0xa5: 0x5d04, // loop
|
|
||||||
0xa6: 0x16408, // disabled
|
|
||||||
0xaa: 0x42307, // onended
|
|
||||||
0xab: 0xb00a, // malignmark
|
|
||||||
0xad: 0x67b09, // onsuspend
|
|
||||||
0xae: 0x35105, // mtext
|
|
||||||
0xaf: 0x64f06, // onsort
|
|
||||||
0xb0: 0x19d08, // itemprop
|
|
||||||
0xb3: 0x67109, // itemscope
|
|
||||||
0xb4: 0x17305, // blink
|
|
||||||
0xb6: 0x3b106, // ondrag
|
|
||||||
0xb7: 0xa702, // ul
|
|
||||||
0xb8: 0x26e04, // form
|
|
||||||
0xb9: 0x12907, // sandbox
|
|
||||||
0xba: 0x8b05, // frame
|
|
||||||
0xbb: 0x1505, // value
|
|
||||||
0xbc: 0x66209, // onstorage
|
|
||||||
0xbf: 0xaa07, // acronym
|
|
||||||
0xc0: 0x19a02, // rt
|
|
||||||
0xc2: 0x202, // br
|
|
||||||
0xc3: 0x22608, // fieldset
|
|
||||||
0xc4: 0x2900d, // typemustmatch
|
|
||||||
0xc5: 0xa208, // nomodule
|
|
||||||
0xc6: 0x6c07, // noembed
|
|
||||||
0xc7: 0x69e0d, // onbeforeprint
|
|
||||||
0xc8: 0x19106, // button
|
|
||||||
0xc9: 0x2f507, // onclick
|
|
||||||
0xca: 0x70407, // summary
|
|
||||||
0xcd: 0xfb04, // ruby
|
|
||||||
0xce: 0x56405, // class
|
|
||||||
0xcf: 0x3f40b, // ondragstart
|
|
||||||
0xd0: 0x23107, // caption
|
|
||||||
0xd4: 0xdd0e, // allowusermedia
|
|
||||||
0xd5: 0x4cf0b, // onloadstart
|
|
||||||
0xd9: 0x16b03, // div
|
|
||||||
0xda: 0x4a904, // list
|
|
||||||
0xdb: 0x32e04, // math
|
|
||||||
0xdc: 0x44b05, // input
|
|
||||||
0xdf: 0x3ea0a, // ondragover
|
|
||||||
0xe0: 0x2de02, // h2
|
|
||||||
0xe2: 0x1b209, // plaintext
|
|
||||||
0xe4: 0x4f30c, // onmouseenter
|
|
||||||
0xe7: 0x47907, // checked
|
|
||||||
0xe8: 0x47003, // pre
|
|
||||||
0xea: 0x35f08, // multiple
|
|
||||||
0xeb: 0xba03, // bdi
|
|
||||||
0xec: 0x33d09, // maxlength
|
|
||||||
0xed: 0xcf01, // q
|
|
||||||
0xee: 0x61f0a, // onauxclick
|
|
||||||
0xf0: 0x57c03, // wbr
|
|
||||||
0xf2: 0x3b04, // base
|
|
||||||
0xf3: 0x6e306, // option
|
|
||||||
0xf5: 0x41310, // ondurationchange
|
|
||||||
0xf7: 0x8908, // noframes
|
|
||||||
0xf9: 0x40508, // dropzone
|
|
||||||
0xfb: 0x67505, // scope
|
|
||||||
0xfc: 0x8008, // reversed
|
|
||||||
0xfd: 0x3ba0b, // ondragenter
|
|
||||||
0xfe: 0x3fa05, // start
|
|
||||||
0xff: 0x12f03, // xmp
|
|
||||||
0x100: 0x5f907, // srclang
|
|
||||||
0x101: 0x30703, // img
|
|
||||||
0x104: 0x101, // b
|
|
||||||
0x105: 0x25403, // for
|
|
||||||
0x106: 0x10705, // aside
|
|
||||||
0x107: 0x44907, // oninput
|
|
||||||
0x108: 0x35604, // area
|
|
||||||
0x109: 0x2a40a, // formmethod
|
|
||||||
0x10a: 0x72604, // wrap
|
|
||||||
0x10c: 0x23c02, // rp
|
|
||||||
0x10d: 0x46b0a, // onkeypress
|
|
||||||
0x10e: 0x6802, // tt
|
|
||||||
0x110: 0x34702, // mi
|
|
||||||
0x111: 0x36705, // muted
|
|
||||||
0x112: 0xf303, // alt
|
|
||||||
0x113: 0x5c504, // code
|
|
||||||
0x114: 0x6e02, // em
|
|
||||||
0x115: 0x3c50a, // ondragexit
|
|
||||||
0x117: 0x9f04, // span
|
|
||||||
0x119: 0x6d708, // manifest
|
|
||||||
0x11a: 0x38708, // menuitem
|
|
||||||
0x11b: 0x58b07, // content
|
|
||||||
0x11d: 0x6c109, // onwaiting
|
|
||||||
0x11f: 0x4c609, // onloadend
|
|
||||||
0x121: 0x37e0d, // oncontextmenu
|
|
||||||
0x123: 0x56d06, // onblur
|
|
||||||
0x124: 0x3fc07, // article
|
|
||||||
0x125: 0x9303, // dir
|
|
||||||
0x126: 0xef04, // ping
|
|
||||||
0x127: 0x24c08, // required
|
|
||||||
0x128: 0x45509, // oninvalid
|
|
||||||
0x129: 0xb105, // align
|
|
||||||
0x12b: 0x58a04, // icon
|
|
||||||
0x12c: 0x64d02, // h6
|
|
||||||
0x12d: 0x1c404, // cols
|
|
||||||
0x12e: 0x22e0a, // figcaption
|
|
||||||
0x12f: 0x45e09, // onkeydown
|
|
||||||
0x130: 0x66b08, // onsubmit
|
|
||||||
0x131: 0x14d09, // oncanplay
|
|
||||||
0x132: 0x70b03, // sup
|
|
||||||
0x133: 0xc01, // p
|
|
||||||
0x135: 0x40a09, // onemptied
|
|
||||||
0x136: 0x39106, // oncopy
|
|
||||||
0x137: 0x19c04, // cite
|
|
||||||
0x138: 0x3a70a, // ondblclick
|
|
||||||
0x13a: 0x50b0b, // onmousemove
|
|
||||||
0x13c: 0x66d03, // sub
|
|
||||||
0x13d: 0x48703, // rel
|
|
||||||
0x13e: 0x5f08, // optgroup
|
|
||||||
0x142: 0x9c07, // rowspan
|
|
||||||
0x143: 0x37806, // source
|
|
||||||
0x144: 0x21608, // noscript
|
|
||||||
0x145: 0x1a304, // open
|
|
||||||
0x146: 0x20403, // ins
|
|
||||||
0x147: 0x2540d, // foreignObject
|
|
||||||
0x148: 0x5ad0a, // onpopstate
|
|
||||||
0x14a: 0x28d07, // enctype
|
|
||||||
0x14b: 0x2760e, // onautocomplete
|
|
||||||
0x14c: 0x35208, // textarea
|
|
||||||
0x14e: 0x2780c, // autocomplete
|
|
||||||
0x14f: 0x15702, // hr
|
|
||||||
0x150: 0x1de08, // controls
|
|
||||||
0x151: 0x10902, // id
|
|
||||||
0x153: 0x2360c, // onafterprint
|
|
||||||
0x155: 0x2610d, // foreignobject
|
|
||||||
0x156: 0x32707, // marquee
|
|
||||||
0x157: 0x59a07, // onpause
|
|
||||||
0x158: 0x5e602, // dl
|
|
||||||
0x159: 0x5206, // height
|
|
||||||
0x15a: 0x34703, // min
|
|
||||||
0x15b: 0x9307, // dirname
|
|
||||||
0x15c: 0x1f209, // translate
|
|
||||||
0x15d: 0x5604, // html
|
|
||||||
0x15e: 0x34709, // minlength
|
|
||||||
0x15f: 0x48607, // preload
|
|
||||||
0x160: 0x71408, // template
|
|
||||||
0x161: 0x3df0b, // ondragleave
|
|
||||||
0x162: 0x3a02, // rb
|
|
||||||
0x164: 0x5c003, // src
|
|
||||||
0x165: 0x6dd06, // strong
|
|
||||||
0x167: 0x7804, // samp
|
|
||||||
0x168: 0x6f307, // address
|
|
||||||
0x169: 0x55108, // ononline
|
|
||||||
0x16b: 0x1310b, // placeholder
|
|
||||||
0x16c: 0x2c406, // target
|
|
||||||
0x16d: 0x20605, // small
|
|
||||||
0x16e: 0x6ca07, // onwheel
|
|
||||||
0x16f: 0x1c90a, // annotation
|
|
||||||
0x170: 0x4740a, // spellcheck
|
|
||||||
0x171: 0x7207, // details
|
|
||||||
0x172: 0x10306, // canvas
|
|
||||||
0x173: 0x12109, // autofocus
|
|
||||||
0x174: 0xc05, // param
|
|
||||||
0x176: 0x46308, // download
|
|
||||||
0x177: 0x45203, // del
|
|
||||||
0x178: 0x36c07, // onclose
|
|
||||||
0x179: 0xb903, // kbd
|
|
||||||
0x17a: 0x31906, // applet
|
|
||||||
0x17b: 0x2e004, // href
|
|
||||||
0x17c: 0x5f108, // onresize
|
|
||||||
0x17e: 0x49d0c, // onloadeddata
|
|
||||||
0x180: 0xcc02, // tr
|
|
||||||
0x181: 0x2c00a, // formtarget
|
|
||||||
0x182: 0x11005, // title
|
|
||||||
0x183: 0x6ff05, // style
|
|
||||||
0x184: 0xd206, // strike
|
|
||||||
0x185: 0x59e06, // usemap
|
|
||||||
0x186: 0x2fc06, // iframe
|
|
||||||
0x187: 0x1004, // main
|
|
||||||
0x189: 0x7b07, // picture
|
|
||||||
0x18c: 0x31605, // ismap
|
|
||||||
0x18e: 0x4a504, // data
|
|
||||||
0x18f: 0x5905, // label
|
|
||||||
0x191: 0x3d10e, // referrerpolicy
|
|
||||||
0x192: 0x15602, // th
|
|
||||||
0x194: 0x53606, // prompt
|
|
||||||
0x195: 0x56807, // section
|
|
||||||
0x197: 0x6d107, // optimum
|
|
||||||
0x198: 0x2db04, // high
|
|
||||||
0x199: 0x15c02, // h1
|
|
||||||
0x19a: 0x65909, // onstalled
|
|
||||||
0x19b: 0x16d03, // var
|
|
||||||
0x19c: 0x4204, // time
|
|
||||||
0x19e: 0x67402, // ms
|
|
||||||
0x19f: 0x33106, // header
|
|
||||||
0x1a0: 0x4da09, // onmessage
|
|
||||||
0x1a1: 0x1a605, // nonce
|
|
||||||
0x1a2: 0x26e0a, // formaction
|
|
||||||
0x1a3: 0x22006, // center
|
|
||||||
0x1a4: 0x3704, // nobr
|
|
||||||
0x1a5: 0x59505, // table
|
|
||||||
0x1a6: 0x4a907, // listing
|
|
||||||
0x1a7: 0x18106, // legend
|
|
||||||
0x1a9: 0x29b09, // challenge
|
|
||||||
0x1aa: 0x24806, // figure
|
|
||||||
0x1ab: 0xe605, // media
|
|
||||||
0x1ae: 0xd904, // type
|
|
||||||
0x1af: 0x3f04, // font
|
|
||||||
0x1b0: 0x4da0e, // onmessageerror
|
|
||||||
0x1b1: 0x37108, // seamless
|
|
||||||
0x1b2: 0x8703, // dfn
|
|
||||||
0x1b3: 0x5c705, // defer
|
|
||||||
0x1b4: 0xc303, // low
|
|
||||||
0x1b5: 0x19a03, // rtc
|
|
||||||
0x1b6: 0x5230b, // onmouseover
|
|
||||||
0x1b7: 0x2b20a, // novalidate
|
|
||||||
0x1b8: 0x71c0a, // workertype
|
|
||||||
0x1ba: 0x3cd07, // itemref
|
|
||||||
0x1bd: 0x1, // a
|
|
||||||
0x1be: 0x31803, // map
|
|
||||||
0x1bf: 0x400c, // ontimeupdate
|
|
||||||
0x1c0: 0x15e07, // bgsound
|
|
||||||
0x1c1: 0x3206, // keygen
|
|
||||||
0x1c2: 0x2705, // tbody
|
|
||||||
0x1c5: 0x64406, // onshow
|
|
||||||
0x1c7: 0x2501, // s
|
|
||||||
0x1c8: 0x6607, // pattern
|
|
||||||
0x1cc: 0x14d10, // oncanplaythrough
|
|
||||||
0x1ce: 0x2d702, // dd
|
|
||||||
0x1cf: 0x6f906, // srcset
|
|
||||||
0x1d0: 0x17003, // big
|
|
||||||
0x1d2: 0x65108, // sortable
|
|
||||||
0x1d3: 0x48007, // onkeyup
|
|
||||||
0x1d5: 0x5a406, // onplay
|
|
||||||
0x1d7: 0x4b804, // meta
|
|
||||||
0x1d8: 0x40306, // ondrop
|
|
||||||
0x1da: 0x60008, // onscroll
|
|
||||||
0x1db: 0x1fb0b, // crossorigin
|
|
||||||
0x1dc: 0x5730a, // onpageshow
|
|
||||||
0x1dd: 0x4, // abbr
|
|
||||||
0x1de: 0x9202, // td
|
|
||||||
0x1df: 0x58b0f, // contenteditable
|
|
||||||
0x1e0: 0x27206, // action
|
|
||||||
0x1e1: 0x1400b, // playsinline
|
|
||||||
0x1e2: 0x43107, // onfocus
|
|
||||||
0x1e3: 0x2e008, // hreflang
|
|
||||||
0x1e5: 0x5160a, // onmouseout
|
|
||||||
0x1e6: 0x5ea07, // onreset
|
|
||||||
0x1e7: 0x13c08, // autoplay
|
|
||||||
0x1e8: 0x63109, // onseeking
|
|
||||||
0x1ea: 0x67506, // scoped
|
|
||||||
0x1ec: 0x30a, // radiogroup
|
|
||||||
0x1ee: 0x3800b, // contextmenu
|
|
||||||
0x1ef: 0x52e09, // onmouseup
|
|
||||||
0x1f1: 0x2ca06, // hgroup
|
|
||||||
0x1f2: 0x2080f, // allowfullscreen
|
|
||||||
0x1f3: 0x4be08, // tabindex
|
|
||||||
0x1f6: 0x30f07, // isindex
|
|
||||||
0x1f7: 0x1a0e, // accept-charset
|
|
||||||
0x1f8: 0x2ae0e, // formnovalidate
|
|
||||||
0x1fb: 0x1c90e, // annotation-xml
|
|
||||||
0x1fc: 0x6e05, // embed
|
|
||||||
0x1fd: 0x21806, // script
|
|
||||||
0x1fe: 0xbb06, // dialog
|
|
||||||
0x1ff: 0x1d707, // command
|
|
||||||
}
|
|
||||||
|
|
||||||
const atomText = "abbradiogrouparamainavalueaccept-charsetbodyaccesskeygenobrb" +
|
|
||||||
"asefontimeupdateviacacheightmlabelooptgroupatternoembedetail" +
|
|
||||||
"sampictureversedfnoframesetdirnameterowspanomoduleacronymali" +
|
|
||||||
"gnmarkbdialogallowpaymentrequestrikeytypeallowusermediagroup" +
|
|
||||||
"ingaltfooterubyasyncanvasidefaultitleaudioncancelautofocusan" +
|
|
||||||
"dboxmplaceholderautoplaysinlinebdoncanplaythrough1bgsoundisa" +
|
|
||||||
"bledivarbigblinkindraggablegendblockquotebuttonabortcitempro" +
|
|
||||||
"penoncecolgrouplaintextrackcolorcolspannotation-xmlcommandco" +
|
|
||||||
"ntrolshapecoordslotranslatecrossoriginsmallowfullscreenoscri" +
|
|
||||||
"ptfacenterfieldsetfigcaptionafterprintegrityfigurequiredfore" +
|
|
||||||
"ignObjectforeignobjectformactionautocompleteerrorformenctype" +
|
|
||||||
"mustmatchallengeformmethodformnovalidatetimeformtargethgroup" +
|
|
||||||
"osterhiddenhigh2hreflanghttp-equivideonclickiframeimageimgly" +
|
|
||||||
"ph3isindexismappletitemtypemarqueematheadersortedmaxlength4m" +
|
|
||||||
"inlength5mtextareadonlymultiplemutedoncloseamlessourceoncont" +
|
|
||||||
"extmenuitemidoncopyoncuechangeoncutondblclickondragendondrag" +
|
|
||||||
"enterondragexitemreferrerpolicyondragleaveondragoverondragst" +
|
|
||||||
"articleondropzonemptiedondurationchangeonendedonerroronfocus" +
|
|
||||||
"paceronhashchangeoninputmodeloninvalidonkeydownloadonkeypres" +
|
|
||||||
"spellcheckedonkeyupreloadonlanguagechangeonloadeddatalisting" +
|
|
||||||
"onloadedmetadatabindexonloadendonloadstartonmessageerroronmo" +
|
|
||||||
"usedownonmouseenteronmouseleaveonmousemoveonmouseoutputonmou" +
|
|
||||||
"seoveronmouseupromptonmousewheelonofflineononlineonpagehides" +
|
|
||||||
"classectionbluronpageshowbronpastepublicontenteditableonpaus" +
|
|
||||||
"emaponplayingonpopstateonprogressrcdocodeferonratechangeonre" +
|
|
||||||
"jectionhandledonresetonresizesrclangonscrollonsecuritypolicy" +
|
|
||||||
"violationauxclickonseekedonseekingonselectedonshowidth6onsor" +
|
|
||||||
"tableonstalledonstorageonsubmitemscopedonsuspendontoggleonun" +
|
|
||||||
"handledrejectionbeforeprintonunloadonvolumechangeonwaitingon" +
|
|
||||||
"wheeloptimumanifestrongoptionbeforeunloaddressrcsetstylesumm" +
|
|
||||||
"arysupsvgsystemplateworkertypewrap"
|
|
104
vendor/golang.org/x/net/html/const.go
generated
vendored
104
vendor/golang.org/x/net/html/const.go
generated
vendored
|
@ -1,104 +0,0 @@
|
||||||
// Copyright 2011 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package html
|
|
||||||
|
|
||||||
// Section 12.2.4.2 of the HTML5 specification says "The following elements
|
|
||||||
// have varying levels of special parsing rules".
|
|
||||||
// https://html.spec.whatwg.org/multipage/syntax.html#the-stack-of-open-elements
|
|
||||||
var isSpecialElementMap = map[string]bool{
|
|
||||||
"address": true,
|
|
||||||
"applet": true,
|
|
||||||
"area": true,
|
|
||||||
"article": true,
|
|
||||||
"aside": true,
|
|
||||||
"base": true,
|
|
||||||
"basefont": true,
|
|
||||||
"bgsound": true,
|
|
||||||
"blockquote": true,
|
|
||||||
"body": true,
|
|
||||||
"br": true,
|
|
||||||
"button": true,
|
|
||||||
"caption": true,
|
|
||||||
"center": true,
|
|
||||||
"col": true,
|
|
||||||
"colgroup": true,
|
|
||||||
"dd": true,
|
|
||||||
"details": true,
|
|
||||||
"dir": true,
|
|
||||||
"div": true,
|
|
||||||
"dl": true,
|
|
||||||
"dt": true,
|
|
||||||
"embed": true,
|
|
||||||
"fieldset": true,
|
|
||||||
"figcaption": true,
|
|
||||||
"figure": true,
|
|
||||||
"footer": true,
|
|
||||||
"form": true,
|
|
||||||
"frame": true,
|
|
||||||
"frameset": true,
|
|
||||||
"h1": true,
|
|
||||||
"h2": true,
|
|
||||||
"h3": true,
|
|
||||||
"h4": true,
|
|
||||||
"h5": true,
|
|
||||||
"h6": true,
|
|
||||||
"head": true,
|
|
||||||
"header": true,
|
|
||||||
"hgroup": true,
|
|
||||||
"hr": true,
|
|
||||||
"html": true,
|
|
||||||
"iframe": true,
|
|
||||||
"img": true,
|
|
||||||
"input": true,
|
|
||||||
"isindex": true, // The 'isindex' element has been removed, but keep it for backwards compatibility.
|
|
||||||
"keygen": true,
|
|
||||||
"li": true,
|
|
||||||
"link": true,
|
|
||||||
"listing": true,
|
|
||||||
"main": true,
|
|
||||||
"marquee": true,
|
|
||||||
"menu": true,
|
|
||||||
"meta": true,
|
|
||||||
"nav": true,
|
|
||||||
"noembed": true,
|
|
||||||
"noframes": true,
|
|
||||||
"noscript": true,
|
|
||||||
"object": true,
|
|
||||||
"ol": true,
|
|
||||||
"p": true,
|
|
||||||
"param": true,
|
|
||||||
"plaintext": true,
|
|
||||||
"pre": true,
|
|
||||||
"script": true,
|
|
||||||
"section": true,
|
|
||||||
"select": true,
|
|
||||||
"source": true,
|
|
||||||
"style": true,
|
|
||||||
"summary": true,
|
|
||||||
"table": true,
|
|
||||||
"tbody": true,
|
|
||||||
"td": true,
|
|
||||||
"template": true,
|
|
||||||
"textarea": true,
|
|
||||||
"tfoot": true,
|
|
||||||
"th": true,
|
|
||||||
"thead": true,
|
|
||||||
"title": true,
|
|
||||||
"tr": true,
|
|
||||||
"track": true,
|
|
||||||
"ul": true,
|
|
||||||
"wbr": true,
|
|
||||||
"xmp": true,
|
|
||||||
}
|
|
||||||
|
|
||||||
func isSpecialElement(element *Node) bool {
|
|
||||||
switch element.Namespace {
|
|
||||||
case "", "html":
|
|
||||||
return isSpecialElementMap[element.Data]
|
|
||||||
case "svg":
|
|
||||||
return element.Data == "foreignObject"
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
106
vendor/golang.org/x/net/html/doc.go
generated
vendored
106
vendor/golang.org/x/net/html/doc.go
generated
vendored
|
@ -1,106 +0,0 @@
|
||||||
// Copyright 2010 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
/*
|
|
||||||
Package html implements an HTML5-compliant tokenizer and parser.
|
|
||||||
|
|
||||||
Tokenization is done by creating a Tokenizer for an io.Reader r. It is the
|
|
||||||
caller's responsibility to ensure that r provides UTF-8 encoded HTML.
|
|
||||||
|
|
||||||
z := html.NewTokenizer(r)
|
|
||||||
|
|
||||||
Given a Tokenizer z, the HTML is tokenized by repeatedly calling z.Next(),
|
|
||||||
which parses the next token and returns its type, or an error:
|
|
||||||
|
|
||||||
for {
|
|
||||||
tt := z.Next()
|
|
||||||
if tt == html.ErrorToken {
|
|
||||||
// ...
|
|
||||||
return ...
|
|
||||||
}
|
|
||||||
// Process the current token.
|
|
||||||
}
|
|
||||||
|
|
||||||
There are two APIs for retrieving the current token. The high-level API is to
|
|
||||||
call Token; the low-level API is to call Text or TagName / TagAttr. Both APIs
|
|
||||||
allow optionally calling Raw after Next but before Token, Text, TagName, or
|
|
||||||
TagAttr. In EBNF notation, the valid call sequence per token is:
|
|
||||||
|
|
||||||
Next {Raw} [ Token | Text | TagName {TagAttr} ]
|
|
||||||
|
|
||||||
Token returns an independent data structure that completely describes a token.
|
|
||||||
Entities (such as "<") are unescaped, tag names and attribute keys are
|
|
||||||
lower-cased, and attributes are collected into a []Attribute. For example:
|
|
||||||
|
|
||||||
for {
|
|
||||||
if z.Next() == html.ErrorToken {
|
|
||||||
// Returning io.EOF indicates success.
|
|
||||||
return z.Err()
|
|
||||||
}
|
|
||||||
emitToken(z.Token())
|
|
||||||
}
|
|
||||||
|
|
||||||
The low-level API performs fewer allocations and copies, but the contents of
|
|
||||||
the []byte values returned by Text, TagName and TagAttr may change on the next
|
|
||||||
call to Next. For example, to extract an HTML page's anchor text:
|
|
||||||
|
|
||||||
depth := 0
|
|
||||||
for {
|
|
||||||
tt := z.Next()
|
|
||||||
switch tt {
|
|
||||||
case html.ErrorToken:
|
|
||||||
return z.Err()
|
|
||||||
case html.TextToken:
|
|
||||||
if depth > 0 {
|
|
||||||
// emitBytes should copy the []byte it receives,
|
|
||||||
// if it doesn't process it immediately.
|
|
||||||
emitBytes(z.Text())
|
|
||||||
}
|
|
||||||
case html.StartTagToken, html.EndTagToken:
|
|
||||||
tn, _ := z.TagName()
|
|
||||||
if len(tn) == 1 && tn[0] == 'a' {
|
|
||||||
if tt == html.StartTagToken {
|
|
||||||
depth++
|
|
||||||
} else {
|
|
||||||
depth--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Parsing is done by calling Parse with an io.Reader, which returns the root of
|
|
||||||
the parse tree (the document element) as a *Node. It is the caller's
|
|
||||||
responsibility to ensure that the Reader provides UTF-8 encoded HTML. For
|
|
||||||
example, to process each anchor node in depth-first order:
|
|
||||||
|
|
||||||
doc, err := html.Parse(r)
|
|
||||||
if err != nil {
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
var f func(*html.Node)
|
|
||||||
f = func(n *html.Node) {
|
|
||||||
if n.Type == html.ElementNode && n.Data == "a" {
|
|
||||||
// Do something with n...
|
|
||||||
}
|
|
||||||
for c := n.FirstChild; c != nil; c = c.NextSibling {
|
|
||||||
f(c)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
f(doc)
|
|
||||||
|
|
||||||
The relevant specifications include:
|
|
||||||
https://html.spec.whatwg.org/multipage/syntax.html and
|
|
||||||
https://html.spec.whatwg.org/multipage/syntax.html#tokenization
|
|
||||||
*/
|
|
||||||
package html // import "golang.org/x/net/html"
|
|
||||||
|
|
||||||
// The tokenization algorithm implemented by this package is not a line-by-line
|
|
||||||
// transliteration of the relatively verbose state-machine in the WHATWG
|
|
||||||
// specification. A more direct approach is used instead, where the program
|
|
||||||
// counter implies the state, such as whether it is tokenizing a tag or a text
|
|
||||||
// node. Specification compliance is verified by checking expected and actual
|
|
||||||
// outputs over a test suite rather than aiming for algorithmic fidelity.
|
|
||||||
|
|
||||||
// TODO(nigeltao): Does a DOM API belong in this package or a separate one?
|
|
||||||
// TODO(nigeltao): How does parsing interact with a JavaScript engine?
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue