mirror of https://github.com/misterzym/jdocset.git
298 changed files with 356403 additions and 0 deletions
@ -0,0 +1,57 @@
|
||||
{ |
||||
"ImportPath": "github.com/william8th/javadocset", |
||||
"GoVersion": "go1.8", |
||||
"GodepVersion": "v79", |
||||
"Deps": [ |
||||
{ |
||||
"ImportPath": "github.com/go-stack/stack", |
||||
"Comment": "v1.5.3", |
||||
"Rev": "7a2f19628aabfe68f0766b59e74d6315f8347d22" |
||||
}, |
||||
{ |
||||
"ImportPath": "github.com/inconshreveable/log15", |
||||
"Comment": "v2.3-92-g39bacc2", |
||||
"Rev": "39bacc234bf1afd0b68573e95b45871f67ba2cd4" |
||||
}, |
||||
{ |
||||
"ImportPath": "github.com/inconshreveable/log15/term", |
||||
"Comment": "v2.3-92-g39bacc2", |
||||
"Rev": "39bacc234bf1afd0b68573e95b45871f67ba2cd4" |
||||
}, |
||||
{ |
||||
"ImportPath": "github.com/mattn/go-colorable", |
||||
"Comment": "v0.0.7-24-gded68f7", |
||||
"Rev": "ded68f7a9561c023e790de24279db7ebf473ea80" |
||||
}, |
||||
{ |
||||
"ImportPath": "github.com/mattn/go-isatty", |
||||
"Comment": "v0.0.2", |
||||
"Rev": "fc9e8d8ef48496124e79ae0df75490096eccf6fe" |
||||
}, |
||||
{ |
||||
"ImportPath": "github.com/mattn/go-sqlite3", |
||||
"Comment": "v1.2.0-80-gcf7286f", |
||||
"Rev": "cf7286f069c3ef596efcc87781a4653a2e7607bd" |
||||
}, |
||||
{ |
||||
"ImportPath": "github.com/yhat/scrape", |
||||
"Rev": "24b7890b0945459dbf91743e4d2ac5d75a51fee2" |
||||
}, |
||||
{ |
||||
"ImportPath": "golang.org/x/net/context", |
||||
"Rev": "7dcfb8076726a3fdd9353b6b8a1f1b6be6811bd6" |
||||
}, |
||||
{ |
||||
"ImportPath": "golang.org/x/net/html", |
||||
"Rev": "7dcfb8076726a3fdd9353b6b8a1f1b6be6811bd6" |
||||
}, |
||||
{ |
||||
"ImportPath": "golang.org/x/net/html/atom", |
||||
"Rev": "7dcfb8076726a3fdd9353b6b8a1f1b6be6811bd6" |
||||
}, |
||||
{ |
||||
"ImportPath": "golang.org/x/sys/unix", |
||||
"Rev": "a55a76086885b80f79961eacb876ebd8caf3868d" |
||||
} |
||||
] |
||||
} |
@ -0,0 +1,5 @@
|
||||
This directory tree is generated automatically by godep. |
||||
|
||||
Please do not edit. |
||||
|
||||
See https://github.com/tools/godep for more information. |
@ -0,0 +1,16 @@
|
||||
language: go |
||||
sudo: false |
||||
go: |
||||
- 1.2 |
||||
- 1.3 |
||||
- 1.4 |
||||
- 1.5 |
||||
- 1.6 |
||||
- tip |
||||
|
||||
before_install: |
||||
- go get github.com/mattn/goveralls |
||||
- go get golang.org/x/tools/cmd/cover |
||||
|
||||
script: |
||||
- goveralls -service=travis-ci |
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT) |
||||
|
||||
Copyright (c) 2014 Chris Hines |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
of this software and associated documentation files (the "Software"), to deal |
||||
in the Software without restriction, including without limitation the rights |
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
copies of the Software, and to permit persons to whom the Software is |
||||
furnished to do so, subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included in all |
||||
copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
SOFTWARE. |
@ -0,0 +1,38 @@
|
||||
[](https://godoc.org/github.com/go-stack/stack) |
||||
[](https://goreportcard.com/report/go-stack/stack) |
||||
[](https://travis-ci.org/go-stack/stack) |
||||
[](https://coveralls.io/github/go-stack/stack?branch=master) |
||||
|
||||
# stack |
||||
|
||||
Package stack implements utilities to capture, manipulate, and format call |
||||
stacks. It provides a simpler API than package runtime. |
||||
|
||||
The implementation takes care of the minutia and special cases of interpreting |
||||
the program counter (pc) values returned by runtime.Callers. |
||||
|
||||
## Versioning |
||||
|
||||
Package stack publishes releases via [semver](http://semver.org/) compatible Git |
||||
tags prefixed with a single 'v'. The master branch always contains the latest |
||||
release. The develop branch contains unreleased commits. |
||||
|
||||
## Formatting |
||||
|
||||
Package stack's types implement fmt.Formatter, which provides a simple and |
||||
flexible way to declaratively configure formatting when used with logging or |
||||
error tracking packages. |
||||
|
||||
```go |
||||
func DoTheThing() { |
||||
c := stack.Caller(0) |
||||
log.Print(c) // "source.go:10" |
||||
log.Printf("%+v", c) // "pkg/path/source.go:10" |
||||
log.Printf("%n", c) // "DoTheThing" |
||||
|
||||
s := stack.Trace().TrimRuntime() |
||||
log.Print(s) // "[source.go:15 caller.go:42 main.go:14]" |
||||
} |
||||
``` |
||||
|
||||
See the docs for all of the supported formatting options. |
@ -0,0 +1,349 @@
|
||||
// Package stack implements utilities to capture, manipulate, and format call
|
||||
// stacks. It provides a simpler API than package runtime.
|
||||
//
|
||||
// The implementation takes care of the minutia and special cases of
|
||||
// interpreting the program counter (pc) values returned by runtime.Callers.
|
||||
//
|
||||
// Package stack's types implement fmt.Formatter, which provides a simple and
|
||||
// flexible way to declaratively configure formatting when used with logging
|
||||
// or error tracking packages.
|
||||
package stack |
||||
|
||||
import ( |
||||
"bytes" |
||||
"errors" |
||||
"fmt" |
||||
"io" |
||||
"runtime" |
||||
"strconv" |
||||
"strings" |
||||
) |
||||
|
||||
// Call records a single function invocation from a goroutine stack.
|
||||
type Call struct { |
||||
fn *runtime.Func |
||||
pc uintptr |
||||
} |
||||
|
||||
// Caller returns a Call from the stack of the current goroutine. The argument
|
||||
// skip is the number of stack frames to ascend, with 0 identifying the
|
||||
// calling function.
|
||||
func Caller(skip int) Call { |
||||
var pcs [2]uintptr |
||||
n := runtime.Callers(skip+1, pcs[:]) |
||||
|
||||
var c Call |
||||
|
||||
if n < 2 { |
||||
return c |
||||
} |
||||
|
||||
c.pc = pcs[1] |
||||
if runtime.FuncForPC(pcs[0]) != sigpanic { |
||||
c.pc-- |
||||
} |
||||
c.fn = runtime.FuncForPC(c.pc) |
||||
return c |
||||
} |
||||
|
||||
// String implements fmt.Stinger. It is equivalent to fmt.Sprintf("%v", c).
|
||||
func (c Call) String() string { |
||||
return fmt.Sprint(c) |
||||
} |
||||
|
||||
// MarshalText implements encoding.TextMarshaler. It formats the Call the same
|
||||
// as fmt.Sprintf("%v", c).
|
||||
func (c Call) MarshalText() ([]byte, error) { |
||||
if c.fn == nil { |
||||
return nil, ErrNoFunc |
||||
} |
||||
buf := bytes.Buffer{} |
||||
fmt.Fprint(&buf, c) |
||||
return buf.Bytes(), nil |
||||
} |
||||
|
||||
// ErrNoFunc means that the Call has a nil *runtime.Func. The most likely
|
||||
// cause is a Call with the zero value.
|
||||
var ErrNoFunc = errors.New("no call stack information") |
||||
|
||||
// Format implements fmt.Formatter with support for the following verbs.
|
||||
//
|
||||
// %s source file
|
||||
// %d line number
|
||||
// %n function name
|
||||
// %v equivalent to %s:%d
|
||||
//
|
||||
// It accepts the '+' and '#' flags for most of the verbs as follows.
|
||||
//
|
||||
// %+s path of source file relative to the compile time GOPATH
|
||||
// %#s full path of source file
|
||||
// %+n import path qualified function name
|
||||
// %+v equivalent to %+s:%d
|
||||
// %#v equivalent to %#s:%d
|
||||
func (c Call) Format(s fmt.State, verb rune) { |
||||
if c.fn == nil { |
||||
fmt.Fprintf(s, "%%!%c(NOFUNC)", verb) |
||||
return |
||||
} |
||||
|
||||
switch verb { |
||||
case 's', 'v': |
||||
file, line := c.fn.FileLine(c.pc) |
||||
switch { |
||||
case s.Flag('#'): |
||||
// done
|
||||
case s.Flag('+'): |
||||
file = file[pkgIndex(file, c.fn.Name()):] |
||||
default: |
||||
const sep = "/" |
||||
if i := strings.LastIndex(file, sep); i != -1 { |
||||
file = file[i+len(sep):] |
||||
} |
||||
} |
||||
io.WriteString(s, file) |
||||
if verb == 'v' { |
||||
buf := [7]byte{':'} |
||||
s.Write(strconv.AppendInt(buf[:1], int64(line), 10)) |
||||
} |
||||
|
||||
case 'd': |
||||
_, line := c.fn.FileLine(c.pc) |
||||
buf := [6]byte{} |
||||
s.Write(strconv.AppendInt(buf[:0], int64(line), 10)) |
||||
|
||||
case 'n': |
||||
name := c.fn.Name() |
||||
if !s.Flag('+') { |
||||
const pathSep = "/" |
||||
if i := strings.LastIndex(name, pathSep); i != -1 { |
||||
name = name[i+len(pathSep):] |
||||
} |
||||
const pkgSep = "." |
||||
if i := strings.Index(name, pkgSep); i != -1 { |
||||
name = name[i+len(pkgSep):] |
||||
} |
||||
} |
||||
io.WriteString(s, name) |
||||
} |
||||
} |
||||
|
||||
// PC returns the program counter for this call frame; multiple frames may
|
||||
// have the same PC value.
|
||||
func (c Call) PC() uintptr { |
||||
return c.pc |
||||
} |
||||
|
||||
// name returns the import path qualified name of the function containing the
|
||||
// call.
|
||||
func (c Call) name() string { |
||||
if c.fn == nil { |
||||
return "???" |
||||
} |
||||
return c.fn.Name() |
||||
} |
||||
|
||||
func (c Call) file() string { |
||||
if c.fn == nil { |
||||
return "???" |
||||
} |
||||
file, _ := c.fn.FileLine(c.pc) |
||||
return file |
||||
} |
||||
|
||||
func (c Call) line() int { |
||||
if c.fn == nil { |
||||
return 0 |
||||
} |
||||
_, line := c.fn.FileLine(c.pc) |
||||
return line |
||||
} |
||||
|
||||
// CallStack records a sequence of function invocations from a goroutine
|
||||
// stack.
|
||||
type CallStack []Call |
||||
|
||||
// String implements fmt.Stinger. It is equivalent to fmt.Sprintf("%v", cs).
|
||||
func (cs CallStack) String() string { |
||||
return fmt.Sprint(cs) |
||||
} |
||||
|
||||
var ( |
||||
openBracketBytes = []byte("[") |
||||
closeBracketBytes = []byte("]") |
||||
spaceBytes = []byte(" ") |
||||
) |
||||
|
||||
// MarshalText implements encoding.TextMarshaler. It formats the CallStack the
|
||||
// same as fmt.Sprintf("%v", cs).
|
||||
func (cs CallStack) MarshalText() ([]byte, error) { |
||||
buf := bytes.Buffer{} |
||||
buf.Write(openBracketBytes) |
||||
for i, pc := range cs { |
||||
if pc.fn == nil { |
||||
return nil, ErrNoFunc |
||||
} |
||||
if i > 0 { |
||||
buf.Write(spaceBytes) |
||||
} |
||||
fmt.Fprint(&buf, pc) |
||||
} |
||||
buf.Write(closeBracketBytes) |
||||
return buf.Bytes(), nil |
||||
} |
||||
|
||||
// Format implements fmt.Formatter by printing the CallStack as square brackets
|
||||
// ([, ]) surrounding a space separated list of Calls each formatted with the
|
||||
// supplied verb and options.
|
||||
func (cs CallStack) Format(s fmt.State, verb rune) { |
||||
s.Write(openBracketBytes) |
||||
for i, pc := range cs { |
||||
if i > 0 { |
||||
s.Write(spaceBytes) |
||||
} |
||||
pc.Format(s, verb) |
||||
} |
||||
s.Write(closeBracketBytes) |
||||
} |
||||
|
||||
// findSigpanic intentionally executes faulting code to generate a stack trace
|
||||
// containing an entry for runtime.sigpanic.
|
||||
func findSigpanic() *runtime.Func { |
||||
var fn *runtime.Func |
||||
var p *int |
||||
func() int { |
||||
defer func() { |
||||
if p := recover(); p != nil { |
||||
var pcs [512]uintptr |
||||
n := runtime.Callers(2, pcs[:]) |
||||
for _, pc := range pcs[:n] { |
||||
f := runtime.FuncForPC(pc) |
||||
if f.Name() == "runtime.sigpanic" { |
||||
fn = f |
||||
break |
||||
} |
||||
} |
||||
} |
||||
}() |
||||
// intentional nil pointer dereference to trigger sigpanic
|
||||
return *p |
||||
}() |
||||
return fn |
||||
} |
||||
|
||||
var sigpanic = findSigpanic() |
||||
|
||||
// Trace returns a CallStack for the current goroutine with element 0
|
||||
// identifying the calling function.
|
||||
func Trace() CallStack { |
||||
var pcs [512]uintptr |
||||
n := runtime.Callers(2, pcs[:]) |
||||
cs := make([]Call, n) |
||||
|
||||
for i, pc := range pcs[:n] { |
||||
pcFix := pc |
||||
if i > 0 && cs[i-1].fn != sigpanic { |
||||
pcFix-- |
||||
} |
||||
cs[i] = Call{ |
||||
fn: runtime.FuncForPC(pcFix), |
||||
pc: pcFix, |
||||
} |
||||
} |
||||
|
||||
return cs |
||||
} |
||||
|
||||
// TrimBelow returns a slice of the CallStack with all entries below c
|
||||
// removed.
|
||||
func (cs CallStack) TrimBelow(c Call) CallStack { |
||||
for len(cs) > 0 && cs[0].pc != c.pc { |
||||
cs = cs[1:] |
||||
} |
||||
return cs |
||||
} |
||||
|
||||
// TrimAbove returns a slice of the CallStack with all entries above c
|
||||
// removed.
|
||||
func (cs CallStack) TrimAbove(c Call) CallStack { |
||||
for len(cs) > 0 && cs[len(cs)-1].pc != c.pc { |
||||
cs = cs[:len(cs)-1] |
||||
} |
||||
return cs |
||||
} |
||||
|
||||
// pkgIndex returns the index that results in file[index:] being the path of
|
||||
// file relative to the compile time GOPATH, and file[:index] being the
|
||||
// $GOPATH/src/ portion of file. funcName must be the name of a function in
|
||||
// file as returned by runtime.Func.Name.
|
||||
func pkgIndex(file, funcName string) int { |
||||
// As of Go 1.6.2 there is no direct way to know the compile time GOPATH
|
||||
// at runtime, but we can infer the number of path segments in the GOPATH.
|
||||
// We note that runtime.Func.Name() returns the function name qualified by
|
||||
// the import path, which does not include the GOPATH. Thus we can trim
|
||||
// segments from the beginning of the file path until the number of path
|
||||
// separators remaining is one more than the number of path separators in
|
||||
// the function name. For example, given:
|
||||
//
|
||||
// GOPATH /home/user
|
||||
// file /home/user/src/pkg/sub/file.go
|
||||
// fn.Name() pkg/sub.Type.Method
|
||||
//
|
||||
// We want to produce:
|
||||
//
|
||||
// file[:idx] == /home/user/src/
|
||||
// file[idx:] == pkg/sub/file.go
|
||||
//
|
||||
// From this we can easily see that fn.Name() has one less path separator
|
||||
// than our desired result for file[idx:]. We count separators from the
|
||||
// end of the file path until it finds two more than in the function name
|
||||
// and then move one character forward to preserve the initial path
|
||||
// segment without a leading separator.
|
||||
const sep = "/" |
||||
i := len(file) |
||||
for n := strings.Count(funcName, sep) + 2; n > 0; n-- { |
||||
i = strings.LastIndex(file[:i], sep) |
||||
if i == -1 { |
||||
i = -len(sep) |
||||
break |
||||
} |
||||
} |
||||
// get back to 0 or trim the leading separator
|
||||
return i + len(sep) |
||||
} |
||||
|
||||
var runtimePath string |
||||
|
||||
func init() { |
||||
var pcs [1]uintptr |
||||
runtime.Callers(0, pcs[:]) |
||||
fn := runtime.FuncForPC(pcs[0]) |
||||
file, _ := fn.FileLine(pcs[0]) |
||||
|
||||
idx := pkgIndex(file, fn.Name()) |
||||
|
||||
runtimePath = file[:idx] |
||||
if runtime.GOOS == "windows" { |
||||
runtimePath = strings.ToLower(runtimePath) |
||||
} |
||||
} |
||||
|
||||
func inGoroot(c Call) bool { |
||||
file := c.file() |
||||
if len(file) == 0 || file[0] == '?' { |
||||
return true |
||||
} |
||||
if runtime.GOOS == "windows" { |
||||
file = strings.ToLower(file) |
||||
} |
||||
return strings.HasPrefix(file, runtimePath) || strings.HasSuffix(file, "/_testmain.go") |
||||
} |
||||
|
||||
// TrimRuntime returns a slice of the CallStack with the topmost entries from
|
||||
// the go runtime removed. It considers any calls originating from unknown
|
||||
// files, files under GOROOT, or _testmain.go as part of the runtime.
|
||||
func (cs CallStack) TrimRuntime() CallStack { |
||||
for len(cs) > 0 && inGoroot(cs[len(cs)-1]) { |
||||
cs = cs[:len(cs)-1] |
||||
} |
||||
return cs |
||||
} |
@ -0,0 +1,12 @@
|
||||
language: go |
||||
go_import_path: github.com/inconshreveable/log15 |
||||
sudo: false |
||||
|
||||
go: |
||||
- 1.3 |
||||
- 1.4 |
||||
- 1.5 |
||||
- 1.6 |
||||
- 1.7 |
||||
- 1.8 |
||||
- tip |
@ -0,0 +1,11 @@
|
||||
Contributors to log15: |
||||
|
||||
- Aaron L |
||||
- Alan Shreve |
||||
- Chris Hines |
||||
- Ciaran Downey |
||||
- Dmitry Chestnykh |
||||
- Evan Shaw |
||||
- Péter Szilágyi |
||||
- Trevor Gattis |
||||
- Vincent Vanackere |
@ -0,0 +1,13 @@
|
||||
Copyright 2014 Alan Shreve |
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License"); |
||||
you may not use this file except in compliance with the License. |
||||
You may obtain a copy of the License at |
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0 |
||||
|
||||
Unless required by applicable law or agreed to in writing, software |
||||
distributed under the License is distributed on an "AS IS" BASIS, |
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
See the License for the specific language governing permissions and |
||||
limitations under the License. |
@ -0,0 +1,77 @@
|
||||
 |
||||
|
||||
# log15 [](https://godoc.org/github.com/inconshreveable/log15) [](https://travis-ci.org/inconshreveable/log15) |
||||
|
||||
Package log15 provides an opinionated, simple toolkit for best-practice logging in Go (golang) that is both human and machine readable. It is modeled after the Go standard library's [`io`](http://golang.org/pkg/io/) and [`net/http`](http://golang.org/pkg/net/http/) packages and is an alternative to the standard library's [`log`](http://golang.org/pkg/log/) package. |
||||
|
||||
## Features |
||||
- A simple, easy-to-understand API |
||||
- Promotes structured logging by encouraging use of key/value pairs |
||||
- Child loggers which inherit and add their own private context |
||||
- Lazy evaluation of expensive operations |
||||
- Simple Handler interface allowing for construction of flexible, custom logging configurations with a tiny API. |
||||
- Color terminal support |
||||
- Built-in support for logging to files, streams, syslog, and the network |
||||
- Support for forking records to multiple handlers, buffering records for output, failing over from failed handler writes, + more |
||||
|
||||
## Versioning |
||||
The API of the master branch of log15 should always be considered unstable. If you want to rely on a stable API, |
||||
you must vendor the library. |
||||
|
||||
## Importing |
||||
|
||||
```go |
||||
import log "github.com/inconshreveable/log15" |
||||
``` |
||||
|
||||
## Examples |
||||
|
||||
```go |
||||
// all loggers can have key/value context |
||||
srvlog := log.New("module", "app/server") |
||||
|
||||
// all log messages can have key/value context |
||||
srvlog.Warn("abnormal conn rate", "rate", curRate, "low", lowRate, "high", highRate) |
||||
|
||||
// child loggers with inherited context |
||||
connlog := srvlog.New("raddr", c.RemoteAddr()) |
||||
connlog.Info("connection open") |
||||
|
||||
// lazy evaluation |
||||
connlog.Debug("ping remote", "latency", log.Lazy{pingRemote}) |
||||
|
||||
// flexible configuration |
||||
srvlog.SetHandler(log.MultiHandler( |
||||
log.StreamHandler(os.Stderr, log.LogfmtFormat()), |
||||
log.LvlFilterHandler( |
||||
log.LvlError, |
||||
log.Must.FileHandler("errors.json", log.JsonFormat())))) |
||||
``` |
||||
|
||||
Will result in output that looks like this: |
||||
|
||||
``` |
||||
WARN[06-17|21:58:10] abnormal conn rate module=app/server rate=0.500 low=0.100 high=0.800 |
||||
INFO[06-17|21:58:10] connection open module=app/server raddr=10.0.0.1 |
||||
``` |
||||
|
||||
## Breaking API Changes |
||||
The following commits broke API stability. This reference is intended to help you understand the consequences of updating to a newer version |
||||
of log15. |
||||
|
||||
- 57a084d014d4150152b19e4e531399a7145d1540 - Added a `Get()` method to the `Logger` interface to retrieve the current handler |
||||
- 93404652ee366648fa622b64d1e2b67d75a3094a - `Record` field `Call` changed to `stack.Call` with switch to `github.com/go-stack/stack` |
||||
- a5e7613673c73281f58e15a87d2cf0cf111e8152 - Restored `syslog.Priority` argument to the `SyslogXxx` handler constructors |
||||
|
||||
## FAQ |
||||
|
||||
### The varargs style is brittle and error prone! Can I have type safety please? |
||||
Yes. Use `log.Ctx`: |
||||
|
||||
```go |
||||
srvlog := log.New(log.Ctx{"module": "app/server"}) |
||||
srvlog.Warn("abnormal conn rate", log.Ctx{"rate": curRate, "low": lowRate, "high": highRate}) |
||||
``` |
||||
|
||||
## License |
||||
Apache |
@ -0,0 +1,333 @@
|
||||
/* |
||||
Package log15 provides an opinionated, simple toolkit for best-practice logging that is |
||||
both human and machine readable. It is modeled after the standard library's io and net/http |
||||
packages. |
||||
|
||||
This package enforces you to only log key/value pairs. Keys must be strings. Values may be |
||||
any type that you like. The default output format is logfmt, but you may also choose to use |
||||
JSON instead if that suits you. Here's how you log: |
||||
|
||||
log.Info("page accessed", "path", r.URL.Path, "user_id", user.id) |
||||
|
||||
This will output a line that looks like: |
||||
|
||||
lvl=info t=2014-05-02T16:07:23-0700 msg="page accessed" path=/org/71/profile user_id=9 |
||||
|
||||
Getting Started |
||||
|
||||
To get started, you'll want to import the library: |
||||
|
||||
import log "github.com/inconshreveable/log15" |
||||
|
||||
|
||||
Now you're ready to start logging: |
||||
|
||||
func main() { |
||||
log.Info("Program starting", "args", os.Args()) |
||||
} |
||||
|
||||
|
||||
Convention |
||||
|
||||
Because recording a human-meaningful message is common and good practice, the first argument to every |
||||
logging method is the value to the *implicit* key 'msg'. |
||||
|
||||
Additionally, the level you choose for a message will be automatically added with the key 'lvl', and so |
||||
will the current timestamp with key 't'. |
||||
|
||||
You may supply any additional context as a set of key/value pairs to the logging function. log15 allows |
||||
you to favor terseness, ordering, and speed over safety. This is a reasonable tradeoff for |
||||
logging functions. You don't need to explicitly state keys/values, log15 understands that they alternate |
||||
in the variadic argument list: |
||||
|
||||
log.Warn("size out of bounds", "low", lowBound, "high", highBound, "val", val) |
||||
|
||||
If you really do favor your type-safety, you may choose to pass a log.Ctx instead: |
||||
|
||||
log.Warn("size out of bounds", log.Ctx{"low": lowBound, "high": highBound, "val": val}) |
||||
|
||||
|
||||
Context loggers |
||||
|
||||
Frequently, you want to add context to a logger so that you can track actions associated with it. An http |
||||
request is a good example. You can easily create new loggers that have context that is automatically included |
||||
with each log line: |
||||
|
||||
requestlogger := log.New("path", r.URL.Path) |
||||
|
||||
// later
|
||||
requestlogger.Debug("db txn commit", "duration", txnTimer.Finish()) |
||||
|
||||
This will output a log line that includes the path context that is attached to the logger: |
||||
|
||||
lvl=dbug t=2014-05-02T16:07:23-0700 path=/repo/12/add_hook msg="db txn commit" duration=0.12 |
||||
|
||||
|
||||
Handlers |
||||
|
||||
The Handler interface defines where log lines are printed to and how they are formated. Handler is a |
||||
single interface that is inspired by net/http's handler interface: |
||||
|
||||
type Handler interface { |
||||
Log(r *Record) error |
||||
} |
||||
|
||||
|
||||
Handlers can filter records, format them, or dispatch to multiple other Handlers. |
||||
This package implements a number of Handlers for common logging patterns that are |
||||
easily composed to create flexible, custom logging structures. |
||||
|
||||
Here's an example handler that prints logfmt output to Stdout: |
||||
|
||||
handler := log.StreamHandler(os.Stdout, log.LogfmtFormat()) |
||||
|
||||
Here's an example handler that defers to two other handlers. One handler only prints records |
||||
from the rpc package in logfmt to standard out. The other prints records at Error level |
||||
or above in JSON formatted output to the file /var/log/service.json |
||||
|
||||
handler := log.MultiHandler( |
||||
log.LvlFilterHandler(log.LvlError, log.Must.FileHandler("/var/log/service.json", log.JsonFormat())), |
||||
log.MatchFilterHandler("pkg", "app/rpc" log.StdoutHandler()) |
||||
) |
||||
|
||||
Logging File Names and Line Numbers |
||||
|
||||
This package implements three Handlers that add debugging information to the |
||||
context, CallerFileHandler, CallerFuncHandler and CallerStackHandler. Here's |
||||
an example that adds the source file and line number of each logging call to |
||||
the context. |
||||
|
||||
h := log.CallerFileHandler(log.StdoutHandler) |
||||
log.Root().SetHandler(h) |
||||
... |
||||
log.Error("open file", "err", err) |
||||
|
||||
This will output a line that looks like: |
||||
|
||||
lvl=eror t=2014-05-02T16:07:23-0700 msg="open file" err="file not found" caller=data.go:42 |
||||
|
||||
Here's an example that logs the call stack rather than just the call site. |
||||
|
||||
h := log.CallerStackHandler("%+v", log.StdoutHandler) |
||||
log.Root().SetHandler(h) |
||||
... |
||||
log.Error("open file", "err", err) |
||||
|
||||
This will output a line that looks like: |
||||
|
||||
lvl=eror t=2014-05-02T16:07:23-0700 msg="open file" err="file not found" stack="[pkg/data.go:42 pkg/cmd/main.go]" |
||||
|
||||
The "%+v" format instructs the handler to include the path of the source file |
||||
relative to the compile time GOPATH. The github.com/go-stack/stack package |
||||
documents the full list of formatting verbs and modifiers available. |
||||
|
||||
Custom Handlers |
||||
|
||||
The Handler interface is so simple that it's also trivial to write your own. Let's create an |
||||
example handler which tries to write to one handler, but if that fails it falls back to |
||||
writing to another handler and includes the error that it encountered when trying to write |
||||
to the primary. This might be useful when trying to log over a network socket, but if that |
||||
fails you want to log those records to a file on disk. |
||||
|
||||
type BackupHandler struct { |
||||
Primary Handler |
||||
Secondary Handler |
||||
} |
||||
|
||||
func (h *BackupHandler) Log (r *Record) error { |
||||
err := h.Primary.Log(r) |
||||
if err != nil { |
||||
r.Ctx = append(ctx, "primary_err", err) |
||||
return h.Secondary.Log(r) |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
This pattern is so useful that a generic version that handles an arbitrary number of Handlers |
||||
is included as part of this library called FailoverHandler. |
||||
|
||||
Logging Expensive Operations |
||||
|
||||
Sometimes, you want to log values that are extremely expensive to compute, but you don't want to pay |
||||
the price of computing them if you haven't turned up your logging level to a high level of detail. |
||||
|
||||
This package provides a simple type to annotate a logging operation that you want to be evaluated |
||||
lazily, just when it is about to be logged, so that it would not be evaluated if an upstream Handler |
||||
filters it out. Just wrap any function which takes no arguments with the log.Lazy type. For example: |
||||
|
||||
func factorRSAKey() (factors []int) { |
||||
// return the factors of a very large number
|
||||
} |
||||
|
||||
log.Debug("factors", log.Lazy{factorRSAKey}) |
||||
|
||||
If this message is not logged for any reason (like logging at the Error level), then |
||||
factorRSAKey is never evaluated. |
||||
|
||||
Dynamic context values |
||||
|
||||
The same log.Lazy mechanism can be used to attach context to a logger which you want to be |
||||
evaluated when the message is logged, but not when the logger is created. For example, let's imagine |
||||
a game where you have Player objects: |
||||
|
||||
type Player struct { |
||||
name string |
||||
alive bool |
||||
log.Logger |
||||
} |
||||
|
||||
You always want to log a player's name and whether they're alive or dead, so when you create the player |
||||
object, you might do: |
||||
|
||||
p := &Player{name: name, alive: true} |
||||
p.Logger = log.New("name", p.name, "alive", p.alive) |
||||
|
||||
Only now, even after a player has died, the logger will still report they are alive because the logging |
||||
context is evaluated when the logger was created. By using the Lazy wrapper, we can defer the evaluation |
||||
of whether the player is alive or not to each log message, so that the log records will reflect the player's |
||||
current state no matter when the log message is written: |
||||
|
||||
p := &Player{name: name, alive: true} |
||||
isAlive := func() bool { return p.alive } |
||||
player.Logger = log.New("name", p.name, "alive", log.Lazy{isAlive}) |
||||
|
||||
Terminal Format |
||||
|
||||
If log15 detects that stdout is a terminal, it will configure the default |
||||
handler for it (which is log.StdoutHandler) to use TerminalFormat. This format |
||||
logs records nicely for your terminal, including color-coded output based |
||||
on log level. |
||||
|
||||
Error Handling |
||||
|
||||
Becasuse log15 allows you to step around the type system, there are a few ways you can specify |
||||
invalid arguments to the logging functions. You could, for example, wrap something that is not |
||||
a zero-argument function with log.Lazy or pass a context key that is not a string. Since logging libraries |
||||
are typically the mechanism by which errors are reported, it would be onerous for the logging functions |
||||
to return errors. Instead, log15 handles errors by making these guarantees to you: |
||||
|
||||
- Any log record containing an error will still be printed with the error explained to you as part of the log record. |
||||
|
||||
- Any log record containing an error will include the context key LOG15_ERROR, enabling you to easily |
||||
(and if you like, automatically) detect if any of your logging calls are passing bad values. |
||||
|
||||
Understanding this, you might wonder why the Handler interface can return an error value in its Log method. Handlers |
||||
are encouraged to return errors only if they fail to write their log records out to an external source like if the |
||||
syslog daemon is not responding. This allows the construction of useful handlers which cope with those failures |
||||
like the FailoverHandler. |
||||
|
||||
Library Use |
||||
|
||||
log15 is intended to be useful for library authors as a way to provide configurable logging to |
||||
users of their library. Best practice for use in a library is to always disable all output for your logger |
||||
by default and to provide a public Logger instance that consumers of your library can configure. Like so: |
||||
|
||||
package yourlib |
||||
|
||||
import "github.com/inconshreveable/log15" |
||||
|
||||
var Log = log.New() |
||||
|
||||
func init() { |
||||
Log.SetHandler(log.DiscardHandler()) |
||||
} |
||||
|
||||
Users of your library may then enable it if they like: |
||||
|
||||
import "github.com/inconshreveable/log15" |
||||
import "example.com/yourlib" |
||||
|
||||
func main() { |
||||
handler := // custom handler setup
|
||||
yourlib.Log.SetHandler(handler) |
||||
} |
||||
|
||||
Best practices attaching logger context |
||||
|
||||
The ability to attach context to a logger is a powerful one. Where should you do it and why? |
||||
I favor embedding a Logger directly into any persistent object in my application and adding |
||||
unique, tracing context keys to it. For instance, imagine I am writing a web browser: |
||||
|
||||
type Tab struct { |
||||
url string |
||||
render *RenderingContext |
||||
// ...
|
||||
|
||||
Logger |
||||
} |
||||
|
||||
func NewTab(url string) *Tab { |
||||
return &Tab { |
||||
// ...
|
||||
url: url, |
||||
|
||||
Logger: log.New("url", url), |
||||
} |
||||
} |
||||
|
||||
When a new tab is created, I assign a logger to it with the url of |
||||
the tab as context so it can easily be traced through the logs. |
||||
Now, whenever we perform any operation with the tab, we'll log with its |
||||
embedded logger and it will include the tab title automatically: |
||||
|
||||
tab.Debug("moved position", "idx", tab.idx) |
||||
|
||||
There's only one problem. What if the tab url changes? We could |
||||
use log.Lazy to make sure the current url is always written, but that |
||||
would mean that we couldn't trace a tab's full lifetime through our |
||||
logs after the user navigate to a new URL. |
||||
|
||||
Instead, think about what values to attach to your loggers the |
||||
same way you think about what to use as a key in a SQL database schema. |
||||
If it's possible to use a natural key that is unique for the lifetime of the |
||||
object, do so. But otherwise, log15's ext package has a handy RandId |
||||
function to let you generate what you might call "surrogate keys" |
||||
They're just random hex identifiers to use for tracing. Back to our |
||||
Tab example, we would prefer to set up our Logger like so: |
||||
|
||||
import logext "github.com/inconshreveable/log15/ext" |
||||
|
||||
t := &Tab { |
||||
// ...
|
||||
url: url, |
||||
} |
||||
|
||||
t.Logger = log.New("id", logext.RandId(8), "url", log.Lazy{t.getUrl}) |
||||
return t |
||||
|
||||
Now we'll have a unique traceable identifier even across loading new urls, but |
||||
we'll still be able to see the tab's current url in the log messages. |
||||
|
||||
Must |
||||
|
||||
For all Handler functions which can return an error, there is a version of that |
||||
function which will return no error but panics on failure. They are all available |
||||
on the Must object. For example: |
||||
|
||||
log.Must.FileHandler("/path", log.JsonFormat) |
||||
log.Must.NetHandler("tcp", ":1234", log.JsonFormat) |
||||
|
||||
Inspiration and Credit |
||||
|
||||
All of the following excellent projects inspired the design of this library: |
||||
|
||||
code.google.com/p/log4go |
||||
|
||||
github.com/op/go-logging |
||||
|
||||
github.com/technoweenie/grohl |
||||
|
||||
github.com/Sirupsen/logrus |
||||
|
||||
github.com/kr/logfmt |
||||
|
||||
github.com/spacemonkeygo/spacelog |
||||
|
||||
golang's stdlib, notably io and net/http |
||||
|
||||
The Name |
||||
|
||||
https://xkcd.com/927/
|
||||
|
||||
*/ |
||||
package log15 |
@ -0,0 +1,279 @@
|
||||
package log15 |
||||
|
||||
import ( |
||||
"bytes" |
||||
"encoding/json" |
||||
"fmt" |
||||
"reflect" |
||||
"strconv" |
||||
"strings" |
||||
"sync" |
||||
"time" |
||||
) |
||||
|
||||
const ( |
||||
timeFormat = "2006-01-02T15:04:05-0700" |
||||
termTimeFormat = "01-02|15:04:05" |
||||
floatFormat = 'f' |
||||
termMsgJust = 40 |
||||
) |
||||
|
||||
type Format interface { |
||||
Format(r *Record) []byte |
||||
} |
||||
|
||||
// FormatFunc returns a new Format object which uses
|
||||
// the given function to perform record formatting.
|
||||
func FormatFunc(f func(*Record) []byte) Format { |
||||
return formatFunc(f) |
||||
} |
||||
|
||||
type formatFunc func(*Record) []byte |
||||
|
||||
func (f formatFunc) Format(r *Record) []byte { |
||||
return f(r) |
||||
} |
||||
|
||||
// TerminalFormat formats log records optimized for human readability on
|
||||
// a terminal with color-coded level output and terser human friendly timestamp.
|
||||
// This format should only be used for interactive programs or while developing.
|
||||
//
|
||||
// [TIME] [LEVEL] MESAGE key=value key=value ...
|
||||
//
|
||||
// Example:
|
||||
//
|
||||
// [May 16 20:58:45] [DBUG] remove route ns=haproxy addr=127.0.0.1:50002
|
||||
//
|
||||
func TerminalFormat() Format { |
||||
return FormatFunc(func(r *Record) []byte { |
||||
var color = 0 |
||||
switch r.Lvl { |
||||
case LvlCrit: |
||||
color = 35 |
||||
case LvlError: |
||||
color = 31 |
||||
case LvlWarn: |
||||
color = 33 |
||||
case LvlInfo: |
||||
color = 32 |
||||
case LvlDebug: |
||||
color = 36 |
||||
} |
||||
|
||||
b := &bytes.Buffer{} |
||||
lvl := strings.ToUpper(r.Lvl.String()) |
||||
if color > 0 { |
||||
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s] %s ", color, lvl, r.Time.Format(termTimeFormat), r.Msg) |
||||
} else { |
||||
fmt.Fprintf(b, "[%s] [%s] %s ", lvl, r.Time.Format(termTimeFormat), r.Msg) |
||||
} |
||||
|
||||
// try to justify the log output for short messages
|
||||
if len(r.Ctx) > 0 && len(r.Msg) < termMsgJust { |
||||
b.Write(bytes.Repeat([]byte{' '}, termMsgJust-len(r.Msg))) |
||||
} |
||||
|
||||
// print the keys logfmt style
|
||||
logfmt(b, r.Ctx, color) |
||||
return b.Bytes() |
||||
}) |
||||
} |
||||
|
||||
// LogfmtFormat prints records in logfmt format, an easy machine-parseable but human-readable
|
||||
// format for key/value pairs.
|
||||
//
|
||||
// For more details see: http://godoc.org/github.com/kr/logfmt
|
||||
//
|
||||
func LogfmtFormat() Format { |
||||
return FormatFunc(func(r *Record) []byte { |
||||
common := []interface{}{r.KeyNames.Time, r.Time, r.KeyNames.Lvl, r.Lvl, r.KeyNames.Msg, r.Msg} |
||||
buf := &bytes.Buffer{} |
||||
logfmt(buf, append(common, r.Ctx...), 0) |
||||
return buf.Bytes() |
||||
}) |
||||
} |
||||
|
||||
func logfmt(buf *bytes.Buffer, ctx []interface{}, color int) { |
||||
for i := 0; i < len(ctx); i += 2 { |
||||
if i != 0 { |
||||
buf.WriteByte(' ') |
||||
} |
||||
|
||||
k, ok := ctx[i].(string) |
||||
v := formatLogfmtValue(ctx[i+1]) |
||||
if !ok { |
||||
k, v = errorKey, formatLogfmtValue(k) |
||||
} |
||||
|
||||
// XXX: we should probably check that all of your key bytes aren't invalid
|
||||
if color > 0 { |
||||
fmt.Fprintf(buf, "\x1b[%dm%s\x1b[0m=%s", color, k, v) |
||||
} else { |
||||
buf.WriteString(k) |
||||
buf.WriteByte('=') |
||||
buf.WriteString(v) |
||||
} |
||||
} |
||||
|
||||
buf.WriteByte('\n') |
||||
} |
||||
|
||||
// JsonFormat formats log records as JSON objects separated by newlines.
|
||||
// It is the equivalent of JsonFormatEx(false, true).
|
||||
func JsonFormat() Format { |
||||
return JsonFormatEx(false, true) |
||||
} |
||||
|
||||
// JsonFormatEx formats log records as JSON objects. If pretty is true,
|
||||
// records will be pretty-printed. If lineSeparated is true, records
|
||||
// will be logged with a new line between each record.
|
||||
func JsonFormatEx(pretty, lineSeparated bool) Format { |
||||
jsonMarshal := json.Marshal |
||||
if pretty { |
||||
jsonMarshal = func(v interface{}) ([]byte, error) { |
||||
return json.MarshalIndent(v, "", " ") |
||||
} |
||||
} |
||||
|
||||
return FormatFunc(func(r *Record) []byte { |
||||
props := make(map[string]interface{}) |
||||
|
||||
props[r.KeyNames.Time] = r.Time |
||||
props[r.KeyNames.Lvl] = r.Lvl.String() |
||||
props[r.KeyNames.Msg] = r.Msg |
||||
|
||||
for i := 0; i < len(r.Ctx); i += 2 { |
||||
k, ok := r.Ctx[i].(string) |
||||
if !ok { |
||||
props[errorKey] = fmt.Sprintf("%+v is not a string key", r.Ctx[i]) |
||||
} |
||||
props[k] = formatJsonValue(r.Ctx[i+1]) |
||||
} |
||||
|
||||
b, err := jsonMarshal(props) |
||||
if err != nil { |
||||
b, _ = jsonMarshal(map[string]string{ |
||||
errorKey: err.Error(), |
||||
}) |
||||
return b |
||||
} |
||||
|
||||
if lineSeparated { |
||||
b = append(b, '\n') |
||||
} |
||||
|
||||
return b |
||||
}) |
||||
} |
||||
|
||||
func formatShared(value interface{}) (result interface{}) { |
||||
defer func() { |
||||
if err := recover(); err != nil { |
||||
if v := reflect.ValueOf(value); v.Kind() == reflect.Ptr && v.IsNil() { |
||||
result = "nil" |
||||
} else { |
||||
panic(err) |
||||
} |
||||
} |
||||
}() |
||||
|
||||
switch v := value.(type) { |
||||
case time.Time: |
||||
return v.Format(timeFormat) |
||||
|
||||
case error: |
||||
return v.Error() |
||||
|
||||
case fmt.Stringer: |
||||
return v.String() |
||||
|
||||
default: |
||||
return v |
||||
} |
||||
} |
||||
|
||||
func formatJsonValue(value interface{}) interface{} { |
||||
value = formatShared(value) |
||||
switch value.(type) { |
||||
case int, int8, int16, int32, int64, float32, float64, uint, uint8, uint16, uint32, uint64, string: |
||||
return value |
||||
default: |
||||
return fmt.Sprintf("%+v", value) |
||||
} |
||||
} |
||||
|
||||
// formatValue formats a value for serialization
|
||||
func formatLogfmtValue(value interface{}) string { |
||||
if value == nil { |
||||
return "nil" |
||||
} |
||||
|
||||
if t, ok := value.(time.Time); ok { |
||||
// Performance optimization: No need for escaping since the provided
|
||||
// timeFormat doesn't have any escape characters, and escaping is
|
||||
// expensive.
|
||||
return t.Format(timeFormat) |
||||
} |
||||
value = formatShared(value) |
||||
switch v := value.(type) { |
||||
case bool: |
||||
return strconv.FormatBool(v) |
||||
case float32: |
||||
return strconv.FormatFloat(float64(v), floatFormat, 3, 64) |
||||
case float64: |
||||
return strconv.FormatFloat(v, floatFormat, 3, 64) |
||||
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64: |
||||
return fmt.Sprintf("%d", value) |
||||
case string: |
||||
return escapeString(v) |
||||
default: |
||||
return escapeString(fmt.Sprintf("%+v", value)) |
||||
} |
||||
} |
||||
|
||||
var stringBufPool = sync.Pool{ |
||||
New: func() interface{} { return new(bytes.Buffer) }, |
||||
} |
||||
|
||||
func escapeString(s string) string { |
||||
needsQuotes := false |
||||
needsEscape := false |
||||
for _, r := range s { |
||||
if r <= ' ' || r == '=' || r == '"' { |
||||
needsQuotes = true |
||||
} |
||||
if r == '\\' || r == '"' || r == '\n' || r == '\r' || r == '\t' { |
||||
needsEscape = true |
||||
} |
||||
} |
||||
if needsEscape == false && needsQuotes == false { |
||||
return s |
||||
} |
||||
e := stringBufPool.Get().(*bytes.Buffer) |
||||
e.WriteByte('"') |
||||
for _, r := range s { |
||||
switch r { |
||||
case '\\', '"': |
||||
e.WriteByte('\\') |
||||
e.WriteByte(byte(r)) |
||||
case '\n': |
||||
e.WriteString("\\n") |
||||
case '\r': |
||||
e.WriteString("\\r") |
||||
case '\t': |
||||
e.WriteString("\\t") |
||||
default: |
||||
e.WriteRune(r) |
||||
} |
||||
} |
||||
e.WriteByte('"') |
||||
var ret string |
||||
if needsQuotes { |
||||
ret = e.String() |
||||
} else { |
||||
ret = string(e.Bytes()[1 : e.Len()-1]) |
||||
} |
||||
e.Reset() |
||||
stringBufPool.Put(e) |
||||
return ret |
||||
} |
@ -0,0 +1,356 @@
|
||||
package log15 |
||||
|
||||
import ( |
||||
"fmt" |
||||
"io" |
||||
"net" |
||||
"os" |
||||
"reflect" |
||||
"sync" |
||||
|
||||
"github.com/go-stack/stack" |
||||
) |
||||
|
||||
// A Logger prints its log records by writing to a Handler.
|
||||
// The Handler interface defines where and how log records are written.
|
||||
// Handlers are composable, providing you great flexibility in combining
|
||||
// them to achieve the logging structure that suits your applications.
|
||||
type Handler interface { |
||||
Log(r *Record) error |
||||
} |
||||
|
||||
// FuncHandler returns a Handler that logs records with the given
|
||||
// function.
|
||||
func FuncHandler(fn func(r *Record) error) Handler { |
||||
return funcHandler(fn) |
||||
} |
||||
|
||||
type funcHandler func(r *Record) error |
||||
|
||||
func (h funcHandler) Log(r *Record) error { |
||||
return h(r) |
||||
} |
||||
|
||||
// StreamHandler writes log records to an io.Writer
|
||||
// with the given format. StreamHandler can be used
|
||||
// to easily begin writing log records to other
|
||||
// outputs.
|
||||
//
|
||||
// StreamHandler wraps itself with LazyHandler and SyncHandler
|
||||
// to evaluate Lazy objects and perform safe concurrent writes.
|
||||
func StreamHandler(wr io.Writer, fmtr Format) Handler { |
||||
h := FuncHandler(func(r *Record) error { |
||||
_, err := wr.Write(fmtr.Format(r)) |
||||
return err |
||||
}) |
||||
return LazyHandler(SyncHandler(h)) |
||||
} |
||||
|
||||
// SyncHandler can be wrapped around a handler to guarantee that
|
||||
// only a single Log operation can proceed at a time. It's necessary
|
||||
// for thread-safe concurrent writes.
|
||||
func SyncHandler(h Handler) Handler { |
||||
var mu sync.Mutex |
||||
return FuncHandler(func(r *Record) error { |
||||
defer mu.Unlock() |
||||
mu.Lock() |
||||
return h.Log(r) |
||||
}) |
||||
} |
||||
|
||||
// FileHandler returns a handler which writes log records to the give file
|
||||
// using the given format. If the path
|
||||
// already exists, FileHandler will append to the given file. If it does not,
|
||||
// FileHandler will create the file with mode 0644.
|
||||
func FileHandler(path string, fmtr Format) (Handler, error) { |
||||
f, err := os.OpenFile(path, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return closingHandler{f, StreamHandler(f, fmtr)}, nil |
||||
} |
||||
|
||||
// NetHandler opens a socket to the given address and writes records
|
||||
// over the connection.
|
||||
func NetHandler(network, addr string, fmtr Format) (Handler, error) { |
||||
conn, err := net.Dial(network, addr) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
return closingHandler{conn, StreamHandler(conn, fmtr)}, nil |
||||
} |
||||
|
||||
// XXX: closingHandler is essentially unused at the moment
|
||||
// it's meant for a future time when the Handler interface supports
|
||||
// a possible Close() operation
|
||||
type closingHandler struct { |
||||
io.WriteCloser |
||||
Handler |
||||
} |
||||
|
||||
func (h *closingHandler) Close() error { |
||||
return h.WriteCloser.Close() |
||||
} |
||||
|
||||
// CallerFileHandler returns a Handler that adds the line number and file of
|
||||
// the calling function to the context with key "caller".
|
||||
func CallerFileHandler(h Handler) Handler { |
||||
return FuncHandler(func(r *Record) error { |
||||
r.Ctx = append(r.Ctx, "caller", fmt.Sprint(r.Call)) |
||||
return h.Log(r) |
||||
}) |
||||
} |
||||
|
||||
// CallerFuncHandler returns a Handler that adds the calling function name to
|
||||
// the context with key "fn".
|
||||
func CallerFuncHandler(h Handler) Handler { |
||||
return FuncHandler(func(r *Record) error { |
||||
r.Ctx = append(r.Ctx, "fn", fmt.Sprintf("%+n", r.Call)) |
||||
return h.Log(r) |
||||
}) |
||||
} |
||||
|
||||
// CallerStackHandler returns a Handler that adds a stack trace to the context
|
||||
// with key "stack". The stack trace is formated as a space separated list of
|
||||
// call sites inside matching []'s. The most recent call site is listed first.
|
||||
// Each call site is formatted according to format. See the documentation of
|
||||
// package github.com/go-stack/stack for the list of supported formats.
|
||||
func CallerStackHandler(format string, h Handler) Handler { |
||||
return FuncHandler(func(r *Record) error { |
||||
s := stack.Trace().TrimBelow(r.Call).TrimRuntime() |
||||
if len(s) > 0 { |
||||
r.Ctx = append(r.Ctx, "stack", fmt.Sprintf(format, s)) |
||||
} |
||||
return h.Log(r) |
||||
}) |
||||
} |
||||
|
||||
// FilterHandler returns a Handler that only writes records to the
|
||||
// wrapped Handler if the given function evaluates true. For example,
|
||||
// to only log records where the 'err' key is not nil:
|
||||
//
|
||||
// logger.SetHandler(FilterHandler(func(r *Record) bool {
|
||||
// for i := 0; i < len(r.Ctx); i += 2 {
|
||||
// if r.Ctx[i] == "err" {
|
||||
// return r.Ctx[i+1] != nil
|
||||
// }
|
||||
// }
|
||||
// return false
|
||||
// }, h))
|
||||
//
|
||||
func FilterHandler(fn func(r *Record) bool, h Handler) Handler { |
||||
return FuncHandler(func(r *Record) error { |
||||
if fn(r) { |
||||
return h.Log(r) |
||||
} |
||||
return nil |
||||
}) |
||||
} |
||||
|
||||
// MatchFilterHandler returns a Handler that only writes records
|
||||
// to the wrapped Handler if the given key in the logged
|
||||
// context matches the value. For example, to only log records
|
||||
// from your ui package:
|
||||
//
|
||||
// log.MatchFilterHandler("pkg", "app/ui", log.StdoutHandler)
|
||||
//
|
||||
func MatchFilterHandler(key string, value interface{}, h Handler) Handler { |
||||
return FilterHandler(func(r *Record) (pass bool) { |
||||
switch key { |
||||
case r.KeyNames.Lvl: |
||||
return r.Lvl == value |
||||
case r.KeyNames.Time: |
||||
return r.Time == value |
||||
case r.KeyNames.Msg: |
||||
return r.Msg == value |
||||
} |
||||
|
||||
for i := 0; i < len(r.Ctx); i += 2 { |
||||
if r.Ctx[i] == key { |
||||
return r.Ctx[i+1] == value |
||||
} |
||||
} |
||||
return false |
||||
}, h) |
||||
} |
||||
|
||||
// LvlFilterHandler returns a Handler that only writes
|
||||
// records which are less than the given verbosity
|
||||
// level to the wrapped Handler. For example, to only
|
||||
// log Error/Crit records:
|
||||
//
|
||||
// log.LvlFilterHandler(log.LvlError, log.StdoutHandler)
|
||||
//
|
||||
func LvlFilterHandler(maxLvl Lvl, h Handler) Handler { |
||||
return FilterHandler(func(r *Record) (pass bool) { |
||||
return r.Lvl <= maxLvl |
||||
}, h) |
||||
} |
||||
|
||||
// A MultiHandler dispatches any write to each of its handlers.
|
||||
// This is useful for writing different types of log information
|
||||
// to different locations. For example, to log to a file and
|
||||
// standard error:
|
||||
//
|
||||
// log.MultiHandler(
|
||||
// log.Must.FileHandler("/var/log/app.log", log.LogfmtFormat()),
|
||||
// log.StderrHandler)
|
||||
//
|
||||
func MultiHandler(hs ...Handler) Handler { |
||||
return FuncHandler(func(r *Record) error { |
||||
for _, h := range hs { |
||||
// what to do about failures?
|
||||
h.Log(r) |
||||
} |
||||
return nil |
||||
}) |
||||
} |
||||
|
||||
// A FailoverHandler writes all log records to the first handler
|
||||
// specified, but will failover and write to the second handler if
|
||||
// the first handler has failed, and so on for all handlers specified.
|
||||
// For example you might want to log to a network socket, but failover
|
||||
// to writing to a file if the network fails, and then to
|
||||
// standard out if the file write fails:
|
||||
//
|
||||
// log.FailoverHandler(
|
||||
// log.Must.NetHandler("tcp", ":9090", log.JsonFormat()),
|
||||
// log.Must.FileHandler("/var/log/app.log", log.LogfmtFormat()),
|
||||
// log.StdoutHandler)
|
||||
//
|
||||
// All writes that do not go to the first handler will add context with keys of
|
||||
// the form "failover_err_{idx}" which explain the error encountered while
|
||||
// trying to write to the handlers before them in the list.
|
||||
func FailoverHandler(hs ...Handler) Handler { |
||||
return FuncHandler(func(r *Record) error { |
||||
var err error |
||||
for i, h := range hs { |
||||
err = h.Log(r) |
||||
if err == nil { |
||||
return nil |
||||
} else { |
||||
r.Ctx = append(r.Ctx, fmt.Sprintf("failover_err_%d", i), err) |
||||
} |
||||
} |
||||
|
||||
return err |
||||
}) |
||||
} |
||||
|
||||
// ChannelHandler writes all records to the given channel.
|
||||
// It blocks if the channel is full. Useful for async processing
|
||||
// of log messages, it's used by BufferedHandler.
|
||||
func ChannelHandler(recs chan<- *Record) Handler { |
||||
return FuncHandler(func(r *Record) error { |
||||
recs <- r |
||||
return nil |
||||
}) |
||||
} |
||||
|
||||
// BufferedHandler writes all records to a buffered
|
||||
// channel of the given size which flushes into the wrapped
|
||||
// handler whenever it is available for writing. Since these
|
||||
// writes happen asynchronously, all writes to a BufferedHandler
|
||||
// never return an error and any errors from the wrapped handler are ignored.
|
||||
func BufferedHandler(bufSize int, h Handler) Handler { |
||||
recs := make(chan *Record, bufSize) |
||||
go func() { |
||||
for m := range recs { |
||||
_ = h.Log(m) |
||||
} |
||||
}() |
||||
return ChannelHandler(recs) |
||||
} |
||||
|
||||
// LazyHandler writes all values to the wrapped handler after evaluating
|
||||
// any lazy functions in the record's context. It is already wrapped
|
||||
// around StreamHandler and SyslogHandler in this library, you'll only need
|
||||
// it if you write your own Handler.
|
||||
func LazyHandler(h Handler) Handler { |
||||
return FuncHandler(func(r *Record) error { |
||||
// go through the values (odd indices) and reassign
|
||||
// the values of any lazy fn to the result of its execution
|
||||
hadErr := false |
||||
for i := 1; i < len(r.Ctx); i += 2 { |
||||
lz, ok := r.Ctx[i].(Lazy) |
||||
if ok { |
||||
v, err := evaluateLazy(lz) |
||||
if err != nil { |
||||
hadErr = true |
||||
r.Ctx[i] = err |
||||
} else { |
||||
if cs, ok := v.(stack.CallStack); ok { |
||||
v = cs.TrimBelow(r.Call).TrimRuntime() |
||||
} |
||||
r.Ctx[i] = v |
||||
} |
||||
} |
||||
} |
||||
|
||||
if hadErr { |
||||
r.Ctx = append(r.Ctx, errorKey, "bad lazy") |
||||
} |
||||
|
||||
return h.Log(r) |
||||
}) |
||||
} |
||||
|
||||
func evaluateLazy(lz Lazy) (interface{}, error) { |
||||
t := reflect.TypeOf(lz.Fn) |
||||
|
||||
if t.Kind() != reflect.Func { |
||||
return nil, fmt.Errorf("INVALID_LAZY, not func: %+v", lz.Fn) |
||||
} |
||||
|
||||
if t.NumIn() > 0 { |
||||
return nil, fmt.Errorf("INVALID_LAZY, func takes args: %+v", lz.Fn) |
||||
} |
||||
|
||||
if t.NumOut() == 0 { |
||||
return nil, fmt.Errorf("INVALID_LAZY, no func return val: %+v", lz.Fn) |
||||
} |
||||
|
||||
value := reflect.ValueOf(lz.Fn) |
||||
results := value.Call([]reflect.Value{}) |
||||
if len(results) == 1 { |
||||
return results[0].Interface(), nil |
||||
} else { |
||||
values := make([]interface{}, len(results)) |
||||
for i, v := range results { |
||||
values[i] = v.Interface() |
||||
} |
||||
return values, nil |
||||
} |
||||
} |
||||
|
||||
// DiscardHandler reports success for all writes but does nothing.
|
||||
// It is useful for dynamically disabling logging at runtime via
|
||||
// a Logger's SetHandler method.
|
||||
func DiscardHandler() Handler { |
||||
return FuncHandler(func(r *Record) error { |
||||
return nil |
||||
}) |
||||
} |
||||
|
||||
// The Must object provides the following Handler creation functions
|
||||
// which instead of returning an error parameter only return a Handler
|
||||
// and panic on failure: FileHandler, NetHandler, SyslogHandler, SyslogNetHandler
|
||||
var Must muster |
||||
|
||||
func must(h Handler, err error) Handler { |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
return h |
||||
} |
||||
|
||||
type muster struct{} |
||||
|
||||
func (m muster) FileHandler(path string, fmtr Format) Handler { |
||||
return must(FileHandler(path, fmtr)) |
||||
} |
||||
|
||||
func (m muster) NetHandler(network, addr string, fmtr Format) Handler { |
||||
return must(NetHandler(network, addr, fmtr)) |
||||
} |
@ -0,0 +1,26 @@
|
||||
// +build !go1.4
|
||||
|
||||
package log15 |
||||
|
||||
import ( |
||||
"sync/atomic" |
||||
"unsafe" |
||||
) |
||||
|
||||
// swapHandler wraps another handler that may be swapped out
|
||||
// dynamically at runtime in a thread-safe fashion.
|
||||
type swapHandler struct { |
||||
handler unsafe.Pointer |
||||
} |
||||
|
||||
func (h *swapHandler) Log(r *Record) error { |
||||
return h.Get().Log(r) |
||||
} |
||||
|
||||
func (h *swapHandler) Get() Handler { |
||||
return *(*Handler)(atomic.LoadPointer(&h.handler)) |
||||
} |
||||
|
||||
func (h *swapHandler) Swap(newHandler Handler) { |
||||
atomic.StorePointer(&h.handler, unsafe.Pointer(&newHandler)) |
||||
} |
@ -0,0 +1,23 @@
|
||||
// +build go1.4
|
||||
|
||||
package log15 |
||||
|
||||
import "sync/atomic" |
||||
|
||||
// swapHandler wraps another handler that may be swapped out
|
||||
// dynamically at runtime in a thread-safe fashion.
|
||||
type swapHandler struct { |
||||
handler atomic.Value |
||||
} |
||||
|
||||
func (h *swapHandler) Log(r *Record) error { |
||||
return (*h.handler.Load().(*Handler)).Log(r) |
||||
} |
||||
|
||||
func (h *swapHandler) Swap(newHandler Handler) { |
||||
h.handler.Store(&newHandler) |
||||
} |
||||
|
||||
func (h *swapHandler) Get() Handler { |
||||
return *h.handler.Load().(*Handler) |
||||
} |
@ -0,0 +1,208 @@
|
||||
package log15 |
||||
|
||||
import ( |
||||
"fmt" |
||||
"time" |
||||
|
||||
"github.com/go-stack/stack" |
||||
) |
||||
|
||||
const timeKey = "t" |
||||
const lvlKey = "lvl" |
||||
const msgKey = "msg" |
||||
const errorKey = "LOG15_ERROR" |
||||
|
||||
type Lvl int |
||||
|
||||
const ( |
||||
LvlCrit Lvl = iota |
||||
LvlError |
||||
LvlWarn |
||||
LvlInfo |
||||
LvlDebug |
||||
) |
||||
|
||||
// Returns the name of a Lvl
|
||||
func (l Lvl) String() string { |
||||
switch l { |
||||
case LvlDebug: |
||||
return "dbug" |
||||
case LvlInfo: |
||||
return "info" |
||||
case LvlWarn: |
||||
return "warn" |
||||
case LvlError: |
||||
return "eror" |
||||
case LvlCrit: |
||||
return "crit" |
||||
default: |
||||
panic("bad level") |
||||
} |
||||
} |
||||
|
||||
// Returns the appropriate Lvl from a string name.
|
||||
// Useful for parsing command line args and configuration files.
|
||||
func LvlFromString(lvlString string) (Lvl, error) { |
||||
switch lvlString { |
||||
case "debug", "dbug": |
||||
return LvlDebug, nil |
||||
case "info": |
||||
return LvlInfo, nil |
||||
case "warn": |
||||
return LvlWarn, nil |
||||
case "error", "eror": |
||||
return LvlError, nil |
||||
case "crit": |
||||
return LvlCrit, nil |
||||
default: |
||||
return LvlDebug, fmt.Errorf("Unknown level: %v", lvlString) |
||||
} |
||||
} |
||||
|
||||
// A Record is what a Logger asks its handler to write
|
||||
type Record struct { |
||||
Time time.Time |
||||
Lvl Lvl |
||||
Msg string |
||||
Ctx []interface{} |
||||
Call stack.Call |
||||
KeyNames RecordKeyNames |
||||
} |
||||
|
||||
type RecordKeyNames struct { |
||||
Time string |
||||
Msg string |
||||
Lvl string |
||||
} |
||||
|
||||
// A Logger writes key/value pairs to a Handler
|
||||
type Logger interface { |
||||
// New returns a new Logger that has this logger's context plus the given context
|
||||
New(ctx ...interface{}) Logger |
||||
|
||||
// GetHandler gets the handler associated with the logger.
|
||||
GetHandler() Handler |
||||
|
||||
// SetHandler updates the logger to write records to the specified handler.
|
||||
SetHandler(h Handler) |
||||
|
||||
// Log a message at the given level with context key/value pairs
|
||||
Debug(msg string, ctx ...interface{}) |
||||
Info(msg string, ctx ...interface{}) |
||||
Warn(msg string, ctx ...interface{}) |
||||
Error(msg string, ctx ...interface{}) |
||||
Crit(msg string, ctx ...interface{}) |
||||
} |
||||
|
||||
type logger struct { |
||||
ctx []interface{} |
||||
h *swapHandler |
||||
} |
||||
|
||||
func (l *logger) write(msg string, lvl Lvl, ctx []interface{}) { |
||||
l.h.Log(&Record{ |
||||
Time: time.Now(), |
||||
Lvl: lvl, |
||||
Msg: msg, |
||||
Ctx: newContext(l.ctx, ctx), |
||||
Call: stack.Caller(2), |
||||
KeyNames: RecordKeyNames{ |
||||
Time: timeKey, |
||||
Msg: msgKey, |
||||
Lvl: lvlKey, |
||||
}, |
||||
}) |
||||
} |
||||
|
||||
func (l *logger) New(ctx ...interface{}) Logger { |
||||
child := &logger{newContext(l.ctx, ctx), new(swapHandler)} |
||||
child.SetHandler(l.h) |
||||
return child |
||||
} |
||||
|
||||
func newContext(prefix []interface{}, suffix []interface{}) []interface{} { |
||||
normalizedSuffix := normalize(suffix) |
||||
newCtx := make([]interface{}, len(prefix)+len(normalizedSuffix)) |
||||
n := copy(newCtx, prefix) |
||||
copy(newCtx[n:], normalizedSuffix) |
||||
return newCtx |
||||
} |
||||
|
||||
func (l *logger) Debug(msg string, ctx ...interface{}) { |
||||
l.write(msg, LvlDebug, ctx) |
||||
} |
||||
|
||||
func (l *logger) Info(msg string, ctx ...interface{}) { |
||||
l.write(msg, LvlInfo, ctx) |
||||
} |
||||
|
||||
func (l *logger) Warn(msg string, ctx ...interface{}) { |
||||
l.write(msg, LvlWarn, ctx) |
||||
} |
||||
|
||||
func (l *logger) Error(msg string, ctx ...interface{}) { |
||||
l.write(msg, LvlError, ctx) |
||||
} |
||||
|
||||
func (l *logger) Crit(msg string, ctx ...interface{}) { |
||||
l.write(msg, LvlCrit, ctx) |
||||
} |
||||
|
||||
func (l *logger) GetHandler() Handler { |
||||
return l.h.Get() |
||||
} |
||||
|
||||
func (l *logger) SetHandler(h Handler) { |
||||
l.h.Swap(h) |
||||
} |
||||
|
||||
func normalize(ctx []interface{}) []interface{} { |
||||
// if the caller passed a Ctx object, then expand it
|
||||
if len(ctx) == 1 { |
||||
if ctxMap, ok := ctx[0].(Ctx); ok { |
||||
ctx = ctxMap.toArray() |
||||
} |
||||
} |
||||
|
||||
// ctx needs to be even because it's a series of key/value pairs
|
||||
// no one wants to check for errors on logging functions,
|
||||
// so instead of erroring on bad input, we'll just make sure
|
||||
// that things are the right length and users can fix bugs
|
||||
// when they see the output looks wrong
|
||||
if len(ctx)%2 != 0 { |
||||
ctx = append(ctx, nil, errorKey, "Normalized odd number of arguments by adding nil") |
||||
} |
||||
|
||||
return ctx |
||||
} |
||||
|
||||
// Lazy allows you to defer calculation of a logged value that is expensive
|
||||
// to compute until it is certain that it must be evaluated with the given filters.
|
||||
//
|
||||
// Lazy may also be used in conjunction with a Logger's New() function
|
||||
// to generate a child logger which always reports the current value of changing
|
||||
// state.
|
||||
//
|
||||
// You may wrap any function which takes no arguments to Lazy. It may return any
|
||||
// number of values of any type.
|
||||
type Lazy struct { |
||||
Fn interface{} |
||||
} |
||||
|
||||
// Ctx is a map of key/value pairs to pass as context to a log function
|
||||
// Use this only if you really need greater safety around the arguments you pass
|
||||
// to the logging functions.
|
||||
type Ctx map[string]interface{} |
||||
|
||||
func (c Ctx) toArray() []interface{} { |
||||
arr := make([]interface{}, len(c)*2) |
||||
|
||||
i := 0 |
||||
for k, v := range c { |
||||
arr[i] = k |
||||
arr[i+1] = v |
||||
i += 2 |
||||
} |
||||
|
||||
return arr |
||||
} |
@ -0,0 +1,67 @@
|
||||
package log15 |
||||
|
||||
import ( |
||||
"os" |
||||
|
||||
"github.com/inconshreveable/log15/term" |
||||
"github.com/mattn/go-colorable" |
||||
) |
||||
|
||||
var ( |
||||
root *logger |
||||
StdoutHandler = StreamHandler(os.Stdout, LogfmtFormat()) |
||||
StderrHandler = StreamHandler(os.Stderr, LogfmtFormat()) |
||||
) |
||||
|
||||
func init() { |
||||
if term.IsTty(os.Stdout.Fd()) { |
||||
StdoutHandler = StreamHandler(colorable.NewColorableStdout(), TerminalFormat()) |
||||
} |
||||
|
||||
if term.IsTty(os.Stderr.Fd()) { |
||||
StderrHandler = StreamHandler(colorable.NewColorableStderr(), TerminalFormat()) |
||||
} |
||||
|
||||
root = &logger{[]interface{}{}, new(swapHandler)} |
||||
root.SetHandler(StdoutHandler) |
||||
} |
||||
|
||||
// New returns a new logger with the given context.
|
||||
// New is a convenient alias for Root().New
|
||||
func New(ctx ...interface{}) Logger { |
||||
return root.New(ctx...) |
||||
} |
||||
|
||||
// Root returns the root logger
|
||||
func Root() Logger { |
||||
return root |
||||
} |
||||
|
||||
// The following functions bypass the exported logger methods (logger.Debug,
|
||||
// etc.) to keep the call depth the same for all paths to logger.write so
|
||||
// runtime.Caller(2) always refers to the call site in client code.
|
||||
|
||||
// Debug is a convenient alias for Root().Debug
|
||||
func Debug(msg string, ctx ...interface{}) { |
||||
root.write(msg, LvlDebug, ctx) |
||||
} |
||||
|
||||
// Info is a convenient alias for Root().Info
|
||||
func Info(msg string, ctx ...interface{}) { |
||||
root.write(msg, LvlInfo, ctx) |
||||
} |
||||
|
||||
// Warn is a convenient alias for Root().Warn
|
||||
func Warn(msg string, ctx ...interface{}) { |
||||
root.write(msg, LvlWarn, ctx) |
||||
} |
||||
|
||||
// Error is a convenient alias for Root().Error
|
||||
func Error(msg string, ctx ...interface{}) { |
||||
root.write(msg, LvlError, ctx) |
||||
} |
||||
|
||||
// Crit is a convenient alias for Root().Crit
|
||||
func Crit(msg string, ctx ...interface{}) { |
||||
root.write(msg, LvlCrit, ctx) |
||||
} |
@ -0,0 +1,55 @@
|
||||
// +build !windows,!plan9
|
||||
|
||||
package log15 |
||||
|
||||
import ( |
||||
"log/syslog" |
||||
"strings" |
||||
) |
||||
|
||||
// SyslogHandler opens a connection to the system syslog daemon by calling
|
||||
// syslog.New and writes all records to it.
|
||||
func SyslogHandler(priority syslog.Priority, tag string, fmtr Format) (Handler, error) { |
||||
wr, err := syslog.New(priority, tag) |
||||
return sharedSyslog(fmtr, wr, err) |
||||
} |
||||
|
||||
// SyslogNetHandler opens a connection to a log daemon over the network and writes
|
||||
// all log records to it.
|
||||
func SyslogNetHandler(net, addr string, priority syslog.Priority, tag string, fmtr Format) (Handler, error) { |
||||
wr, err := syslog.Dial(net, addr, priority, tag) |
||||
return sharedSyslog(fmtr, wr, err) |
||||
} |
||||
|
||||
func sharedSyslog(fmtr Format, sysWr *syslog.Writer, err error) (Handler, error) { |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
h := FuncHandler(func(r *Record) error { |
||||
var syslogFn = sysWr.Info |
||||
switch r.Lvl { |
||||
case LvlCrit: |
||||
syslogFn = sysWr.Crit |
||||
case LvlError: |
||||
syslogFn = sysWr.Err |
||||
case LvlWarn: |
||||
syslogFn = sysWr.Warning |
||||
case LvlInfo: |
||||
syslogFn = sysWr.Info |
||||
case LvlDebug: |
||||
syslogFn = sysWr.Debug |
||||
} |
||||
|
||||
s := strings.TrimSpace(string(fmtr.Format(r))) |
||||
return syslogFn(s) |
||||
}) |
||||
return LazyHandler(&closingHandler{sysWr, h}), nil |
||||
} |
||||
|
||||
func (m muster) SyslogHandler(priority syslog.Priority, tag string, fmtr Format) Handler { |
||||
return must(SyslogHandler(priority, tag, fmtr)) |
||||
} |
||||
|
||||
func (m muster) SyslogNetHandler(net, addr string, priority syslog.Priority, tag string, fmtr Format) Handler { |
||||
return must(SyslogNetHandler(net, addr, priority, tag, fmtr)) |
||||
} |
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT) |
||||
|
||||
Copyright (c) 2014 Simon Eskildsen |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
of this software and associated documentation files (the "Software"), to deal |
||||
in the Software without restriction, including without limitation the rights |
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
copies of the Software, and to permit persons to whom the Software is |
||||
furnished to do so, subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included in |
||||
all copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
||||
THE SOFTWARE. |
@ -0,0 +1,13 @@
|
||||
// Based on ssh/terminal:
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build appengine
|
||||
|
||||
package term |
||||
|
||||
// IsTty always returns false on AppEngine.
|
||||
func IsTty(fd uintptr) bool { |
||||
return false |
||||
} |
@ -0,0 +1,13 @@
|
||||
// Based on ssh/terminal:
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// +build !appengine
|
||||
|
||||
package term |
||||
|
||||
import "syscall" |
||||
|
||||
const ioctlReadTermios = syscall.TIOCGETA |
||||
|
||||
type Termios syscall.Termios |
@ -0,0 +1,18 @@
|
||||
package term |
||||
|
||||
import ( |
||||
"syscall" |
||||
) |
||||
|
||||
const ioctlReadTermios = syscall.TIOCGETA |
||||
|
||||
// Go 1.2 doesn't include Termios for FreeBSD. This should be added in 1.3 and this could be merged with terminal_darwin.
|
||||
type Termios struct { |
||||
Iflag uint32 |
||||
Oflag uint32 |
||||
Cflag uint32 |
||||
Lflag uint32 |
||||
Cc [20]uint8 |
||||
Ispeed uint32 |
||||
Ospeed uint32 |
||||
} |
@ -0,0 +1,14 @@
|
||||
// Based on ssh/terminal:
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build !appengine
|
||||
|
||||
package term |
||||
|
||||
import "syscall" |
||||
|
||||
const ioctlReadTermios = syscall.TCGETS |
||||
|
||||
type Termios syscall.Termios |
@ -0,0 +1,7 @@
|
||||
package term |
||||
|
||||
import "syscall" |
||||
|
||||
const ioctlReadTermios = syscall.TIOCGETA |
||||
|
||||
type Termios syscall.Termios |
@ -0,0 +1,20 @@
|
||||
// Based on ssh/terminal:
|
||||
// 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.
|
||||
|
||||
// +build linux,!appengine darwin freebsd openbsd netbsd
|
||||
|
||||
package term |
||||
|
||||
import ( |
||||
"syscall" |
||||
"unsafe" |
||||
) |
||||
|
||||
// IsTty returns true if the given file descriptor is a terminal.
|
||||
func IsTty(fd uintptr) bool { |
||||
var termios Termios |
||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) |
||||
return err == 0 |
||||
} |
@ -0,0 +1,7 @@
|
||||
package term |
||||
|
||||
import "syscall" |
||||
|
||||
const ioctlReadTermios = syscall.TIOCGETA |
||||
|
||||
type Termios syscall.Termios |
@ -0,0 +1,9 @@
|
||||
package term |
||||
|
||||
import "golang.org/x/sys/unix" |
||||
|
||||
// IsTty returns true if the given file descriptor is a terminal.
|
||||
func IsTty(fd uintptr) bool { |
||||
_, err := unix.IoctlGetTermios(int(fd), unix.TCGETA) |
||||
return err == nil |
||||
} |
@ -0,0 +1,26 @@
|
||||
// Based on ssh/terminal:
|
||||
// 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.
|
||||
|
||||
// +build windows
|
||||
|
||||
package term |
||||
|
||||
import ( |
||||
"syscall" |
||||
"unsafe" |
||||
) |
||||
|
||||
var kernel32 = syscall.NewLazyDLL("kernel32.dll") |
||||
|
||||
var ( |
||||
procGetConsoleMode = kernel32.NewProc("GetConsoleMode") |
||||
) |
||||
|
||||
// IsTty returns true if the given file descriptor is a terminal.
|
||||
func IsTty(fd uintptr) bool { |
||||
var st uint32 |
||||
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0) |
||||
return r != 0 && e == 0 |
||||
} |
@ -0,0 +1,9 @@
|
||||
language: go |
||||
go: |
||||
- tip |
||||
|
||||
before_install: |
||||
- go get github.com/mattn/goveralls |
||||
- go get golang.org/x/tools/cmd/cover |
||||
script: |
||||
- $HOME/gopath/bin/goveralls -repotoken xnXqRGwgW3SXIguzxf90ZSK1GPYZPaGrw |
@ -0,0 +1,21 @@
|
||||
The MIT License (MIT) |
||||
|
||||
Copyright (c) 2016 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. |
@ -0,0 +1,48 @@
|
||||
# go-colorable |
||||
|
||||
[](http://godoc.org/github.com/mattn/go-colorable) |
||||
[](https://travis-ci.org/mattn/go-colorable) |
||||
[](https://coveralls.io/github/mattn/go-colorable?branch=master) |
||||
[](https://goreportcard.com/report/mattn/go-colorable) |
||||
|
||||
Colorable writer for windows. |
||||
|
||||
For example, most of logger packages doesn't show colors on windows. (I know we can do it with ansicon. But I don't want.) |
||||
This package is possible to handle escape sequence for ansi color on windows. |
||||
|
||||
## Too Bad! |
||||
|
||||
 |
||||
|
||||
|
||||
## So Good! |
||||
|
||||
 |
||||
|
||||
## Usage |
||||
|
||||
```go |
||||
logrus.SetFormatter(&logrus.TextFormatter{ForceColors: true}) |
||||
logrus.SetOutput(colorable.NewColorableStdout()) |
||||
|
||||
logrus.Info("succeeded") |
||||
logrus.Warn("not correct") |
||||
logrus.Error("something error") |
||||
logrus.Fatal("panic") |
||||
``` |
||||
|
||||
You can compile above code on non-windows OSs. |
||||
|
||||
## Installation |
||||
|
||||
``` |
||||
$ go get github.com/mattn/go-colorable |
||||
``` |
||||
|
||||
# License |
||||
|
||||
MIT |
||||
|
||||
# Author |
||||
|
||||
Yasuhiro Matsumoto (a.k.a mattn) |
@ -0,0 +1,27 @@
|
||||
// +build appengine
|
||||
|
||||
package colorable |
||||
|
||||
import ( |
||||
"io" |
||||
"os" |
||||
) |
||||
|
||||
// NewColorable return new instance of Writer which handle escape sequence.
|
||||
func NewColorable(file *os.File) io.Writer { |
||||
if file == nil { |
||||
panic("nil passed instead of *os.File to NewColorable()") |
||||
} |
||||
|
||||
return file |
||||
} |
||||
|
||||
// NewColorableStdout return new instance of Writer which handle escape sequence for stdout.
|
||||
func NewColorableStdout() io.Writer { |
||||
return os.Stdout |
||||
} |
||||
|
||||
// NewColorableStderr return new instance of Writer which handle escape sequence for stderr.
|
||||
func NewColorableStderr() io.Writer { |
||||
return os.Stderr |
||||
} |
@ -0,0 +1,28 @@
|
||||
// +build !windows
|
||||
// +build !appengine
|
||||
|
||||
package colorable |
||||
|
||||
import ( |
||||
"io" |
||||
"os" |
||||
) |
||||
|
||||
// NewColorable return new instance of Writer which handle escape sequence.
|
||||
func NewColorable(file *os.File) io.Writer { |
||||
if file == nil { |
||||
panic("nil passed instead of *os.File to NewColorable()") |
||||
} |
||||
|
||||
return file |
||||
} |
||||
|
||||
// NewColorableStdout return new instance of Writer which handle escape sequence for stdout.
|
||||
func NewColorableStdout() io.Writer { |
||||
return os.Stdout |
||||
} |
||||
|
||||
// NewColorableStderr return new instance of Writer which handle escape sequence for stderr.
|
||||
func NewColorableStderr() io.Writer { |
||||
return os.Stderr |
||||
} |
@ -0,0 +1,870 @@
|
||||
// +build windows
|
||||
// +build !appengine
|
||||
|
||||
package colorable |
||||
|
||||
import ( |
||||
"bytes" |
||||
"io" |
||||
"math" |
||||
"os" |
||||
"strconv" |
||||
"strings" |
||||
"syscall" |
||||
"unsafe" |
||||
|
||||
"github.com/mattn/go-isatty" |
||||
) |
||||
|
||||
const ( |
||||
foregroundBlue = 0x1 |
||||
foregroundGreen = 0x2 |
||||
foregroundRed = 0x4 |
||||
foregroundIntensity = 0x8 |
||||
foregroundMask = (foregroundRed | foregroundBlue | foregroundGreen | foregroundIntensity) |
||||
backgroundBlue = 0x10 |
||||
backgroundGreen = 0x20 |
||||
backgroundRed = 0x40 |
||||
backgroundIntensity = 0x80 |
||||
backgroundMask = (backgroundRed | backgroundBlue | backgroundGreen | backgroundIntensity) |
||||
) |
||||
|
||||
type wchar uint16 |
||||
type short int16 |
||||
type dword uint32 |
||||
type word uint16 |
||||
|
||||
type coord struct { |
||||
x short |
||||
y short |
||||
} |
||||
|
||||
type smallRect struct { |
||||
left short |
||||
top short |
||||
right short |
||||
bottom short |
||||
} |
||||
|
||||
type consoleScreenBufferInfo struct { |
||||
size coord |
||||
cursorPosition coord |
||||
attributes word |
||||
window smallRect |
||||
maximumWindowSize coord |
||||
} |
||||
|
||||
type consoleCursorInfo struct { |
||||
size dword |
||||
visible int32 |
||||
} |
||||
|
||||
var ( |
||||
kernel32 = syscall.NewLazyDLL("kernel32.dll") |
||||
procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo") |
||||
procSetConsoleTextAttribute = kernel32.NewProc("SetConsoleTextAttribute") |
||||
procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition") |
||||
procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW") |
||||
procFillConsoleOutputAttribute = kernel32.NewProc("FillConsoleOutputAttribute") |
||||
procGetConsoleCursorInfo = kernel32.NewProc("GetConsoleCursorInfo") |
||||
procSetConsoleCursorInfo = kernel32.NewProc("SetConsoleCursorInfo") |
||||
procSetConsoleTitle = kernel32.NewProc("SetConsoleTitleW") |
||||
) |
||||
|
||||
// Writer provide colorable Writer to the console
|
||||
type Writer struct { |
||||
out io.Writer |
||||
handle syscall.Handle |
||||
oldattr word |
||||
oldpos coord |
||||
} |
||||
|
||||
// NewColorable return new instance of Writer which handle escape sequence from File.
|
||||
func NewColorable(file *os.File) io.Writer { |
||||
if file == nil { |
||||
panic("nil passed instead of *os.File to NewColorable()") |
||||
} |
||||
|
||||
if isatty.IsTerminal(file.Fd()) { |
||||
var csbi consoleScreenBufferInfo |
||||
handle := syscall.Handle(file.Fd()) |
||||
procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi))) |
||||
return &Writer{out: file, handle: handle, oldattr: csbi.attributes, oldpos: coord{0, 0}} |
||||
} |
||||
return file |
||||
} |
||||
|
||||
// NewColorableStdout return new instance of Writer which handle escape sequence for stdout.
|
||||
func NewColorableStdout() io.Writer { |
||||
return NewColorable(os.Stdout) |
||||
} |
||||
|
||||
// NewColorableStderr return new instance of Writer which handle escape sequence for stderr.
|
||||
func NewColorableStderr() io.Writer { |
||||
return NewColorable(os.Stderr) |
||||
} |
||||
|
||||
var color256 = map[int]int{ |
||||
0: 0x000000, |
||||
1: 0x800000, |
||||
2: 0x008000, |
||||
3: 0x808000, |
||||
4: 0x000080, |
||||
5: 0x800080, |
||||
6: 0x008080, |
||||
7: 0xc0c0c0, |
||||
8: 0x808080, |
||||
9: 0xff0000, |
||||
10: 0x00ff00, |
||||
11: 0xffff00, |
||||
12: 0x0000ff, |
||||
13: 0xff00ff, |
||||
14: 0x00ffff, |
||||
15: 0xffffff, |
||||
16: 0x000000, |
||||
17: 0x00005f, |
||||
18: 0x000087, |
||||
19: 0x0000af, |
||||
20: 0x0000d7, |
||||
21: 0x0000ff, |
||||
22: 0x005f00, |
||||
23: 0x005f5f, |
||||
24: 0x005f87, |
||||
25: 0x005faf, |
||||
26: 0x005fd7, |
||||
27: 0x005fff, |
||||
28: 0x008700, |
||||
29: 0x00875f, |
||||
30: 0x008787, |
||||
31: 0x0087af, |
||||
32: 0x0087d7, |
||||
33: 0x0087ff, |
||||
34: 0x00af00, |
||||
35: 0x00af5f, |
||||
36: 0x00af87, |
||||
37: 0x00afaf, |
||||
38: 0x00afd7, |
||||
39: 0x00afff, |
||||
40: 0x00d700, |
||||
41: 0x00d75f, |
||||
42: 0x00d787, |
||||
43: 0x00d7af, |
||||
44: 0x00d7d7, |
||||
45: 0x00d7ff, |
||||
46: 0x00ff00, |
||||
47: 0x00ff5f, |
||||
48: 0x00ff87, |
||||
49: 0x00ffaf, |
||||
50: 0x00ffd7, |
||||
51: 0x00ffff, |
||||
52: 0x5f0000, |
||||
53: 0x5f005f, |
||||
54: 0x5f0087, |
||||
55: 0x5f00af, |
||||
56: 0x5f00d7, |
||||
57: 0x5f00ff, |
||||
58: 0x5f5f00, |
||||
59: 0x5f5f5f, |
||||
60: 0x5f5f87, |
||||
61: 0x5f5faf, |
||||
62: 0x5f5fd7, |
||||
63: 0x5f5fff, |
||||
64: 0x5f8700, |
||||
65: 0x5f875f, |
||||
66: 0x5f8787, |
||||
67: 0x5f87af, |
||||
68: 0x5f87d7, |
||||
69: 0x5f87ff, |
||||
70: 0x5faf00, |
||||
71: 0x5faf5f, |
||||
72: 0x5faf87, |
||||
73: 0x5fafaf, |
||||
74: 0x5fafd7, |
||||
75: 0x5fafff, |
||||
76: 0x5fd700, |
||||
77: 0x5fd75f, |
||||
78: 0x5fd787, |
||||
79: 0x5fd7af, |
||||
80: 0x5fd7d7, |
||||
81: 0x5fd7ff, |
||||
82: 0x5fff00, |
||||
83: 0x5fff5f, |
||||
84: 0x5fff87, |
||||
85: 0x5fffaf, |
||||
86: 0x5fffd7, |
||||
87: 0x5fffff, |
||||
88: 0x870000, |
||||
89: 0x87005f, |
||||
90: 0x870087, |
||||
91: 0x8700af, |
||||
92: 0x8700d7, |
||||
93: 0x8700ff, |
||||
94: 0x875f00, |
||||
95: 0x875f5f, |
||||
96: 0x875f87, |
||||
97: 0x875faf, |
||||
98: 0x875fd7, |
||||
99: 0x875fff, |
||||
100: 0x878700, |
||||
101: 0x87875f, |
||||
102: 0x878787, |
||||
103: 0x8787af, |
||||
104: 0x8787d7, |
||||
105: 0x8787ff, |
||||
106: 0x87af00, |
||||
107: 0x87af5f, |
||||
108: 0x87af87, |
||||
109: 0x87afaf, |
||||
110: 0x87afd7, |
||||
111: 0x87afff, |
||||
112: 0x87d700, |
||||
113: 0x87d75f, |
||||
114: 0x87d787, |
||||
115: 0x87d7af, |
||||
116: 0x87d7d7, |
||||
117: 0x87d7ff, |
||||
118: 0x87ff00, |
||||
119: 0x87ff5f, |
||||
120: 0x87ff87, |
||||
121: 0x87ffaf, |
||||
122: 0x87ffd7, |
||||
123: 0x87ffff, |
||||
124: 0xaf0000, |
||||
125: 0xaf005f, |
||||
126: 0xaf0087, |
||||
127: 0xaf00af, |
||||
128: 0xaf00d7, |
||||
129: 0xaf00ff, |
||||
130: 0xaf5f00, |
||||
131: 0xaf5f5f, |
||||
132: 0xaf5f87, |
||||
133: 0xaf5faf, |
||||
134: 0xaf5fd7, |
||||
135: 0xaf5fff, |
||||
136: 0xaf8700, |
||||
137: 0xaf875f, |
||||
138: 0xaf8787, |
||||
139: 0xaf87af, |
||||
140: 0xaf87d7, |
||||
141: 0xaf87ff, |
||||
142: 0xafaf00, |
||||
143: 0xafaf5f, |
||||
144: 0xafaf87, |
||||
145: 0xafafaf, |
||||
146: 0xafafd7, |
||||
147: 0xafafff, |
||||
148: 0xafd700, |
||||
149: 0xafd75f, |
||||
150: 0xafd787, |
||||
151: 0xafd7af, |
||||
152: 0xafd7d7, |
||||
153: 0xafd7ff, |
||||
154: 0xafff00, |
||||
155: 0xafff5f, |
||||
156: 0xafff87, |
||||
157: 0xafffaf, |
||||
158: 0xafffd7, |
||||
159: 0xafffff, |
||||
160: 0xd70000, |
||||
161: 0xd7005f, |
||||
162: 0xd70087, |
||||
163: 0xd700af, |
||||
164: 0xd700d7, |
||||
165: 0xd700ff, |
||||
166: 0xd75f00, |
||||
167: 0xd75f5f, |
||||
168: 0xd75f87, |
||||
169: 0xd75faf, |
||||
170: 0xd75fd7, |
||||
171: 0xd75fff, |
||||
172: 0xd78700, |
||||
173: 0xd7875f, |
||||
174: 0xd78787, |
||||
175: 0xd787af, |
||||
176: 0xd787d7, |
||||
177: 0xd787ff, |
||||
178: 0xd7af00, |
||||
179: 0xd7af5f, |
||||
180: 0xd7af87, |
||||
181: 0xd7afaf, |
||||
182: 0xd7afd7, |
||||
183: 0xd7afff, |
||||
184: 0xd7d700, |
||||
185: 0xd7d75f, |
||||
186: 0xd7d787, |
||||
187: 0xd7d7af, |
||||
188: 0xd7d7d7, |
||||
189: 0xd7d7ff, |
||||
190: 0xd7ff00, |
||||
191: 0xd7ff5f, |
||||
192: 0xd7ff87, |
||||
193: 0xd7ffaf, |
||||
194: 0xd7ffd7, |
||||
195: 0xd7ffff, |
||||
196: 0xff0000, |
||||
197: 0xff005f, |
||||
198: 0xff0087, |
||||
199: 0xff00af, |
||||
200: 0xff00d7, |
||||
201: 0xff00ff, |
||||
202: 0xff5f00, |
||||
203: 0xff5f5f, |
||||
204: 0xff5f87, |
||||
205: 0xff5faf, |
||||
206: 0xff5fd7, |
||||
207: 0xff5fff, |
||||
208: 0xff8700, |
||||
209: 0xff875f, |
||||
210: 0xff8787, |
||||
211: 0xff87af, |
||||
212: 0xff87d7, |
||||
213: 0xff87ff, |
||||
214: 0xffaf00, |
||||
215: 0xffaf5f, |
||||
216: 0xffaf87, |
||||
217: 0xffafaf, |
||||
218: 0xffafd7, |
||||
219: 0xffafff, |
||||
220: 0xffd700, |
||||
221: 0xffd75f, |
||||
222: 0xffd787, |
||||
223: 0xffd7af, |
||||
224: 0xffd7d7, |
||||
225: 0xffd7ff, |
||||
226: 0xffff00, |
||||
227: 0xffff5f, |
||||
228: 0xffff87, |
||||
229: 0xffffaf, |
||||
230: 0xffffd7, |
||||
231: 0xffffff, |
||||
232: 0x080808, |
||||
233: 0x121212, |
||||
234: 0x1c1c1c, |
||||
235: 0x262626, |
||||
236: 0x303030, |
||||
237: 0x3a3a3a, |
||||
238: 0x444444, |
||||
239: 0x4e4e4e, |
||||
240: 0x585858, |
||||
241: 0x626262, |
||||
242: 0x6c6c6c, |
||||
243: 0x767676, |
||||
244: 0x808080, |
||||
245: 0x8a8a8a, |
||||
246: 0x949494, |
||||
247: 0x9e9e9e, |
||||
248: 0xa8a8a8, |
||||
249: 0xb2b2b2, |
||||
250: 0xbcbcbc, |
||||
251: 0xc6c6c6, |
||||
252: 0xd0d0d0, |
||||
253: 0xdadada, |
||||
254: 0xe4e4e4, |
||||
255: 0xeeeeee, |
||||
} |
||||
|
||||
// `\033]0;TITLESTR\007`
|
||||
func doTitleSequence(er *bytes.Reader) error { |
||||
var c byte |
||||
var err error |
||||
|
||||
c, err = er.ReadByte() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if c != '0' && c != '2' { |
||||
return nil |
||||
} |
||||
c, err = er.ReadByte() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if c != ';' { |
||||
return nil |
||||
} |
||||
title := make([]byte, 0, 80) |
||||
for { |
||||
c, err = er.ReadByte() |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if c == 0x07 || c == '\n' { |
||||
break |
||||
} |
||||
title = append(title, c) |
||||
} |
||||
if len(title) > 0 { |
||||
title8, err := syscall.UTF16PtrFromString(string(title)) |
||||
if err == nil { |
||||
procSetConsoleTitle.Call(uintptr(unsafe.Pointer(title8))) |
||||
} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// Write write data on console
|
||||
func (w *Writer) Write(data []byte) (n int, err error) { |
||||
var csbi consoleScreenBufferInfo |
||||
procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) |
||||
|
||||
er := bytes.NewReader(data) |
||||
var bw [1]byte |
||||
loop: |
||||
for { |
||||
r1, _, err := procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi))) |
||||
if r1 == 0 { |
||||
break loop |
||||
} |
||||
|
||||
c1, err := er.ReadByte() |
||||
if err != nil { |
||||
break loop |
||||
} |
||||
if c1 != 0x1b { |
||||
bw[0] = c1 |
||||
w.out.Write(bw[:]) |
||||
continue |
||||
} |
||||
c2, err := er.ReadByte() |
||||
if err != nil { |
||||
break loop |
||||
} |
||||
|
||||
if c2 == ']' { |
||||
if err := doTitleSequence(er); err != nil { |
||||
break loop |
||||
} |
||||
continue |
||||
} |
||||
if c2 != 0x5b { |
||||
continue |
||||
} |
||||
|
||||
var buf bytes.Buffer |
||||
var m byte |
||||
for { |
||||
c, err := er.ReadByte() |
||||
if err != nil { |
||||
break loop |
||||
} |
||||
if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' { |
||||
m = c |
||||
break |
||||
} |
||||
buf.Write([]byte(string(c))) |
||||
} |
||||
|
||||
switch m { |
||||
case 'A': |
||||
n, err = strconv.Atoi(buf.String()) |
||||
if err != nil { |
||||
continue |
||||
} |
||||
csbi.cursorPosition.y -= short(n) |
||||
procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) |
||||
case 'B': |
||||
n, err = strconv.Atoi(buf.String()) |
||||
if err != nil { |
||||
continue |
||||
} |
||||
csbi.cursorPosition.y += short(n) |
||||
procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) |
||||
case 'C': |
||||
n, err = strconv.Atoi(buf.String()) |
||||
if err != nil { |
||||
continue |
||||
} |
||||
csbi.cursorPosition.x -= short(n) |
||||
procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) |
||||
case 'D': |
||||
n, err = strconv.Atoi(buf.String()) |
||||
if err != nil { |
||||
continue |
||||
} |
||||
if n, err = strconv.Atoi(buf.String()); err == nil { |
||||
var csbi consoleScreenBufferInfo |
||||
csbi.cursorPosition.x += short(n) |
||||
procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) |
||||
} |
||||
case 'E': |
||||
n, err = strconv.Atoi(buf.String()) |
||||
if err != nil { |
||||
continue |
||||
} |
||||
csbi.cursorPosition.x = 0 |
||||
csbi.cursorPosition.y += short(n) |
||||
procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) |
||||
case 'F': |
||||
n, err = strconv.Atoi(buf.String()) |
||||
if err != nil { |
||||
continue |
||||
} |
||||
csbi.cursorPosition.x = 0 |
||||
csbi.cursorPosition.y -= short(n) |
||||
procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) |
||||
case 'G': |
||||
n, err = strconv.Atoi(buf.String()) |
||||
if err != nil { |
||||
continue |
||||
} |
||||
csbi.cursorPosition.x = short(n - 1) |
||||
procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) |
||||
case 'H': |
||||
if buf.Len() > 0 { |
||||
token := strings.Split(buf.String(), ";") |
||||
switch len(token) { |
||||
case 1: |
||||
n1, err := strconv.Atoi(token[0]) |
||||
if err != nil { |
||||
continue |
||||
} |
||||
csbi.cursorPosition.y = short(n1 - 1) |
||||
case 2: |
||||
n1, err := strconv.Atoi(token[0]) |
||||
if err != nil { |
||||
continue |
||||
} |
||||
n2, err := strconv.Atoi(token[1]) |
||||
if err != nil { |
||||
continue |
||||
} |
||||
csbi.cursorPosition.x = short(n2 - 1) |
||||
csbi.cursorPosition.y = short(n1 - 1) |
||||
} |
||||
} else { |
||||
csbi.cursorPosition.y = 0 |
||||
} |
||||
procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition))) |
||||
case 'J': |
||||
n := 0 |
||||
if buf.Len() > 0 { |
||||
n, err = strconv.Atoi(buf.String()) |
||||
if err != nil { |
||||
continue |
||||
} |
||||
} |
||||
var count, written dword |
||||
var cursor coord |
||||
switch n { |
||||
case 0: |
||||
cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y} |
||||
count = dword(csbi.size.x - csbi.cursorPosition.x + (csbi.size.y-csbi.cursorPosition.y)*csbi.size.x) |
||||
case 1: |
||||
cursor = coord{x: csbi.window.left, y: csbi.window.top} |
||||
count = dword(csbi.size.x - csbi.cursorPosition.x + (csbi.window.top-csbi.cursorPosition.y)*csbi.size.x) |
||||
case 2: |
||||
cursor = coord{x: csbi.window.left, y: csbi.window.top} |
||||
count = dword(csbi.size.x - csbi.cursorPosition.x + (csbi.size.y-csbi.cursorPosition.y)*csbi.size.x) |
||||
} |
||||
procFillConsoleOutputCharacter.Call(uintptr(w.handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) |
||||
procFillConsoleOutputAttribute.Call(uintptr(w.handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) |
||||
case 'K': |
||||
n := 0 |
||||
if buf.Len() > 0 { |
||||
n, err = strconv.Atoi(buf.String()) |
||||
if err != nil { |
||||
continue |
||||
} |
||||
} |
||||
var cursor coord |
||||
switch n { |
||||
case 0: |
||||
cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y} |
||||
case 1: |
||||
cursor = coord{x: csbi.window.left, y: csbi.window.top + csbi.cursorPosition.y} |
||||
case 2: |
||||
cursor = coord{x: csbi.window.left, y: csbi.window.top + csbi.cursorPosition.y} |
||||
} |
||||
var count, written dword |
||||
count = dword(csbi.size.x - csbi.cursorPosition.x) |
||||
procFillConsoleOutputCharacter.Call(uintptr(w.handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) |
||||
procFillConsoleOutputAttribute.Call(uintptr(w.handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written))) |
||||
case 'm': |
||||
attr := csbi.attributes |
||||
cs := buf.String() |
||||
if cs == "" { |
||||
procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(w.oldattr)) |
||||
continue |
||||
} |
||||
token := strings.Split(cs, ";") |
||||
for i := 0; i < len(token); i++ { |
||||
ns := token[i] |
||||
if n, err = strconv.Atoi(ns); err == nil { |
||||
switch { |
||||
case n == 0 || n == 100: |
||||
attr = w.oldattr |
||||
case 1 <= n && n <= 5: |
||||
attr |= foregroundIntensity |
||||
case n == 7: |
||||
attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4) |
||||
case n == 22 || n == 25: |
||||
attr |= foregroundIntensity |
||||
case n == 27: |
||||
attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4) |
||||
case 30 <= n && n <= 37: |
||||
attr &= backgroundMask |
||||
if (n-30)&1 != 0 { |
||||
attr |= foregroundRed |
||||
} |
||||
if (n-30)&2 != 0 { |
||||
attr |= foregroundGreen |
||||
} |
||||
if (n-30)&4 != 0 { |
||||
attr |= foregroundBlue |
||||
} |
||||
case n == 38: // set foreground color.
|
||||
if i < len(token)-2 && (token[i+1] == "5" || token[i+1] == "05") { |
||||
if n256, err := strconv.Atoi(token[i+2]); err == nil { |
||||
if n256foreAttr == nil { |
||||
n256setup() |
||||
} |
||||
attr &= backgroundMask |
||||
attr |= n256foreAttr[n256] |
||||
i += 2 |
||||
} |
||||
} else { |
||||
attr = attr & (w.oldattr & backgroundMask) |
||||
} |
||||
case n == 39: // reset foreground color.
|
||||
attr &= backgroundMask |
||||
attr |= w.oldattr & foregroundMask |
||||
case 40 <= n && n <= 47: |
||||
attr &= foregroundMask |
||||
if (n-40)&1 != 0 { |
||||
attr |= backgroundRed |
||||
} |
||||
if (n-40)&2 != 0 { |
||||
attr |= backgroundGreen |
||||
} |
||||
if (n-40)&4 != 0 { |
||||
attr |= backgroundBlue |
||||
} |
||||
case n == 48: // set background color.
|
||||
if i < len(token)-2 && token[i+1] == "5" { |
||||
if n256, err := strconv.Atoi(token[i+2]); err == nil { |
||||
if n256backAttr == nil { |
||||
n256setup() |
||||
} |
||||
attr &= foregroundMask |
||||
attr |= n256backAttr[n256] |
||||
i += 2 |
||||
} |
||||
} else { |
||||
attr = attr & (w.oldattr & foregroundMask) |
||||
} |
||||
case n == 49: // reset foreground color.
|
||||
attr &= foregroundMask |
||||
attr |= w.oldattr & backgroundMask |
||||
case 90 <= n && n <= 97: |
||||
attr = (attr & backgroundMask) |
||||
attr |= foregroundIntensity |
||||
if (n-90)&1 != 0 { |
||||
attr |= foregroundRed |
||||
} |
||||
if (n-90)&2 != 0 { |
||||
attr |= foregroundGreen |
||||
} |
||||
if (n-90)&4 != 0 { |
||||
attr |= foregroundBlue |
||||
} |
||||
case 100 <= n && n <= 107: |
||||
attr = (attr & foregroundMask) |
||||
attr |= backgroundIntensity |
||||
if (n-100)&1 != 0 { |
||||
attr |= backgroundRed |
||||
} |
||||
if (n-100)&2 != 0 { |
||||
attr |= backgroundGreen |
||||
} |
||||
if (n-100)&4 != 0 { |
||||
attr |= backgroundBlue |
||||
} |
||||
} |
||||
procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(attr)) |
||||
} |
||||
} |
||||
case 'h': |
||||
cs := buf.String() |
||||
if cs == "?25" { |
||||
var ci consoleCursorInfo |
||||
procGetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci))) |
||||
ci.visible = 1 |
||||
procSetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci))) |
||||
} |
||||
case 'l': |
||||
cs := buf.String() |
||||
if cs == "?25" { |
||||
var ci consoleCursorInfo |
||||
procGetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci))) |
||||
ci.visible = 0 |
||||
procSetConsoleCursorInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&ci))) |
||||
} |
||||
case 's': |
||||
w.oldpos = csbi.cursorPosition |
||||
case 'u': |
||||
procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&w.oldpos))) |
||||
} |
||||
} |
||||
|
||||
return len(data), nil |
||||
} |
||||
|
||||
type consoleColor struct { |
||||
rgb int |
||||
red bool |
||||
green bool |
||||
blue bool |
||||
intensity bool |
||||
} |
||||
|
||||
func (c consoleColor) foregroundAttr() (attr word) { |
||||
if c.red { |
||||
attr |= foregroundRed |
||||
} |
||||
if c.green { |
||||
attr |= foregroundGreen |
||||
} |
||||
if c.blue { |
||||
attr |= foregroundBlue |
||||
} |
||||
if c.intensity { |
||||
attr |= foregroundIntensity |
||||
} |
||||
return |
||||
} |
||||
|
||||
func (c consoleColor) backgroundAttr() (attr word) { |
||||
if c.red { |
||||
attr |= backgroundRed |
||||
} |
||||
if c.green { |
||||
attr |= backgroundGreen |
||||
} |
||||
if c.blue { |
||||
attr |= backgroundBlue |
||||
} |
||||
if c.intensity { |
||||
attr |= backgroundIntensity |
||||
} |
||||
return |
||||
} |
||||
|
||||
var color16 = []consoleColor{ |
||||
{0x000000, false, false, false, false}, |
||||
{0x000080, false, false, true, false}, |
||||
{0x008000, false, true, false, false}, |
||||
{0x008080, false, true, true, false}, |
||||
{0x800000, true, false, false, false}, |
||||
{0x800080, true, false, true, false}, |
||||
{0x808000, true, true, false, false}, |
||||
{0xc0c0c0, true, true, true, false}, |
||||
{0x808080, false, false, false, true}, |
||||
{0x0000ff, false, false, true, true}, |
||||
{0x00ff00, false, true, false, true}, |
||||
{0x00ffff, false, true, true, true}, |
||||
{0xff0000, true, false, false, true}, |
||||
{0xff00ff, true, false, true, true}, |
||||
{0xffff00, true, true, false, true}, |
||||
{0xffffff, true, true, true, true}, |
||||
} |
||||
|
||||
type hsv struct { |
||||
h, s, v float32 |
||||
} |
||||
|
||||
func (a hsv) dist(b hsv) float32 { |
||||
dh := a.h - b.h |
||||
switch { |
||||
case dh > 0.5: |
||||
dh = 1 - dh |
||||
case dh < -0.5: |
||||
dh = -1 - dh |
||||
} |
||||
ds := a.s - b.s |
||||
dv := a.v - b.v |
||||
return float32(math.Sqrt(float64(dh*dh + ds*ds + dv*dv))) |
||||
} |
||||
|
||||
func toHSV(rgb int) hsv { |
||||
r, g, b := float32((rgb&0xFF0000)>>16)/256.0, |
||||
float32((rgb&0x00FF00)>>8)/256.0, |
||||
float32(rgb&0x0000FF)/256.0 |
||||
min, max := minmax3f(r, g, b) |
||||
h := max - min |
||||
if h > 0 { |
||||
if max == r { |
||||
h = (g - b) / h |
||||
if h < 0 { |
||||
h += 6 |
||||
} |
||||
} else if max == g { |
||||
h = 2 + (b-r)/h |
||||
} else { |
||||
h = 4 + (r-g)/h |
||||
} |
||||
} |
||||
h /= 6.0 |
||||
s := max - min |
||||
if max != 0 { |
||||
s /= max |
||||
} |
||||
v := max |
||||
return hsv{h: h, s: s, v: v} |
||||
} |
||||
|
||||
type hsvTable []hsv |
||||
|
||||
func toHSVTable(rgbTable []consoleColor) hsvTable { |
||||
t := make(hsvTable, len(rgbTable)) |
||||
for i, c := range rgbTable { |
||||
t[i] = toHSV(c.rgb) |
||||
} |
||||
return t |
||||
} |
||||
|
||||
func (t hsvTable) find(rgb int) consoleColor { |
||||
hsv := toHSV(rgb) |
||||
n := 7 |
||||
l := float32(5.0) |
||||
for i, p := range t { |
||||
d := hsv.dist(p) |
||||
if d < l { |
||||
l, n = d, i |
||||
} |
||||
} |
||||
return color16[n] |
||||
} |
||||
|
||||
func minmax3f(a, b, c float32) (min, max float32) { |
||||
if a < b { |
||||
if b < c { |
||||
return a, c |
||||
} else if a < c { |
||||
return a, b |
||||
} else { |
||||
return c, b |
||||
} |
||||
} else { |
||||
if a < c { |
||||
return b, c |
||||
} else if b < c { |
||||
return b, a |
||||
} else { |
||||
return c, a |
||||
} |
||||
} |
||||
} |
||||
|
||||
var n256foreAttr []word |
||||
var n256backAttr []word |
||||
|
||||
func n256setup() { |
||||
n256foreAttr = make([]word, 256) |
||||
n256backAttr = make([]word, 256) |
||||
t := toHSVTable(color16) |
||||
for i, rgb := range color256 { |
||||
c := t.find(rgb) |
||||
n256foreAttr[i] = c.foregroundAttr() |
||||
n256backAttr[i] = c.backgroundAttr() |
||||
} |
||||
} |
@ -0,0 +1,55 @@
|
||||
package colorable |
||||
|
||||
import ( |
||||
"bytes" |
||||
"io" |
||||
) |
||||
|
||||
// NonColorable hold writer but remove escape sequence.
|
||||
type NonColorable struct { |
||||
out io.Writer |
||||
} |
||||
|
||||
// NewNonColorable return new instance of Writer which remove escape sequence from Writer.
|
||||
func NewNonColorable(w io.Writer) io.Writer { |
||||
return &NonColorable{out: w} |
||||
} |
||||
|
||||
// Write write data on console
|
||||
func (w *NonColorable) Write(data []byte) (n int, err error) { |
||||
er := bytes.NewReader(data) |
||||
var bw [1]byte |
||||
loop: |
||||
for { |
||||
c1, err := er.ReadByte() |
||||
if err != nil { |
||||
break loop |
||||
} |
||||
if c1 != 0x1b { |
||||
bw[0] = c1 |
||||
w.out.Write(bw[:]) |
||||
continue |
||||
} |
||||
c2, err := er.ReadByte() |
||||
if err != nil { |
||||
break loop |
||||
} |
||||
if c2 != 0x5b { |
||||
continue |
||||
} |
||||
|
||||
var buf bytes.Buffer |
||||
for { |
||||
c, err := er.ReadByte() |
||||
if err != nil { |
||||
break loop |
||||
} |
||||
if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' { |
||||
break |
||||
} |
||||
buf.Write([]byte(string(c))) |
||||
} |
||||
} |
||||
|
||||
return len(data), nil |
||||
} |
@ -0,0 +1,9 @@
|
||||
language: go |
||||
go: |
||||
- tip |
||||
|
||||
before_install: |
||||
- go get github.com/mattn/goveralls |
||||
- go get golang.org/x/tools/cmd/cover |
||||
script: |
||||
- $HOME/gopath/bin/goveralls -repotoken 3gHdORO5k5ziZcWMBxnd9LrMZaJs8m9x5 |
@ -0,0 +1,9 @@
|
||||
Copyright (c) Yasuhiro MATSUMOTO <[email protected]> |
||||
|
||||
MIT License (Expat) |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
@ -0,0 +1,50 @@
|
||||
# go-isatty |
||||
|
||||
[](http://godoc.org/github.com/mattn/go-isatty) |
||||
[](https://travis-ci.org/mattn/go-isatty) |
||||
[](https://coveralls.io/github/mattn/go-isatty?branch=master) |
||||
[](https://goreportcard.com/report/mattn/go-isatty) |
||||
|
||||
isatty for golang |
||||
|
||||
## Usage |
||||
|
||||
```go |
||||
package main |
||||
|
||||
import ( |
||||
"fmt" |
||||
"github.com/mattn/go-isatty" |
||||
"os" |
||||
) |
||||
|
||||
func main() { |
||||
if isatty.IsTerminal(os.Stdout.Fd()) { |
||||
fmt.Println("Is Terminal") |
||||
} else if isatty.IsCygwinTerminal(os.Stdout.Fd()) { |
||||
fmt.Println("Is Cygwin/MSYS2 Terminal") |
||||
} else { |
||||
fmt.Println("Is Not Terminal") |
||||
} |
||||
} |
||||
``` |
||||
|
||||
## Installation |
||||
|
||||
``` |
||||
$ go get github.com/mattn/go-isatty |
||||
``` |
||||
|
||||
## License |
||||
|
||||
MIT |
||||
|
||||
## Author |
||||
|
||||
Yasuhiro Matsumoto (a.k.a mattn) |
||||
|
||||
## Thanks |
||||
|
||||
* k-takata: base idea for IsCygwinTerminal |
||||
|
||||
https://github.com/k-takata/go-iscygpty |
@ -0,0 +1,2 @@
|
||||
// Package isatty implements interface to isatty
|
||||
package isatty |
@ -0,0 +1,15 @@
|
||||
// +build appengine
|
||||
|
||||
package isatty |
||||
|
||||
// IsTerminal returns true if the file descriptor is terminal which
|
||||
// is always false on on appengine classic which is a sandboxed PaaS.
|
||||
func IsTerminal(fd uintptr) bool { |
||||
return false |
||||
} |
||||
|
||||
// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2
|
||||
// terminal. This is also always false on this environment.
|
||||
func IsCygwinTerminal(fd uintptr) bool { |
||||
return false |
||||
} |
@ -0,0 +1,18 @@
|
||||
// +build darwin freebsd openbsd netbsd dragonfly
|
||||
// +build !appengine
|
||||
|
||||
package isatty |
||||
|
||||
import ( |
||||
"syscall" |
||||
"unsafe" |
||||
) |
||||
|
||||
const ioctlReadTermios = syscall.TIOCGETA |
||||
|
||||
// IsTerminal return true if the file descriptor is terminal.
|
||||
func IsTerminal(fd uintptr) bool { |
||||
var termios syscall.Termios |
||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) |
||||
return err == 0 |
||||
} |
@ -0,0 +1,18 @@
|
||||
// +build linux
|
||||
// +build !appengine
|
||||
|
||||
package isatty |
||||
|
||||
import ( |
||||
"syscall" |
||||
"unsafe" |
||||
) |
||||
|
||||
const ioctlReadTermios = syscall.TCGETS |
||||
|
||||
// IsTerminal return true if the file descriptor is terminal.
|
||||
func IsTerminal(fd uintptr) bool { |
||||
var termios syscall.Termios |
||||
_, _, err := syscall.Syscall6(syscall.SYS_IOCTL, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&termios)), 0, 0, 0) |
||||
return err == 0 |
||||
} |
@ -0,0 +1,10 @@
|
||||
// +build !windows
|
||||
// +build !appengine
|
||||
|
||||
package isatty |
||||
|
||||
// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2
|
||||
// terminal. This is also always false on this environment.
|
||||
func IsCygwinTerminal(fd uintptr) bool { |
||||
return false |
||||
} |
@ -0,0 +1,16 @@
|
||||
// +build solaris
|
||||
// +build !appengine
|
||||
|
||||
package isatty |
||||
|
||||
import ( |
||||
"golang.org/x/sys/unix" |
||||
) |
||||
|
||||
// IsTerminal returns true if the given file descriptor is a terminal.
|
||||
// see: http://src.illumos.org/source/xref/illumos-gate/usr/src/lib/libbc/libc/gen/common/isatty.c
|
||||
func IsTerminal(fd uintptr) bool { |
||||
var termio unix.Termio |
||||
err := unix.IoctlSetTermio(int(fd), unix.TCGETA, &termio) |
||||
return err == nil |
||||
} |
@ -0,0 +1,94 @@
|
||||
// +build windows
|
||||
// +build !appengine
|
||||
|
||||
package isatty |
||||
|
||||
import ( |
||||
"strings" |
||||
"syscall" |
||||
"unicode/utf16" |
||||
"unsafe" |
||||
) |
||||
|
||||
const ( |
||||
fileNameInfo uintptr = 2 |
||||
fileTypePipe = 3 |
||||
) |
||||
|
||||
var ( |
||||
kernel32 = syscall.NewLazyDLL("kernel32.dll") |
||||
procGetConsoleMode = kernel32.NewProc("GetConsoleMode") |
||||
procGetFileInformationByHandleEx = kernel32.NewProc("GetFileInformationByHandleEx") |
||||
procGetFileType = kernel32.NewProc("GetFileType") |
||||
) |
||||
|
||||
func init() { |
||||
// Check if GetFileInformationByHandleEx is available.
|
||||
if procGetFileInformationByHandleEx.Find() != nil { |
||||
procGetFileInformationByHandleEx = nil |
||||
} |
||||
} |
||||
|
||||
// IsTerminal return true if the file descriptor is terminal.
|
||||
func IsTerminal(fd uintptr) bool { |
||||
var st uint32 |
||||
r, _, e := syscall.Syscall(procGetConsoleMode.Addr(), 2, fd, uintptr(unsafe.Pointer(&st)), 0) |
||||
return r != 0 && e == 0 |
||||
} |
||||
|
||||
// Check pipe name is used for cygwin/msys2 pty.
|
||||
// Cygwin/MSYS2 PTY has a name like:
|
||||
// \{cygwin,msys}-XXXXXXXXXXXXXXXX-ptyN-{from,to}-master
|
||||
func isCygwinPipeName(name string) bool { |
||||
token := strings.Split(name, "-") |
||||
if len(token) < 5 { |
||||
return false |
||||
} |
||||
|
||||
if token[0] != `\msys` && token[0] != `\cygwin` { |
||||
return false |
||||
} |
||||
|
||||
if token[1] == "" { |
||||
return false |
||||
} |
||||
|
||||
if !strings.HasPrefix(token[2], "pty") { |
||||
return false |
||||
} |
||||
|
||||
if token[3] != `from` && token[3] != `to` { |
||||
return false |
||||
} |
||||
|
||||
if token[4] != "master" { |
||||
return false |
||||
} |
||||
|
||||
return true |
||||
} |
||||
|
||||
// IsCygwinTerminal() return true if the file descriptor is a cygwin or msys2
|
||||
// terminal.
|
||||
func IsCygwinTerminal(fd uintptr) bool { |
||||
if procGetFileInformationByHandleEx == nil { |
||||
return false |
||||
} |
||||
|
||||
// Cygwin/msys's pty is a pipe.
|
||||
ft, _, e := syscall.Syscall(procGetFileType.Addr(), 1, fd, 0, 0) |
||||
if ft != fileTypePipe || e != 0 { |
||||
return false |
||||
} |
||||
|
||||
var buf [2 + syscall.MAX_PATH]uint16 |
||||
r, _, e := syscall.Syscall6(procGetFileInformationByHandleEx.Addr(), |
||||
4, fd, fileNameInfo, uintptr(unsafe.Pointer(&buf)), |
||||
uintptr(len(buf)*2), 0, 0) |
||||
if r == 0 || e != 0 { |
||||
return false |
||||
} |
||||
|
||||
l := *(*uint32)(unsafe.Pointer(&buf)) |
||||
return isCygwinPipeName(string(utf16.Decode(buf[2 : 2+l/2]))) |
||||
} |
@ -0,0 +1,4 @@
|
||||
*.db |
||||
*.exe |
||||
*.dll |
||||
*.o |
@ -0,0 +1,18 @@
|
||||
language: go |
||||
sudo: required |
||||
dist: trusty |
||||
env: |
||||
- GOTAGS= |
||||
- GOTAGS=libsqlite3 |
||||
- GOTAGS=trace |
||||
- GOTAGS=vtable |
||||
go: |
||||
- 1.7 |
||||
- 1.8 |
||||
- tip |
||||
before_install: |
||||
- go get github.com/mattn/goveralls |
||||
- go get golang.org/x/tools/cmd/cover |
||||
script: |
||||
- $HOME/gopath/bin/goveralls -repotoken 3qJVUE0iQwqnCbmNcDsjYu1nh4J4KIFXx |
||||
- go test -race -v . -tags "$GOTAGS" |
@ -0,0 +1,21 @@
|
||||
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. |
@ -0,0 +1,97 @@
|
||||
go-sqlite3 |
||||
========== |
||||
|
||||
[](http://godoc.org/github.com/mattn/go-sqlite3) |
||||
[](https://travis-ci.org/mattn/go-sqlite3) |
||||
[](https://coveralls.io/r/mattn/go-sqlite3?branch=master) |
||||
[](https://goreportcard.com/report/github.com/mattn/go-sqlite3) |
||||
|
||||
Description |
||||
----------- |
||||
|
||||
sqlite3 driver conforming to the built-in database/sql interface |
||||
|
||||
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, if you install _go-sqlite3_ with `go install github.com/mattn/go-sqlite3`, you don't need gcc to build your app anymore. |
||||
|
||||
Documentation |
||||
------------- |
||||
|
||||
API documentation can be found here: http://godoc.org/github.com/mattn/go-sqlite3 |
||||
|
||||
Examples can be found under the `./_example` directory |
||||
|
||||
FAQ |
||||
--- |
||||
|
||||
* Want to build go-sqlite3 with libsqlite3 on my linux. |
||||
|
||||
Use `go build --tags "libsqlite3 linux"` |
||||
|
||||
* Want to build go-sqlite3 with libsqlite3 on OS X. |
||||
|
||||
Install sqlite3 from homebrew: `brew install sqlite3` |
||||
|
||||
Use `go build --tags "libsqlite3 darwin"` |
||||
|
||||
* Want to build go-sqlite3 with icu extension. |
||||
|
||||
Use `go build --tags "icu"` |
||||
|
||||
Available extensions: `json1`, `fts5`, `icu` |
||||
|
||||
* 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) |
||||
|
||||
* 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 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). |
||||
|
||||
* Why is it racy if I use a `sql.Open("sqlite", ":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. See |
||||
[#204](https://github.com/mattn/go-sqlite3/issues/204) for more info. |
||||
|
||||
License |
||||
------- |
||||
|
||||
MIT: http://mattn.mit-license.org/2012 |
||||
|
||||
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) |
@ -0,0 +1,85 @@
|
||||
// Copyright (C) 2014 Yasuhiro Matsumoto <[email protected]>.
|
||||
//
|
||||
// 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 |
||||
} |
@ -0,0 +1,340 @@
|
||||
// Copyright (C) 2014 Yasuhiro Matsumoto <[email protected]>.
|
||||
//
|
||||
// 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) |
||||
} |
||||
|
||||
// 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 callbackRet(typ reflect.Type) (callbackRetConverter, error) { |
||||
switch typ.Kind() { |
||||
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 |
||||
} |
||||
} |
@ -0,0 +1,112 @@
|
||||
/* |
||||
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 |
@ -0,0 +1,135 @@
|
||||
// Copyright (C) 2014 Yasuhiro Matsumoto <[email protected]>.
|
||||
//
|
||||
// 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) |
||||
) |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,103 @@
|
||||
// Copyright (C) 2014 Yasuhiro Matsumoto <[email protected]>.
|
||||
//
|
||||
// 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)) |
||||
} |
@ -0,0 +1,13 @@
|
||||
// Copyright (C) 2014 Yasuhiro Matsumoto <[email protected]>.
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// +build fts5
|
||||
|
||||
package sqlite3 |
||||
|
||||
/* |
||||
#cgo CFLAGS: -DSQLITE_ENABLE_FTS5 |
||||
#cgo LDFLAGS: -lm |
||||
*/ |
||||
import "C" |
@ -0,0 +1,69 @@
|
||||
// Copyright (C) 2014 Yasuhiro Matsumoto <[email protected]>.
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +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) |
||||
} |
@ -0,0 +1,13 @@
|
||||
// Copyright (C) 2014 Yasuhiro Matsumoto <[email protected]>.
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// +build icu
|
||||
|
||||
package sqlite3 |
||||
|
||||
/* |
||||
#cgo LDFLAGS: -licuuc -licui18n |
||||
#cgo CFLAGS: -DSQLITE_ENABLE_ICU |
||||
*/ |
||||
import "C" |
@ -0,0 +1,12 @@
|
||||
// Copyright (C) 2014 Yasuhiro Matsumoto <[email protected]>.
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// +build json1
|
||||
|
||||
package sqlite3 |
||||
|
||||
/* |
||||
#cgo CFLAGS: -DSQLITE_ENABLE_JSON1 |
||||
*/ |
||||
import "C" |
@ -0,0 +1,14 @@
|
||||
// Copyright (C) 2014 Yasuhiro Matsumoto <[email protected]>.
|
||||
//
|
||||
// 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 |
||||
*/ |
||||
import "C" |
@ -0,0 +1,69 @@
|
||||
// Copyright (C) 2014 Yasuhiro Matsumoto <[email protected]>.
|
||||
//
|
||||
// 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 |
||||
} |
@ -0,0 +1,23 @@
|
||||
// Copyright (C) 2014 Yasuhiro Matsumoto <[email protected]>.
|
||||
//
|
||||
// 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") |
||||
} |
@ -0,0 +1,13 @@
|
||||
// Copyright (C) 2014 Yasuhiro Matsumoto <[email protected]>.
|
||||
//
|
||||
// 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" |
@ -0,0 +1,414 @@
|
||||
// Copyright (C) 2016 Yasuhiro Matsumoto <[email protected]>.
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// +build trace
|
||||
|
||||
package sqlite3 |
||||
|
||||
/* |
||||
#ifndef USE_LIBSQLITE3 |
||||
#include <sqlite3-binding.h> |
||||
#else |
||||
#include <sqlite3.h> |
||||
#endif |
||||
#include <stdlib.h> |
||||
|
||||
void stepTrampoline(sqlite3_context*, int, sqlite3_value**); |
||||
void doneTrampoline(sqlite3_context*); |
||||
int traceCallbackTrampoline(unsigned int traceEventCode, void *ctx, void *p, void *x); |
||||
*/ |
||||
import "C" |
||||
|
||||
import ( |
||||
"errors" |
||||
"fmt" |
||||
"reflect" |
||||
"strings" |
||||
"sync" |
||||
"unsafe" |
||||
) |
||||
|
||||
// Trace... constants identify the possible events causing callback invocation.
|
||||
// Values are same as the corresponding SQLite Trace Event Codes.
|
||||
const ( |
||||
TraceStmt = C.SQLITE_TRACE_STMT |
||||
TraceProfile = C.SQLITE_TRACE_PROFILE |
||||
TraceRow = C.SQLITE_TRACE_ROW |
||||
TraceClose = 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 C.uint |
||||
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 { |
||||
|
||||
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 traceEventCode == 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, traceEventCode)) |
||||
} |
||||
|
||||
var info TraceInfo |
||||
|
||||
info.EventCode = uint32(traceEventCode) |
||||
info.AutoCommit = (int(C.sqlite3_get_autocommit(contextDB)) != 0) |
||||
info.ConnHandle = connHandle |
||||
|
||||
switch traceEventCode { |
||||
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&traceEventCode == 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 |
||||
} |
||||
|
||||
// RegisterAggregator makes a Go type available as a SQLite aggregation function.
|
||||
//
|
||||
// Because aggregation is incremental, it's implemented in Go with a
|
||||
// type that has 2 methods: func Step(values) accumulates one row of
|
||||
// data into the accumulator, and func Done() ret finalizes and
|
||||
// returns the aggregate value. "values" and "ret" may be any type
|
||||
// supported by RegisterFunc.
|
||||
//
|
||||
// RegisterAggregator takes as implementation a constructor function
|
||||
// that constructs an instance of the aggregator type each time an
|
||||
// aggregation begins. The constructor must return a pointer to a
|
||||
// type, or an interface that implements Step() and Done().
|
||||
//
|
||||
// The constructor function and the Step/Done methods may optionally
|
||||
// return an error in addition to their other return values.
|
||||
//
|
||||
// See _example/go_custom_funcs for a detailed example.
|
||||
func (c *SQLiteConn) RegisterAggregator(name string, impl interface{}, pure bool) error { |
||||
var ai aggInfo |
||||
ai.constructor = reflect.ValueOf(impl) |
||||
t := ai.constructor.Type() |
||||
if t.Kind() != reflect.Func { |
||||
return errors.New("non-function passed to RegisterAggregator") |
||||
} |
||||
if t.NumOut() != 1 && t.NumOut() != 2 { |
||||
return errors.New("SQLite aggregator constructors must return 1 or 2 values") |
||||
} |
||||
if t.NumOut() == 2 && !t.Out(1).Implements(reflect.TypeOf((*error)(nil)).Elem()) { |
||||
return errors.New("Second return value of SQLite function must be error") |
||||
} |
||||
if t.NumIn() != 0 { |
||||
return errors.New("SQLite aggregator constructors must not have arguments") |
||||
} |
||||
|
||||
agg := t.Out(0) |
||||
switch agg.Kind() { |
||||
case reflect.Ptr, reflect.Interface: |
||||
default: |
||||
return errors.New("SQlite aggregator constructor must return a pointer object") |
||||
} |
||||
stepFn, found := agg.MethodByName("Step") |
||||
if !found { |
||||
return errors.New("SQlite aggregator doesn't have a Step() function") |
||||
} |
||||
step := stepFn.Type |
||||
if step.NumOut() != 0 && step.NumOut() != 1 { |
||||
return errors.New("SQlite aggregator Step() function must return 0 or 1 values") |
||||
} |
||||
if step.NumOut() == 1 && !step.Out(0).Implements(reflect.TypeOf((*error)(nil)).Elem()) { |
||||
return errors.New("type of SQlite aggregator Step() return value must be error") |
||||
} |
||||
|
||||
stepNArgs := step.NumIn() |
||||
start := 0 |
||||
if agg.Kind() == reflect.Ptr { |
||||
// Skip over the method receiver
|
||||
stepNArgs-- |
||||
start++ |
||||
} |
||||
if step.IsVariadic() { |
||||
stepNArgs-- |
||||
} |
||||
for i := start; i < start+stepNArgs; i++ { |
||||
conv, err := callbackArg(step.In(i)) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
ai.stepArgConverters = append(ai.stepArgConverters, conv) |
||||
} |
||||
if step.IsVariadic() { |
||||
conv, err := callbackArg(t.In(start + stepNArgs).Elem()) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
ai.stepVariadicConverter = conv |
||||
// Pass -1 to sqlite so that it allows any number of
|
||||
// arguments. The call helper verifies that the minimum number
|
||||
// of arguments is present for variadic functions.
|
||||
stepNArgs = -1 |
||||
} |
||||
|
||||
doneFn, found := agg.MethodByName("Done") |
||||
if !found { |
||||
return errors.New("SQlite aggregator doesn't have a Done() function") |
||||
} |
||||
done := doneFn.Type |
||||
doneNArgs := done.NumIn() |
||||
if agg.Kind() == reflect.Ptr { |
||||
// Skip over the method receiver
|
||||
doneNArgs-- |
||||
} |
||||
if doneNArgs != 0 { |
||||
return errors.New("SQlite aggregator Done() function must have no arguments") |
||||
} |
||||
if done.NumOut() != 1 && done.NumOut() != 2 { |
||||
return errors.New("SQLite aggregator Done() function must return 1 or 2 values") |
||||
} |
||||
if done.NumOut() == 2 && !done.Out(1).Implements(reflect.TypeOf((*error)(nil)).Elem()) { |
||||
return errors.New("second return value of SQLite aggregator Done() function must be error") |
||||
} |
||||
|
||||
conv, err := callbackRet(done.Out(0)) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
ai.doneRetConverter = conv |
||||
ai.active = make(map[int64]reflect.Value) |
||||
ai.next = 1 |
||||
|
||||
// ai must outlast the database connection, or we'll have dangling pointers.
|
||||
c.aggregators = append(c.aggregators, &ai) |
||||
|
||||
cname := C.CString(name) |
||||
defer C.free(unsafe.Pointer(cname)) |
||||
opts := C.SQLITE_UTF8 |
||||
if pure { |
||||
opts |= C.SQLITE_DETERMINISTIC |
||||
} |
||||
rv := sqlite3CreateFunction(c.db, cname, C.int(stepNArgs), C.int(opts), newHandle(c, &ai), nil, C.stepTrampoline, C.doneTrampoline) |
||||
if rv != C.SQLITE_OK { |
||||
return c.lastError() |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// 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 |
||||
} |
@ -0,0 +1,57 @@
|
||||
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))) |
||||
} |
@ -0,0 +1,646 @@
|
||||
// Copyright (C) 2014 Yasuhiro Matsumoto <[email protected]>.
|
||||
//
|
||||
// Use of this source code is governed by an MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
// +build vtable
|
||||
|
||||
package sqlite3 |
||||
|
||||
/* |
||||
#cgo CFLAGS: -std=gnu99 |
||||
#cgo CFLAGS: -DSQLITE_ENABLE_RTREE -DSQLITE_THREADSAFE |
||||
#cgo CFLAGS: -DSQLITE_ENABLE_FTS3 -DSQLITE_ENABLE_FTS3_PARENTHESIS -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 |
||||
} |
@ -0,0 +1,14 @@
|
||||
// Copyright (C) 2014 Yasuhiro Matsumoto <[email protected]>.
|
||||
//
|
||||
// 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. -fno-stack-check -fno-stack-protector -mno-stack-arg-probe |
||||
#cgo windows,386 CFLAGS: -D_USE_32BIT_TIME_T |
||||
#cgo LDFLAGS: -lmingwex -lmingw32 |
||||
*/ |
||||
import "C" |
@ -0,0 +1,565 @@
|
||||
#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.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 * (*snprintf)(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 *(*vsnprintf)(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*); |
||||
}; |
||||
|
||||
/*
|
||||
** 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->snprintf |
||||
#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->vsnprintf |
||||
#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->vsnprintf |
||||
#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 |
||||
#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 |
@ -0,0 +1,23 @@
|
||||
Copyright (c) 2015, yhat |
||||
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. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND |
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR |
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON |
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@ -0,0 +1,72 @@
|
||||
# scrape |
||||
|
||||
A simple, higher level interface for Go web scraping. |
||||
|
||||
When scraping with Go, I find myself redefining tree traversal and other |
||||
utility functions. |
||||
|
||||
This package is a place to put some simple tools which build on top of the |
||||
[Go HTML parsing library](https://godoc.org/golang.org/x/net/html). |
||||
|
||||
For the full interface check out the godoc |
||||
[](https://godoc.org/github.com/yhat/scrape) |
||||
|
||||
## Sample |
||||
|
||||
Scrape defines traversal functions like `Find` and `FindAll` while attempting |
||||
to be generic. It also defines convenience functions such as `Attr` and `Text`. |
||||
|
||||
```go |
||||
// Parse the page |
||||
root, err := html.Parse(resp.Body) |
||||
if err != nil { |
||||
// handle error |
||||
} |
||||
// Search for the title |
||||
title, ok := scrape.Find(root, scrape.ByTag(atom.Title)) |
||||
if ok { |
||||
// Print the title |
||||
fmt.Println(scrape.Text(title)) |
||||
} |
||||
``` |
||||
|
||||
## A full example: Scraping Hacker News |
||||
|
||||
```go |
||||
package main |
||||
|
||||
import ( |
||||
"fmt" |
||||
"net/http" |
||||
|
||||
"github.com/yhat/scrape" |
||||
"golang.org/x/net/html" |
||||
"golang.org/x/net/html/atom" |
||||
) |
||||
|
||||
func main() { |
||||
// request and parse the front page |
||||
resp, err := http.Get("https://news.ycombinator.com/") |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
root, err := html.Parse(resp.Body) |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
|
||||
// define a matcher |
||||
matcher := func(n *html.Node) bool { |
||||
// must check for nil values |
||||
if n.DataAtom == atom.A && n.Parent != nil && n.Parent.Parent != nil { |
||||
return scrape.Attr(n.Parent.Parent, "class") == "athing" |
||||
} |
||||
return false |
||||
} |
||||
// grab all articles and print them |
||||
articles := scrape.FindAll(root, matcher) |
||||
for i, article := range articles { |
||||
fmt.Printf("%2d %s (%s)\n", i, scrape.Text(article), scrape.Attr(article, "href")) |
||||
} |
||||
} |
||||
``` |
@ -0,0 +1,189 @@
|
||||
// Package scrape provides a searching api on top of golang.org/x/net/html
|
||||
package scrape |
||||
|
||||
import ( |
||||
"strings" |
||||
|
||||
"golang.org/x/net/html" |
||||
"golang.org/x/net/html/atom" |
||||
) |
||||
|
||||
// Matcher should return true when a desired node is found.
|
||||
type Matcher func(node *html.Node) bool |
||||
|
||||
// FindAll returns all nodes which match the provided Matcher. After discovering a matching
|
||||
// node, it will _not_ discover matching subnodes of that node.
|
||||
func FindAll(node *html.Node, matcher Matcher) []*html.Node { |
||||
return findAllInternal(node, matcher, false) |
||||
} |
||||
|
||||
// FindAllNested returns all nodes which match the provided Matcher and _will_ discover
|
||||
// matching subnodes of matching nodes.
|
||||
func FindAllNested(node *html.Node, matcher Matcher) []*html.Node { |
||||
return findAllInternal(node, matcher, true) |
||||
} |
||||
|
||||
// Find returns the first node which matches the matcher using depth-first search.
|
||||
// If no node is found, ok will be false.
|
||||
//
|
||||
// root, err := html.Parse(resp.Body)
|
||||
// if err != nil {
|
||||
// // handle error
|
||||
// }
|
||||
// matcher := func(n *html.Node) bool {
|
||||
// return n.DataAtom == atom.Body
|
||||
// }
|
||||
// body, ok := scrape.Find(root, matcher)
|
||||
func Find(node *html.Node, matcher Matcher) (n *html.Node, ok bool) { |
||||
if matcher(node) { |
||||
return node, true |
||||
} |
||||
|
||||
for c := node.FirstChild; c != nil; c = c.NextSibling { |
||||
n, ok := Find(c, matcher) |
||||
if ok { |
||||
return n, true |
||||
} |
||||
} |
||||
return nil, false |
||||
} |
||||
|
||||
// FindParent searches up HTML tree from the current node until either a
|
||||
// match is found or the top is hit.
|
||||
func FindParent(node *html.Node, matcher Matcher) (n *html.Node, ok bool) { |
||||
for p := node.Parent; p != nil; p = p.Parent { |
||||
if matcher(p) { |
||||
return p, true |
||||
} |
||||
} |
||||
return nil, false |
||||
} |
||||
|
||||
// Text returns text from all descendant text nodes joined.
|
||||
// For control over the join function, see TextJoin.
|
||||
func Text(node *html.Node) string { |
||||
joiner := func(s []string) string { |
||||
n := 0 |
||||
for i := range s { |
||||
trimmed := strings.TrimSpace(s[i]) |
||||
if trimmed != "" { |
||||
s[n] = trimmed |
||||
n++ |
||||
} |
||||
} |
||||
return strings.Join(s[:n], " ") |
||||
} |
||||
return TextJoin(node, joiner) |
||||
} |
||||
|
||||
// TextJoin returns a string from all descendant text nodes joined by a
|
||||
// caller provided join function.
|
||||
func TextJoin(node *html.Node, join func([]string) string) string { |
||||
nodes := FindAll(node, func(n *html.Node) bool { return n.Type == html.TextNode }) |
||||
parts := make([]string, len(nodes)) |
||||
for i, n := range nodes { |
||||
parts[i] = n.Data |
||||
} |
||||
return join(parts) |
||||
} |
||||
|
||||
// Attr returns the value of an HTML attribute.
|
||||
func Attr(node *html.Node, key string) string { |
||||
for _, a := range node.Attr { |
||||
if a.Key == key { |
||||
return a.Val |
||||
} |
||||
} |
||||
return "" |
||||
} |
||||
|
||||
// ByTag returns a Matcher which matches all nodes of the provided tag type.
|
||||
//
|
||||
// root, err := html.Parse(resp.Body)
|
||||
// if err != nil {
|
||||
// // handle error
|
||||
// }
|
||||
// title, ok := scrape.Find(root, scrape.ByTag(atom.Title))
|
||||
func ByTag(a atom.Atom) Matcher { |
||||
return func(node *html.Node) bool { return node.DataAtom == a } |
||||
} |
||||
|
||||
// ById returns a Matcher which matches all nodes with the provided id.
|
||||
func ById(id string) Matcher { |
||||
return func(node *html.Node) bool { return Attr(node, "id") == id } |
||||
} |
||||
|
||||
// ByClass returns a Matcher which matches all nodes with the provided class.
|
||||
func ByClass(class string) Matcher { |
||||
return func(node *html.Node) bool { |
||||
classes := strings.Fields(Attr(node, "class")) |
||||
for _, c := range classes { |
||||
if c == class { |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
} |
||||
|
||||
// findAllInternal encapsulates the node tree traversal
|
||||
func findAllInternal(node *html.Node, matcher Matcher, searchNested bool) []*html.Node { |
||||
matched := []*html.Node{} |
||||
|
||||
if matcher(node) { |
||||
matched = append(matched, node) |
||||
|
||||
if !searchNested { |
||||
return matched |
||||
} |
||||
} |
||||
|
||||
for c := node.FirstChild; c != nil; c = c.NextSibling { |
||||
found := findAllInternal(c, matcher, searchNested) |
||||
if len(found) > 0 { |
||||
matched = append(matched, found...) |
||||
} |
||||
} |
||||
return matched |
||||
} |
||||
|
||||
// Find returns the first node which matches the matcher using next sibling search.
|
||||
// If no node is found, ok will be false.
|
||||
//
|
||||
// root, err := html.Parse(resp.Body)
|
||||
// if err != nil {
|
||||
// // handle error
|
||||
// }
|
||||
// matcher := func(n *html.Node) bool {
|
||||
// return n.DataAtom == atom.Body
|
||||
// }
|
||||
// body, ok := scrape.FindNextSibling(root, matcher)
|
||||
func FindNextSibling(node *html.Node, matcher Matcher) (n *html.Node, ok bool) { |
||||
|
||||
for s := node.NextSibling; s != nil; s = s.NextSibling { |
||||
if matcher(s) { |
||||
return s, true |
||||
} |
||||
} |
||||
return nil, false |
||||
} |
||||
|
||||
// Find returns the first node which matches the matcher using previous sibling search.
|
||||
// If no node is found, ok will be false.
|
||||
//
|
||||
// root, err := html.Parse(resp.Body)
|
||||
// if err != nil {
|
||||
// // handle error
|
||||
// }
|
||||
// matcher := func(n *html.Node) bool {
|
||||
// return n.DataAtom == atom.Body
|
||||
// }
|
||||
// body, ok := scrape.FindPrevSibling(root, matcher)
|
||||
func FindPrevSibling(node *html.Node, matcher Matcher) (n *html.Node, ok bool) { |
||||
for s := node.PrevSibling; s != nil; s = s.PrevSibling { |
||||
if matcher(s) { |
||||
return s, true |
||||
} |
||||
} |
||||
return nil, false |
||||
} |
@ -0,0 +1,3 @@
|
||||
# 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. |
@ -0,0 +1,3 @@
|
||||
# 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. |
@ -0,0 +1,27 @@
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions are |
||||
met: |
||||
|
||||
* Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
* Redistributions in binary form must reproduce the above |
||||
copyright notice, this list of conditions and the following disclaimer |
||||
in the documentation and/or other materials provided with the |
||||
distribution. |
||||
* Neither the name of Google Inc. nor the names of its |
||||
contributors may be used to endorse or promote products derived from |
||||
this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@ -0,0 +1,22 @@
|
||||
Additional IP Rights Grant (Patents) |
||||
|
||||
"This implementation" means the copyrightable works distributed by |
||||
Google as part of the Go project. |
||||
|
||||
Google hereby grants to You a perpetual, worldwide, non-exclusive, |
||||
no-charge, royalty-free, irrevocable (except as stated in this section) |
||||
patent license to make, have made, use, offer to sell, sell, import, |
||||
transfer and otherwise run, modify and propagate the contents of this |
||||
implementation of Go, where such license applies only to those patent |
||||
claims, both currently owned or controlled by Google and acquired in |
||||
the future, licensable by Google that are necessarily infringed by this |
||||
implementation of Go. This grant does not include claims that would be |
||||
infringed only as a consequence of further modification of this |
||||
implementation. If you or your agent or exclusive licensee institute or |
||||
order or agree to the institution of patent litigation against any |
||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging |
||||
that this implementation of Go or any code incorporated within this |
||||
implementation of Go constitutes direct or contributory patent |
||||
infringement, or inducement of patent infringement, then any patent |
||||
rights granted to you under this License for this implementation of Go |
||||
shall terminate as of the date such litigation is filed. |
@ -0,0 +1,156 @@
|
||||
// Copyright 2014 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 context defines the Context type, which carries deadlines,
|
||||
// cancelation signals, and other request-scoped values across API boundaries
|
||||
// and between processes.
|
||||
//
|
||||
// Incoming requests to a server should create a Context, and outgoing calls to
|
||||
// servers should accept a Context. The chain of function calls between must
|
||||
// propagate the Context, optionally replacing it with a modified copy created
|
||||
// using WithDeadline, WithTimeout, WithCancel, or WithValue.
|
||||
//
|
||||
// Programs that use Contexts should follow these rules to keep interfaces
|
||||
// consistent across packages and enable static analysis tools to check context
|
||||
// propagation:
|
||||
//
|
||||
// Do not store Contexts inside a struct type; instead, pass a Context
|
||||
// explicitly to each function that needs it. The Context should be the first
|
||||
// parameter, typically named ctx:
|
||||
//
|
||||
// func DoSomething(ctx context.Context, arg Arg) error {
|
||||
// // ... use ctx ...
|
||||
// }
|
||||
//
|
||||
// Do not pass a nil Context, even if a function permits it. Pass context.TODO
|
||||
// if you are unsure about which Context to use.
|
||||
//
|
||||
// Use context Values only for request-scoped data that transits processes and
|
||||
// APIs, not for passing optional parameters to functions.
|
||||
//
|
||||
// The same Context may be passed to functions running in different goroutines;
|
||||
// Contexts are safe for simultaneous use by multiple goroutines.
|
||||
//
|
||||
// See http://blog.golang.org/context for example code for a server that uses
|
||||
// Contexts.
|
||||
package context |
||||
|
||||
import "time" |
||||
|
||||
// A Context carries a deadline, a cancelation signal, and other values across
|
||||
// API boundaries.
|
||||
//
|
||||
// Context's methods may be called by multiple goroutines simultaneously.
|
||||
type Context interface { |
||||
// Deadline returns the time when work done on behalf of this context
|
||||
// should be canceled. Deadline returns ok==false when no deadline is
|
||||
// set. Successive calls to Deadline return the same results.
|
||||
Deadline() (deadline time.Time, ok bool) |
||||
|
||||
// Done returns a channel that's closed when work done on behalf of this
|
||||
// context should be canceled. Done may return nil if this context can
|
||||
// never be canceled. Successive calls to Done return the same value.
|
||||
//
|
||||
// WithCancel arranges for Done to be closed when cancel is called;
|
||||
// WithDeadline arranges for Done to be closed when the deadline
|
||||
// expires; WithTimeout arranges for Done to be closed when the timeout
|
||||
// elapses.
|
||||
//
|
||||
// Done is provided for use in select statements:
|
||||
//
|
||||
// // Stream generates values with DoSomething and sends them to out
|
||||
// // until DoSomething returns an error or ctx.Done is closed.
|
||||
// func Stream(ctx context.Context, out chan<- Value) error {
|
||||
// for {
|
||||
// v, err := DoSomething(ctx)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// select {
|
||||
// case <-ctx.Done():
|
||||
// return ctx.Err()
|
||||
// case out <- v:
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// See http://blog.golang.org/pipelines for more examples of how to use
|
||||
// a Done channel for cancelation.
|
||||
Done() <-chan struct{} |
||||
|
||||
// Err returns a non-nil error value after Done is closed. Err returns
|
||||
// Canceled if the context was canceled or DeadlineExceeded if the
|
||||
// context's deadline passed. No other values for Err are defined.
|
||||
// After Done is closed, successive calls to Err return the same value.
|
||||
Err() error |
||||
|
||||
// Value returns the value associated with this context for key, or nil
|
||||
// if no value is associated with key. Successive calls to Value with
|
||||
// the same key returns the same result.
|
||||
//
|
||||
// Use context values only for request-scoped data that transits
|
||||
// processes and API boundaries, not for passing optional parameters to
|
||||
// functions.
|
||||
//
|
||||
// A key identifies a specific value in a Context. Functions that wish
|
||||
// to store values in Context typically allocate a key in a global
|
||||
// variable then use that key as the argument to context.WithValue and
|
||||
// Context.Value. A key can be any type that supports equality;
|
||||
// packages should define keys as an unexported type to avoid
|
||||
// collisions.
|
||||
//
|
||||
// Packages that define a Context key should provide type-safe accessors
|
||||
// for the values stores using that key:
|
||||
//
|
||||
// // Package user defines a User type that's stored in Contexts.
|
||||
// package user
|
||||
//
|
||||
// import "golang.org/x/net/context"
|
||||
//
|
||||
// // User is the type of value stored in the Contexts.
|
||||
// type User struct {...}
|
||||
//
|
||||
// // key is an unexported type for keys defined in this package.
|
||||
// // This prevents collisions with keys defined in other packages.
|
||||
// type key int
|
||||
//
|
||||
// // userKey is the key for user.User values in Contexts. It is
|
||||
// // unexported; clients use user.NewContext and user.FromContext
|
||||
// // instead of using this key directly.
|
||||
// var userKey key = 0
|
||||
//
|
||||
// // NewContext returns a new Context that carries value u.
|
||||
// func NewContext(ctx context.Context, u *User) context.Context {
|
||||
// return context.WithValue(ctx, userKey, u)
|
||||
// }
|
||||
//
|
||||
// // FromContext returns the User value stored in ctx, if any.
|
||||
// func FromContext(ctx context.Context) (*User, bool) {
|
||||
// u, ok := ctx.Value(userKey).(*User)
|
||||
// return u, ok
|
||||
// }
|
||||
Value(key interface{}) interface{} |
||||
} |
||||
|
||||
// Background returns a non-nil, empty Context. It is never canceled, has no
|
||||
// values, and has no deadline. It is typically used by the main function,
|
||||
// initialization, and tests, and as the top-level Context for incoming
|
||||
// requests.
|
||||
func Background() Context { |
||||
return background |
||||
} |
||||
|
||||
// TODO returns a non-nil, empty Context. Code should use context.TODO when
|
||||
// it's unclear which Context to use or it is not yet available (because the
|
||||
// surrounding function has not yet been extended to accept a Context
|
||||
// parameter). TODO is recognized by static analysis tools that determine
|
||||
// whether Contexts are propagated correctly in a program.
|
||||
func TODO() Context { |
||||
return todo |
||||
} |
||||
|
||||
// A CancelFunc tells an operation to abandon its work.
|
||||
// A CancelFunc does not wait for the work to stop.
|
||||
// After the first call, subsequent calls to a CancelFunc do nothing.
|
||||
type CancelFunc func() |
@ -0,0 +1,72 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// +build go1.7
|
||||
|
||||
package context |
||||
|
||||
import ( |
||||
"context" // standard library's context, as of Go 1.7
|
||||
"time" |
||||
) |
||||
|
||||
var ( |
||||
todo = context.TODO() |
||||
background = context.Background() |
||||
) |
||||
|
||||
// Canceled is the error returned by Context.Err when the context is canceled.
|
||||
var Canceled = context.Canceled |
||||
|
||||
// DeadlineExceeded is the error returned by Context.Err when the context's
|
||||
// deadline passes.
|
||||
var DeadlineExceeded = context.DeadlineExceeded |
||||
|
||||
// WithCancel returns a copy of parent with a new Done channel. The returned
|
||||
// context's Done channel is closed when the returned cancel function is called
|
||||
// or when the parent context's Done channel is closed, whichever happens first.
|
||||
//
|
||||
// Canceling this context releases resources associated with it, so code should
|
||||
// call cancel as soon as the operations running in this Context complete.
|
||||
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { |
||||
ctx, f := context.WithCancel(parent) |
||||
return ctx, CancelFunc(f) |
||||
} |
||||
|
||||
// WithDeadline returns a copy of the parent context with the deadline adjusted
|
||||
// to be no later than d. If the parent's deadline is already earlier than d,
|
||||
// WithDeadline(parent, d) is semantically equivalent to parent. The returned
|
||||
// context's Done channel is closed when the deadline expires, when the returned
|
||||
// cancel function is called, or when the parent context's Done channel is
|
||||
// closed, whichever happens first.
|
||||
//
|
||||
// Canceling this context releases resources associated with it, so code should
|
||||
// call cancel as soon as the operations running in this Context complete.
|
||||
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) { |
||||
ctx, f := context.WithDeadline(parent, deadline) |
||||
return ctx, CancelFunc(f) |
||||
} |
||||
|
||||
// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
|
||||
//
|
||||
// Canceling this context releases resources associated with it, so code should
|
||||
// call cancel as soon as the operations running in this Context complete:
|
||||
//
|
||||
// func slowOperationWithTimeout(ctx context.Context) (Result, error) {
|
||||
// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
|
||||
// defer cancel() // releases resources if slowOperation completes before timeout elapses
|
||||
// return slowOperation(ctx)
|
||||
// }
|
||||
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { |
||||
return WithDeadline(parent, time.Now().Add(timeout)) |
||||
} |
||||
|
||||
// WithValue returns a copy of parent in which the value associated with key is
|
||||
// val.
|
||||
//
|
||||
// Use context Values only for request-scoped data that transits processes and
|
||||
// APIs, not for passing optional parameters to functions.
|
||||
func WithValue(parent Context, key interface{}, val interface{}) Context { |
||||
return context.WithValue(parent, key, val) |
||||
} |
@ -0,0 +1,300 @@
|
||||
// Copyright 2014 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 !go1.7
|
||||
|
||||
package context |
||||
|
||||
import ( |
||||
"errors" |
||||
"fmt" |
||||
"sync" |
||||
"time" |
||||
) |
||||
|
||||
// An emptyCtx is never canceled, has no values, and has no deadline. It is not
|
||||
// struct{}, since vars of this type must have distinct addresses.
|
||||
type emptyCtx int |
||||
|
||||
func (*emptyCtx) Deadline() (deadline time.Time, ok bool) { |
||||
return |
||||
} |
||||
|
||||
func (*emptyCtx) Done() <-chan struct{} { |
||||
return nil |
||||
} |
||||
|
||||
func (*emptyCtx) Err() error { |
||||
return nil |
||||
} |
||||
|
||||
func (*emptyCtx) Value(key interface{}) interface{} { |
||||
return nil |
||||
} |
||||
|
||||
func (e *emptyCtx) String() string { |
||||
switch e { |
||||
case background: |
||||
return "context.Background" |
||||
case todo: |
||||
return "context.TODO" |
||||
} |
||||
return "unknown empty Context" |
||||
} |
||||
|
||||
var ( |
||||
background = new(emptyCtx) |
||||
todo = new(emptyCtx) |
||||
) |
||||
|
||||
// Canceled is the error returned by Context.Err when the context is canceled.
|
||||
var Canceled = errors.New("context canceled") |
||||
|
||||
// DeadlineExceeded is the error returned by Context.Err when the context's
|
||||
// deadline passes.
|
||||
var DeadlineExceeded = errors.New("context deadline exceeded") |
||||
|
||||
// WithCancel returns a copy of parent with a new Done channel. The returned
|
||||
// context's Done channel is closed when the returned cancel function is called
|
||||
// or when the parent context's Done channel is closed, whichever happens first.
|
||||
//
|
||||
// Canceling this context releases resources associated with it, so code should
|
||||
// call cancel as soon as the operations running in this Context complete.
|
||||
func WithCancel(parent Context) (ctx Context, cancel CancelFunc) { |
||||
c := newCancelCtx(parent) |
||||
propagateCancel(parent, c) |
||||
return c, func() { c.cancel(true, Canceled) } |
||||
} |
||||
|
||||
// newCancelCtx returns an initialized cancelCtx.
|
||||
func newCancelCtx(parent Context) *cancelCtx { |
||||
return &cancelCtx{ |
||||
Context: parent, |
||||
done: make(chan struct{}), |
||||
} |
||||
} |
||||
|
||||
// propagateCancel arranges for child to be canceled when parent is.
|
||||
func propagateCancel(parent Context, child canceler) { |
||||
if parent.Done() == nil { |
||||
return // parent is never canceled
|
||||
} |
||||
if p, ok := parentCancelCtx(parent); ok { |
||||
p.mu.Lock() |
||||
if p.err != nil { |
||||
// parent has already been canceled
|
||||
child.cancel(false, p.err) |
||||
} else { |
||||
if p.children == nil { |
||||
p.children = make(map[canceler]bool) |
||||
} |
||||
p.children[child] = true |
||||
} |
||||
p.mu.Unlock() |
||||
} else { |
||||
go func() { |
||||
select { |
||||
case <-parent.Done(): |
||||
child.cancel(false, parent.Err()) |
||||
case <-child.Done(): |
||||
} |
||||
}() |
||||
} |
||||
} |
||||
|
||||
// parentCancelCtx follows a chain of parent references until it finds a
|
||||
// *cancelCtx. This function understands how each of the concrete types in this
|
||||
// package represents its parent.
|
||||
func parentCancelCtx(parent Context) (*cancelCtx, bool) { |
||||
for { |
||||
switch c := parent.(type) { |
||||
case *cancelCtx: |
||||
return c, true |
||||
case *timerCtx: |
||||
return c.cancelCtx, true |
||||
case *valueCtx: |
||||
parent = c.Context |
||||
default: |
||||
return nil, false |
||||
} |
||||
} |
||||
} |
||||
|
||||
// removeChild removes a context from its parent.
|
||||
func removeChild(parent Context, child canceler) { |
||||
p, ok := parentCancelCtx(parent) |
||||
if !ok { |
||||
return |
||||
} |
||||
p.mu.Lock() |
||||
if p.children != nil { |
||||
delete(p.children, child) |
||||
} |
||||
p.mu.Unlock() |
||||
} |
||||
|
||||
// A canceler is a context type that can be canceled directly. The
|
||||
// implementations are *cancelCtx and *timerCtx.
|
||||
type canceler interface { |
||||
cancel(removeFromParent bool, err error) |
||||
Done() <-chan struct{} |
||||
} |
||||
|
||||
// A cancelCtx can be canceled. When canceled, it also cancels any children
|
||||
// that implement canceler.
|
||||
type cancelCtx struct { |
||||
Context |
||||
|
||||
done chan struct{} // closed by the first cancel call.
|
||||
|
||||
mu sync.Mutex |
||||
children map[canceler]bool // set to nil by the first cancel call
|
||||
err error // set to non-nil by the first cancel call
|
||||
} |
||||
|
||||
func (c *cancelCtx) Done() <-chan struct{} { |
||||
return c.done |
||||
} |
||||
|
||||
func (c *cancelCtx) Err() error { |
||||
c.mu.Lock() |
||||
defer c.mu.Unlock() |
||||
return c.err |
||||
} |
||||
|
||||
func (c *cancelCtx) String() string { |
||||
return fmt.Sprintf("%v.WithCancel", c.Context) |
||||
} |
||||
|
||||
// cancel closes c.done, cancels each of c's children, and, if
|
||||
// removeFromParent is true, removes c from its parent's children.
|
||||
func (c *cancelCtx) cancel(removeFromParent bool, err error) { |
||||
if err == nil { |
||||
panic("context: internal error: missing cancel error") |
||||
} |
||||
c.mu.Lock() |
||||
if c.err != nil { |
||||
c.mu.Unlock() |
||||
return // already canceled
|
||||
} |
||||
c.err = err |
||||
close(c.done) |
||||
for child := range c.children { |
||||
// NOTE: acquiring the child's lock while holding parent's lock.
|
||||
child.cancel(false, err) |
||||
} |
||||
c.children = nil |
||||
c.mu.Unlock() |
||||
|
||||
if removeFromParent { |
||||
removeChild(c.Context, c) |
||||
} |
||||
} |
||||
|
||||
// WithDeadline returns a copy of the parent context with the deadline adjusted
|
||||
// to be no later than d. If the parent's deadline is already earlier than d,
|
||||
// WithDeadline(parent, d) is semantically equivalent to parent. The returned
|
||||
// context's Done channel is closed when the deadline expires, when the returned
|
||||
// cancel function is called, or when the parent context's Done channel is
|
||||
// closed, whichever happens first.
|
||||
//
|
||||
// Canceling this context releases resources associated with it, so code should
|
||||
// call cancel as soon as the operations running in this Context complete.
|
||||
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc) { |
||||
if cur, ok := parent.Deadline(); ok && cur.Before(deadline) { |
||||
// The current deadline is already sooner than the new one.
|
||||
return WithCancel(parent) |
||||
} |
||||
c := &timerCtx{ |
||||
cancelCtx: newCancelCtx(parent), |
||||
deadline: deadline, |
||||
} |
||||
propagateCancel(parent, c) |
||||
d := deadline.Sub(time.Now()) |
||||
if d <= 0 { |
||||
c.cancel(true, DeadlineExceeded) // deadline has already passed
|
||||
return c, func() { c.cancel(true, Canceled) } |
||||
} |
||||
c.mu.Lock() |
||||
defer c.mu.Unlock() |
||||
if c.err == nil { |
||||
c.timer = time.AfterFunc(d, func() { |
||||
c.cancel(true, DeadlineExceeded) |
||||
}) |
||||
} |
||||
return c, func() { c.cancel(true, Canceled) } |
||||
} |
||||
|
||||
// A timerCtx carries a timer and a deadline. It embeds a cancelCtx to
|
||||
// implement Done and Err. It implements cancel by stopping its timer then
|
||||
// delegating to cancelCtx.cancel.
|
||||
type timerCtx struct { |
||||
*cancelCtx |
||||
timer *time.Timer // Under cancelCtx.mu.
|
||||
|
||||
deadline time.Time |
||||
} |
||||
|
||||
func (c *timerCtx) Deadline() (deadline time.Time, ok bool) { |
||||
return c.deadline, true |
||||
} |
||||
|
||||
func (c *timerCtx) String() string { |
||||
return fmt.Sprintf("%v.WithDeadline(%s [%s])", c.cancelCtx.Context, c.deadline, c.deadline.Sub(time.Now())) |
||||
} |
||||
|
||||
func (c *timerCtx) cancel(removeFromParent bool, err error) { |
||||
c.cancelCtx.cancel(false, err) |
||||
if removeFromParent { |
||||
// Remove this timerCtx from its parent cancelCtx's children.
|
||||
removeChild(c.cancelCtx.Context, c) |
||||
} |
||||
c.mu.Lock() |
||||
if c.timer != nil { |
||||
c.timer.Stop() |
||||
c.timer = nil |
||||
} |
||||
c.mu.Unlock() |
||||
} |
||||
|
||||
// WithTimeout returns WithDeadline(parent, time.Now().Add(timeout)).
|
||||
//
|
||||
// Canceling this context releases resources associated with it, so code should
|
||||
// call cancel as soon as the operations running in this Context complete:
|
||||
//
|
||||
// func slowOperationWithTimeout(ctx context.Context) (Result, error) {
|
||||
// ctx, cancel := context.WithTimeout(ctx, 100*time.Millisecond)
|
||||
// defer cancel() // releases resources if slowOperation completes before timeout elapses
|
||||
// return slowOperation(ctx)
|
||||
// }
|
||||
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc) { |
||||
return WithDeadline(parent, time.Now().Add(timeout)) |
||||
} |
||||
|
||||
// WithValue returns a copy of parent in which the value associated with key is
|
||||
// val.
|
||||
//
|
||||
// Use context Values only for request-scoped data that transits processes and
|
||||
// APIs, not for passing optional parameters to functions.
|
||||
func WithValue(parent Context, key interface{}, val interface{}) Context { |
||||
return &valueCtx{parent, key, val} |
||||
} |
||||
|
||||
// A valueCtx carries a key-value pair. It implements Value for that key and
|
||||
// delegates all other calls to the embedded Context.
|
||||
type valueCtx struct { |
||||
Context |
||||
key, val interface{} |
||||
} |
||||
|
||||
func (c *valueCtx) String() string { |
||||
return fmt.Sprintf("%v.WithValue(%#v, %#v)", c.Context, c.key, c.val) |
||||
} |
||||
|
||||
func (c *valueCtx) Value(key interface{}) interface{} { |
||||
if c.key == key { |
||||
return c.val |
||||
} |
||||
return c.Context.Value(key) |
||||
} |
@ -0,0 +1,78 @@
|
||||
// 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 |
||||
|
||||
// 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) |
||||
} |
@ -0,0 +1,648 @@
|
||||
// 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
|
||||
|
||||
package main |
||||
|
||||
// This program generates table.go and table_test.go.
|
||||
// Invoke as
|
||||
//
|
||||
// go run gen.go |gofmt >table.go
|
||||
// go run gen.go -test |gofmt >table_test.go
|
||||
|
||||
import ( |
||||
"flag" |
||||
"fmt" |
||||
"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 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) |
||||
|
||||
if *test { |
||||
fmt.Printf("// generated by go run gen.go -test; DO NOT EDIT\n\n") |
||||
fmt.Printf("package atom\n\n") |
||||
fmt.Printf("var testAtomList = []string{\n") |
||||
for _, s := range all { |
||||
fmt.Printf("\t%q,\n", s) |
||||
} |
||||
fmt.Printf("}\n") |
||||
return |
||||
} |
||||
|
||||
// uniq - lists have dups
|
||||
// compute max len too
|
||||
maxLen := 0 |
||||
w := 0 |
||||
for _, s := range all { |
||||
if w == 0 || all[w-1] != s { |
||||
if maxLen < len(s) { |
||||
maxLen = len(s) |
||||
} |
||||
all[w] = s |
||||
w++ |
||||
} |
||||
} |
||||
all = all[:w] |
||||
|
||||
// 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)) |
||||
} |
||||
|
||||
// Generate the Go code.
|
||||
fmt.Printf("// generated by go run gen.go; DO NOT EDIT\n\n") |
||||
fmt.Printf("package atom\n\nconst (\n") |
||||
for _, s := range all { |
||||
fmt.Printf("\t%s Atom = %#x\n", identifier(s), atom[s]) |
||||
} |
||||
fmt.Printf(")\n\n") |
||||
|
||||
fmt.Printf("const hash0 = %#x\n\n", best.h0) |
||||
fmt.Printf("const maxAtomLen = %d\n\n", maxLen) |
||||
|
||||
fmt.Printf("var table = [1<<%d]Atom{\n", best.k) |
||||
for i, s := range best.tab { |
||||
if s == "" { |
||||
continue |
||||
} |
||||
fmt.Printf("\t%#x: %#x, // %s\n", i, atom[s], s) |
||||
} |
||||
fmt.Printf("}\n") |
||||
datasize := (1 << best.k) * 4 |
||||
|
||||
fmt.Printf("const atomText =\n") |
||||
textsize := len(text) |
||||
for len(text) > 60 { |
||||
fmt.Printf("\t%q +\n", text[:60]) |
||||
text = text[60:] |
||||
} |
||||
fmt.Printf("\t%q\n\n", text) |
||||
|
||||
fmt.Fprintf(os.Stderr, "%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 21 February 2015" version.
|
||||
|
||||
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", |
||||
"map", |
||||
"mark", |
||||
"menu", |
||||
"menuitem", |
||||
"meta", |
||||
"meter", |
||||
"nav", |
||||
"noscript", |
||||
"object", |
||||
"ol", |
||||
"optgroup", |
||||
"option", |
||||
"output", |
||||
"p", |
||||
"param", |
||||
"pre", |
||||
"progress", |
||||
"q", |
||||
"rp", |
||||
"rt", |
||||
"ruby", |
||||
"s", |
||||
"samp", |
||||
"script", |
||||
"section", |
||||
"select", |
||||
"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
|
||||
|
||||
var attributes = []string{ |
||||
"abbr", |
||||
"accept", |
||||
"accept-charset", |
||||
"accesskey", |
||||
"action", |
||||
"alt", |
||||
"async", |
||||
"autocomplete", |
||||
"autofocus", |
||||
"autoplay", |
||||
"challenge", |
||||
"charset", |
||||
"checked", |
||||
"cite", |
||||
"class", |
||||
"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", |
||||
"ismap", |
||||
"itemid", |
||||
"itemprop", |
||||
"itemref", |
||||
"itemscope", |
||||
"itemtype", |
||||
"keytype", |
||||
"kind", |
||||
"label", |
||||
"lang", |
||||
"list", |
||||
"loop", |
||||
"low", |
||||
"manifest", |
||||
"max", |
||||
"maxlength", |
||||
"media", |
||||
"mediagroup", |
||||
"method", |
||||
"min", |
||||
"minlength", |
||||
"multiple", |
||||
"muted", |
||||
"name", |
||||
"novalidate", |
||||
"open", |
||||
"optimum", |
||||
"pattern", |
||||
"ping", |
||||
"placeholder", |
||||
"poster", |
||||
"preload", |
||||
"radiogroup", |
||||
"readonly", |
||||
"rel", |
||||
"required", |
||||
"reversed", |
||||
"rows", |
||||
"rowspan", |
||||
"sandbox", |
||||
"spellcheck", |
||||
"scope", |
||||
"scoped", |
||||
"seamless", |
||||
"selected", |
||||
"shape", |
||||
"size", |
||||
"sizes", |
||||
"sortable", |
||||
"sorted", |
||||
"span", |
||||
"src", |
||||
"srcdoc", |
||||
"srclang", |
||||
"start", |
||||
"step", |
||||
"style", |
||||
"tabindex", |
||||
"target", |
||||
"title", |
||||
"translate", |
||||
"type", |
||||
"typemustmatch", |
||||
"usemap", |
||||
"value", |
||||
"width", |
||||
"wrap", |
||||
} |
||||
|
||||
var eventHandlers = []string{ |
||||
"onabort", |
||||
"onautocomplete", |
||||
"onautocompleteerror", |
||||
"onafterprint", |
||||
"onbeforeprint", |
||||
"onbeforeunload", |
||||
"onblur", |
||||
"oncancel", |
||||
"oncanplay", |
||||
"oncanplaythrough", |
||||
"onchange", |
||||
"onclick", |
||||
"onclose", |
||||
"oncontextmenu", |
||||
"oncuechange", |
||||
"ondblclick", |
||||
"ondrag", |
||||
"ondragend", |
||||
"ondragenter", |
||||
"ondragleave", |
||||
"ondragover", |
||||
"ondragstart", |
||||
"ondrop", |
||||
"ondurationchange", |
||||
"onemptied", |
||||
"onended", |
||||
"onerror", |
||||
"onfocus", |
||||
"onhashchange", |
||||
"oninput", |
||||
"oninvalid", |
||||
"onkeydown", |
||||
"onkeypress", |
||||
"onkeyup", |
||||
"onlanguagechange", |
||||
"onload", |
||||
"onloadeddata", |
||||
"onloadedmetadata", |
||||
"onloadstart", |
||||
"onmessage", |
||||
"onmousedown", |
||||
"onmousemove", |
||||
"onmouseout", |
||||
"onmouseover", |
||||
"onmouseup", |
||||
"onmousewheel", |
||||
"onoffline", |
||||
"ononline", |
||||
"onpagehide", |
||||
"onpageshow", |
||||
"onpause", |
||||
"onplay", |
||||
"onplaying", |
||||
"onpopstate", |
||||
"onprogress", |
||||
"onratechange", |
||||
"onreset", |
||||
"onresize", |
||||
"onscroll", |
||||
"onseeked", |
||||
"onseeking", |
||||
"onselect", |
||||
"onshow", |
||||
"onsort", |
||||
"onstalled", |
||||
"onstorage", |
||||
"onsubmit", |
||||
"onsuspend", |
||||
"ontimeupdate", |
||||
"ontoggle", |
||||
"onunload", |
||||
"onvolumechange", |
||||
"onwaiting", |
||||
} |
||||
|
||||
// extra are ad-hoc values not covered by any of the lists above.
|
||||
var extra = []string{ |
||||
"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", |
||||
"spacer", |
||||
"strike", |
||||
"svg", |
||||
"system", |
||||
"tt", |
||||
"xmp", |
||||
} |
@ -0,0 +1,713 @@
|
||||
// generated by go run gen.go; DO NOT EDIT
|
||||
|
||||
package atom |
||||
|
||||
const ( |
||||
A Atom = 0x1 |
||||
Abbr Atom = 0x4 |
||||
Accept Atom = 0x2106 |
||||
AcceptCharset Atom = 0x210e |
||||
Accesskey Atom = 0x3309 |
||||
Action Atom = 0x1f606 |
||||
Address Atom = 0x4f307 |
||||
Align Atom = 0x1105 |
||||
Alt Atom = 0x4503 |
||||
Annotation Atom = 0x1670a |
||||
AnnotationXml Atom = 0x1670e |
||||
Applet Atom = 0x2b306 |
||||
Area Atom = 0x2fa04 |
||||
Article Atom = 0x38807 |
||||
Aside Atom = 0x8305 |
||||
Async Atom = 0x7b05 |
||||
Audio Atom = 0xa605 |
||||
Autocomplete Atom = 0x1fc0c |
||||
Autofocus Atom = 0xb309 |
||||
Autoplay Atom = 0xce08 |
||||
B Atom = 0x101 |
||||
Base Atom = 0xd604 |
||||
Basefont Atom = 0xd608 |
||||
Bdi Atom = 0x1a03 |
||||
Bdo Atom = 0xe703 |
||||
Bgsound Atom = 0x11807 |
||||
Big Atom = 0x12403 |
||||
Blink Atom = 0x12705 |
||||
Blockquote Atom = 0x12c0a |
||||
Body Atom = 0x2f04 |
||||
Br Atom = 0x202 |
||||
Button Atom = 0x13606 |
||||
Canvas Atom = 0x7f06 |
||||
Caption Atom = 0x1bb07 |
||||
Center Atom = 0x5b506 |
||||
Challenge Atom = 0x21f09 |
||||
Charset Atom = 0x2807 |
||||
Checked Atom = 0x32807 |
||||
Cite Atom = 0x3c804 |
||||
Class Atom = 0x4de05 |
||||
Code Atom = 0x14904 |
||||
Col Atom = 0x15003 |
||||
Colgroup Atom = 0x15008 |
||||
Color Atom = 0x15d05 |
||||
Cols Atom = 0x16204 |
||||
Colspan Atom = 0x16207 |
||||
Command Atom = 0x17507 |
||||
Content Atom = 0x42307 |
||||
Contenteditable Atom = 0x4230f |
||||
Contextmenu Atom = 0x3310b |
||||
Controls Atom = 0x18808 |
||||
Coords Atom = 0x19406 |
||||
Crossorigin Atom = 0x19f0b |
||||
Data Atom = 0x44a04 |
||||
Datalist Atom = 0x44a08 |
||||
Datetime Atom = 0x23c08 |
||||
Dd Atom = 0x26702 |
||||
Default Atom = 0x8607 |
||||
Defer Atom = 0x14b05 |
||||
Del Atom = 0x3ef03 |
||||
Desc Atom = 0x4db04 |
||||
Details Atom = 0x4807 |
||||
Dfn Atom = 0x6103 |
||||
Dialog Atom = 0x1b06 |
||||
Dir Atom = 0x6903 |
||||
Dirname Atom = 0x6907 |
||||
Disabled Atom = 0x10c08 |
||||
Div Atom = 0x11303 |
||||
Dl Atom = 0x11e02 |
||||
Download Atom = 0x40008 |
||||
Draggable Atom = 0x17b09 |
||||
Dropzone Atom = 0x39108 |
||||
Dt Atom = 0x50902 |
||||
Em Atom = 0x6502 |
||||
Embed Atom = 0x6505 |
||||
Enctype Atom = 0x21107 |
||||
Face Atom = 0x5b304 |
||||
Fieldset Atom = 0x1b008 |
||||
Figcaption Atom = 0x1b80a |
||||
Figure Atom = 0x1cc06 |
||||
Font Atom = 0xda04 |
||||
Footer Atom = 0x8d06 |
||||
For Atom = 0x1d803 |
||||
ForeignObject Atom = 0x1d80d |
||||
Foreignobject Atom = 0x1e50d |
||||
Form Atom = 0x1f204 |
||||
Formaction Atom = 0x1f20a |
||||
Formenctype Atom = 0x20d0b |
||||
Formmethod Atom = 0x2280a |
||||
Formnovalidate Atom = 0x2320e |
||||
Formtarget Atom = 0x2470a |
||||
Frame Atom = 0x9a05 |
||||
Frameset Atom = 0x9a08 |
||||
H1 Atom = 0x26e02 |
||||
H2 Atom = 0x29402 |
||||
H3 Atom = 0x2a702 |
||||
H4 Atom = 0x2e902 |
||||
H5 Atom = 0x2f302 |
||||
H6 Atom = 0x50b02 |
||||
Head Atom = 0x2d504 |
||||
Header Atom = 0x2d506 |
||||
Headers Atom = 0x2d507 |
||||
Height Atom = 0x25106 |
||||
Hgroup Atom = 0x25906 |
||||
Hidden Atom = 0x26506 |
||||
High Atom = 0x26b04 |
||||
Hr Atom = 0x27002 |
||||
Href Atom = 0x27004 |
||||
Hreflang Atom = 0x27008 |
||||
Html Atom = 0x25504 |
||||
HttpEquiv Atom = 0x2780a |
||||
I Atom = 0x601 |
||||
Icon Atom = 0x42204 |
||||
Id Atom = 0x8502 |
||||
Iframe Atom = 0x29606 |
||||
Image Atom = 0x29c05 |
||||
Img Atom = 0x2a103 |
||||
Input Atom = 0x3e805 |
||||
Inputmode Atom = 0x3e809 |
||||
Ins Atom = 0x1a803 |
||||
Isindex Atom = 0x2a907 |
||||
Ismap Atom = 0x2b005 |
||||
Itemid Atom = 0x33c06 |
||||
Itemprop Atom = 0x3c908 |
||||
Itemref Atom = 0x5ad07 |
||||
Itemscope Atom = 0x2b909 |
||||
Itemtype Atom = 0x2c308 |
||||
Kbd Atom = 0x1903 |
||||
Keygen Atom = 0x3906 |
||||
Keytype Atom = 0x53707 |
||||
Kind Atom = 0x10904 |
||||
Label Atom = 0xf005 |
||||
Lang Atom = 0x27404 |
||||
Legend Atom = 0x18206 |
||||
Li Atom = 0x1202 |
||||
Link Atom = 0x12804 |
||||
List Atom = 0x44e04 |
||||
Listing Atom = 0x44e07 |
||||
Loop Atom = 0xf404 |
||||
Low Atom = 0x11f03 |
||||
Malignmark Atom = 0x100a |
||||
Manifest Atom = 0x5f108 |
||||
Map Atom = 0x2b203 |
||||
Mark Atom = 0x1604 |
||||
Marquee Atom = 0x2cb07 |
||||
Math Atom = 0x2d204 |
||||
Max Atom = 0x2e103 |
||||
Maxlength Atom = 0x2e109 |
||||
Media Atom = 0x6e05 |
||||
Mediagroup Atom = 0x6e0a |
||||
Menu Atom = 0x33804 |
||||
Menuitem Atom = 0x33808 |
||||
Meta Atom = 0x45d04 |
||||
Meter Atom = 0x24205 |
||||
Method Atom = 0x22c06 |
||||
Mglyph Atom = 0x2a206 |
||||
Mi Atom = 0x2eb02 |
||||
Min Atom = 0x2eb03 |
||||
Minlength Atom = 0x2eb09 |
||||
Mn Atom = 0x23502 |
||||
Mo Atom = 0x3ed02 |
||||
Ms Atom = 0x2bc02 |
||||
Mtext Atom = 0x2f505 |
||||
Multiple Atom = 0x30308 |
||||
Muted Atom = 0x30b05 |
||||
Name Atom = 0x6c04 |
||||
Nav Atom = 0x3e03 |
||||
Nobr Atom = 0x5704 |
||||
Noembed Atom = 0x6307 |
||||
Noframes Atom = 0x9808 |
||||
Noscript Atom = 0x3d208 |
||||
Novalidate Atom = 0x2360a |
||||
Object Atom = 0x1ec06 |
||||
Ol Atom = 0xc902 |
||||
Onabort Atom = 0x13a07 |
||||
Onafterprint Atom = 0x1c00c |
||||
Onautocomplete Atom = 0x1fa0e |
||||
Onautocompleteerror Atom = 0x1fa13 |
||||
Onbeforeprint Atom = 0x6040d |
||||
Onbeforeunload Atom = 0x4e70e |
||||
Onblur Atom = 0xaa06 |
||||
Oncancel Atom = 0xe908 |
||||
Oncanplay Atom = 0x28509 |
||||
Oncanplaythrough Atom = 0x28510 |
||||
Onchange Atom = 0x3a708 |
||||
Onclick Atom = 0x31007 |
||||
Onclose Atom = 0x31707 |
||||
Oncontextmenu Atom = 0x32f0d |
||||
Oncuechange Atom = 0x3420b |
||||
Ondblclick Atom = 0x34d0a |
||||
Ondrag Atom = 0x35706 |
||||
Ondragend Atom = 0x35709 |
||||
Ondragenter Atom = 0x3600b |
||||
Ondragleave Atom = 0x36b0b |
||||
Ondragover Atom = 0x3760a |
||||
Ondragstart Atom = 0x3800b |
||||
Ondrop Atom = 0x38f06 |
||||
Ondurationchange Atom = 0x39f10 |
||||
Onemptied Atom = 0x39609 |
||||
Onended Atom = 0x3af07 |
||||
Onerror Atom = 0x3b607 |
||||
Onfocus Atom = 0x3bd07 |
||||
Onhashchange Atom = 0x3da0c |
||||
Oninput Atom = 0x3e607 |
||||
Oninvalid Atom = 0x3f209 |
||||
Onkeydown Atom = 0x3fb09 |
||||
Onkeypress Atom = 0x4080a |
||||
Onkeyup Atom = 0x41807 |
||||
Onlanguagechange Atom = 0x43210 |
||||
Onload Atom = 0x44206 |
||||
Onloadeddata Atom = 0x4420c |
||||
Onloadedmetadata Atom = 0x45510 |
||||
Onloadstart Atom = 0x46b0b |
||||
Onmessage Atom = 0x47609 |
||||
Onmousedown Atom = 0x47f0b |
||||
Onmousemove Atom = 0x48a0b |
||||
Onmouseout Atom = 0x4950a |
||||
Onmouseover Atom = 0x4a20b |
||||
Onmouseup Atom = 0x4ad09 |
||||
Onmousewheel Atom = 0x4b60c |
||||
Onoffline Atom = 0x4c209 |
||||
Ononline Atom = 0x4cb08 |
||||
Onpagehide Atom = 0x4d30a |
||||
Onpageshow Atom = 0x4fe0a |
||||
Onpause Atom = 0x50d07 |
||||
Onplay Atom = 0x51706 |
||||
Onplaying Atom = 0x51709 |
||||
Onpopstate Atom = 0x5200a |
||||
Onprogress Atom = 0x52a0a |
||||
Onratechange Atom = 0x53e0c |
||||
Onreset Atom = 0x54a07 |
||||
Onresize Atom = 0x55108 |
||||
Onscroll Atom = 0x55f08 |
||||
Onseeked Atom = 0x56708 |
||||
Onseeking Atom = 0x56f09 |
||||
Onselect Atom = 0x57808 |
||||
Onshow Atom = 0x58206 |
||||
Onsort Atom = 0x58b06 |
||||
Onstalled Atom = 0x59509 |
||||
Onstorage Atom = 0x59e09 |
||||
Onsubmit Atom = 0x5a708 |
||||
Onsuspend Atom = 0x5bb09 |
||||
Ontimeupdate Atom = 0xdb0c |
||||
Ontoggle Atom = 0x5c408 |
||||
Onunload Atom = 0x5cc08 |
||||
Onvolumechange Atom = 0x5d40e |
||||
Onwaiting Atom = 0x5e209 |
||||
Open Atom = 0x3cf04 |
||||
Optgroup Atom = 0xf608 |
||||
Optimum Atom = 0x5eb07 |
||||
Option Atom = 0x60006 |
||||
Output Atom = 0x49c06 |
||||
P Atom = 0xc01 |
||||
Param Atom = 0xc05 |
||||
Pattern Atom = 0x5107 |
||||
Ping Atom = 0x7704 |
||||
Placeholder Atom = 0xc30b |
||||
Plaintext Atom = 0xfd09 |
||||
Poster Atom = 0x15706 |
||||
Pre Atom = 0x25e03 |
||||
Preload Atom = 0x25e07 |
||||
Progress Atom = 0x52c08 |
||||
Prompt Atom = 0x5fa06 |
||||
Public Atom = 0x41e06 |
||||
Q Atom = 0x13101 |
||||
Radiogroup Atom = 0x30a |
||||
Readonly Atom = 0x2fb08 |
||||
Rel Atom = 0x25f03 |
||||
Required Atom = 0x1d008 |
||||
Reversed Atom = 0x5a08 |
||||
Rows Atom = 0x9204 |
||||
Rowspan Atom = 0x9207 |
||||
Rp Atom = 0x1c602 |
||||
Rt Atom = 0x13f02 |
||||
Ruby Atom = 0xaf04 |
||||
S Atom = 0x2c01 |
||||
Samp Atom = 0x4e04 |
||||
Sandbox Atom = 0xbb07 |
||||
Scope Atom = 0x2bd05 |
||||
Scoped Atom = 0x2bd06 |
||||
Script Atom = 0x3d406 |
||||
Seamless Atom = 0x31c08 |
||||
Section Atom = 0x4e207 |
||||
Select Atom = 0x57a06 |
||||
Selected Atom = 0x57a08 |
||||
Shape Atom = 0x4f905 |
||||
Size Atom = 0x55504 |
||||
Sizes Atom = 0x55505 |
||||
Small Atom = 0x18f05 |
||||
Sortable Atom = 0x58d08 |
||||
Sorted Atom = 0x19906 |
||||
Source Atom = 0x1aa06 |
||||
Spacer Atom = 0x2db06 |
||||
Span Atom = 0x9504 |
||||
Spellcheck Atom = 0x3230a |
||||
Src Atom = 0x3c303 |
||||
Srcdoc Atom = 0x3c306 |
||||
Srclang Atom = 0x41107 |
||||
Start Atom = 0x38605 |
||||
Step Atom = 0x5f704 |
||||
Strike Atom = 0x53306 |
||||
Strong Atom = 0x55906 |
||||
Style Atom = 0x61105 |
||||
Sub Atom = 0x5a903 |
||||
Summary Atom = 0x61607 |
||||
Sup Atom = 0x61d03 |
||||
Svg Atom = 0x62003 |
||||
System Atom = 0x62306 |
||||
Tabindex Atom = 0x46308 |
||||
Table Atom = 0x42d05 |
||||
Target Atom = 0x24b06 |
||||
Tbody Atom = 0x2e05 |
||||
Td Atom = 0x4702 |
||||
Template Atom = 0x62608 |
||||
Textarea Atom = 0x2f608 |
||||
Tfoot Atom = 0x8c05 |
||||
Th Atom = 0x22e02 |
||||
Thead Atom = 0x2d405 |
||||
Time Atom = 0xdd04 |
||||
Title Atom = 0xa105 |
||||
Tr Atom = 0x10502 |
||||
Track Atom = 0x10505 |
||||
Translate Atom = 0x14009 |
||||
Tt Atom = 0x5302 |
||||
Type Atom = 0x21404 |
||||
Typemustmatch Atom = 0x2140d |
||||
U Atom = 0xb01 |
||||
Ul Atom = 0x8a02 |
||||
Usemap Atom = 0x51106 |
||||
Value Atom = 0x4005 |
||||
Var Atom = 0x11503 |
||||
Video Atom = 0x28105 |
||||
Wbr Atom = 0x12103 |
||||
Width Atom = 0x50705 |
||||
Wrap Atom = 0x58704 |
||||
Xmp Atom = 0xc103 |
||||
) |
||||
|
||||
const hash0 = 0xc17da63e |
||||
|
||||
const maxAtomLen = 19 |
||||
|
||||
var table = [1 << 9]Atom{ |
||||
0x1: 0x48a0b, // onmousemove
|
||||
0x2: 0x5e209, // onwaiting
|
||||
0x3: 0x1fa13, // onautocompleteerror
|
||||
0x4: 0x5fa06, // prompt
|
||||
0x7: 0x5eb07, // optimum
|
||||
0x8: 0x1604, // mark
|
||||
0xa: 0x5ad07, // itemref
|
||||
0xb: 0x4fe0a, // onpageshow
|
||||
0xc: 0x57a06, // select
|
||||
0xd: 0x17b09, // draggable
|
||||
0xe: 0x3e03, // nav
|
||||
0xf: 0x17507, // command
|
||||
0x11: 0xb01, // u
|
||||
0x14: 0x2d507, // headers
|
||||
0x15: 0x44a08, // datalist
|
||||
0x17: 0x4e04, // samp
|
||||
0x1a: 0x3fb09, // onkeydown
|
||||
0x1b: 0x55f08, // onscroll
|
||||
0x1c: 0x15003, // col
|
||||
0x20: 0x3c908, // itemprop
|
||||
0x21: 0x2780a, // http-equiv
|
||||
0x22: 0x61d03, // sup
|
||||
0x24: 0x1d008, // required
|
||||
0x2b: 0x25e07, // preload
|
||||
0x2c: 0x6040d, // onbeforeprint
|
||||
0x2d: 0x3600b, // ondragenter
|
||||
0x2e: 0x50902, // dt
|
||||
0x2f: 0x5a708, // onsubmit
|
||||
0x30: 0x27002, // hr
|
||||
0x31: 0x32f0d, // oncontextmenu
|
||||
0x33: 0x29c05, // image
|
||||
0x34: 0x50d07, // onpause
|
||||
0x35: 0x25906, // hgroup
|
||||
0x36: 0x7704, // ping
|
||||
0x37: 0x57808, // onselect
|
||||
0x3a: 0x11303, // div
|
||||
0x3b: 0x1fa0e, // onautocomplete
|
||||
0x40: 0x2eb02, // mi
|
||||
0x41: 0x31c08, // seamless
|
||||
0x42: 0x2807, // charset
|
||||
0x43: 0x8502, // id
|
||||
0x44: 0x5200a, // onpopstate
|
||||
0x45: 0x3ef03, // del
|
||||
0x46: 0x2cb07, // marquee
|
||||
0x47: 0x3309, // accesskey
|
||||
0x49: 0x8d06, // footer
|
||||
0x4a: 0x44e04, // list
|
||||
0x4b: 0x2b005, // ismap
|
||||
0x51: 0x33804, // menu
|
||||
0x52: 0x2f04, // body
|
||||
0x55: 0x9a08, // frameset
|
||||
0x56: 0x54a07, // onreset
|
||||
0x57: 0x12705, // blink
|
||||
0x58: 0xa105, // title
|
||||
0x59: 0x38807, // article
|
||||
0x5b: 0x22e02, // th
|
||||
0x5d: 0x13101, // q
|
||||
0x5e: 0x3cf04, // open
|
||||
0x5f: 0x2fa04, // area
|
||||
0x61: 0x44206, // onload
|
||||
0x62: 0xda04, // font
|
||||
0x63: 0xd604, // base
|
||||
0x64: 0x16207, // colspan
|
||||
0x65: 0x53707, // keytype
|
||||
0x66: 0x11e02, // dl
|
||||
0x68: 0x1b008, // fieldset
|
||||
0x6a: 0x2eb03, // min
|
||||
0x6b: 0x11503, // var
|
||||
0x6f: 0x2d506, // header
|
||||
0x70: 0x13f02, // rt
|
||||
0x71: 0x15008, // colgroup
|
||||
0x72: 0x23502, // mn
|
||||
0x74: 0x13a07, // onabort
|
||||
0x75: 0x3906, // keygen
|
||||
0x76: 0x4c209, // onoffline
|
||||
0x77: 0x21f09, // challenge
|
||||
0x78: 0x2b203, // map
|
||||
0x7a: 0x2e902, // h4
|
||||
0x7b: 0x3b607, // onerror
|
||||
0x7c: 0x2e109, // maxlength
|
||||
0x7d: 0x2f505, // mtext
|
||||
0x7e: 0xbb07, // sandbox
|
||||
0x7f: 0x58b06, // onsort
|
||||
0x80: 0x100a, // malignmark
|
||||
0x81: 0x45d04, // meta
|
||||
0x82: 0x7b05, // async
|
||||
0x83: 0x2a702, // h3
|
||||
0x84: 0x26702, // dd
|
||||
0x85: 0x27004, // href
|
||||
0x86: 0x6e0a, // mediagroup
|
||||
0x87: 0x19406, // coords
|
||||
0x88: 0x41107, // srclang
|
||||
0x89: 0x34d0a, // ondblclick
|
||||
0x8a: 0x4005, // value
|
||||
0x8c: 0xe908, // oncancel
|
||||
0x8e: 0x3230a, // spellcheck
|
||||
0x8f: 0x9a05, // frame
|
||||
0x91: 0x12403, // big
|
||||
0x94: 0x1f606, // action
|
||||
0x95: 0x6903, // dir
|
||||
0x97: 0x2fb08, // readonly
|
||||
0x99: 0x42d05, // table
|
||||
0x9a: 0x61607, // summary
|
||||
0x9b: 0x12103, // wbr
|
||||
0x9c: 0x30a, // radiogroup
|
||||
0x9d: 0x6c04, // name
|
||||
0x9f: 0x62306, // system
|
||||
0xa1: 0x15d05, // color
|
||||
0xa2: 0x7f06, // canvas
|
||||
0xa3: 0x25504, // html
|
||||
0xa5: 0x56f09, // onseeking
|
||||
0xac: 0x4f905, // shape
|
||||
0xad: 0x25f03, // rel
|
||||
0xae: 0x28510, // oncanplaythrough
|
||||
0xaf: 0x3760a, // ondragover
|
||||
0xb0: 0x62608, // template
|
||||
0xb1: 0x1d80d, // foreignObject
|
||||
0xb3: 0x9204, // rows
|
||||
0xb6: 0x44e07, // listing
|
||||
0xb7: 0x49c06, // output
|
||||
0xb9: 0x3310b, // contextmenu
|
||||
0xbb: 0x11f03, // low
|
||||
0xbc: 0x1c602, // rp
|
||||
0xbd: 0x5bb09, // onsuspend
|
||||
0xbe: 0x13606, // button
|
||||
0xbf: 0x4db04, // desc
|
||||
0xc1: 0x4e207, // section
|
||||
0xc2: 0x52a0a, // onprogress
|
||||
0xc3: 0x59e09, // onstorage
|
||||
0xc4: 0x2d204, // math
|
||||
0xc5: 0x4503, // alt
|
||||
0xc7: 0x8a02, // ul
|
||||
0xc8: 0x5107, // pattern
|
||||
0xc9: 0x4b60c, // onmousewheel
|
||||
0xca: 0x35709, // ondragend
|
||||
0xcb: 0xaf04, // ruby
|
||||
0xcc: 0xc01, // p
|
||||
0xcd: 0x31707, // onclose
|
||||
0xce: 0x24205, // meter
|
||||
0xcf: 0x11807, // bgsound
|
||||
0xd2: 0x25106, // height
|
||||
0xd4: 0x101, // b
|
||||
0xd5: 0x2c308, // itemtype
|
||||
0xd8: 0x1bb07, // caption
|
||||
0xd9: 0x10c08, // disabled
|
||||
0xdb: 0x33808, // menuitem
|
||||
0xdc: 0x62003, // svg
|
||||
0xdd: 0x18f05, // small
|
||||
0xde: 0x44a04, // data
|
||||
0xe0: 0x4cb08, // ononline
|
||||
0xe1: 0x2a206, // mglyph
|
||||
0xe3: 0x6505, // embed
|
||||
0xe4: 0x10502, // tr
|
||||
0xe5: 0x46b0b, // onloadstart
|
||||
0xe7: 0x3c306, // srcdoc
|
||||
0xeb: 0x5c408, // ontoggle
|
||||
0xed: 0xe703, // bdo
|
||||
0xee: 0x4702, // td
|
||||
0xef: 0x8305, // aside
|
||||
0xf0: 0x29402, // h2
|
||||
0xf1: 0x52c08, // progress
|
||||
0xf2: 0x12c0a, // blockquote
|
||||
0xf4: 0xf005, // label
|
||||
0xf5: 0x601, // i
|
||||
0xf7: 0x9207, // rowspan
|
||||
0xfb: 0x51709, // onplaying
|
||||
0xfd: 0x2a103, // img
|
||||
0xfe: 0xf608, // optgroup
|
||||
0xff: 0x42307, // content
|
||||
0x101: 0x53e0c, // onratechange
|
||||
0x103: 0x3da0c, // onhashchange
|
||||
0x104: 0x4807, // details
|
||||
0x106: 0x40008, // download
|
||||
0x109: 0x14009, // translate
|
||||
0x10b: 0x4230f, // contenteditable
|
||||
0x10d: 0x36b0b, // ondragleave
|
||||
0x10e: 0x2106, // accept
|
||||
0x10f: 0x57a08, // selected
|
||||
0x112: 0x1f20a, // formaction
|
||||
0x113: 0x5b506, // center
|
||||
0x115: 0x45510, // onloadedmetadata
|
||||
0x116: 0x12804, // link
|
||||
0x117: 0xdd04, // time
|
||||
0x118: 0x19f0b, // crossorigin
|
||||
0x119: 0x3bd07, // onfocus
|
||||
0x11a: 0x58704, // wrap
|
||||
0x11b: 0x42204, // icon
|
||||
0x11d: 0x28105, // video
|
||||
0x11e: 0x4de05, // class
|
||||
0x121: 0x5d40e, // onvolumechange
|
||||
0x122: 0xaa06, // onblur
|
||||
0x123: 0x2b909, // itemscope
|
||||
0x124: 0x61105, // style
|
||||
0x127: 0x41e06, // public
|
||||
0x129: 0x2320e, // formnovalidate
|
||||
0x12a: 0x58206, // onshow
|
||||
0x12c: 0x51706, // onplay
|
||||
0x12d: 0x3c804, // cite
|
||||
0x12e: 0x2bc02, // ms
|
||||
0x12f: 0xdb0c, // ontimeupdate
|
||||
0x130: 0x10904, // kind
|
||||
0x131: 0x2470a, // formtarget
|
||||
0x135: 0x3af07, // onended
|
||||
0x136: 0x26506, // hidden
|
||||
0x137: 0x2c01, // s
|
||||
0x139: 0x2280a, // formmethod
|
||||
0x13a: 0x3e805, // input
|
||||
0x13c: 0x50b02, // h6
|
||||
0x13d: 0xc902, // ol
|
||||
0x13e: 0x3420b, // oncuechange
|
||||
0x13f: 0x1e50d, // foreignobject
|
||||
0x143: 0x4e70e, // onbeforeunload
|
||||
0x144: 0x2bd05, // scope
|
||||
0x145: 0x39609, // onemptied
|
||||
0x146: 0x14b05, // defer
|
||||
0x147: 0xc103, // xmp
|
||||
0x148: 0x39f10, // ondurationchange
|
||||
0x149: 0x1903, // kbd
|
||||
0x14c: 0x47609, // onmessage
|
||||
0x14d: 0x60006, // option
|
||||
0x14e: 0x2eb09, // minlength
|
||||
0x14f: 0x32807, // checked
|
||||
0x150: 0xce08, // autoplay
|
||||
0x152: 0x202, // br
|
||||
0x153: 0x2360a, // novalidate
|
||||
0x156: 0x6307, // noembed
|
||||
0x159: 0x31007, // onclick
|
||||
0x15a: 0x47f0b, // onmousedown
|
||||
0x15b: 0x3a708, // onchange
|
||||
0x15e: 0x3f209, // oninvalid
|
||||
0x15f: 0x2bd06, // scoped
|
||||
0x160: 0x18808, // controls
|
||||
0x161: 0x30b05, // muted
|
||||
0x162: 0x58d08, // sortable
|
||||
0x163: 0x51106, // usemap
|
||||
0x164: 0x1b80a, // figcaption
|
||||
0x165: 0x35706, // ondrag
|
||||
0x166: 0x26b04, // high
|
||||
0x168: 0x3c303, // src
|
||||
0x169: 0x15706, // poster
|
||||
0x16b: 0x1670e, // annotation-xml
|
||||
0x16c: 0x5f704, // step
|
||||
0x16d: 0x4, // abbr
|
||||
0x16e: 0x1b06, // dialog
|
||||
0x170: 0x1202, // li
|
||||
0x172: 0x3ed02, // mo
|
||||
0x175: 0x1d803, // for
|
||||
0x176: 0x1a803, // ins
|
||||
0x178: 0x55504, // size
|
||||
0x179: 0x43210, // onlanguagechange
|
||||
0x17a: 0x8607, // default
|
||||
0x17b: 0x1a03, // bdi
|
||||
0x17c: 0x4d30a, // onpagehide
|
||||
0x17d: 0x6907, // dirname
|
||||
0x17e: 0x21404, // type
|
||||
0x17f: 0x1f204, // form
|
||||
0x181: 0x28509, // oncanplay
|
||||
0x182: 0x6103, // dfn
|
||||
0x183: 0x46308, // tabindex
|
||||
0x186: 0x6502, // em
|
||||
0x187: 0x27404, // lang
|
||||
0x189: 0x39108, // dropzone
|
||||
0x18a: 0x4080a, // onkeypress
|
||||
0x18b: 0x23c08, // datetime
|
||||
0x18c: 0x16204, // cols
|
||||
0x18d: 0x1, // a
|
||||
0x18e: 0x4420c, // onloadeddata
|
||||
0x190: 0xa605, // audio
|
||||
0x192: 0x2e05, // tbody
|
||||
0x193: 0x22c06, // method
|
||||
0x195: 0xf404, // loop
|
||||
0x196: 0x29606, // iframe
|
||||
0x198: 0x2d504, // head
|
||||
0x19e: 0x5f108, // manifest
|
||||
0x19f: 0xb309, // autofocus
|
||||
0x1a0: 0x14904, // code
|
||||
0x1a1: 0x55906, // strong
|
||||
0x1a2: 0x30308, // multiple
|
||||
0x1a3: 0xc05, // param
|
||||
0x1a6: 0x21107, // enctype
|
||||
0x1a7: 0x5b304, // face
|
||||
0x1a8: 0xfd09, // plaintext
|
||||
0x1a9: 0x26e02, // h1
|
||||
0x1aa: 0x59509, // onstalled
|
||||
0x1ad: 0x3d406, // script
|
||||
0x1ae: 0x2db06, // spacer
|
||||
0x1af: 0x55108, // onresize
|
||||
0x1b0: 0x4a20b, // onmouseover
|
||||
0x1b1: 0x5cc08, // onunload
|
||||
0x1b2: 0x56708, // onseeked
|
||||
0x1b4: 0x2140d, // typemustmatch
|
||||
0x1b5: 0x1cc06, // figure
|
||||
0x1b6: 0x4950a, // onmouseout
|
||||
0x1b7: 0x25e03, // pre
|
||||
0x1b8: 0x50705, // width
|
||||
0x1b9: 0x19906, // sorted
|
||||
0x1bb: 0x5704, // nobr
|
||||
0x1be: 0x5302, // tt
|
||||
0x1bf: 0x1105, // align
|
||||
0x1c0: 0x3e607, // oninput
|
||||
0x1c3: 0x41807, // onkeyup
|
||||
0x1c6: 0x1c00c, // onafterprint
|
||||
0x1c7: 0x210e, // accept-charset
|
||||
0x1c8: 0x33c06, // itemid
|
||||
0x1c9: 0x3e809, // inputmode
|
||||
0x1cb: 0x53306, // strike
|
||||
0x1cc: 0x5a903, // sub
|
||||
0x1cd: 0x10505, // track
|
||||
0x1ce: 0x38605, // start
|
||||
0x1d0: 0xd608, // basefont
|
||||
0x1d6: 0x1aa06, // source
|
||||
0x1d7: 0x18206, // legend
|
||||
0x1d8: 0x2d405, // thead
|
||||
0x1da: 0x8c05, // tfoot
|
||||
0x1dd: 0x1ec06, // object
|
||||
0x1de: 0x6e05, // media
|
||||
0x1df: 0x1670a, // annotation
|
||||
0x1e0: 0x20d0b, // formenctype
|
||||
0x1e2: 0x3d208, // noscript
|
||||
0x1e4: 0x55505, // sizes
|
||||
0x1e5: 0x1fc0c, // autocomplete
|
||||
0x1e6: 0x9504, // span
|
||||
0x1e7: 0x9808, // noframes
|
||||
0x1e8: 0x24b06, // target
|
||||
0x1e9: 0x38f06, // ondrop
|
||||
0x1ea: 0x2b306, // applet
|
||||
0x1ec: 0x5a08, // reversed
|
||||
0x1f0: 0x2a907, // isindex
|
||||
0x1f3: 0x27008, // hreflang
|
||||
0x1f5: 0x2f302, // h5
|
||||
0x1f6: 0x4f307, // address
|
||||
0x1fa: 0x2e103, // max
|
||||
0x1fb: 0xc30b, // placeholder
|
||||
0x1fc: 0x2f608, // textarea
|
||||
0x1fe: 0x4ad09, // onmouseup
|
||||
0x1ff: 0x3800b, // ondragstart
|
||||
} |
||||
|
||||
const atomText = "abbradiogrouparamalignmarkbdialogaccept-charsetbodyaccesskey" + |
||||
"genavaluealtdetailsampatternobreversedfnoembedirnamediagroup" + |
||||
"ingasyncanvasidefaultfooterowspanoframesetitleaudionblurubya" + |
||||
"utofocusandboxmplaceholderautoplaybasefontimeupdatebdoncance" + |
||||
"labelooptgrouplaintextrackindisabledivarbgsoundlowbrbigblink" + |
||||
"blockquotebuttonabortranslatecodefercolgroupostercolorcolspa" + |
||||
"nnotation-xmlcommandraggablegendcontrolsmallcoordsortedcross" + |
||||
"originsourcefieldsetfigcaptionafterprintfigurequiredforeignO" + |
||||
"bjectforeignobjectformactionautocompleteerrorformenctypemust" + |
||||
"matchallengeformmethodformnovalidatetimeterformtargetheightm" + |
||||
"lhgroupreloadhiddenhigh1hreflanghttp-equivideoncanplaythroug" + |
||||
"h2iframeimageimglyph3isindexismappletitemscopeditemtypemarqu" + |
||||
"eematheaderspacermaxlength4minlength5mtextareadonlymultiplem" + |
||||
"utedonclickoncloseamlesspellcheckedoncontextmenuitemidoncuec" + |
||||
"hangeondblclickondragendondragenterondragleaveondragoverondr" + |
||||
"agstarticleondropzonemptiedondurationchangeonendedonerroronf" + |
||||
"ocusrcdocitempropenoscriptonhashchangeoninputmodeloninvalido" + |
||||
"nkeydownloadonkeypressrclangonkeyupublicontenteditableonlang" + |
||||
"uagechangeonloadeddatalistingonloadedmetadatabindexonloadsta" + |
||||
"rtonmessageonmousedownonmousemoveonmouseoutputonmouseoveronm" + |
||||
"ouseuponmousewheelonofflineononlineonpagehidesclassectionbef" + |
||||
"oreunloaddresshapeonpageshowidth6onpausemaponplayingonpopsta" + |
||||
"teonprogresstrikeytypeonratechangeonresetonresizestrongonscr" + |
||||
"ollonseekedonseekingonselectedonshowraponsortableonstalledon" + |
||||
"storageonsubmitemrefacenteronsuspendontoggleonunloadonvolume" + |
||||
"changeonwaitingoptimumanifestepromptoptionbeforeprintstylesu" + |
||||
"mmarysupsvgsystemplate" |
@ -0,0 +1,102 @@
|
||||
// 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.3.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, |
||||
"li": true, |
||||
"link": true, |
||||
"listing": 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 |
||||
} |
@ -0,0 +1,106 @@
|
||||
// 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 ErrorToken: |
||||
return z.Err() |
||||
case TextToken: |
||||
if depth > 0 { |
||||
// emitBytes should copy the []byte it receives,
|
||||
// if it doesn't process it immediately.
|
||||
emitBytes(z.Text()) |
||||
} |
||||
case StartTagToken, EndTagToken: |
||||
tn, _ := z.TagName() |
||||
if len(tn) == 1 && tn[0] == 'a' { |
||||
if tt == 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 |
||||
|
||||
// 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?
|
@ -0,0 +1,156 @@
|
||||
// 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 |
||||
|
||||
import ( |
||||
"strings" |
||||
) |
||||
|
||||
// parseDoctype parses the data from a DoctypeToken into a name,
|
||||
// public identifier, and system identifier. It returns a Node whose Type
|
||||
// is DoctypeNode, whose Data is the name, and which has attributes
|
||||
// named "system" and "public" for the two identifiers if they were present.
|
||||
// quirks is whether the document should be parsed in "quirks mode".
|
||||
func parseDoctype(s string) (n *Node, quirks bool) { |
||||
n = &Node{Type: DoctypeNode} |
||||
|
||||
// Find the name.
|
||||
space := strings.IndexAny(s, whitespace) |
||||
if space == -1 { |
||||
space = len(s) |
||||
} |
||||
n.Data = s[:space] |
||||
// The comparison to "html" is case-sensitive.
|
||||
if n.Data != "html" { |
||||
quirks = true |
||||
} |
||||
n.Data = strings.ToLower(n.Data) |
||||
s = strings.TrimLeft(s[space:], whitespace) |
||||
|
||||
if len(s) < 6 { |
||||
// It can't start with "PUBLIC" or "SYSTEM".
|
||||
// Ignore the rest of the string.
|
||||
return n, quirks || s != "" |
||||
} |
||||
|
||||
key := strings.ToLower(s[:6]) |
||||
s = s[6:] |
||||
for key == "public" || key == "system" { |
||||
s = strings.TrimLeft(s, whitespace) |
||||
if s == "" { |
||||
break |
||||
} |
||||
quote := s[0] |
||||
if quote != '"' && quote != '\'' { |
||||
break |
||||
} |
||||
s = s[1:] |
||||
q := strings.IndexRune(s, rune(quote)) |
||||
var id string |
||||
if q == -1 { |
||||
id = s |
||||
s = "" |
||||
} else { |
||||
id = s[:q] |
||||
s = s[q+1:] |
||||
} |
||||
n.Attr = append(n.Attr, Attribute{Key: key, Val: id}) |
||||
if key == "public" { |
||||
key = "system" |
||||
} else { |
||||
key = "" |
||||
} |
||||
} |
||||
|
||||
if key != "" || s != "" { |
||||
quirks = true |
||||
} else if len(n.Attr) > 0 { |
||||
if n.Attr[0].Key == "public" { |
||||
public := strings.ToLower(n.Attr[0].Val) |
||||
switch public { |
||||
case "-//w3o//dtd w3 html strict 3.0//en//", "-/w3d/dtd html 4.0 transitional/en", "html": |
||||
quirks = true |
||||
default: |
||||
for _, q := range quirkyIDs { |
||||
if strings.HasPrefix(public, q) { |
||||
quirks = true |
||||
break |
||||
} |
||||
} |
||||
} |
||||
// The following two public IDs only cause quirks mode if there is no system ID.
|
||||
if len(n.Attr) == 1 && (strings.HasPrefix(public, "-//w3c//dtd html 4.01 frameset//") || |
||||
strings.HasPrefix(public, "-//w3c//dtd html 4.01 transitional//")) { |
||||
quirks = true |
||||
} |
||||
} |
||||
if lastAttr := n.Attr[len(n.Attr)-1]; lastAttr.Key == "system" && |
||||
strings.ToLower(lastAttr.Val) == "http://www.ibm.com/data/dtd/v11/ibmxhtml1-transitional.dtd" { |
||||
quirks = true |
||||
} |
||||
} |
||||
|
||||
return n, quirks |
||||
} |
||||
|
||||
// quirkyIDs is a list of public doctype identifiers that cause a document
|
||||
// to be interpreted in quirks mode. The identifiers should be in lower case.
|
||||
var quirkyIDs = []string{ |
||||
"+//silmaril//dtd html pro v0r11 19970101//", |
||||
"-//advasoft ltd//dtd html 3.0 aswedit + extensions//", |
||||
"-//as//dtd html 3.0 aswedit + extensions//", |
||||
"-//ietf//dtd html 2.0 level 1//", |
||||
"-//ietf//dtd html 2.0 level 2//", |
||||
"-//ietf//dtd html 2.0 strict level 1//", |
||||
"-//ietf//dtd html 2.0 strict level 2//", |
||||
"-//ietf//dtd html 2.0 strict//", |
||||
"-//ietf//dtd html 2.0//", |
||||
"-//ietf//dtd html 2.1e//", |
||||
"-//ietf//dtd html 3.0//", |
||||
"-//ietf//dtd html 3.2 final//", |
||||
"-//ietf//dtd html 3.2//", |
||||
"-//ietf//dtd html 3//", |
||||
"-//ietf//dtd html level 0//", |
||||
"-//ietf//dtd html level 1//", |
||||
"-//ietf//dtd html level 2//", |
||||
"-//ietf//dtd html level 3//", |
||||
"-//ietf//dtd html strict level 0//", |
||||
"-//ietf//dtd html strict level 1//", |
||||
"-//ietf//dtd html strict level 2//", |
||||
"-//ietf//dtd html strict level 3//", |
||||
"-//ietf//dtd html strict//", |
||||
"-//ietf//dtd html//", |
||||
"-//metrius//dtd metrius presentational//", |
||||
"-//microsoft//dtd internet explorer 2.0 html strict//", |
||||
"-//microsoft//dtd internet explorer 2.0 html//", |
||||
"-//microsoft//dtd internet explorer 2.0 tables//", |
||||
"-//microsoft//dtd internet explorer 3.0 html strict//", |
||||
"-//microsoft//dtd internet explorer 3.0 html//", |
||||
"-//microsoft//dtd internet explorer 3.0 tables//", |
||||
"-//netscape comm. corp.//dtd html//", |
||||
"-//netscape comm. corp.//dtd strict html//", |
||||
"-//o'reilly and associates//dtd html 2.0//", |
||||
"-//o'reilly and associates//dtd html extended 1.0//", |
||||
"-//o'reilly and associates//dtd html extended relaxed 1.0//", |
||||
"-//softquad software//dtd hotmetal pro 6.0::19990601::extensions to html 4.0//", |
||||
"-//softquad//dtd hotmetal pro 4.0::19971010::extensions to html 4.0//", |
||||
"-//spyglass//dtd html 2.0 extended//", |
||||
"-//sq//dtd html 2.0 hotmetal + extensions//", |
||||
"-//sun microsystems corp.//dtd hotjava html//", |
||||
"-//sun microsystems corp.//dtd hotjava strict html//", |
||||
"-//w3c//dtd html 3 1995-03-24//", |
||||
"-//w3c//dtd html 3.2 draft//", |
||||
"-//w3c//dtd html 3.2 final//", |
||||
"-//w3c//dtd html 3.2//", |
||||
"-//w3c//dtd html 3.2s draft//", |
||||
"-//w3c//dtd html 4.0 frameset//", |
||||
"-//w3c//dtd html 4.0 transitional//", |
||||
"-//w3c//dtd html experimental 19960712//", |
||||
"-//w3c//dtd html experimental 970421//", |
||||
"-//w3c//dtd w3 html//", |
||||
"-//w3o//dtd w3 html 3.0//", |
||||
"-//webtechs//dtd mozilla html 2.0//", |
||||
"-//webtechs//dtd mozilla html//", |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,258 @@
|
||||
// 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 |
||||
|
||||
import ( |
||||
"bytes" |
||||
"strings" |
||||
"unicode/utf8" |
||||
) |
||||
|
||||
// These replacements permit compatibility with old numeric entities that
|
||||
// assumed Windows-1252 encoding.
|
||||
// https://html.spec.whatwg.org/multipage/syntax.html#consume-a-character-reference
|
||||
var replacementTable = [...]rune{ |
||||
'\u20AC', // First entry is what 0x80 should be replaced with.
|
||||
'\u0081', |
||||
'\u201A', |
||||
'\u0192', |
||||
'\u201E', |
||||
'\u2026', |
||||
'\u2020', |
||||
'\u2021', |
||||
'\u02C6', |
||||
'\u2030', |
||||
'\u0160', |
||||
'\u2039', |
||||
'\u0152', |
||||
'\u008D', |
||||
'\u017D', |
||||
'\u008F', |
||||
'\u0090', |
||||
'\u2018', |
||||
'\u2019', |
||||
'\u201C', |
||||
'\u201D', |
||||
'\u2022', |
||||
'\u2013', |
||||
'\u2014', |
||||
'\u02DC', |
||||
'\u2122', |
||||
'\u0161', |
||||
'\u203A', |
||||
'\u0153', |
||||
'\u009D', |
||||
'\u017E', |
||||
'\u0178', // Last entry is 0x9F.
|
||||
// 0x00->'\uFFFD' is handled programmatically.
|
||||
// 0x0D->'\u000D' is a no-op.
|
||||
} |
||||
|
||||
// unescapeEntity reads an entity like "<" from b[src:] and writes the
|
||||
// corresponding "<" to b[dst:], returning the incremented dst and src cursors.
|
||||
// Precondition: b[src] == '&' && dst <= src.
|
||||
// attribute should be true if parsing an attribute value.
|
||||
func unescapeEntity(b []byte, dst, src int, attribute bool) (dst1, src1 int) { |
||||
// https://html.spec.whatwg.org/multipage/syntax.html#consume-a-character-reference
|
||||
|
||||
// i starts at 1 because we already know that s[0] == '&'.
|
||||
i, s := 1, b[src:] |
||||
|
||||
if len(s) <= 1 { |
||||
b[dst] = b[src] |
||||
return dst + 1, src + 1 |
||||
} |
||||
|
||||
if s[i] == '#' { |
||||
if len(s) <= 3 { // We need to have at least "&#.".
|
||||
b[dst] = b[src] |
||||
return dst + 1, src + 1 |
||||
} |
||||
i++ |
||||
c := s[i] |
||||
hex := false |
||||
if c == 'x' || c == 'X' { |
||||
hex = true |
||||
i++ |
||||
} |
||||
|
||||
x := '\x00' |
||||
for i < len(s) { |
||||
c = s[i] |
||||
i++ |
||||
if hex { |
||||
if '0' <= c && c <= '9' { |
||||
x = 16*x + rune(c) - '0' |
||||
continue |
||||
} else if 'a' <= c && c <= 'f' { |
||||
x = 16*x + rune(c) - 'a' + 10 |
||||
continue |
||||
} else if 'A' <= c && c <= 'F' { |
||||
x = 16*x + rune(c) - 'A' + 10 |
||||
continue |
||||
} |
||||
} else if '0' <= c && c <= '9' { |
||||
x = 10*x + rune(c) - '0' |
||||
continue |
||||
} |
||||
if c != ';' { |
||||
i-- |
||||
} |
||||
break |
||||
} |
||||
|
||||
if i <= 3 { // No characters matched.
|
||||
b[dst] = b[src] |
||||
return dst + 1, src + 1 |
||||
} |
||||
|
||||
if 0x80 <= x && x <= 0x9F { |
||||
// Replace characters from Windows-1252 with UTF-8 equivalents.
|
||||
x = replacementTable[x-0x80] |
||||
} else if x == 0 || (0xD800 <= x && x <= 0xDFFF) || x > 0x10FFFF { |
||||
// Replace invalid characters with the replacement character.
|
||||
x = '\uFFFD' |
||||
} |
||||
|
||||
return dst + utf8.EncodeRune(b[dst:], x), src + i |
||||
} |
||||
|
||||
// Consume the maximum number of characters possible, with the
|
||||
// consumed characters matching one of the named references.
|
||||
|
||||
for i < len(s) { |
||||
c := s[i] |
||||
i++ |
||||
// Lower-cased characters are more common in entities, so we check for them first.
|
||||
if 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z' || '0' <= c && c <= '9' { |
||||
continue |
||||
} |
||||
if c != ';' { |
||||
i-- |
||||
} |
||||
break |
||||
} |
||||
|
||||
entityName := string(s[1:i]) |
||||
if entityName == "" { |
||||
// No-op.
|
||||
} else if attribute && entityName[len(entityName)-1] != ';' && len(s) > i && s[i] == '=' { |
||||
// No-op.
|
||||
} else if x := entity[entityName]; x != 0 { |
||||
return dst + utf8.EncodeRune(b[dst:], x), src + i |
||||
} else if x := entity2[entityName]; x[0] != 0 { |
||||
dst1 := dst + utf8.EncodeRune(b[dst:], x[0]) |
||||
return dst1 + utf8.EncodeRune(b[dst1:], x[1]), src + i |
||||
} else if !attribute { |
||||
maxLen := len(entityName) - 1 |
||||
if maxLen > longestEntityWithoutSemicolon { |
||||
maxLen = longestEntityWithoutSemicolon |
||||
} |
||||
for j := maxLen; j > 1; j-- { |
||||
if x := entity[entityName[:j]]; x != 0 { |
||||
return dst + utf8.EncodeRune(b[dst:], x), src + j + 1 |
||||
} |
||||
} |
||||
} |
||||
|
||||
dst1, src1 = dst+i, src+i |
||||
copy(b[dst:dst1], b[src:src1]) |
||||
return dst1, src1 |
||||
} |
||||
|
||||
// unescape unescapes b's entities in-place, so that "a<b" becomes "a<b".
|
||||
// attribute should be true if parsing an attribute value.
|
||||
func unescape(b []byte, attribute bool) []byte { |
||||
for i, c := range b { |
||||
if c == '&' { |
||||
dst, src := unescapeEntity(b, i, i, attribute) |
||||
for src < len(b) { |
||||
c := b[src] |
||||
if c == '&' { |
||||
dst, src = unescapeEntity(b, dst, src, attribute) |
||||
} else { |
||||
b[dst] = c |
||||
dst, src = dst+1, src+1 |
||||
} |
||||
} |
||||
return b[0:dst] |
||||
} |
||||
} |
||||
return b |
||||
} |
||||
|
||||
// lower lower-cases the A-Z bytes in b in-place, so that "aBc" becomes "abc".
|
||||
func lower(b []byte) []byte { |
||||
for i, c := range b { |
||||
if 'A' <= c && c <= 'Z' { |
||||
b[i] = c + 'a' - 'A' |
||||
} |
||||
} |
||||
return b |
||||
} |
||||
|
||||
const escapedChars = "&'<>\"\r" |
||||
|
||||
func escape(w writer, s string) error { |
||||
i := strings.IndexAny(s, escapedChars) |
||||
for i != -1 { |
||||
if _, err := w.WriteString(s[:i]); err != nil { |
||||
return err |
||||
} |
||||
var esc string |
||||
switch s[i] { |
||||
case '&': |
||||
esc = "&" |
||||
case '\'': |
||||
// "'" is shorter than "'" and apos was not in HTML until HTML5.
|
||||
esc = "'" |
||||
case '<': |
||||
esc = "<" |
||||
case '>': |
||||
esc = ">" |
||||
case '"': |
||||
// """ is shorter than """.
|
||||
esc = """ |
||||
case '\r': |
||||
esc = " " |
||||
default: |
||||
panic("unrecognized escape character") |
||||
} |
||||
s = s[i+1:] |
||||
if _, err := w.WriteString(esc); err != nil { |
||||
return err |
||||
} |
||||
i = strings.IndexAny(s, escapedChars) |
||||
} |
||||
_, err := w.WriteString(s) |
||||
return err |
||||
} |
||||
|
||||
// EscapeString escapes special characters like "<" to become "<". It
|
||||
// escapes only five such characters: <, >, &, ' and ".
|
||||
// UnescapeString(EscapeString(s)) == s always holds, but the converse isn't
|
||||
// always true.
|
||||
func EscapeString(s string) string { |
||||
if strings.IndexAny(s, escapedChars) == -1 { |
||||
return s |
||||
} |
||||
var buf bytes.Buffer |
||||
escape(&buf, s) |
||||
return buf.String() |
||||
} |
||||
|
||||
// UnescapeString unescapes entities like "<" to become "<". It unescapes a
|
||||
// larger range of entities than EscapeString escapes. For example, "á"
|
||||
// unescapes to "á", as does "á" and "&xE1;".
|
||||
// UnescapeString(EscapeString(s)) == s always holds, but the converse isn't
|
||||
// always true.
|
||||
func UnescapeString(s string) string { |
||||
for _, c := range s { |
||||
if c == '&' { |
||||
return string(unescape([]byte(s), false)) |
||||
} |
||||
} |
||||
return s |
||||
} |
@ -0,0 +1,226 @@
|
||||
// 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 |
||||
|
||||
import ( |
||||
"strings" |
||||
) |
||||
|
||||
func adjustAttributeNames(aa []Attribute, nameMap map[string]string) { |
||||
for i := range aa { |
||||
if newName, ok := nameMap[aa[i].Key]; ok { |
||||
aa[i].Key = newName |
||||
} |
||||
} |
||||
} |
||||
|
||||
func adjustForeignAttributes(aa []Attribute) { |
||||
for i, a := range aa { |
||||
if a.Key == "" || a.Key[0] != 'x' { |
||||
continue |
||||
} |
||||
switch a.Key { |
||||
case "xlink:actuate", "xlink:arcrole", "xlink:href", "xlink:role", "xlink:show", |
||||
"xlink:title", "xlink:type", "xml:base", "xml:lang", "xml:space", "xmlns:xlink": |
||||
j := strings.Index(a.Key, ":") |
||||
aa[i].Namespace = a.Key[:j] |
||||
aa[i].Key = a.Key[j+1:] |
||||
} |
||||
} |
||||
} |
||||
|
||||
func htmlIntegrationPoint(n *Node) bool { |
||||
if n.Type != ElementNode { |
||||
return false |
||||
} |
||||
switch n.Namespace { |
||||
case "math": |
||||
if n.Data == "annotation-xml" { |
||||
for _, a := range n.Attr { |
||||
if a.Key == "encoding" { |
||||
val := strings.ToLower(a.Val) |
||||
if val == "text/html" || val == "application/xhtml+xml" { |
||||
return true |
||||
} |
||||
} |
||||
} |
||||
} |
||||
case "svg": |
||||
switch n.Data { |
||||
case "desc", "foreignObject", "title": |
||||
return true |
||||
} |
||||
} |
||||
return false |
||||
} |
||||
|
||||
func mathMLTextIntegrationPoint(n *Node) bool { |
||||
if n.Namespace != "math" { |
||||
return false |
||||
} |
||||
switch n.Data { |
||||
case "mi", "mo", "mn", "ms", "mtext": |
||||
return true |
||||
} |
||||
return false |
||||
} |
||||
|
||||
// Section 12.2.5.5.
|
||||
var breakout = map[string]bool{ |
||||
"b": true, |
||||
"big": true, |
||||
"blockquote": true, |
||||
"body": true, |
||||
"br": true, |
||||
"center": true, |
||||
"code": true, |
||||
"dd": true, |
||||
"div": true, |
||||
"dl": true, |
||||
"dt": true, |
||||
"em": true, |
||||
"embed": true, |
||||
"h1": true, |
||||
"h2": true, |
||||
"h3": true, |
||||
"h4": true, |
||||
"h5": true, |
||||
"h6": true, |
||||
"head": true, |
||||
"hr": true, |
||||
"i": true, |
||||
"img": true, |
||||
"li": true, |
||||
"listing": true, |
||||
"menu": true, |
||||
"meta": true, |
||||
"nobr": true, |
||||
"ol": true, |
||||
"p": true, |
||||
"pre": true, |
||||
"ruby": true, |
||||
"s": true, |
||||
"small": true, |
||||
"span": true, |
||||
"strong": true, |
||||
"strike": true, |
||||
"sub": true, |
||||
"sup": true, |
||||
"table": true, |
||||
"tt": true, |
||||
"u": true, |
||||
"ul": true, |
||||
"var": true, |
||||
} |
||||
|
||||
// Section 12.2.5.5.
|
||||
var svgTagNameAdjustments = map[string]string{ |
||||
"altglyph": "altGlyph", |
||||
"altglyphdef": "altGlyphDef", |
||||
"altglyphitem": "altGlyphItem", |
||||
"animatecolor": "animateColor", |
||||
"animatemotion": "animateMotion", |
||||
"animatetransform": "animateTransform", |
||||
"clippath": "clipPath", |
||||
"feblend": "feBlend", |
||||
"fecolormatrix": "feColorMatrix", |
||||
"fecomponenttransfer": "feComponentTransfer", |
||||
"fecomposite": "feComposite", |
||||
"feconvolvematrix": "feConvolveMatrix", |
||||
"fediffuselighting": "feDiffuseLighting", |
||||
"fedisplacementmap": "feDisplacementMap", |
||||
"fedistantlight": "feDistantLight", |
||||
"feflood": "feFlood", |
||||
"fefunca": "feFuncA", |
||||
"fefuncb": "feFuncB", |
||||
"fefuncg": "feFuncG", |
||||
"fefuncr": "feFuncR", |
||||
"fegaussianblur": "feGaussianBlur", |
||||
"feimage": "feImage", |
||||
"femerge": "feMerge", |
||||
"femergenode": "feMergeNode", |
||||
"femorphology": "feMorphology", |
||||
"feoffset": "feOffset", |
||||
"fepointlight": "fePointLight", |
||||
"fespecularlighting": "feSpecularLighting", |
||||
"fespotlight": "feSpotLight", |
||||
"fetile": "feTile", |
||||
"feturbulence": "feTurbulence", |
||||
"foreignobject": "foreignObject", |
||||
"glyphref": "glyphRef", |
||||
"lineargradient": "linearGradient", |
||||
"radialgradient": "radialGradient", |
||||
"textpath": "textPath", |
||||
} |
||||
|
||||
// Section 12.2.5.1
|
||||
var mathMLAttributeAdjustments = map[string]string{ |
||||
"definitionurl": "definitionURL", |
||||
} |
||||
|
||||
var svgAttributeAdjustments = map[string]string{ |
||||
"attributename": "attributeName", |
||||
"attributetype": "attributeType", |
||||
"basefrequency": "baseFrequency", |
||||
"baseprofile": "baseProfile", |
||||
"calcmode": "calcMode", |
||||
"clippathunits": "clipPathUnits", |
||||
"contentscripttype": "contentScriptType", |
||||
"contentstyletype": "contentStyleType", |
||||
"diffuseconstant": "diffuseConstant", |
||||
"edgemode": "edgeMode", |
||||
"externalresourcesrequired": "externalResourcesRequired", |
||||
"filterres": "filterRes", |
||||
"filterunits": "filterUnits", |
||||
"glyphref": "glyphRef", |
||||
"gradienttransform": "gradientTransform", |
||||
"gradientunits": "gradientUnits", |
||||
"kernelmatrix": "kernelMatrix", |
||||
"kernelunitlength": "kernelUnitLength", |
||||
"keypoints": "keyPoints", |
||||
"keysplines": "keySplines", |
||||
"keytimes": "keyTimes", |
||||
"lengthadjust": "lengthAdjust", |
||||
"limitingconeangle": "limitingConeAngle", |
||||
"markerheight": "markerHeight", |
||||
"markerunits": "markerUnits", |
||||
"markerwidth": "markerWidth", |
||||
"maskcontentunits": "maskContentUnits", |
||||
"maskunits": "maskUnits", |
||||
"numoctaves": "numOctaves", |
||||
"pathlength": "pathLength", |
||||
"patterncontentunits": "patternContentUnits", |
||||
"patterntransform": "patternTransform", |
||||
"patternunits": "patternUnits", |
||||
"pointsatx": "pointsAtX", |
||||
"pointsaty": "pointsAtY", |
||||
"pointsatz": "pointsAtZ", |
||||
"preservealpha": "preserveAlpha", |
||||
"preserveaspectratio": "preserveAspectRatio", |
||||
"primitiveunits": "primitiveUnits", |
||||
"refx": "refX", |
||||
"refy": "refY", |
||||
"repeatcount": "repeatCount", |
||||
"repeatdur": "repeatDur", |
||||
"requiredextensions": "requiredExtensions", |
||||
"requiredfeatures": "requiredFeatures", |
||||
"specularconstant": "specularConstant", |
||||
"specularexponent": "specularExponent", |
||||
"spreadmethod": "spreadMethod", |
||||
"startoffset": "startOffset", |
||||
"stddeviation": "stdDeviation", |
||||
"stitchtiles": "stitchTiles", |
||||
"surfacescale": "surfaceScale", |
||||
"systemlanguage": "systemLanguage", |
||||
"tablevalues": "tableValues", |
||||
"targetx": "targetX", |
||||
"targety": "targetY", |
||||
"textlength": "textLength", |
||||
"viewbox": "viewBox", |
||||
"viewtarget": "viewTarget", |
||||
"xchannelselector": "xChannelSelector", |
||||
"ychannelselector": "yChannelSelector", |
||||
"zoomandpan": "zoomAndPan", |
||||
} |
@ -0,0 +1,193 @@
|
||||
// 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 |
||||
|
||||
import ( |
||||
"golang.org/x/net/html/atom" |
||||
) |
||||
|
||||
// A NodeType is the type of a Node.
|
||||
type NodeType uint32 |
||||
|
||||
const ( |
||||
ErrorNode NodeType = iota |
||||
TextNode |
||||
DocumentNode |
||||
ElementNode |
||||
CommentNode |
||||
DoctypeNode |
||||
scopeMarkerNode |
||||
) |
||||
|
||||
// Section 12.2.3.3 says "scope markers are inserted when entering applet
|
||||
// elements, buttons, object elements, marquees, table cells, and table
|
||||
// captions, and are used to prevent formatting from 'leaking'".
|
||||
var scopeMarker = Node{Type: scopeMarkerNode} |
||||
|
||||
// A Node consists of a NodeType and some Data (tag name for element nodes,
|
||||
// content for text) and are part of a tree of Nodes. Element nodes may also
|
||||
// have a Namespace and contain a slice of Attributes. Data is unescaped, so
|
||||
// that it looks like "a<b" rather than "a<b". For element nodes, DataAtom
|
||||
// is the atom for Data, or zero if Data is not a known tag name.
|
||||
//
|
||||
// An empty Namespace implies a "http://www.w3.org/1999/xhtml" namespace.
|
||||
// Similarly, "math" is short for "http://www.w3.org/1998/Math/MathML", and
|
||||
// "svg" is short for "http://www.w3.org/2000/svg".
|
||||
type Node struct { |
||||
Parent, FirstChild, LastChild, PrevSibling, NextSibling *Node |
||||
|
||||
Type NodeType |
||||
DataAtom atom.Atom |
||||
Data string |
||||
Namespace string |
||||
Attr []Attribute |
||||
} |
||||
|
||||
// InsertBefore inserts newChild as a child of n, immediately before oldChild
|
||||
// in the sequence of n's children. oldChild may be nil, in which case newChild
|
||||
// is appended to the end of n's children.
|
||||
//
|
||||
// It will panic if newChild already has a parent or siblings.
|
||||
func (n *Node) InsertBefore(newChild, oldChild *Node) { |
||||
if newChild.Parent != nil || newChild.PrevSibling != nil || newChild.NextSibling != nil { |
||||
panic("html: InsertBefore called for an attached child Node") |
||||
} |
||||
var prev, next *Node |
||||
if oldChild != nil { |
||||
prev, next = oldChild.PrevSibling, oldChild |
||||
} else { |
||||
prev = n.LastChild |
||||
} |
||||
if prev != nil { |
||||
prev.NextSibling = newChild |
||||
} else { |
||||
n.FirstChild = newChild |
||||
} |
||||
if next != nil { |
||||
next.PrevSibling = newChild |
||||
} else { |
||||
n.LastChild = newChild |
||||
} |
||||
newChild.Parent = n |
||||
newChild.PrevSibling = prev |
||||
newChild.NextSibling = next |
||||
} |
||||
|
||||
// AppendChild adds a node c as a child of n.
|
||||
//
|
||||
// It will panic if c already has a parent or siblings.
|
||||
func (n *Node) AppendChild(c *Node) { |
||||
if c.Parent != nil || c.PrevSibling != nil || c.NextSibling != nil { |
||||
panic("html: AppendChild called for an attached child Node") |
||||
} |
||||
last := n.LastChild |
||||
if last != nil { |
||||
last.NextSibling = c |
||||
} else { |
||||
n.FirstChild = c |
||||
} |
||||
n.LastChild = c |
||||
c.Parent = n |
||||
c.PrevSibling = last |
||||
} |
||||
|
||||
// RemoveChild removes a node c that is a child of n. Afterwards, c will have
|
||||
// no parent and no siblings.
|
||||
//
|
||||
// It will panic if c's parent is not n.
|
||||
func (n *Node) RemoveChild(c *Node) { |
||||
if c.Parent != n { |
||||
panic("html: RemoveChild called for a non-child Node") |
||||
} |
||||
if n.FirstChild == c { |
||||
n.FirstChild = c.NextSibling |
||||
} |
||||
if c.NextSibling != nil { |
||||
c.NextSibling.PrevSibling = c.PrevSibling |
||||
} |
||||
if n.LastChild == c { |
||||
n.LastChild = c.PrevSibling |
||||
} |
||||
if c.PrevSibling != nil { |
||||
c.PrevSibling.NextSibling = c.NextSibling |
||||
} |
||||
c.Parent = nil |
||||
c.PrevSibling = nil |
||||
c.NextSibling = nil |
||||
} |
||||
|
||||
// reparentChildren reparents all of src's child nodes to dst.
|
||||
func reparentChildren(dst, src *Node) { |
||||
for { |
||||
child := src.FirstChild |
||||
if child == nil { |
||||
break |
||||
} |
||||
src.RemoveChild(child) |
||||
dst.AppendChild(child) |
||||
} |
||||
} |
||||
|
||||
// clone returns a new node with the same type, data and attributes.
|
||||
// The clone has no parent, no siblings and no children.
|
||||
func (n *Node) clone() *Node { |
||||
m := &Node{ |
||||
Type: n.Type, |
||||
DataAtom: n.DataAtom, |
||||
Data: n.Data, |
||||
Attr: make([]Attribute, len(n.Attr)), |
||||
} |
||||
copy(m.Attr, n.Attr) |
||||
return m |
||||
} |
||||
|
||||
// nodeStack is a stack of nodes.
|
||||
type nodeStack []*Node |
||||
|
||||
// pop pops the stack. It will panic if s is empty.
|
||||
func (s *nodeStack) pop() *Node { |
||||
i := len(*s) |
||||
n := (*s)[i-1] |
||||
*s = (*s)[:i-1] |
||||
return n |
||||
} |
||||
|
||||
// top returns the most recently pushed node, or nil if s is empty.
|
||||
func (s *nodeStack) top() *Node { |
||||
if i := len(*s); i > 0 { |
||||
return (*s)[i-1] |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// index returns the index of the top-most occurrence of n in the stack, or -1
|
||||
// if n is not present.
|
||||
func (s *nodeStack) index(n *Node) int { |
||||
for i := len(*s) - 1; i >= 0; i-- { |
||||
if (*s)[i] == n { |
||||
return i |
||||
} |
||||
} |
||||
return -1 |
||||
} |
||||
|
||||
// insert inserts a node at the given index.
|
||||
func (s *nodeStack) insert(i int, n *Node) { |
||||
(*s) = append(*s, nil) |
||||
copy((*s)[i+1:], (*s)[i:]) |
||||
(*s)[i] = n |
||||
} |
||||
|
||||
// remove removes a node from the stack. It is a no-op if n is not present.
|
||||
func (s *nodeStack) remove(n *Node) { |
||||
i := s.index(n) |
||||
if i == -1 { |
||||
return |
||||
} |
||||
copy((*s)[i:], (*s)[i+1:]) |
||||
j := len(*s) - 1 |
||||
(*s)[j] = nil |
||||
*s = (*s)[:j] |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,271 @@
|
||||
// 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 |
||||
|
||||
import ( |
||||
"bufio" |
||||
"errors" |
||||
"fmt" |
||||
"io" |
||||
"strings" |
||||
) |
||||
|
||||
type writer interface { |
||||
io.Writer |
||||
io.ByteWriter |
||||
WriteString(string) (int, error) |
||||
} |
||||
|
||||
// Render renders the parse tree n to the given writer.
|
||||
//
|
||||
// Rendering is done on a 'best effort' basis: calling Parse on the output of
|
||||
// Render will always result in something similar to the original tree, but it
|
||||
// is not necessarily an exact clone unless the original tree was 'well-formed'.
|
||||
// 'Well-formed' is not easily specified; the HTML5 specification is
|
||||
// complicated.
|
||||
//
|
||||
// Calling Parse on arbitrary input typically results in a 'well-formed' parse
|
||||
// tree. However, it is possible for Parse to yield a 'badly-formed' parse tree.
|
||||
// For example, in a 'well-formed' parse tree, no <a> element is a child of
|
||||
// another <a> element: parsing "<a><a>" results in two sibling elements.
|
||||
// Similarly, in a 'well-formed' parse tree, no <a> element is a child of a
|
||||
// <table> element: parsing "<p><table><a>" results in a <p> with two sibling
|
||||
// children; the <a> is reparented to the <table>'s parent. However, calling
|
||||
// Parse on "<a><table><a>" does not return an error, but the result has an <a>
|
||||
// element with an <a> child, and is therefore not 'well-formed'.
|
||||
//
|
||||
// Programmatically constructed trees are typically also 'well-formed', but it
|
||||
// is possible to construct a tree that looks innocuous but, when rendered and
|
||||
// re-parsed, results in a different tree. A simple example is that a solitary
|
||||
// text node would become a tree containing <html>, <head> and <body> elements.
|
||||
// Another example is that the programmatic equivalent of "a<head>b</head>c"
|
||||
// becomes "<html><head><head/><body>abc</body></html>".
|
||||
func Render(w io.Writer, n *Node) error { |
||||
if x, ok := w.(writer); ok { |
||||
return render(x, n) |
||||
} |
||||
buf := bufio.NewWriter(w) |
||||
if err := render(buf, n); err != nil { |
||||
return err |
||||
} |
||||
return buf.Flush() |
||||
} |
||||
|
||||
// plaintextAbort is returned from render1 when a <plaintext> element
|
||||
// has been rendered. No more end tags should be rendered after that.
|
||||
var plaintextAbort = errors.New("html: internal error (plaintext abort)") |
||||
|
||||
func render(w writer, n *Node) error { |
||||
err := render1(w, n) |
||||
if err == plaintextAbort { |
||||
err = nil |
||||
} |
||||
return err |
||||
} |
||||
|
||||
func render1(w writer, n *Node) error { |
||||
// Render non-element nodes; these are the easy cases.
|
||||
switch n.Type { |
||||
case ErrorNode: |
||||
return errors.New("html: cannot render an ErrorNode node") |
||||
case TextNode: |
||||
return escape(w, n.Data) |
||||
case DocumentNode: |
||||
for c := n.FirstChild; c != nil; c = c.NextSibling { |
||||
if err := render1(w, c); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
return nil |
||||
case ElementNode: |
||||
// No-op.
|
||||
case CommentNode: |
||||
if _, err := w.WriteString("<!--"); err != nil { |
||||
return err |
||||
} |
||||
if _, err := w.WriteString(n.Data); err != nil { |
||||
return err |
||||
} |
||||
if _, err := w.WriteString("-->"); err != nil { |
||||
return err |
||||
} |
||||
return nil |
||||
case DoctypeNode: |
||||
if _, err := w.WriteString("<!DOCTYPE "); err != nil { |
||||
return err |
||||
} |
||||
if _, err := w.WriteString(n.Data); err != nil { |
||||
return err |
||||
} |
||||
if n.Attr != nil { |
||||
var p, s string |
||||
for _, a := range n.Attr { |
||||
switch a.Key { |
||||
case "public": |
||||
p = a.Val |
||||
case "system": |
||||
s = a.Val |
||||
} |
||||
} |
||||
if p != "" { |
||||
if _, err := w.WriteString(" PUBLIC "); err != nil { |
||||
return err |
||||
} |
||||
if err := writeQuoted(w, p); err != nil { |
||||
return err |
||||
} |
||||
if s != "" { |
||||
if err := w.WriteByte(' '); err != nil { |
||||
return err |
||||
} |
||||
if err := writeQuoted(w, s); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
} else if s != "" { |
||||
if _, err := w.WriteString(" SYSTEM "); err != nil { |
||||
return err |
||||
} |
||||
if err := writeQuoted(w, s); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
} |
||||
return w.WriteByte('>') |
||||
default: |
||||
return errors.New("html: unknown node type") |
||||
} |
||||
|
||||
// Render the <xxx> opening tag.
|
||||
if err := w.WriteByte('<'); err != nil { |
||||
return err |
||||
} |
||||
if _, err := w.WriteString(n.Data); err != nil { |
||||
return err |
||||
} |
||||
for _, a := range n.Attr { |
||||
if err := w.WriteByte(' '); err != nil { |
||||
return err |
||||
} |
||||
if a.Namespace != "" { |
||||
if _, err := w.WriteString(a.Namespace); err != nil { |
||||
return err |
||||
} |
||||
if err := w.WriteByte(':'); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
if _, err := w.WriteString(a.Key); err != nil { |
||||
return err |
||||
} |
||||
if _, err := w.WriteString(`="`); err != nil { |
||||
return err |
||||
} |
||||
if err := escape(w, a.Val); err != nil { |
||||
return err |
||||
} |
||||
if err := w.WriteByte('"'); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
if voidElements[n.Data] { |
||||
if n.FirstChild != nil { |
||||
return fmt.Errorf("html: void element <%s> has child nodes", n.Data) |
||||
} |
||||
_, err := w.WriteString("/>") |
||||
return err |
||||
} |
||||
if err := w.WriteByte('>'); err != nil { |
||||
return err |
||||
} |
||||
|
||||
// Add initial newline where there is danger of a newline beging ignored.
|
||||
if c := n.FirstChild; c != nil && c.Type == TextNode && strings.HasPrefix(c.Data, "\n") { |
||||
switch n.Data { |
||||
case "pre", "listing", "textarea": |
||||
if err := w.WriteByte('\n'); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Render any child nodes.
|
||||
switch n.Data { |
||||
case "iframe", "noembed", "noframes", "noscript", "plaintext", "script", "style", "xmp": |
||||
for c := n.FirstChild; c != nil; c = c.NextSibling { |
||||
if c.Type == TextNode { |
||||
if _, err := w.WriteString(c.Data); err != nil { |
||||
return err |
||||
} |
||||
} else { |
||||
if err := render1(w, c); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
} |
||||
if n.Data == "plaintext" { |
||||
// Don't render anything else. <plaintext> must be the
|
||||
// last element in the file, with no closing tag.
|
||||
return plaintextAbort |
||||
} |
||||
default: |
||||
for c := n.FirstChild; c != nil; c = c.NextSibling { |
||||
if err := render1(w, c); err != nil { |
||||
return err |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Render the </xxx> closing tag.
|
||||
if _, err := w.WriteString("</"); err != nil { |
||||
return err |
||||
} |
||||
if _, err := w.WriteString(n.Data); err != nil { |
||||
return err |
||||
} |
||||
return w.WriteByte('>') |
||||
} |
||||
|
||||
// writeQuoted writes s to w surrounded by quotes. Normally it will use double
|
||||
// quotes, but if s contains a double quote, it will use single quotes.
|
||||
// It is used for writing the identifiers in a doctype declaration.
|
||||
// In valid HTML, they can't contain both types of quotes.
|
||||
func writeQuoted(w writer, s string) error { |
||||
var q byte = '"' |
||||
if strings.Contains(s, `"`) { |
||||
q = '\'' |
||||
} |
||||
if err := w.WriteByte(q); err != nil { |
||||
return err |
||||
} |
||||
if _, err := w.WriteString(s); err != nil { |
||||
return err |
||||
} |
||||
if err := w.WriteByte(q); err != nil { |
||||
return err |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
// Section 12.1.2, "Elements", gives this list of void elements. Void elements
|
||||
// are those that can't have any contents.
|
||||
var voidElements = map[string]bool{ |
||||
"area": true, |
||||
"base": true, |
||||
"br": true, |
||||
"col": true, |
||||
"command": true, |
||||
"embed": true, |
||||
"hr": true, |
||||
"img": true, |
||||
"input": true, |
||||
"keygen": true, |
||||
"link": true, |
||||
"meta": true, |
||||
"param": true, |
||||
"source": true, |
||||
"track": true, |
||||
"wbr": true, |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,3 @@
|
||||
# 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. |
@ -0,0 +1,3 @@
|
||||
# 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. |
@ -0,0 +1,27 @@
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved. |
||||
|
||||
Redistribution and use in source and binary forms, with or without |
||||
modification, are permitted provided that the following conditions are |
||||
met: |
||||
|
||||
* Redistributions of source code must retain the above copyright |
||||
notice, this list of conditions and the following disclaimer. |
||||
* Redistributions in binary form must reproduce the above |
||||
copyright notice, this list of conditions and the following disclaimer |
||||
in the documentation and/or other materials provided with the |
||||
distribution. |
||||
* Neither the name of Google Inc. nor the names of its |
||||
contributors may be used to endorse or promote products derived from |
||||
this software without specific prior written permission. |
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
@ -0,0 +1,22 @@
|
||||
Additional IP Rights Grant (Patents) |
||||
|
||||
"This implementation" means the copyrightable works distributed by |
||||
Google as part of the Go project. |
||||
|
||||
Google hereby grants to You a perpetual, worldwide, non-exclusive, |
||||
no-charge, royalty-free, irrevocable (except as stated in this section) |
||||
patent license to make, have made, use, offer to sell, sell, import, |
||||
transfer and otherwise run, modify and propagate the contents of this |
||||
implementation of Go, where such license applies only to those patent |
||||
claims, both currently owned or controlled by Google and acquired in |
||||
the future, licensable by Google that are necessarily infringed by this |
||||
implementation of Go. This grant does not include claims that would be |
||||
infringed only as a consequence of further modification of this |
||||
implementation. If you or your agent or exclusive licensee institute or |
||||
order or agree to the institution of patent litigation against any |
||||
entity (including a cross-claim or counterclaim in a lawsuit) alleging |
||||
that this implementation of Go or any code incorporated within this |
||||
implementation of Go constitutes direct or contributory patent |
||||
infringement, or inducement of patent infringement, then any patent |
||||
rights granted to you under this License for this implementation of Go |
||||
shall terminate as of the date such litigation is filed. |
@ -0,0 +1,173 @@
|
||||
# Building `sys/unix` |
||||
|
||||
The sys/unix package provides access to the raw system call interface of the |
||||
underlying operating system. See: https://godoc.org/golang.org/x/sys/unix |
||||
|
||||
Porting Go to a new architecture/OS combination or adding syscalls, types, or |
||||
constants to an existing architecture/OS pair requires some manual effort; |
||||
however, there are tools that automate much of the process. |
||||
|
||||
## Build Systems |
||||
|
||||
There are currently two ways we generate the necessary files. We are currently |
||||
migrating the build system to use containers so the builds are reproducible. |
||||
This is being done on an OS-by-OS basis. Please update this documentation as |
||||
components of the build system change. |
||||
|
||||
### Old Build System (currently for `GOOS != "Linux" || GOARCH == "sparc64"`) |
||||
|
||||
The old build system generates the Go files based on the C header files |
||||
present on your system. This means that files |
||||
for a given GOOS/GOARCH pair must be generated on a system with that OS and |
||||
architecture. This also means that the generated code can differ from system |
||||
to system, based on differences in the header files. |
||||
|
||||
To avoid this, if you are using the old build system, only generate the Go |
||||
files on an installation with unmodified header files. It is also important to |
||||
keep track of which version of the OS the files were generated from (ex. |
||||
Darwin 14 vs Darwin 15). This makes it easier to track the progress of changes |
||||
and have each OS upgrade correspond to a single change. |
||||
|
||||
To build the files for your current OS and architecture, make sure GOOS and |
||||
GOARCH are set correctly and run `mkall.sh`. This will generate the files for |
||||
your specific system. Running `mkall.sh -n` shows the commands that will be run. |
||||
|
||||
Requirements: bash, perl, go |
||||
|
||||
### New Build System (currently for `GOOS == "Linux" && GOARCH != "sparc64"`) |
||||
|
||||
The new build system uses a Docker container to generate the go files directly |
||||
from source checkouts of the kernel and various system libraries. This means |
||||
that on any platform that supports Docker, all the files using the new build |
||||
system can be generated at once, and generated files will not change based on |
||||
what the person running the scripts has installed on their computer. |
||||
|
||||
The OS specific files for the new build system are located in the `${GOOS}` |
||||
directory, and the build is coordinated by the `${GOOS}/mkall.go` program. When |
||||
the kernel or system library updates, modify the Dockerfile at |
||||
`${GOOS}/Dockerfile` to checkout the new release of the source. |
||||
|
||||
To build all the files under the new build system, you must be on an amd64/Linux |
||||
system and have your GOOS and GOARCH set accordingly. Running `mkall.sh` will |
||||
then generate all of the files for all of the GOOS/GOARCH pairs in the new build |
||||
system. Running `mkall.sh -n` shows the commands that will be run. |
||||
|
||||
Requirements: bash, perl, go, docker |
||||
|
||||
## Component files |
||||
|
||||
This section describes the various files used in the code generation process. |
||||
It also contains instructions on how to modify these files to add a new |
||||
architecture/OS or to add additional syscalls, types, or constants. Note that |
||||
if you are using the new build system, the scripts cannot be called normally. |
||||
They must be called from within the docker container. |
||||
|
||||
### asm files |
||||
|
||||
The hand-written assembly file at `asm_${GOOS}_${GOARCH}.s` implements system |
||||
call dispatch. There are three entry points: |
||||
``` |
||||
func Syscall(trap, a1, a2, a3 uintptr) (r1, r2, err uintptr) |
||||
func Syscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, err uintptr) |
||||
func RawSyscall(trap, a1, a2, a3 uintptr) (r1, r2, err uintptr) |
||||
``` |
||||
The first and second are the standard ones; they differ only in how many |
||||
arguments can be passed to the kernel. The third is for low-level use by the |
||||
ForkExec wrapper. Unlike the first two, it does not call into the scheduler to |
||||
let it know that a system call is running. |
||||
|
||||
When porting Go to an new architecture/OS, this file must be implemented for |
||||
each GOOS/GOARCH pair. |
||||
|
||||
### mksysnum |
||||
|
||||
Mksysnum is a script located at `${GOOS}/mksysnum.pl` (or `mksysnum_${GOOS}.pl` |
||||
for the old system). This script takes in a list of header files containing the |
||||
syscall number declarations and parses them to produce the corresponding list of |
||||
Go numeric constants. See `zsysnum_${GOOS}_${GOARCH}.go` for the generated |
||||
constants. |
||||
|
||||
Adding new syscall numbers is mostly done by running the build on a sufficiently |
||||
new installation of the target OS (or updating the source checkouts for the |
||||
new build system). However, depending on the OS, you make need to update the |
||||
parsing in mksysnum. |
||||
|
||||
### mksyscall.pl |
||||
|
||||
The `syscall.go`, `syscall_${GOOS}.go`, `syscall_${GOOS}_${GOARCH}.go` are |
||||
hand-written Go files which implement system calls (for unix, the specific OS, |
||||
or the specific OS/Architecture pair respectively) that need special handling |
||||
and list `//sys` comments giving prototypes for ones that can be generated. |
||||
|
||||
The mksyscall.pl script takes the `//sys` and `//sysnb` comments and converts |
||||
them into syscalls. This requires the name of the prototype in the comment to |
||||
match a syscall number in the `zsysnum_${GOOS}_${GOARCH}.go` file. The function |
||||
prototype can be exported (capitalized) or not. |
||||
|
||||
Adding a new syscall often just requires adding a new `//sys` function prototype |
||||
with the desired arguments and a capitalized name so it is exported. However, if |
||||
you want the interface to the syscall to be different, often one will make an |
||||
unexported `//sys` prototype, an then write a custom wrapper in |
||||
`syscall_${GOOS}.go`. |
||||
|
||||
### types files |
||||
|
||||
For each OS, there is a hand-written Go file at `${GOOS}/types.go` (or |
||||
`types_${GOOS}.go` on the old system). This file includes standard C headers and |
||||
creates Go type aliases to the corresponding C types. The file is then fed |
||||
through godef to get the Go compatible definitions. Finally, the generated code |
||||
is fed though mkpost.go to format the code correctly and remove any hidden or |
||||
private identifiers. This cleaned-up code is written to |
||||
`ztypes_${GOOS}_${GOARCH}.go`. |
||||
|
||||
The hardest part about preparing this file is figuring out which headers to |
||||
include and which symbols need to be `#define`d to get the actual data |
||||
structures that pass through to the kernel system calls. Some C libraries |
||||
preset alternate versions for binary compatibility and translate them on the |
||||
way in and out of system calls, but there is almost always a `#define` that can |
||||
get the real ones. |
||||
See `types_darwin.go` and `linux/types.go` for examples. |
||||
|
||||
To add a new type, add in the necessary include statement at the top of the |
||||
file (if it is not already there) and add in a type alias line. Note that if |
||||
your type is significantly different on different architectures, you may need |
||||
some `#if/#elif` macros in your include statements. |
||||
|
||||
### mkerrors.sh |
||||
|
||||
This script is used to generate the system's various constants. This doesn't |
||||
just include the error numbers and error strings, but also the signal numbers |
||||
an a wide variety of miscellaneous constants. The constants come from the list |
||||
of include files in the `includes_${uname}` variable. A regex then picks out |
||||
the desired `#define` statements, and generates the corresponding Go constants. |
||||
The error numbers and strings are generated from `#include <errno.h>`, and the |
||||
signal numbers and strings are generated from `#include <signal.h>`. All of |
||||
these constants are written to `zerrors_${GOOS}_${GOARCH}.go` via a C program, |
||||
`_errors.c`, which prints out all the constants. |
||||
|
||||
To add a constant, add the header that includes it to the appropriate variable. |
||||
Then, edit the regex (if necessary) to match the desired constant. Avoid making |
||||
the regex too broad to avoid matching unintended constants. |
||||
|
||||
|
||||
## Generated files |
||||
|
||||
### `zerror_${GOOS}_${GOARCH}.go` |
||||
|
||||
A file containing all of the system's generated error numbers, error strings, |
||||
signal numbers, and constants. Generated by `mkerrors.sh` (see above). |
||||
|
||||
### `zsyscall_${GOOS}_${GOARCH}.go` |
||||
|
||||
A file containing all the generated syscalls for a specific GOOS and GOARCH. |
||||
Generated by `mksyscall.pl` (see above). |
||||
|
||||
### `zsysnum_${GOOS}_${GOARCH}.go` |
||||
|
||||
A list of numeric constants for all the syscall number of the specific GOOS |
||||
and GOARCH. Generated by mksysnum (see above). |
||||
|
||||
### `ztypes_${GOOS}_${GOARCH}.go` |
||||
|
||||
A file containing Go types for passing into (or returning from) syscalls. |
||||
Generated by godefs and the types file (see above). |
@ -0,0 +1,29 @@
|
||||
// Copyright 2009 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 !gccgo |
||||
|
||||
#include "textflag.h" |
||||
|
||||
// |
||||
// System call support for 386, Darwin |
||||
// |
||||
|
||||
// Just jump to package syscall's implementation for all these functions. |
||||
// The runtime may know about them. |
||||
|
||||
TEXT ·Syscall(SB),NOSPLIT,$0-28 |
||||
JMP syscall·Syscall(SB) |
||||
|
||||
TEXT ·Syscall6(SB),NOSPLIT,$0-40 |
||||
JMP syscall·Syscall6(SB) |
||||
|
||||
TEXT ·Syscall9(SB),NOSPLIT,$0-52 |
||||
JMP syscall·Syscall9(SB) |
||||
|
||||
TEXT ·RawSyscall(SB),NOSPLIT,$0-28 |
||||
JMP syscall·RawSyscall(SB) |
||||
|
||||
TEXT ·RawSyscall6(SB),NOSPLIT,$0-40 |
||||
JMP syscall·RawSyscall6(SB) |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue