Add S3 storage support
This commit is contained in:
parent
c05e16b6a0
commit
f519acfd71
351 changed files with 99292 additions and 1331 deletions
21
README.md
21
README.md
|
|
@ -652,16 +652,17 @@ A command is defined as:
|
||||||
|
|
||||||
Currently supported placeholders are:
|
Currently supported placeholders are:
|
||||||
|
|
||||||
| Placeholder | Description | Location |
|
| Placeholder | Description | Location |
|
||||||
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- |
|
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------- |
|
||||||
| `{diskfs}` | Will be replaced by the provided `CORE_STORAGE_DISK_DIR`. | `options`, `input.address`, `input.options`, `output.address`, `output.options` |
|
| `{diskfs}` or `{fs:disk}` | Will be replaced by the provided `CORE_STORAGE_DISK_DIR`. | `options`, `input.address`, `input.options`, `output.address`, `output.options` |
|
||||||
| `{memfs}` | Will be replace by the base URL of the MemFS. | `input.address`, `input.options`, `output.address`, `output.options` |
|
| `{memfs}` or `{fs:mem}` | Will be replaced by the base address of the MemFS. | `input.address`, `input.options`, `output.address`, `output.options` |
|
||||||
| `{processid}` | Will be replaced by the ID of the process. | `input.id`, `input.address`, `input.options`, `output.id`, `output.address`, `output.options`, `output.cleanup.pattern` |
|
| `{fs:*}` | Will be replaces by the base address of the respective filesystem. | See `{memfs}` |
|
||||||
| `{reference}` | Will be replaced by the reference of the process | `input.id`, `input.address`, `input.options`, `output.id`, `output.address`, `output.options`, `output.cleanup.pattern` |
|
| `{processid}` | Will be replaced by the ID of the process. | `input.id`, `input.address`, `input.options`, `output.id`, `output.address`, `output.options`, `output.cleanup.pattern` |
|
||||||
| `{inputid}` | Will be replaced by the ID of the input. | `input.address`, `input.options` |
|
| `{reference}` | Will be replaced by the reference of the process | `input.id`, `input.address`, `input.options`, `output.id`, `output.address`, `output.options`, `output.cleanup.pattern` |
|
||||||
| `{outputid}` | Will be replaced by the ID of the output. | `output.address`, `output.options`, `output.cleanup.pattern` |
|
| `{inputid}` | Will be replaced by the ID of the input. | `input.address`, `input.options` |
|
||||||
| `{rtmp}` | Will be replaced by the internal address of the RTMP server. Requires parameter `name` (name of the stream). | `input.address`, `output.address` |
|
| `{outputid}` | Will be replaced by the ID of the output. | `output.address`, `output.options`, `output.cleanup.pattern` |
|
||||||
| `{srt}` | Will be replaced by the internal address of the SRT server. Requires parameter `name` (name of the stream) and `mode` (either `publish` or `request`). | `input.address`, `output.address` |
|
| `{rtmp}` | Will be replaced by the internal address of the RTMP server. Requires parameter `name` (name of the stream). | `input.address`, `output.address` |
|
||||||
|
| `{srt}` | Will be replaced by the internal address of the SRT server. Requires parameter `name` (name of the stream) and `mode` (either `publish` or `request`). | `input.address`, `output.address` |
|
||||||
|
|
||||||
Before replacing the placeholders in the process config, all references (see below) will be resolved.
|
Before replacing the placeholders in the process config, all references (see below) will be resolved.
|
||||||
|
|
||||||
|
|
|
||||||
175
app/api/api.go
175
app/api/api.go
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"github.com/datarhei/core/v16/ffmpeg"
|
"github.com/datarhei/core/v16/ffmpeg"
|
||||||
"github.com/datarhei/core/v16/http"
|
"github.com/datarhei/core/v16/http"
|
||||||
"github.com/datarhei/core/v16/http/cache"
|
"github.com/datarhei/core/v16/http/cache"
|
||||||
|
httpfs "github.com/datarhei/core/v16/http/fs"
|
||||||
"github.com/datarhei/core/v16/http/jwt"
|
"github.com/datarhei/core/v16/http/jwt"
|
||||||
"github.com/datarhei/core/v16/http/router"
|
"github.com/datarhei/core/v16/http/router"
|
||||||
"github.com/datarhei/core/v16/io/fs"
|
"github.com/datarhei/core/v16/io/fs"
|
||||||
|
|
@ -69,6 +70,7 @@ type api struct {
|
||||||
ffmpeg ffmpeg.FFmpeg
|
ffmpeg ffmpeg.FFmpeg
|
||||||
diskfs fs.Filesystem
|
diskfs fs.Filesystem
|
||||||
memfs fs.Filesystem
|
memfs fs.Filesystem
|
||||||
|
s3fs map[string]fs.Filesystem
|
||||||
rtmpserver rtmp.Server
|
rtmpserver rtmp.Server
|
||||||
srtserver srt.Server
|
srtserver srt.Server
|
||||||
metrics monitor.HistoryMonitor
|
metrics monitor.HistoryMonitor
|
||||||
|
|
@ -118,6 +120,7 @@ var ErrConfigReload = fmt.Errorf("configuration reload")
|
||||||
func New(configpath string, logwriter io.Writer) (API, error) {
|
func New(configpath string, logwriter io.Writer) (API, error) {
|
||||||
a := &api{
|
a := &api{
|
||||||
state: "idle",
|
state: "idle",
|
||||||
|
s3fs: map[string]fs.Filesystem{},
|
||||||
}
|
}
|
||||||
|
|
||||||
a.config.path = configpath
|
a.config.path = configpath
|
||||||
|
|
@ -372,12 +375,13 @@ func (a *api) start() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
diskfs, err := fs.NewDiskFilesystem(fs.DiskConfig{
|
diskfs, err := fs.NewDiskFilesystem(fs.DiskConfig{
|
||||||
|
Name: "disk",
|
||||||
Dir: cfg.Storage.Disk.Dir,
|
Dir: cfg.Storage.Disk.Dir,
|
||||||
Size: cfg.Storage.Disk.Size * 1024 * 1024,
|
Size: cfg.Storage.Disk.Size * 1024 * 1024,
|
||||||
Logger: a.log.logger.core.WithComponent("DiskFS"),
|
Logger: a.log.logger.core.WithComponent("FS"),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("disk filesystem: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
a.diskfs = diskfs
|
a.diskfs = diskfs
|
||||||
|
|
@ -400,10 +404,11 @@ func (a *api) start() error {
|
||||||
|
|
||||||
if a.memfs == nil {
|
if a.memfs == nil {
|
||||||
memfs := fs.NewMemFilesystem(fs.MemConfig{
|
memfs := fs.NewMemFilesystem(fs.MemConfig{
|
||||||
|
Name: "mem",
|
||||||
Base: baseMemFS.String(),
|
Base: baseMemFS.String(),
|
||||||
Size: cfg.Storage.Memory.Size * 1024 * 1024,
|
Size: cfg.Storage.Memory.Size * 1024 * 1024,
|
||||||
Purge: cfg.Storage.Memory.Purge,
|
Purge: cfg.Storage.Memory.Purge,
|
||||||
Logger: a.log.logger.core.WithComponent("MemFS"),
|
Logger: a.log.logger.core.WithComponent("FS"),
|
||||||
})
|
})
|
||||||
|
|
||||||
a.memfs = memfs
|
a.memfs = memfs
|
||||||
|
|
@ -412,23 +417,62 @@ func (a *api) start() error {
|
||||||
a.memfs.Resize(cfg.Storage.Memory.Size * 1024 * 1024)
|
a.memfs.Resize(cfg.Storage.Memory.Size * 1024 * 1024)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, s3 := range cfg.Storage.S3 {
|
||||||
|
baseS3FS := url.URL{
|
||||||
|
Scheme: "http",
|
||||||
|
Path: s3.Mountpoint,
|
||||||
|
}
|
||||||
|
|
||||||
|
host, port, _ := gonet.SplitHostPort(cfg.Address)
|
||||||
|
if len(host) == 0 {
|
||||||
|
baseS3FS.Host = "localhost:" + port
|
||||||
|
} else {
|
||||||
|
baseS3FS.Host = cfg.Address
|
||||||
|
}
|
||||||
|
|
||||||
|
if s3.Auth.Enable {
|
||||||
|
baseS3FS.User = url.UserPassword(s3.Auth.Username, s3.Auth.Password)
|
||||||
|
}
|
||||||
|
|
||||||
|
s3fs, err := fs.NewS3Filesystem(fs.S3Config{
|
||||||
|
Name: s3.Name,
|
||||||
|
Base: baseS3FS.String(),
|
||||||
|
Endpoint: s3.Endpoint,
|
||||||
|
AccessKeyID: s3.AccessKeyID,
|
||||||
|
SecretAccessKey: s3.SecretAccessKey,
|
||||||
|
Region: s3.Region,
|
||||||
|
Bucket: s3.Bucket,
|
||||||
|
UseSSL: s3.UseSSL,
|
||||||
|
Logger: a.log.logger.core.WithComponent("FS"),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("s3 filesystem (%s): %w", s3.Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := a.s3fs[s3.Name]; ok {
|
||||||
|
return fmt.Errorf("the name '%s' for a filesystem is already in use", s3.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
a.s3fs[s3.Name] = s3fs
|
||||||
|
}
|
||||||
|
|
||||||
var portrange net.Portranger
|
var portrange net.Portranger
|
||||||
|
|
||||||
if cfg.Playout.Enable {
|
if cfg.Playout.Enable {
|
||||||
portrange, err = net.NewPortrange(cfg.Playout.MinPort, cfg.Playout.MaxPort)
|
portrange, err = net.NewPortrange(cfg.Playout.MinPort, cfg.Playout.MaxPort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("playout port range: %w", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
validatorIn, err := ffmpeg.NewValidator(cfg.FFmpeg.Access.Input.Allow, cfg.FFmpeg.Access.Input.Block)
|
validatorIn, err := ffmpeg.NewValidator(cfg.FFmpeg.Access.Input.Allow, cfg.FFmpeg.Access.Input.Block)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("input address validator: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
validatorOut, err := ffmpeg.NewValidator(cfg.FFmpeg.Access.Output.Allow, cfg.FFmpeg.Access.Output.Block)
|
validatorOut, err := ffmpeg.NewValidator(cfg.FFmpeg.Access.Output.Allow, cfg.FFmpeg.Access.Output.Block)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("output address validator: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
ffmpeg, err := ffmpeg.New(ffmpeg.Config{
|
ffmpeg, err := ffmpeg.New(ffmpeg.Config{
|
||||||
|
|
@ -442,7 +486,7 @@ func (a *api) start() error {
|
||||||
Collector: a.sessions.Collector("ffmpeg"),
|
Collector: a.sessions.Collector("ffmpeg"),
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return fmt.Errorf("unable to create ffmpeg: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
a.ffmpeg = ffmpeg
|
a.ffmpeg = ffmpeg
|
||||||
|
|
@ -510,6 +554,15 @@ func (a *api) start() error {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filesystems := []fs.Filesystem{
|
||||||
|
a.diskfs,
|
||||||
|
a.memfs,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fs := range a.s3fs {
|
||||||
|
filesystems = append(filesystems, fs)
|
||||||
|
}
|
||||||
|
|
||||||
store := store.NewJSONStore(store.JSONConfig{
|
store := store.NewJSONStore(store.JSONConfig{
|
||||||
Filepath: cfg.DB.Dir + "/db.json",
|
Filepath: cfg.DB.Dir + "/db.json",
|
||||||
FFVersion: a.ffmpeg.Skills().FFmpeg.Version,
|
FFVersion: a.ffmpeg.Skills().FFmpeg.Version,
|
||||||
|
|
@ -520,8 +573,7 @@ func (a *api) start() error {
|
||||||
ID: cfg.ID,
|
ID: cfg.ID,
|
||||||
Name: cfg.Name,
|
Name: cfg.Name,
|
||||||
Store: store,
|
Store: store,
|
||||||
DiskFS: a.diskfs,
|
Filesystems: filesystems,
|
||||||
MemFS: a.memfs,
|
|
||||||
Replace: a.replacer,
|
Replace: a.replacer,
|
||||||
FFmpeg: a.ffmpeg,
|
FFmpeg: a.ffmpeg,
|
||||||
MaxProcesses: cfg.FFmpeg.MaxProcesses,
|
MaxProcesses: cfg.FFmpeg.MaxProcesses,
|
||||||
|
|
@ -592,6 +644,9 @@ func (a *api) start() error {
|
||||||
metrics.Register(monitor.NewDiskCollector(a.diskfs.Base()))
|
metrics.Register(monitor.NewDiskCollector(a.diskfs.Base()))
|
||||||
metrics.Register(monitor.NewFilesystemCollector("diskfs", diskfs))
|
metrics.Register(monitor.NewFilesystemCollector("diskfs", diskfs))
|
||||||
metrics.Register(monitor.NewFilesystemCollector("memfs", a.memfs))
|
metrics.Register(monitor.NewFilesystemCollector("memfs", a.memfs))
|
||||||
|
for name, fs := range a.s3fs {
|
||||||
|
metrics.Register(monitor.NewFilesystemCollector(name, fs))
|
||||||
|
}
|
||||||
metrics.Register(monitor.NewRestreamCollector(a.restream))
|
metrics.Register(monitor.NewRestreamCollector(a.restream))
|
||||||
metrics.Register(monitor.NewFFmpegCollector(a.ffmpeg))
|
metrics.Register(monitor.NewFFmpegCollector(a.ffmpeg))
|
||||||
metrics.Register(monitor.NewSessionCollector(a.sessions, []string{}))
|
metrics.Register(monitor.NewSessionCollector(a.sessions, []string{}))
|
||||||
|
|
@ -666,7 +721,7 @@ func (a *api) start() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Storage.Disk.Cache.Enable {
|
if cfg.Storage.Disk.Cache.Enable {
|
||||||
diskCache, err := cache.NewLRUCache(cache.LRUConfig{
|
cache, err := cache.NewLRUCache(cache.LRUConfig{
|
||||||
TTL: time.Duration(cfg.Storage.Disk.Cache.TTL) * time.Second,
|
TTL: time.Duration(cfg.Storage.Disk.Cache.TTL) * time.Second,
|
||||||
MaxSize: cfg.Storage.Disk.Cache.Size * 1024 * 1024,
|
MaxSize: cfg.Storage.Disk.Cache.Size * 1024 * 1024,
|
||||||
MaxFileSize: cfg.Storage.Disk.Cache.FileSize * 1024 * 1024,
|
MaxFileSize: cfg.Storage.Disk.Cache.FileSize * 1024 * 1024,
|
||||||
|
|
@ -676,10 +731,10 @@ func (a *api) start() error {
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to create disk cache: %w", err)
|
return fmt.Errorf("unable to create cache: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
a.cache = diskCache
|
a.cache = cache
|
||||||
}
|
}
|
||||||
|
|
||||||
var autocertManager *certmagic.Config
|
var autocertManager *certmagic.Config
|
||||||
|
|
@ -867,22 +922,61 @@ func (a *api) start() error {
|
||||||
|
|
||||||
a.log.logger.main = a.log.logger.core.WithComponent(logcontext).WithField("address", cfg.Address)
|
a.log.logger.main = a.log.logger.core.WithComponent(logcontext).WithField("address", cfg.Address)
|
||||||
|
|
||||||
mainserverhandler, err := http.NewServer(http.Config{
|
httpfilesystems := []httpfs.FS{
|
||||||
|
{
|
||||||
|
Name: a.diskfs.Name(),
|
||||||
|
Mountpoint: "",
|
||||||
|
AllowWrite: false,
|
||||||
|
EnableAuth: false,
|
||||||
|
Username: "",
|
||||||
|
Password: "",
|
||||||
|
DefaultFile: "index.html",
|
||||||
|
DefaultContentType: "text/html",
|
||||||
|
Gzip: true,
|
||||||
|
Filesystem: a.diskfs,
|
||||||
|
Cache: a.cache,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: a.memfs.Name(),
|
||||||
|
Mountpoint: "/memfs",
|
||||||
|
AllowWrite: true,
|
||||||
|
EnableAuth: cfg.Storage.Memory.Auth.Enable,
|
||||||
|
Username: cfg.Storage.Memory.Auth.Username,
|
||||||
|
Password: cfg.Storage.Memory.Auth.Password,
|
||||||
|
DefaultFile: "",
|
||||||
|
DefaultContentType: "application/data",
|
||||||
|
Gzip: true,
|
||||||
|
Filesystem: a.memfs,
|
||||||
|
Cache: nil,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s3 := range cfg.Storage.S3 {
|
||||||
|
httpfilesystems = append(httpfilesystems, httpfs.FS{
|
||||||
|
Name: s3.Name,
|
||||||
|
Mountpoint: s3.Mountpoint,
|
||||||
|
AllowWrite: true,
|
||||||
|
EnableAuth: s3.Auth.Enable,
|
||||||
|
Username: s3.Auth.Username,
|
||||||
|
Password: s3.Auth.Password,
|
||||||
|
DefaultFile: "",
|
||||||
|
DefaultContentType: "application/data",
|
||||||
|
Gzip: true,
|
||||||
|
Filesystem: a.s3fs[s3.Name],
|
||||||
|
Cache: a.cache,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
serverConfig := http.Config{
|
||||||
Logger: a.log.logger.main,
|
Logger: a.log.logger.main,
|
||||||
LogBuffer: a.log.buffer,
|
LogBuffer: a.log.buffer,
|
||||||
Restream: a.restream,
|
Restream: a.restream,
|
||||||
Metrics: a.metrics,
|
Metrics: a.metrics,
|
||||||
Prometheus: a.prom,
|
Prometheus: a.prom,
|
||||||
MimeTypesFile: cfg.Storage.MimeTypes,
|
MimeTypesFile: cfg.Storage.MimeTypes,
|
||||||
DiskFS: a.diskfs,
|
Filesystems: httpfilesystems,
|
||||||
MemFS: http.MemFSConfig{
|
IPLimiter: iplimiter,
|
||||||
EnableAuth: cfg.Storage.Memory.Auth.Enable,
|
Profiling: cfg.Debug.Profiling,
|
||||||
Username: cfg.Storage.Memory.Auth.Username,
|
|
||||||
Password: cfg.Storage.Memory.Auth.Password,
|
|
||||||
Filesystem: a.memfs,
|
|
||||||
},
|
|
||||||
IPLimiter: iplimiter,
|
|
||||||
Profiling: cfg.Debug.Profiling,
|
|
||||||
Cors: http.CorsConfig{
|
Cors: http.CorsConfig{
|
||||||
Origins: cfg.Storage.CORS.Origins,
|
Origins: cfg.Storage.CORS.Origins,
|
||||||
},
|
},
|
||||||
|
|
@ -890,11 +984,12 @@ func (a *api) start() error {
|
||||||
SRT: a.srtserver,
|
SRT: a.srtserver,
|
||||||
JWT: a.httpjwt,
|
JWT: a.httpjwt,
|
||||||
Config: a.config.store,
|
Config: a.config.store,
|
||||||
Cache: a.cache,
|
|
||||||
Sessions: a.sessions,
|
Sessions: a.sessions,
|
||||||
Router: router,
|
Router: router,
|
||||||
ReadOnly: cfg.API.ReadOnly,
|
ReadOnly: cfg.API.ReadOnly,
|
||||||
})
|
}
|
||||||
|
|
||||||
|
mainserverhandler, err := http.NewServer(serverConfig)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to create server: %w", err)
|
return fmt.Errorf("unable to create server: %w", err)
|
||||||
|
|
@ -929,34 +1024,10 @@ func (a *api) start() error {
|
||||||
|
|
||||||
a.log.logger.sidecar = a.log.logger.core.WithComponent("HTTP").WithField("address", cfg.Address)
|
a.log.logger.sidecar = a.log.logger.core.WithComponent("HTTP").WithField("address", cfg.Address)
|
||||||
|
|
||||||
sidecarserverhandler, err := http.NewServer(http.Config{
|
serverConfig.Logger = a.log.logger.sidecar
|
||||||
Logger: a.log.logger.sidecar,
|
serverConfig.IPLimiter = iplimiter
|
||||||
LogBuffer: a.log.buffer,
|
|
||||||
Restream: a.restream,
|
sidecarserverhandler, err := http.NewServer(serverConfig)
|
||||||
Metrics: a.metrics,
|
|
||||||
Prometheus: a.prom,
|
|
||||||
MimeTypesFile: cfg.Storage.MimeTypes,
|
|
||||||
DiskFS: a.diskfs,
|
|
||||||
MemFS: http.MemFSConfig{
|
|
||||||
EnableAuth: cfg.Storage.Memory.Auth.Enable,
|
|
||||||
Username: cfg.Storage.Memory.Auth.Username,
|
|
||||||
Password: cfg.Storage.Memory.Auth.Password,
|
|
||||||
Filesystem: a.memfs,
|
|
||||||
},
|
|
||||||
IPLimiter: iplimiter,
|
|
||||||
Profiling: cfg.Debug.Profiling,
|
|
||||||
Cors: http.CorsConfig{
|
|
||||||
Origins: cfg.Storage.CORS.Origins,
|
|
||||||
},
|
|
||||||
RTMP: a.rtmpserver,
|
|
||||||
SRT: a.srtserver,
|
|
||||||
JWT: a.httpjwt,
|
|
||||||
Config: a.config.store,
|
|
||||||
Cache: a.cache,
|
|
||||||
Sessions: a.sessions,
|
|
||||||
Router: router,
|
|
||||||
ReadOnly: cfg.API.ReadOnly,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("unable to create sidecar HTTP server: %w", err)
|
return fmt.Errorf("unable to create sidecar HTTP server: %w", err)
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,12 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
haikunator "github.com/atrox/haikunatorgo/v2"
|
|
||||||
"github.com/datarhei/core/v16/config/copy"
|
"github.com/datarhei/core/v16/config/copy"
|
||||||
"github.com/datarhei/core/v16/config/value"
|
"github.com/datarhei/core/v16/config/value"
|
||||||
"github.com/datarhei/core/v16/config/vars"
|
"github.com/datarhei/core/v16/config/vars"
|
||||||
"github.com/datarhei/core/v16/math/rand"
|
"github.com/datarhei/core/v16/math/rand"
|
||||||
|
|
||||||
|
haikunator "github.com/atrox/haikunatorgo/v2"
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -111,6 +112,7 @@ func (d *Config) Clone() *Config {
|
||||||
data.Storage.CORS.Origins = copy.Slice(d.Storage.CORS.Origins)
|
data.Storage.CORS.Origins = copy.Slice(d.Storage.CORS.Origins)
|
||||||
data.Storage.Disk.Cache.Types.Allow = copy.Slice(d.Storage.Disk.Cache.Types.Allow)
|
data.Storage.Disk.Cache.Types.Allow = copy.Slice(d.Storage.Disk.Cache.Types.Allow)
|
||||||
data.Storage.Disk.Cache.Types.Block = copy.Slice(d.Storage.Disk.Cache.Types.Block)
|
data.Storage.Disk.Cache.Types.Block = copy.Slice(d.Storage.Disk.Cache.Types.Block)
|
||||||
|
data.Storage.S3 = copy.Slice(d.Storage.S3)
|
||||||
|
|
||||||
data.FFmpeg.Access.Input.Allow = copy.Slice(d.FFmpeg.Access.Input.Allow)
|
data.FFmpeg.Access.Input.Allow = copy.Slice(d.FFmpeg.Access.Input.Allow)
|
||||||
data.FFmpeg.Access.Input.Block = copy.Slice(d.FFmpeg.Access.Input.Block)
|
data.FFmpeg.Access.Input.Block = copy.Slice(d.FFmpeg.Access.Input.Block)
|
||||||
|
|
@ -195,6 +197,9 @@ func (d *Config) init() {
|
||||||
d.vars.Register(value.NewInt64(&d.Storage.Memory.Size, 0), "storage.memory.max_size_mbytes", "CORE_STORAGE_MEMORY_MAXSIZEMBYTES", nil, "Max. allowed megabytes for /memfs, 0 for unlimited", false, false)
|
d.vars.Register(value.NewInt64(&d.Storage.Memory.Size, 0), "storage.memory.max_size_mbytes", "CORE_STORAGE_MEMORY_MAXSIZEMBYTES", nil, "Max. allowed megabytes for /memfs, 0 for unlimited", false, false)
|
||||||
d.vars.Register(value.NewBool(&d.Storage.Memory.Purge, false), "storage.memory.purge", "CORE_STORAGE_MEMORY_PURGE", nil, "Automatically remove the oldest files if /memfs is full", false, false)
|
d.vars.Register(value.NewBool(&d.Storage.Memory.Purge, false), "storage.memory.purge", "CORE_STORAGE_MEMORY_PURGE", nil, "Automatically remove the oldest files if /memfs is full", false, false)
|
||||||
|
|
||||||
|
// Storage (S3)
|
||||||
|
d.vars.Register(value.NewS3StorageListValue(&d.Storage.S3, []value.S3Storage{}, "|"), "storage.s3", "CORE_STORAGE_S3", nil, "List of S3 storage URLS", false, false)
|
||||||
|
|
||||||
// Storage (CORS)
|
// Storage (CORS)
|
||||||
d.vars.Register(value.NewCORSOrigins(&d.Storage.CORS.Origins, []string{"*"}, ","), "storage.cors.origins", "CORE_STORAGE_CORS_ORIGINS", nil, "Allowed CORS origins for /memfs and /data", false, false)
|
d.vars.Register(value.NewCORSOrigins(&d.Storage.CORS.Origins, []string{"*"}, ","), "storage.cors.origins", "CORE_STORAGE_CORS_ORIGINS", nil, "Allowed CORS origins for /memfs and /data", false, false)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,7 @@ type Data struct {
|
||||||
Size int64 `json:"max_size_mbytes" format:"int64"`
|
Size int64 `json:"max_size_mbytes" format:"int64"`
|
||||||
Purge bool `json:"purge"`
|
Purge bool `json:"purge"`
|
||||||
} `json:"memory"`
|
} `json:"memory"`
|
||||||
|
S3 []value.S3Storage `json:"s3"`
|
||||||
CORS struct {
|
CORS struct {
|
||||||
Origins []string `json:"origins"`
|
Origins []string `json:"origins"`
|
||||||
} `json:"cors"`
|
} `json:"cors"`
|
||||||
|
|
@ -246,6 +247,8 @@ func MergeV2toV3(data *Data, d *v2.Data) (*Data, error) {
|
||||||
data.Storage.Disk.Cache.TTL = d.Storage.Disk.Cache.TTL
|
data.Storage.Disk.Cache.TTL = d.Storage.Disk.Cache.TTL
|
||||||
data.Storage.Disk.Cache.Types.Allow = copy.Slice(d.Storage.Disk.Cache.Types)
|
data.Storage.Disk.Cache.Types.Allow = copy.Slice(d.Storage.Disk.Cache.Types)
|
||||||
|
|
||||||
|
data.Storage.S3 = []value.S3Storage{}
|
||||||
|
|
||||||
data.Version = 3
|
data.Version = 3
|
||||||
|
|
||||||
return data, nil
|
return data, nil
|
||||||
|
|
|
||||||
179
config/value/s3.go
Normal file
179
config/value/s3.go
Normal file
|
|
@ -0,0 +1,179 @@
|
||||||
|
package value
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/net/publicsuffix"
|
||||||
|
)
|
||||||
|
|
||||||
|
// array of s3 storages
|
||||||
|
// https://access_key_id:secret_access_id@region.endpoint/bucket?name=aaa&mount=/abc&username=xxx&password=yyy
|
||||||
|
|
||||||
|
type S3Storage struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Mountpoint string `json:"mountpoint"`
|
||||||
|
Auth struct {
|
||||||
|
Enable bool `json:"enable"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
Password string `json:"password"`
|
||||||
|
} `json:"auth"`
|
||||||
|
Endpoint string `json:"endpoint"`
|
||||||
|
AccessKeyID string `json:"access_key_id"`
|
||||||
|
SecretAccessKey string `json:"secret_access_key"`
|
||||||
|
Bucket string `json:"bucket"`
|
||||||
|
Region string `json:"region"`
|
||||||
|
UseSSL bool `json:"use_ssl"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *S3Storage) String() string {
|
||||||
|
u := url.URL{}
|
||||||
|
|
||||||
|
if t.UseSSL {
|
||||||
|
u.Scheme = "https"
|
||||||
|
} else {
|
||||||
|
u.Scheme = "http"
|
||||||
|
}
|
||||||
|
|
||||||
|
u.User = url.UserPassword(t.AccessKeyID, "---")
|
||||||
|
|
||||||
|
u.Host = t.Endpoint
|
||||||
|
|
||||||
|
if len(t.Region) != 0 {
|
||||||
|
u.Host = t.Region + "." + u.Host
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(t.Bucket) != 0 {
|
||||||
|
u.Path = "/" + t.Bucket
|
||||||
|
}
|
||||||
|
|
||||||
|
v := url.Values{}
|
||||||
|
v.Set("name", t.Name)
|
||||||
|
v.Set("mountpoint", t.Mountpoint)
|
||||||
|
|
||||||
|
if t.Auth.Enable {
|
||||||
|
if len(t.Auth.Username) != 0 {
|
||||||
|
v.Set("username", t.Auth.Username)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(t.Auth.Password) != 0 {
|
||||||
|
v.Set("password", "---")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u.RawQuery = v.Encode()
|
||||||
|
|
||||||
|
return u.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
type s3StorageListValue struct {
|
||||||
|
p *[]S3Storage
|
||||||
|
separator string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewS3StorageListValue(p *[]S3Storage, val []S3Storage, separator string) *s3StorageListValue {
|
||||||
|
v := &s3StorageListValue{
|
||||||
|
p: p,
|
||||||
|
separator: separator,
|
||||||
|
}
|
||||||
|
|
||||||
|
*p = val
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *s3StorageListValue) Set(val string) error {
|
||||||
|
list := []S3Storage{}
|
||||||
|
|
||||||
|
for _, elm := range strings.Split(val, s.separator) {
|
||||||
|
u, err := url.Parse(elm)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid S3 storage URL (%s): %w", elm, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t := S3Storage{
|
||||||
|
Name: u.Query().Get("name"),
|
||||||
|
Mountpoint: u.Query().Get("mountpoint"),
|
||||||
|
AccessKeyID: u.User.Username(),
|
||||||
|
}
|
||||||
|
|
||||||
|
hostname := u.Hostname()
|
||||||
|
port := u.Port()
|
||||||
|
|
||||||
|
domain, err := publicsuffix.EffectiveTLDPlusOne(hostname)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("invalid eTLD (%s): %w", hostname, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Endpoint = domain
|
||||||
|
if len(port) != 0 {
|
||||||
|
t.Endpoint += ":" + port
|
||||||
|
}
|
||||||
|
|
||||||
|
region := strings.TrimSuffix(hostname, domain)
|
||||||
|
if len(region) != 0 {
|
||||||
|
t.Region = strings.TrimSuffix(region, ".")
|
||||||
|
}
|
||||||
|
|
||||||
|
secret, ok := u.User.Password()
|
||||||
|
if ok {
|
||||||
|
t.SecretAccessKey = secret
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Bucket = strings.TrimPrefix(u.Path, "/")
|
||||||
|
|
||||||
|
if u.Scheme == "https" {
|
||||||
|
t.UseSSL = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if u.Query().Has("username") || u.Query().Has("password") {
|
||||||
|
t.Auth.Enable = true
|
||||||
|
t.Auth.Username = u.Query().Get("username")
|
||||||
|
t.Auth.Username = u.Query().Get("password")
|
||||||
|
}
|
||||||
|
|
||||||
|
list = append(list, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
*s.p = list
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *s3StorageListValue) String() string {
|
||||||
|
if s.IsEmpty() {
|
||||||
|
return "(empty)"
|
||||||
|
}
|
||||||
|
|
||||||
|
list := []string{}
|
||||||
|
|
||||||
|
for _, t := range *s.p {
|
||||||
|
list = append(list, t.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(list, s.separator)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *s3StorageListValue) Validate() error {
|
||||||
|
for i, t := range *s.p {
|
||||||
|
if len(t.Name) == 0 {
|
||||||
|
return fmt.Errorf("the name for s3 storage %d is missing", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(t.Mountpoint) == 0 {
|
||||||
|
return fmt.Errorf("the mountpoint for s3 storage %d is missing", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
if t.Auth.Enable {
|
||||||
|
if len(t.Auth.Username) == 0 && len(t.Auth.Password) == 0 {
|
||||||
|
return fmt.Errorf("auth is enabled, but no username and password are set for s3 storage %d", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *s3StorageListValue) IsEmpty() bool {
|
||||||
|
return len(*s.p) == 0
|
||||||
|
}
|
||||||
484
docs/docs.go
484
docs/docs.go
|
|
@ -311,6 +311,32 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v3/fs": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Listall registered filesystems",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"summary": "List all registered filesystems",
|
||||||
|
"operationId": "filesystem-3-list",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/api.FilesystemInfo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v3/fs/disk": {
|
"/api/v3/fs/disk": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
|
|
@ -757,6 +783,219 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v3/fs/{name}": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "List all files on a filesystem. The listing can be ordered by name, size, or date of last modification in ascending or descending order.",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"summary": "List all files on a filesystem",
|
||||||
|
"operationId": "filesystem-3-list-files",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Name of the filesystem",
|
||||||
|
"name": "name",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "glob pattern for file names",
|
||||||
|
"name": "glob",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "none, name, size, lastmod",
|
||||||
|
"name": "sort",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "asc, desc",
|
||||||
|
"name": "order",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/api.FileInfo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v3/fs/{name}/{path}": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Fetch a file from a filesystem",
|
||||||
|
"produces": [
|
||||||
|
"application/data",
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"summary": "Fetch a file from a filesystem",
|
||||||
|
"operationId": "filesystem-3-get-file",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Name of the filesystem",
|
||||||
|
"name": "name",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path to file",
|
||||||
|
"name": "path",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "file"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"301": {
|
||||||
|
"description": "Moved Permanently",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Writes or overwrites a file on a filesystem",
|
||||||
|
"consumes": [
|
||||||
|
"application/data"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"text/plain",
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"summary": "Add a file to a filesystem",
|
||||||
|
"operationId": "filesystem-3-put-file",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Name of the filesystem",
|
||||||
|
"name": "name",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path to file",
|
||||||
|
"name": "path",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "File data",
|
||||||
|
"name": "data",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "Created",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"204": {
|
||||||
|
"description": "No Content",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"507": {
|
||||||
|
"description": "Insufficient Storage",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Remove a file from a filesystem",
|
||||||
|
"produces": [
|
||||||
|
"text/plain"
|
||||||
|
],
|
||||||
|
"summary": "Remove a file from a filesystem",
|
||||||
|
"operationId": "filesystem-3-delete-file",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Name of the filesystem",
|
||||||
|
"name": "name",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path to file",
|
||||||
|
"name": "path",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v3/log": {
|
"/api/v3/log": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
|
|
@ -2132,140 +2371,6 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/memfs/{path}": {
|
|
||||||
"get": {
|
|
||||||
"description": "Fetch a file from the memory filesystem",
|
|
||||||
"produces": [
|
|
||||||
"application/data",
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"summary": "Fetch a file from the memory filesystem",
|
|
||||||
"operationId": "memfs-get-file",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Path to file",
|
|
||||||
"name": "path",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "file"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"301": {
|
|
||||||
"description": "Moved Permanently",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"404": {
|
|
||||||
"description": "Not Found",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/api.Error"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"put": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"BasicAuth": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Writes or overwrites a file on the memory filesystem",
|
|
||||||
"consumes": [
|
|
||||||
"application/data"
|
|
||||||
],
|
|
||||||
"produces": [
|
|
||||||
"text/plain",
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"summary": "Add a file to the memory filesystem",
|
|
||||||
"operationId": "memfs-put-file",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Path to file",
|
|
||||||
"name": "path",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "File data",
|
|
||||||
"name": "data",
|
|
||||||
"in": "body",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "integer"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"201": {
|
|
||||||
"description": "Created",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"204": {
|
|
||||||
"description": "No Content",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"507": {
|
|
||||||
"description": "Insufficient Storage",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/api.Error"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"delete": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"BasicAuth": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Remove a file from the memory filesystem",
|
|
||||||
"produces": [
|
|
||||||
"text/plain"
|
|
||||||
],
|
|
||||||
"summary": "Remove a file from the memory filesystem",
|
|
||||||
"operationId": "memfs-delete-file",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Path to file",
|
|
||||||
"name": "path",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"404": {
|
|
||||||
"description": "Not Found",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/api.Error"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/metrics": {
|
"/metrics": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Prometheus metrics",
|
"description": "Prometheus metrics",
|
||||||
|
|
@ -2325,46 +2430,6 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"/{path}": {
|
|
||||||
"get": {
|
|
||||||
"description": "Fetch a file from the filesystem. If the file is a directory, a index.html is returned, if it exists.",
|
|
||||||
"produces": [
|
|
||||||
"application/data",
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"summary": "Fetch a file from the filesystem",
|
|
||||||
"operationId": "diskfs-get-file",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Path to file",
|
|
||||||
"name": "path",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "file"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"301": {
|
|
||||||
"description": "Moved Permanently",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"404": {
|
|
||||||
"description": "Not Found",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/api.Error"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"definitions": {
|
"definitions": {
|
||||||
|
|
@ -2569,6 +2634,7 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"created_at": {
|
"created_at": {
|
||||||
|
"description": "When this config has been persisted",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"db": {
|
"db": {
|
||||||
|
|
@ -2953,6 +3019,12 @@ const docTemplate = `{
|
||||||
},
|
},
|
||||||
"mimetypes_file": {
|
"mimetypes_file": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"s3": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/value.S3Storage"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -3031,6 +3103,20 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"api.FilesystemInfo": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"mount": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"api.GraphQuery": {
|
"api.GraphQuery": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
@ -4330,6 +4416,7 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"created_at": {
|
"created_at": {
|
||||||
|
"description": "When this config has been persisted",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"db": {
|
"db": {
|
||||||
|
|
@ -4714,6 +4801,12 @@ const docTemplate = `{
|
||||||
},
|
},
|
||||||
"mimetypes_file": {
|
"mimetypes_file": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"s3": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/value.S3Storage"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -5056,6 +5149,49 @@ const docTemplate = `{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"value.S3Storage": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"access_key_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"enable": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bucket": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"endpoint": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"mountpoint": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"region": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"secret_access_key": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"use_ssl": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"securityDefinitions": {
|
"securityDefinitions": {
|
||||||
|
|
|
||||||
|
|
@ -303,6 +303,32 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v3/fs": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Listall registered filesystems",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"summary": "List all registered filesystems",
|
||||||
|
"operationId": "filesystem-3-list",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/api.FilesystemInfo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v3/fs/disk": {
|
"/api/v3/fs/disk": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
|
|
@ -749,6 +775,219 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/api/v3/fs/{name}": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "List all files on a filesystem. The listing can be ordered by name, size, or date of last modification in ascending or descending order.",
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"summary": "List all files on a filesystem",
|
||||||
|
"operationId": "filesystem-3-list-files",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Name of the filesystem",
|
||||||
|
"name": "name",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "glob pattern for file names",
|
||||||
|
"name": "glob",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "none, name, size, lastmod",
|
||||||
|
"name": "sort",
|
||||||
|
"in": "query"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "asc, desc",
|
||||||
|
"name": "order",
|
||||||
|
"in": "query"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/api.FileInfo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v3/fs/{name}/{path}": {
|
||||||
|
"get": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Fetch a file from a filesystem",
|
||||||
|
"produces": [
|
||||||
|
"application/data",
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"summary": "Fetch a file from a filesystem",
|
||||||
|
"operationId": "filesystem-3-get-file",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Name of the filesystem",
|
||||||
|
"name": "name",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path to file",
|
||||||
|
"name": "path",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "file"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"301": {
|
||||||
|
"description": "Moved Permanently",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"put": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Writes or overwrites a file on a filesystem",
|
||||||
|
"consumes": [
|
||||||
|
"application/data"
|
||||||
|
],
|
||||||
|
"produces": [
|
||||||
|
"text/plain",
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
|
"summary": "Add a file to a filesystem",
|
||||||
|
"operationId": "filesystem-3-put-file",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Name of the filesystem",
|
||||||
|
"name": "name",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path to file",
|
||||||
|
"name": "path",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "File data",
|
||||||
|
"name": "data",
|
||||||
|
"in": "body",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"201": {
|
||||||
|
"description": "Created",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"204": {
|
||||||
|
"description": "No Content",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"507": {
|
||||||
|
"description": "Insufficient Storage",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"delete": {
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"ApiKeyAuth": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "Remove a file from a filesystem",
|
||||||
|
"produces": [
|
||||||
|
"text/plain"
|
||||||
|
],
|
||||||
|
"summary": "Remove a file from a filesystem",
|
||||||
|
"operationId": "filesystem-3-delete-file",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Name of the filesystem",
|
||||||
|
"name": "name",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Path to file",
|
||||||
|
"name": "path",
|
||||||
|
"in": "path",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "Not Found",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/api.Error"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/api/v3/log": {
|
"/api/v3/log": {
|
||||||
"get": {
|
"get": {
|
||||||
"security": [
|
"security": [
|
||||||
|
|
@ -2124,140 +2363,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/memfs/{path}": {
|
|
||||||
"get": {
|
|
||||||
"description": "Fetch a file from the memory filesystem",
|
|
||||||
"produces": [
|
|
||||||
"application/data",
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"summary": "Fetch a file from the memory filesystem",
|
|
||||||
"operationId": "memfs-get-file",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Path to file",
|
|
||||||
"name": "path",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "file"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"301": {
|
|
||||||
"description": "Moved Permanently",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"404": {
|
|
||||||
"description": "Not Found",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/api.Error"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"put": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"BasicAuth": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Writes or overwrites a file on the memory filesystem",
|
|
||||||
"consumes": [
|
|
||||||
"application/data"
|
|
||||||
],
|
|
||||||
"produces": [
|
|
||||||
"text/plain",
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"summary": "Add a file to the memory filesystem",
|
|
||||||
"operationId": "memfs-put-file",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Path to file",
|
|
||||||
"name": "path",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": "File data",
|
|
||||||
"name": "data",
|
|
||||||
"in": "body",
|
|
||||||
"required": true,
|
|
||||||
"schema": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"type": "integer"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"201": {
|
|
||||||
"description": "Created",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"204": {
|
|
||||||
"description": "No Content",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"507": {
|
|
||||||
"description": "Insufficient Storage",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/api.Error"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"delete": {
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"BasicAuth": []
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"description": "Remove a file from the memory filesystem",
|
|
||||||
"produces": [
|
|
||||||
"text/plain"
|
|
||||||
],
|
|
||||||
"summary": "Remove a file from the memory filesystem",
|
|
||||||
"operationId": "memfs-delete-file",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Path to file",
|
|
||||||
"name": "path",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"404": {
|
|
||||||
"description": "Not Found",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/api.Error"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"/metrics": {
|
"/metrics": {
|
||||||
"get": {
|
"get": {
|
||||||
"description": "Prometheus metrics",
|
"description": "Prometheus metrics",
|
||||||
|
|
@ -2317,46 +2422,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"/{path}": {
|
|
||||||
"get": {
|
|
||||||
"description": "Fetch a file from the filesystem. If the file is a directory, a index.html is returned, if it exists.",
|
|
||||||
"produces": [
|
|
||||||
"application/data",
|
|
||||||
"application/json"
|
|
||||||
],
|
|
||||||
"summary": "Fetch a file from the filesystem",
|
|
||||||
"operationId": "diskfs-get-file",
|
|
||||||
"parameters": [
|
|
||||||
{
|
|
||||||
"type": "string",
|
|
||||||
"description": "Path to file",
|
|
||||||
"name": "path",
|
|
||||||
"in": "path",
|
|
||||||
"required": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"responses": {
|
|
||||||
"200": {
|
|
||||||
"description": "OK",
|
|
||||||
"schema": {
|
|
||||||
"type": "file"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"301": {
|
|
||||||
"description": "Moved Permanently",
|
|
||||||
"schema": {
|
|
||||||
"type": "string"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"404": {
|
|
||||||
"description": "Not Found",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/api.Error"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"definitions": {
|
"definitions": {
|
||||||
|
|
@ -2561,6 +2626,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"created_at": {
|
"created_at": {
|
||||||
|
"description": "When this config has been persisted",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"db": {
|
"db": {
|
||||||
|
|
@ -2945,6 +3011,12 @@
|
||||||
},
|
},
|
||||||
"mimetypes_file": {
|
"mimetypes_file": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"s3": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/value.S3Storage"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -3023,6 +3095,20 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"api.FilesystemInfo": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"mount": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"api.GraphQuery": {
|
"api.GraphQuery": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|
@ -4322,6 +4408,7 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"created_at": {
|
"created_at": {
|
||||||
|
"description": "When this config has been persisted",
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"db": {
|
"db": {
|
||||||
|
|
@ -4706,6 +4793,12 @@
|
||||||
},
|
},
|
||||||
"mimetypes_file": {
|
"mimetypes_file": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"s3": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/definitions/value.S3Storage"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -5048,6 +5141,49 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"value.S3Storage": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"access_key_id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"enable": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"username": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"bucket": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"endpoint": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"mountpoint": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"region": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"secret_access_key": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"use_ssl": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"securityDefinitions": {
|
"securityDefinitions": {
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,7 @@ definitions:
|
||||||
type: boolean
|
type: boolean
|
||||||
type: object
|
type: object
|
||||||
created_at:
|
created_at:
|
||||||
|
description: When this config has been persisted
|
||||||
type: string
|
type: string
|
||||||
db:
|
db:
|
||||||
properties:
|
properties:
|
||||||
|
|
@ -391,6 +392,10 @@ definitions:
|
||||||
type: object
|
type: object
|
||||||
mimetypes_file:
|
mimetypes_file:
|
||||||
type: string
|
type: string
|
||||||
|
s3:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/value.S3Storage'
|
||||||
|
type: array
|
||||||
type: object
|
type: object
|
||||||
tls:
|
tls:
|
||||||
properties:
|
properties:
|
||||||
|
|
@ -442,6 +447,15 @@ definitions:
|
||||||
format: int64
|
format: int64
|
||||||
type: integer
|
type: integer
|
||||||
type: object
|
type: object
|
||||||
|
api.FilesystemInfo:
|
||||||
|
properties:
|
||||||
|
mount:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
type:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
api.GraphQuery:
|
api.GraphQuery:
|
||||||
properties:
|
properties:
|
||||||
query:
|
query:
|
||||||
|
|
@ -1388,6 +1402,7 @@ definitions:
|
||||||
type: boolean
|
type: boolean
|
||||||
type: object
|
type: object
|
||||||
created_at:
|
created_at:
|
||||||
|
description: When this config has been persisted
|
||||||
type: string
|
type: string
|
||||||
db:
|
db:
|
||||||
properties:
|
properties:
|
||||||
|
|
@ -1645,6 +1660,10 @@ definitions:
|
||||||
type: object
|
type: object
|
||||||
mimetypes_file:
|
mimetypes_file:
|
||||||
type: string
|
type: string
|
||||||
|
s3:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/value.S3Storage'
|
||||||
|
type: array
|
||||||
type: object
|
type: object
|
||||||
tls:
|
tls:
|
||||||
properties:
|
properties:
|
||||||
|
|
@ -1867,6 +1886,34 @@ definitions:
|
||||||
type: string
|
type: string
|
||||||
type: array
|
type: array
|
||||||
type: object
|
type: object
|
||||||
|
value.S3Storage:
|
||||||
|
properties:
|
||||||
|
access_key_id:
|
||||||
|
type: string
|
||||||
|
auth:
|
||||||
|
properties:
|
||||||
|
enable:
|
||||||
|
type: boolean
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
username:
|
||||||
|
type: string
|
||||||
|
type: object
|
||||||
|
bucket:
|
||||||
|
type: string
|
||||||
|
endpoint:
|
||||||
|
type: string
|
||||||
|
mountpoint:
|
||||||
|
type: string
|
||||||
|
name:
|
||||||
|
type: string
|
||||||
|
region:
|
||||||
|
type: string
|
||||||
|
secret_access_key:
|
||||||
|
type: string
|
||||||
|
use_ssl:
|
||||||
|
type: boolean
|
||||||
|
type: object
|
||||||
info:
|
info:
|
||||||
contact:
|
contact:
|
||||||
email: hello@datarhei.com
|
email: hello@datarhei.com
|
||||||
|
|
@ -1879,34 +1926,6 @@ info:
|
||||||
title: datarhei Core API
|
title: datarhei Core API
|
||||||
version: "3.0"
|
version: "3.0"
|
||||||
paths:
|
paths:
|
||||||
/{path}:
|
|
||||||
get:
|
|
||||||
description: Fetch a file from the filesystem. If the file is a directory, a
|
|
||||||
index.html is returned, if it exists.
|
|
||||||
operationId: diskfs-get-file
|
|
||||||
parameters:
|
|
||||||
- description: Path to file
|
|
||||||
in: path
|
|
||||||
name: path
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
produces:
|
|
||||||
- application/data
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
type: file
|
|
||||||
"301":
|
|
||||||
description: Moved Permanently
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
"404":
|
|
||||||
description: Not Found
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/api.Error'
|
|
||||||
summary: Fetch a file from the filesystem
|
|
||||||
/api:
|
/api:
|
||||||
get:
|
get:
|
||||||
description: API version and build infos in case auth is valid or not required.
|
description: API version and build infos in case auth is valid or not required.
|
||||||
|
|
@ -2091,6 +2110,162 @@ paths:
|
||||||
summary: Reload the currently active configuration
|
summary: Reload the currently active configuration
|
||||||
tags:
|
tags:
|
||||||
- v16.7.2
|
- v16.7.2
|
||||||
|
/api/v3/fs:
|
||||||
|
get:
|
||||||
|
description: Listall registered filesystems
|
||||||
|
operationId: filesystem-3-list
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/api.FilesystemInfo'
|
||||||
|
type: array
|
||||||
|
security:
|
||||||
|
- ApiKeyAuth: []
|
||||||
|
summary: List all registered filesystems
|
||||||
|
/api/v3/fs/{name}:
|
||||||
|
get:
|
||||||
|
description: List all files on a filesystem. The listing can be ordered by name,
|
||||||
|
size, or date of last modification in ascending or descending order.
|
||||||
|
operationId: filesystem-3-list-files
|
||||||
|
parameters:
|
||||||
|
- description: Name of the filesystem
|
||||||
|
in: path
|
||||||
|
name: name
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- description: glob pattern for file names
|
||||||
|
in: query
|
||||||
|
name: glob
|
||||||
|
type: string
|
||||||
|
- description: none, name, size, lastmod
|
||||||
|
in: query
|
||||||
|
name: sort
|
||||||
|
type: string
|
||||||
|
- description: asc, desc
|
||||||
|
in: query
|
||||||
|
name: order
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
items:
|
||||||
|
$ref: '#/definitions/api.FileInfo'
|
||||||
|
type: array
|
||||||
|
security:
|
||||||
|
- ApiKeyAuth: []
|
||||||
|
summary: List all files on a filesystem
|
||||||
|
/api/v3/fs/{name}/{path}:
|
||||||
|
delete:
|
||||||
|
description: Remove a file from a filesystem
|
||||||
|
operationId: filesystem-3-delete-file
|
||||||
|
parameters:
|
||||||
|
- description: Name of the filesystem
|
||||||
|
in: path
|
||||||
|
name: name
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- description: Path to file
|
||||||
|
in: path
|
||||||
|
name: path
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- text/plain
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"404":
|
||||||
|
description: Not Found
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/api.Error'
|
||||||
|
security:
|
||||||
|
- ApiKeyAuth: []
|
||||||
|
summary: Remove a file from a filesystem
|
||||||
|
get:
|
||||||
|
description: Fetch a file from a filesystem
|
||||||
|
operationId: filesystem-3-get-file
|
||||||
|
parameters:
|
||||||
|
- description: Name of the filesystem
|
||||||
|
in: path
|
||||||
|
name: name
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- description: Path to file
|
||||||
|
in: path
|
||||||
|
name: path
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
produces:
|
||||||
|
- application/data
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
type: file
|
||||||
|
"301":
|
||||||
|
description: Moved Permanently
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"404":
|
||||||
|
description: Not Found
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/api.Error'
|
||||||
|
security:
|
||||||
|
- ApiKeyAuth: []
|
||||||
|
summary: Fetch a file from a filesystem
|
||||||
|
put:
|
||||||
|
consumes:
|
||||||
|
- application/data
|
||||||
|
description: Writes or overwrites a file on a filesystem
|
||||||
|
operationId: filesystem-3-put-file
|
||||||
|
parameters:
|
||||||
|
- description: Name of the filesystem
|
||||||
|
in: path
|
||||||
|
name: name
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- description: Path to file
|
||||||
|
in: path
|
||||||
|
name: path
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
- description: File data
|
||||||
|
in: body
|
||||||
|
name: data
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
items:
|
||||||
|
type: integer
|
||||||
|
type: array
|
||||||
|
produces:
|
||||||
|
- text/plain
|
||||||
|
- application/json
|
||||||
|
responses:
|
||||||
|
"201":
|
||||||
|
description: Created
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"204":
|
||||||
|
description: No Content
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
"507":
|
||||||
|
description: Insufficient Storage
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/api.Error'
|
||||||
|
security:
|
||||||
|
- ApiKeyAuth: []
|
||||||
|
summary: Add a file to a filesystem
|
||||||
/api/v3/fs/disk:
|
/api/v3/fs/disk:
|
||||||
get:
|
get:
|
||||||
description: List all files on the filesystem. The listing can be ordered by
|
description: List all files on the filesystem. The listing can be ordered by
|
||||||
|
|
@ -3292,94 +3467,6 @@ paths:
|
||||||
summary: Fetch minimal statistics about a process
|
summary: Fetch minimal statistics about a process
|
||||||
tags:
|
tags:
|
||||||
- v16.7.2
|
- v16.7.2
|
||||||
/memfs/{path}:
|
|
||||||
delete:
|
|
||||||
description: Remove a file from the memory filesystem
|
|
||||||
operationId: memfs-delete-file
|
|
||||||
parameters:
|
|
||||||
- description: Path to file
|
|
||||||
in: path
|
|
||||||
name: path
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
produces:
|
|
||||||
- text/plain
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
"404":
|
|
||||||
description: Not Found
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/api.Error'
|
|
||||||
security:
|
|
||||||
- BasicAuth: []
|
|
||||||
summary: Remove a file from the memory filesystem
|
|
||||||
get:
|
|
||||||
description: Fetch a file from the memory filesystem
|
|
||||||
operationId: memfs-get-file
|
|
||||||
parameters:
|
|
||||||
- description: Path to file
|
|
||||||
in: path
|
|
||||||
name: path
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
produces:
|
|
||||||
- application/data
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"200":
|
|
||||||
description: OK
|
|
||||||
schema:
|
|
||||||
type: file
|
|
||||||
"301":
|
|
||||||
description: Moved Permanently
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
"404":
|
|
||||||
description: Not Found
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/api.Error'
|
|
||||||
summary: Fetch a file from the memory filesystem
|
|
||||||
put:
|
|
||||||
consumes:
|
|
||||||
- application/data
|
|
||||||
description: Writes or overwrites a file on the memory filesystem
|
|
||||||
operationId: memfs-put-file
|
|
||||||
parameters:
|
|
||||||
- description: Path to file
|
|
||||||
in: path
|
|
||||||
name: path
|
|
||||||
required: true
|
|
||||||
type: string
|
|
||||||
- description: File data
|
|
||||||
in: body
|
|
||||||
name: data
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
items:
|
|
||||||
type: integer
|
|
||||||
type: array
|
|
||||||
produces:
|
|
||||||
- text/plain
|
|
||||||
- application/json
|
|
||||||
responses:
|
|
||||||
"201":
|
|
||||||
description: Created
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
"204":
|
|
||||||
description: No Content
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
"507":
|
|
||||||
description: Insufficient Storage
|
|
||||||
schema:
|
|
||||||
$ref: '#/definitions/api.Error'
|
|
||||||
security:
|
|
||||||
- BasicAuth: []
|
|
||||||
summary: Add a file to the memory filesystem
|
|
||||||
/metrics:
|
/metrics:
|
||||||
get:
|
get:
|
||||||
description: Prometheus metrics
|
description: Prometheus metrics
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,9 @@ import (
|
||||||
"github.com/gobwas/glob"
|
"github.com/gobwas/glob"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Match returns whether the name matches the glob pattern, also considering
|
||||||
|
// one or several optionnal separator. An error is only returned if the pattern
|
||||||
|
// is invalid.
|
||||||
func Match(pattern, name string, separators ...rune) (bool, error) {
|
func Match(pattern, name string, separators ...rune) (bool, error) {
|
||||||
g, err := glob.Compile(pattern, separators...)
|
g, err := glob.Compile(pattern, separators...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
13
go.mod
13
go.mod
|
|
@ -18,6 +18,7 @@ require (
|
||||||
github.com/labstack/echo/v4 v4.9.1
|
github.com/labstack/echo/v4 v4.9.1
|
||||||
github.com/lithammer/shortuuid/v4 v4.0.0
|
github.com/lithammer/shortuuid/v4 v4.0.0
|
||||||
github.com/mattn/go-isatty v0.0.16
|
github.com/mattn/go-isatty v0.0.16
|
||||||
|
github.com/minio/minio-go/v7 v7.0.39
|
||||||
github.com/prep/average v0.0.0-20200506183628-d26c465f48c3
|
github.com/prep/average v0.0.0-20200506183628-d26c465f48c3
|
||||||
github.com/prometheus/client_golang v1.13.1
|
github.com/prometheus/client_golang v1.13.1
|
||||||
github.com/shirou/gopsutil/v3 v3.22.10
|
github.com/shirou/gopsutil/v3 v3.22.10
|
||||||
|
|
@ -28,6 +29,7 @@ require (
|
||||||
github.com/xeipuuv/gojsonschema v1.2.0
|
github.com/xeipuuv/gojsonschema v1.2.0
|
||||||
go.uber.org/zap v1.23.0
|
go.uber.org/zap v1.23.0
|
||||||
golang.org/x/mod v0.6.0
|
golang.org/x/mod v0.6.0
|
||||||
|
golang.org/x/net v0.1.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|
@ -38,6 +40,7 @@ require (
|
||||||
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
github.com/cespare/xxhash/v2 v2.1.2 // indirect
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/dustin/go-humanize v1.0.0 // indirect
|
||||||
github.com/go-ole/go-ole v1.2.6 // indirect
|
github.com/go-ole/go-ole v1.2.6 // indirect
|
||||||
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
github.com/go-openapi/jsonpointer v0.19.5 // indirect
|
||||||
github.com/go-openapi/jsonreference v0.20.0 // indirect
|
github.com/go-openapi/jsonreference v0.20.0 // indirect
|
||||||
|
|
@ -51,6 +54,8 @@ require (
|
||||||
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
github.com/hashicorp/golang-lru v0.5.4 // indirect
|
||||||
github.com/iancoleman/orderedmap v0.2.0 // indirect
|
github.com/iancoleman/orderedmap v0.2.0 // indirect
|
||||||
github.com/josharian/intern v1.0.0 // indirect
|
github.com/josharian/intern v1.0.0 // indirect
|
||||||
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
|
github.com/klauspost/compress v1.15.9 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.1.2 // indirect
|
github.com/klauspost/cpuid/v2 v2.1.2 // indirect
|
||||||
github.com/labstack/gommon v0.4.0 // indirect
|
github.com/labstack/gommon v0.4.0 // indirect
|
||||||
github.com/leodido/go-urn v1.2.1 // indirect
|
github.com/leodido/go-urn v1.2.1 // indirect
|
||||||
|
|
@ -61,13 +66,19 @@ require (
|
||||||
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
|
||||||
github.com/mholt/acmez v1.0.4 // indirect
|
github.com/mholt/acmez v1.0.4 // indirect
|
||||||
github.com/miekg/dns v1.1.50 // indirect
|
github.com/miekg/dns v1.1.50 // indirect
|
||||||
|
github.com/minio/md5-simd v1.1.2 // indirect
|
||||||
|
github.com/minio/sha256-simd v1.0.0 // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c // indirect
|
github.com/power-devops/perfstat v0.0.0-20220216144756-c35f1ee13d7c // indirect
|
||||||
github.com/prometheus/client_model v0.3.0 // indirect
|
github.com/prometheus/client_model v0.3.0 // indirect
|
||||||
github.com/prometheus/common v0.37.0 // indirect
|
github.com/prometheus/common v0.37.0 // indirect
|
||||||
github.com/prometheus/procfs v0.8.0 // indirect
|
github.com/prometheus/procfs v0.8.0 // indirect
|
||||||
|
github.com/rs/xid v1.4.0 // indirect
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||||
|
github.com/sirupsen/logrus v1.9.0 // indirect
|
||||||
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a // indirect
|
github.com/swaggo/files v0.0.0-20220728132757-551d4a08d97a // indirect
|
||||||
github.com/tklauser/go-sysconf v0.3.10 // indirect
|
github.com/tklauser/go-sysconf v0.3.10 // indirect
|
||||||
github.com/tklauser/numcpus v0.5.0 // indirect
|
github.com/tklauser/numcpus v0.5.0 // indirect
|
||||||
|
|
@ -81,11 +92,11 @@ require (
|
||||||
go.uber.org/atomic v1.10.0 // indirect
|
go.uber.org/atomic v1.10.0 // indirect
|
||||||
go.uber.org/multierr v1.8.0 // indirect
|
go.uber.org/multierr v1.8.0 // indirect
|
||||||
golang.org/x/crypto v0.1.0 // indirect
|
golang.org/x/crypto v0.1.0 // indirect
|
||||||
golang.org/x/net v0.1.0 // indirect
|
|
||||||
golang.org/x/sys v0.1.0 // indirect
|
golang.org/x/sys v0.1.0 // indirect
|
||||||
golang.org/x/text v0.4.0 // indirect
|
golang.org/x/text v0.4.0 // indirect
|
||||||
golang.org/x/time v0.1.0 // indirect
|
golang.org/x/time v0.1.0 // indirect
|
||||||
golang.org/x/tools v0.2.0 // indirect
|
golang.org/x/tools v0.2.0 // indirect
|
||||||
google.golang.org/protobuf v1.28.1 // indirect
|
google.golang.org/protobuf v1.28.1 // indirect
|
||||||
|
gopkg.in/ini.v1 v1.66.6 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|
|
||||||
22
go.sum
22
go.sum
|
|
@ -89,6 +89,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g=
|
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g=
|
||||||
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
|
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
|
||||||
|
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
|
||||||
|
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||||
|
|
@ -214,6 +216,7 @@ github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX
|
||||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||||
|
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||||
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
|
||||||
|
|
@ -222,6 +225,10 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
|
||||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||||
github.com/kevinmbeaulieu/eq-go v1.0.0/go.mod h1:G3S8ajA56gKBZm4UB9AOyoOS37JO3roToPzKNM8dtdM=
|
github.com/kevinmbeaulieu/eq-go v1.0.0/go.mod h1:G3S8ajA56gKBZm4UB9AOyoOS37JO3roToPzKNM8dtdM=
|
||||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY=
|
||||||
|
github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
|
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||||
github.com/klauspost/cpuid/v2 v2.1.2 h1:XhdX4fqAJUA0yj+kUwMavO0hHrSPAecYdYf1ZmxHvak=
|
github.com/klauspost/cpuid/v2 v2.1.2 h1:XhdX4fqAJUA0yj+kUwMavO0hHrSPAecYdYf1ZmxHvak=
|
||||||
github.com/klauspost/cpuid/v2 v2.1.2/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
github.com/klauspost/cpuid/v2 v2.1.2/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
|
|
@ -270,13 +277,21 @@ github.com/mholt/acmez v1.0.4 h1:N3cE4Pek+dSolbsofIkAYz6H1d3pE+2G0os7QHslf80=
|
||||||
github.com/mholt/acmez v1.0.4/go.mod h1:qFGLZ4u+ehWINeJZjzPlsnjJBCPAADWTcIqE/7DAYQY=
|
github.com/mholt/acmez v1.0.4/go.mod h1:qFGLZ4u+ehWINeJZjzPlsnjJBCPAADWTcIqE/7DAYQY=
|
||||||
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
|
github.com/miekg/dns v1.1.50 h1:DQUfb9uc6smULcREF09Uc+/Gd46YWqJd5DbpPE9xkcA=
|
||||||
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
github.com/miekg/dns v1.1.50/go.mod h1:e3IlAVfNqAllflbibAZEWOXOQ+Ynzk/dDozDxY7XnME=
|
||||||
|
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
|
||||||
|
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
|
||||||
|
github.com/minio/minio-go/v7 v7.0.39 h1:upnbu1jCGOqEvrGSpRauSN9ZG7RCHK7VHxXS8Vmg2zk=
|
||||||
|
github.com/minio/minio-go/v7 v7.0.39/go.mod h1:nCrRzjoSUQh8hgKKtu3Y708OLvRLtuASMg2/nvmbarw=
|
||||||
|
github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
|
||||||
|
github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
|
||||||
github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/mitchellh/mapstructure v1.3.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
|
|
@ -329,6 +344,8 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
|
||||||
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=
|
||||||
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
|
||||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||||
|
github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY=
|
||||||
|
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
|
@ -340,6 +357,8 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||||
|
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||||
|
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
|
@ -575,6 +594,7 @@ golang.org/x/sys v0.0.0-20220422013727-9388b58f7150/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||||
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
|
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
|
||||||
|
|
@ -735,6 +755,8 @@ gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||||
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
|
||||||
|
gopkg.in/ini.v1 v1.66.6 h1:LATuAqN/shcYAOkv3wl2L4rkaKqkcgTBQjOyYDvcPKI=
|
||||||
|
gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
|
|
||||||
|
|
@ -6,3 +6,10 @@ type FileInfo struct {
|
||||||
Size int64 `json:"size_bytes" jsonschema:"minimum=0" format:"int64"`
|
Size int64 `json:"size_bytes" jsonschema:"minimum=0" format:"int64"`
|
||||||
LastMod int64 `json:"last_modified" jsonschema:"minimum=0" format:"int64"`
|
LastMod int64 `json:"last_modified" jsonschema:"minimum=0" format:"int64"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FilesystemInfo represents information about a filesystem
|
||||||
|
type FilesystemInfo struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
Mount string `json:"mount"`
|
||||||
|
}
|
||||||
|
|
|
||||||
25
http/fs/fs.go
Normal file
25
http/fs/fs.go
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/datarhei/core/v16/http/cache"
|
||||||
|
"github.com/datarhei/core/v16/io/fs"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FS struct {
|
||||||
|
Name string
|
||||||
|
Mountpoint string
|
||||||
|
|
||||||
|
AllowWrite bool
|
||||||
|
|
||||||
|
EnableAuth bool
|
||||||
|
Username string
|
||||||
|
Password string
|
||||||
|
|
||||||
|
DefaultFile string
|
||||||
|
DefaultContentType string
|
||||||
|
Gzip bool
|
||||||
|
|
||||||
|
Filesystem fs.Filesystem
|
||||||
|
|
||||||
|
Cache cache.Cacher
|
||||||
|
}
|
||||||
|
|
@ -1,215 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"path/filepath"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/datarhei/core/v16/http/api"
|
|
||||||
"github.com/datarhei/core/v16/http/cache"
|
|
||||||
"github.com/datarhei/core/v16/http/handler"
|
|
||||||
"github.com/datarhei/core/v16/http/handler/util"
|
|
||||||
"github.com/datarhei/core/v16/io/fs"
|
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
|
||||||
)
|
|
||||||
|
|
||||||
// The DiskFSHandler type provides handlers for manipulating a filesystem
|
|
||||||
type DiskFSHandler struct {
|
|
||||||
cache cache.Cacher
|
|
||||||
filesystem fs.Filesystem
|
|
||||||
handler *handler.DiskFSHandler
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDiskFS return a new DiskFS type. You have to provide a filesystem to act on and optionally
|
|
||||||
// a Cacher where files will be purged from if the Cacher is related to the filesystem.
|
|
||||||
func NewDiskFS(fs fs.Filesystem, cache cache.Cacher) *DiskFSHandler {
|
|
||||||
return &DiskFSHandler{
|
|
||||||
cache: cache,
|
|
||||||
filesystem: fs,
|
|
||||||
handler: handler.NewDiskFS(fs, cache),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFile returns the file at the given path
|
|
||||||
// @Summary Fetch a file from the filesystem
|
|
||||||
// @Description Fetch a file from the filesystem. The contents of that file are returned.
|
|
||||||
// @Tags v16.7.2
|
|
||||||
// @ID diskfs-3-get-file
|
|
||||||
// @Produce application/data
|
|
||||||
// @Produce json
|
|
||||||
// @Param path path string true "Path to file"
|
|
||||||
// @Success 200 {file} byte
|
|
||||||
// @Success 301 {string} string
|
|
||||||
// @Failure 404 {object} api.Error
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Router /api/v3/fs/disk/{path} [get]
|
|
||||||
func (h *DiskFSHandler) GetFile(c echo.Context) error {
|
|
||||||
path := util.PathWildcardParam(c)
|
|
||||||
|
|
||||||
mimeType := c.Response().Header().Get(echo.HeaderContentType)
|
|
||||||
c.Response().Header().Del(echo.HeaderContentType)
|
|
||||||
|
|
||||||
file := h.filesystem.Open(path)
|
|
||||||
if file == nil {
|
|
||||||
return api.Err(http.StatusNotFound, "File not found", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
stat, _ := file.Stat()
|
|
||||||
|
|
||||||
if stat.IsDir() {
|
|
||||||
return api.Err(http.StatusNotFound, "File not found", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
c.Response().Header().Set("Last-Modified", stat.ModTime().UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT"))
|
|
||||||
|
|
||||||
if path, ok := stat.IsLink(); ok {
|
|
||||||
path = filepath.Clean("/" + path)
|
|
||||||
|
|
||||||
if path[0] == '/' {
|
|
||||||
path = path[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Redirect(http.StatusMovedPermanently, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Response().Header().Set(echo.HeaderContentType, mimeType)
|
|
||||||
|
|
||||||
if c.Request().Method == "HEAD" {
|
|
||||||
return c.Blob(http.StatusOK, "application/data", nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Stream(http.StatusOK, "application/data", file)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PutFile adds or overwrites a file at the given path
|
|
||||||
// @Summary Add a file to the filesystem
|
|
||||||
// @Description Writes or overwrites a file on the filesystem
|
|
||||||
// @Tags v16.7.2
|
|
||||||
// @ID diskfs-3-put-file
|
|
||||||
// @Accept application/data
|
|
||||||
// @Produce text/plain
|
|
||||||
// @Produce json
|
|
||||||
// @Param path path string true "Path to file"
|
|
||||||
// @Param data body []byte true "File data"
|
|
||||||
// @Success 201 {string} string
|
|
||||||
// @Success 204 {string} string
|
|
||||||
// @Failure 507 {object} api.Error
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Router /api/v3/fs/disk/{path} [put]
|
|
||||||
func (h *DiskFSHandler) PutFile(c echo.Context) error {
|
|
||||||
path := util.PathWildcardParam(c)
|
|
||||||
|
|
||||||
c.Response().Header().Del(echo.HeaderContentType)
|
|
||||||
|
|
||||||
req := c.Request()
|
|
||||||
|
|
||||||
_, created, err := h.filesystem.Store(path, req.Body)
|
|
||||||
if err != nil {
|
|
||||||
return api.Err(http.StatusBadRequest, "%s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if h.cache != nil {
|
|
||||||
h.cache.Delete(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Response().Header().Set("Content-Location", req.URL.RequestURI())
|
|
||||||
|
|
||||||
if created {
|
|
||||||
return c.String(http.StatusCreated, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.NoContent(http.StatusNoContent)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteFile removes a file from the filesystem
|
|
||||||
// @Summary Remove a file from the filesystem
|
|
||||||
// @Description Remove a file from the filesystem
|
|
||||||
// @Tags v16.7.2
|
|
||||||
// @ID diskfs-3-delete-file
|
|
||||||
// @Produce text/plain
|
|
||||||
// @Param path path string true "Path to file"
|
|
||||||
// @Success 200 {string} string
|
|
||||||
// @Failure 404 {object} api.Error
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Router /api/v3/fs/disk/{path} [delete]
|
|
||||||
func (h *DiskFSHandler) DeleteFile(c echo.Context) error {
|
|
||||||
path := util.PathWildcardParam(c)
|
|
||||||
|
|
||||||
c.Response().Header().Del(echo.HeaderContentType)
|
|
||||||
|
|
||||||
size := h.filesystem.Delete(path)
|
|
||||||
|
|
||||||
if size < 0 {
|
|
||||||
return api.Err(http.StatusNotFound, "File not found", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
if h.cache != nil {
|
|
||||||
h.cache.Delete(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.String(http.StatusOK, "OK")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListFiles lists all files on the filesystem
|
|
||||||
// @Summary List all files on the filesystem
|
|
||||||
// @Description List all files on the filesystem. The listing can be ordered by name, size, or date of last modification in ascending or descending order.
|
|
||||||
// @Tags v16.7.2
|
|
||||||
// @ID diskfs-3-list-files
|
|
||||||
// @Produce json
|
|
||||||
// @Param glob query string false "glob pattern for file names"
|
|
||||||
// @Param sort query string false "none, name, size, lastmod"
|
|
||||||
// @Param order query string false "asc, desc"
|
|
||||||
// @Success 200 {array} api.FileInfo
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Router /api/v3/fs/disk [get]
|
|
||||||
func (h *DiskFSHandler) ListFiles(c echo.Context) error {
|
|
||||||
pattern := util.DefaultQuery(c, "glob", "")
|
|
||||||
sortby := util.DefaultQuery(c, "sort", "none")
|
|
||||||
order := util.DefaultQuery(c, "order", "asc")
|
|
||||||
|
|
||||||
files := h.filesystem.List(pattern)
|
|
||||||
|
|
||||||
var sortFunc func(i, j int) bool
|
|
||||||
|
|
||||||
switch sortby {
|
|
||||||
case "name":
|
|
||||||
if order == "desc" {
|
|
||||||
sortFunc = func(i, j int) bool { return files[i].Name() > files[j].Name() }
|
|
||||||
} else {
|
|
||||||
sortFunc = func(i, j int) bool { return files[i].Name() < files[j].Name() }
|
|
||||||
}
|
|
||||||
case "size":
|
|
||||||
if order == "desc" {
|
|
||||||
sortFunc = func(i, j int) bool { return files[i].Size() > files[j].Size() }
|
|
||||||
} else {
|
|
||||||
sortFunc = func(i, j int) bool { return files[i].Size() < files[j].Size() }
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
if order == "asc" {
|
|
||||||
sortFunc = func(i, j int) bool { return files[i].ModTime().Before(files[j].ModTime()) }
|
|
||||||
} else {
|
|
||||||
sortFunc = func(i, j int) bool { return files[i].ModTime().After(files[j].ModTime()) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Slice(files, sortFunc)
|
|
||||||
|
|
||||||
fileinfos := []api.FileInfo{}
|
|
||||||
|
|
||||||
for _, f := range files {
|
|
||||||
if f.IsDir() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
fileinfos = append(fileinfos, api.FileInfo{
|
|
||||||
Name: f.Name(),
|
|
||||||
Size: f.Size(),
|
|
||||||
LastMod: f.ModTime().Unix(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.JSON(http.StatusOK, fileinfos)
|
|
||||||
}
|
|
||||||
146
http/handler/api/filesystems.go
Normal file
146
http/handler/api/filesystems.go
Normal file
|
|
@ -0,0 +1,146 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/datarhei/core/v16/http/api"
|
||||||
|
"github.com/datarhei/core/v16/http/handler"
|
||||||
|
"github.com/datarhei/core/v16/http/handler/util"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
type FSConfig struct {
|
||||||
|
Type string
|
||||||
|
Mountpoint string
|
||||||
|
Handler *handler.FSHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
// The FSHandler type provides handlers for manipulating a filesystem
|
||||||
|
type FSHandler struct {
|
||||||
|
filesystems map[string]FSConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFS return a new FSHanlder type. You have to provide a filesystem to act on.
|
||||||
|
func NewFS(filesystems map[string]FSConfig) *FSHandler {
|
||||||
|
return &FSHandler{
|
||||||
|
filesystems: filesystems,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFileAPI returns the file at the given path
|
||||||
|
// @Summary Fetch a file from a filesystem
|
||||||
|
// @Description Fetch a file from a filesystem
|
||||||
|
// @ID filesystem-3-get-file
|
||||||
|
// @Produce application/data
|
||||||
|
// @Produce json
|
||||||
|
// @Param name path string true "Name of the filesystem"
|
||||||
|
// @Param path path string true "Path to file"
|
||||||
|
// @Success 200 {file} byte
|
||||||
|
// @Success 301 {string} string
|
||||||
|
// @Failure 404 {object} api.Error
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /api/v3/fs/{name}/{path} [get]
|
||||||
|
func (h *FSHandler) GetFile(c echo.Context) error {
|
||||||
|
name := util.PathParam(c, "name")
|
||||||
|
|
||||||
|
config, ok := h.filesystems[name]
|
||||||
|
if !ok {
|
||||||
|
return api.Err(http.StatusNotFound, "File not found", "unknown filesystem: %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return config.Handler.GetFile(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PutFileAPI adds or overwrites a file at the given path
|
||||||
|
// @Summary Add a file to a filesystem
|
||||||
|
// @Description Writes or overwrites a file on a filesystem
|
||||||
|
// @ID filesystem-3-put-file
|
||||||
|
// @Accept application/data
|
||||||
|
// @Produce text/plain
|
||||||
|
// @Produce json
|
||||||
|
// @Param name path string true "Name of the filesystem"
|
||||||
|
// @Param path path string true "Path to file"
|
||||||
|
// @Param data body []byte true "File data"
|
||||||
|
// @Success 201 {string} string
|
||||||
|
// @Success 204 {string} string
|
||||||
|
// @Failure 507 {object} api.Error
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /api/v3/fs/{name}/{path} [put]
|
||||||
|
func (h *FSHandler) PutFile(c echo.Context) error {
|
||||||
|
name := util.PathParam(c, "name")
|
||||||
|
|
||||||
|
config, ok := h.filesystems[name]
|
||||||
|
if !ok {
|
||||||
|
return api.Err(http.StatusNotFound, "File not found", "unknown filesystem: %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return config.Handler.PutFile(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteFileAPI removes a file from a filesystem
|
||||||
|
// @Summary Remove a file from a filesystem
|
||||||
|
// @Description Remove a file from a filesystem
|
||||||
|
// @ID filesystem-3-delete-file
|
||||||
|
// @Produce text/plain
|
||||||
|
// @Param name path string true "Name of the filesystem"
|
||||||
|
// @Param path path string true "Path to file"
|
||||||
|
// @Success 200 {string} string
|
||||||
|
// @Failure 404 {object} api.Error
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /api/v3/fs/{name}/{path} [delete]
|
||||||
|
func (h *FSHandler) DeleteFile(c echo.Context) error {
|
||||||
|
name := util.PathParam(c, "name")
|
||||||
|
|
||||||
|
config, ok := h.filesystems[name]
|
||||||
|
if !ok {
|
||||||
|
return api.Err(http.StatusNotFound, "File not found", "unknown filesystem: %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return config.Handler.DeleteFile(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListFiles lists all files on a filesystem
|
||||||
|
// @Summary List all files on a filesystem
|
||||||
|
// @Description List all files on a filesystem. The listing can be ordered by name, size, or date of last modification in ascending or descending order.
|
||||||
|
// @ID filesystem-3-list-files
|
||||||
|
// @Produce json
|
||||||
|
// @Param name path string true "Name of the filesystem"
|
||||||
|
// @Param glob query string false "glob pattern for file names"
|
||||||
|
// @Param sort query string false "none, name, size, lastmod"
|
||||||
|
// @Param order query string false "asc, desc"
|
||||||
|
// @Success 200 {array} api.FileInfo
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /api/v3/fs/{name} [get]
|
||||||
|
func (h *FSHandler) ListFiles(c echo.Context) error {
|
||||||
|
name := util.PathParam(c, "name")
|
||||||
|
|
||||||
|
config, ok := h.filesystems[name]
|
||||||
|
if !ok {
|
||||||
|
return api.Err(http.StatusNotFound, "File not found", "unknown filesystem: %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return config.Handler.ListFiles(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// List lists all registered filesystems
|
||||||
|
// @Summary List all registered filesystems
|
||||||
|
// @Description Listall registered filesystems
|
||||||
|
// @ID filesystem-3-list
|
||||||
|
// @Produce json
|
||||||
|
// @Success 200 {array} api.FilesystemInfo
|
||||||
|
// @Security ApiKeyAuth
|
||||||
|
// @Router /api/v3/fs [get]
|
||||||
|
func (h *FSHandler) List(c echo.Context) error {
|
||||||
|
fss := []api.FilesystemInfo{}
|
||||||
|
|
||||||
|
for name, config := range h.filesystems {
|
||||||
|
fss = append(fss, api.FilesystemInfo{
|
||||||
|
Name: name,
|
||||||
|
Type: config.Type,
|
||||||
|
Mount: config.Mountpoint,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, fss)
|
||||||
|
}
|
||||||
|
|
@ -1,177 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"github.com/datarhei/core/v16/http/api"
|
|
||||||
"github.com/datarhei/core/v16/http/handler"
|
|
||||||
"github.com/datarhei/core/v16/http/handler/util"
|
|
||||||
"github.com/datarhei/core/v16/io/fs"
|
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
|
||||||
)
|
|
||||||
|
|
||||||
// The MemFSHandler type provides handlers for manipulating a filesystem
|
|
||||||
type MemFSHandler struct {
|
|
||||||
filesystem fs.Filesystem
|
|
||||||
handler *handler.MemFSHandler
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMemFS return a new MemFS type. You have to provide a filesystem to act on.
|
|
||||||
func NewMemFS(fs fs.Filesystem) *MemFSHandler {
|
|
||||||
return &MemFSHandler{
|
|
||||||
filesystem: fs,
|
|
||||||
handler: handler.NewMemFS(fs),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFileAPI returns the file at the given path
|
|
||||||
// @Summary Fetch a file from the memory filesystem
|
|
||||||
// @Description Fetch a file from the memory filesystem
|
|
||||||
// @Tags v16.7.2
|
|
||||||
// @ID memfs-3-get-file
|
|
||||||
// @Produce application/data
|
|
||||||
// @Produce json
|
|
||||||
// @Param path path string true "Path to file"
|
|
||||||
// @Success 200 {file} byte
|
|
||||||
// @Success 301 {string} string
|
|
||||||
// @Failure 404 {object} api.Error
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Router /api/v3/fs/mem/{path} [get]
|
|
||||||
func (h *MemFSHandler) GetFile(c echo.Context) error {
|
|
||||||
return h.handler.GetFile(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PutFileAPI adds or overwrites a file at the given path
|
|
||||||
// @Summary Add a file to the memory filesystem
|
|
||||||
// @Description Writes or overwrites a file on the memory filesystem
|
|
||||||
// @Tags v16.7.2
|
|
||||||
// @ID memfs-3-put-file
|
|
||||||
// @Accept application/data
|
|
||||||
// @Produce text/plain
|
|
||||||
// @Produce json
|
|
||||||
// @Param path path string true "Path to file"
|
|
||||||
// @Param data body []byte true "File data"
|
|
||||||
// @Success 201 {string} string
|
|
||||||
// @Success 204 {string} string
|
|
||||||
// @Failure 507 {object} api.Error
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Router /api/v3/fs/mem/{path} [put]
|
|
||||||
func (h *MemFSHandler) PutFile(c echo.Context) error {
|
|
||||||
return h.handler.PutFile(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteFileAPI removes a file from the filesystem
|
|
||||||
// @Summary Remove a file from the memory filesystem
|
|
||||||
// @Description Remove a file from the memory filesystem
|
|
||||||
// @Tags v16.7.2
|
|
||||||
// @ID memfs-3-delete-file
|
|
||||||
// @Produce text/plain
|
|
||||||
// @Param path path string true "Path to file"
|
|
||||||
// @Success 200 {string} string
|
|
||||||
// @Failure 404 {object} api.Error
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Router /api/v3/fs/mem/{path} [delete]
|
|
||||||
func (h *MemFSHandler) DeleteFile(c echo.Context) error {
|
|
||||||
return h.handler.DeleteFile(c)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PatchFile creates a symbolic link to a file in the filesystem
|
|
||||||
// @Summary Create a link to a file in the memory filesystem
|
|
||||||
// @Description Create a link to a file in the memory filesystem. The file linked to has to exist.
|
|
||||||
// @Tags v16.7.2
|
|
||||||
// @ID memfs-3-patch
|
|
||||||
// @Accept application/data
|
|
||||||
// @Produce text/plain
|
|
||||||
// @Produce json
|
|
||||||
// @Param path path string true "Path to file"
|
|
||||||
// @Param url body string true "Path to the file to link to"
|
|
||||||
// @Success 201 {string} string
|
|
||||||
// @Failure 400 {object} api.Error
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Router /api/v3/fs/mem/{path} [patch]
|
|
||||||
func (h *MemFSHandler) PatchFile(c echo.Context) error {
|
|
||||||
path := util.PathWildcardParam(c)
|
|
||||||
|
|
||||||
c.Response().Header().Del(echo.HeaderContentType)
|
|
||||||
|
|
||||||
req := c.Request()
|
|
||||||
|
|
||||||
body, err := io.ReadAll(req.Body)
|
|
||||||
if err != nil {
|
|
||||||
return api.Err(http.StatusBadRequest, "Failed reading request body", "%s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
u, err := url.Parse(string(body))
|
|
||||||
if err != nil {
|
|
||||||
return api.Err(http.StatusBadRequest, "Body doesn't contain a valid path", "%s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := h.filesystem.Symlink(u.Path, path); err != nil {
|
|
||||||
return api.Err(http.StatusBadRequest, "Failed to create symlink", "%s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Response().Header().Set("Content-Location", req.URL.RequestURI())
|
|
||||||
|
|
||||||
return c.String(http.StatusCreated, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
// ListFiles lists all files on the filesystem
|
|
||||||
// @Summary List all files on the memory filesystem
|
|
||||||
// @Description List all files on the memory filesystem. The listing can be ordered by name, size, or date of last modification in ascending or descending order.
|
|
||||||
// @Tags v16.7.2
|
|
||||||
// @ID memfs-3-list-files
|
|
||||||
// @Produce json
|
|
||||||
// @Param glob query string false "glob pattern for file names"
|
|
||||||
// @Param sort query string false "none, name, size, lastmod"
|
|
||||||
// @Param order query string false "asc, desc"
|
|
||||||
// @Success 200 {array} api.FileInfo
|
|
||||||
// @Security ApiKeyAuth
|
|
||||||
// @Router /api/v3/fs/mem [get]
|
|
||||||
func (h *MemFSHandler) ListFiles(c echo.Context) error {
|
|
||||||
pattern := util.DefaultQuery(c, "glob", "")
|
|
||||||
sortby := util.DefaultQuery(c, "sort", "none")
|
|
||||||
order := util.DefaultQuery(c, "order", "asc")
|
|
||||||
|
|
||||||
files := h.filesystem.List(pattern)
|
|
||||||
|
|
||||||
var sortFunc func(i, j int) bool
|
|
||||||
|
|
||||||
switch sortby {
|
|
||||||
case "name":
|
|
||||||
if order == "desc" {
|
|
||||||
sortFunc = func(i, j int) bool { return files[i].Name() > files[j].Name() }
|
|
||||||
} else {
|
|
||||||
sortFunc = func(i, j int) bool { return files[i].Name() < files[j].Name() }
|
|
||||||
}
|
|
||||||
case "size":
|
|
||||||
if order == "desc" {
|
|
||||||
sortFunc = func(i, j int) bool { return files[i].Size() > files[j].Size() }
|
|
||||||
} else {
|
|
||||||
sortFunc = func(i, j int) bool { return files[i].Size() < files[j].Size() }
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
if order == "asc" {
|
|
||||||
sortFunc = func(i, j int) bool { return files[i].ModTime().Before(files[j].ModTime()) }
|
|
||||||
} else {
|
|
||||||
sortFunc = func(i, j int) bool { return files[i].ModTime().After(files[j].ModTime()) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Slice(files, sortFunc)
|
|
||||||
|
|
||||||
var fileinfos []api.FileInfo = make([]api.FileInfo, len(files))
|
|
||||||
|
|
||||||
for i, f := range files {
|
|
||||||
fileinfos[i] = api.FileInfo{
|
|
||||||
Name: f.Name(),
|
|
||||||
Size: f.Size(),
|
|
||||||
LastMod: f.ModTime().Unix(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.JSON(http.StatusOK, fileinfos)
|
|
||||||
}
|
|
||||||
|
|
@ -1,88 +0,0 @@
|
||||||
package handler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/datarhei/core/v16/http/api"
|
|
||||||
"github.com/datarhei/core/v16/http/cache"
|
|
||||||
"github.com/datarhei/core/v16/http/handler/util"
|
|
||||||
"github.com/datarhei/core/v16/io/fs"
|
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
|
||||||
)
|
|
||||||
|
|
||||||
// The DiskFSHandler type provides handlers for manipulating a filesystem
|
|
||||||
type DiskFSHandler struct {
|
|
||||||
cache cache.Cacher
|
|
||||||
filesystem fs.Filesystem
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDiskFS return a new DiskFS type. You have to provide a filesystem to act on and optionally
|
|
||||||
// a Cacher where files will be purged from if the Cacher is related to the filesystem.
|
|
||||||
func NewDiskFS(fs fs.Filesystem, cache cache.Cacher) *DiskFSHandler {
|
|
||||||
return &DiskFSHandler{
|
|
||||||
cache: cache,
|
|
||||||
filesystem: fs,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFile returns the file at the given path
|
|
||||||
// @Summary Fetch a file from the filesystem
|
|
||||||
// @Description Fetch a file from the filesystem. If the file is a directory, a index.html is returned, if it exists.
|
|
||||||
// @ID diskfs-get-file
|
|
||||||
// @Produce application/data
|
|
||||||
// @Produce json
|
|
||||||
// @Param path path string true "Path to file"
|
|
||||||
// @Success 200 {file} byte
|
|
||||||
// @Success 301 {string} string
|
|
||||||
// @Failure 404 {object} api.Error
|
|
||||||
// @Router /{path} [get]
|
|
||||||
func (h *DiskFSHandler) GetFile(c echo.Context) error {
|
|
||||||
path := util.PathWildcardParam(c)
|
|
||||||
|
|
||||||
mimeType := c.Response().Header().Get(echo.HeaderContentType)
|
|
||||||
c.Response().Header().Del(echo.HeaderContentType)
|
|
||||||
|
|
||||||
file := h.filesystem.Open(path)
|
|
||||||
if file == nil {
|
|
||||||
return api.Err(http.StatusNotFound, "File not found", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
stat, _ := file.Stat()
|
|
||||||
|
|
||||||
if stat.IsDir() {
|
|
||||||
path = filepath.Join(path, "index.html")
|
|
||||||
|
|
||||||
file.Close()
|
|
||||||
|
|
||||||
file = h.filesystem.Open(path)
|
|
||||||
if file == nil {
|
|
||||||
return api.Err(http.StatusNotFound, "File not found", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
stat, _ = file.Stat()
|
|
||||||
}
|
|
||||||
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
c.Response().Header().Set("Last-Modified", stat.ModTime().UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT"))
|
|
||||||
|
|
||||||
if path, ok := stat.IsLink(); ok {
|
|
||||||
path = filepath.Clean("/" + path)
|
|
||||||
|
|
||||||
if path[0] == '/' {
|
|
||||||
path = path[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Redirect(http.StatusMovedPermanently, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Response().Header().Set(echo.HeaderContentType, mimeType)
|
|
||||||
|
|
||||||
if c.Request().Method == "HEAD" {
|
|
||||||
return c.Blob(http.StatusOK, "application/data", nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Stream(http.StatusOK, "application/data", file)
|
|
||||||
}
|
|
||||||
164
http/handler/filesystem.go
Normal file
164
http/handler/filesystem.go
Normal file
|
|
@ -0,0 +1,164 @@
|
||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/datarhei/core/v16/http/api"
|
||||||
|
"github.com/datarhei/core/v16/http/fs"
|
||||||
|
"github.com/datarhei/core/v16/http/handler/util"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
)
|
||||||
|
|
||||||
|
// The FSHandler type provides handlers for manipulating a filesystem
|
||||||
|
type FSHandler struct {
|
||||||
|
fs fs.FS
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFS return a new FSHandler type. You have to provide a filesystem to act on.
|
||||||
|
func NewFS(fs fs.FS) *FSHandler {
|
||||||
|
return &FSHandler{
|
||||||
|
fs: fs,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *FSHandler) GetFile(c echo.Context) error {
|
||||||
|
path := util.PathWildcardParam(c)
|
||||||
|
|
||||||
|
mimeType := c.Response().Header().Get(echo.HeaderContentType)
|
||||||
|
c.Response().Header().Del(echo.HeaderContentType)
|
||||||
|
|
||||||
|
file := h.fs.Filesystem.Open(path)
|
||||||
|
if file == nil {
|
||||||
|
return api.Err(http.StatusNotFound, "File not found", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
stat, _ := file.Stat()
|
||||||
|
|
||||||
|
if len(h.fs.DefaultFile) != 0 {
|
||||||
|
if stat.IsDir() {
|
||||||
|
path = filepath.Join(path, h.fs.DefaultFile)
|
||||||
|
|
||||||
|
file.Close()
|
||||||
|
|
||||||
|
file = h.fs.Filesystem.Open(path)
|
||||||
|
if file == nil {
|
||||||
|
return api.Err(http.StatusNotFound, "File not found", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
stat, _ = file.Stat()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
c.Response().Header().Set("Last-Modified", stat.ModTime().UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT"))
|
||||||
|
|
||||||
|
if path, ok := stat.IsLink(); ok {
|
||||||
|
path = filepath.Clean("/" + path)
|
||||||
|
|
||||||
|
if path[0] == '/' {
|
||||||
|
path = path[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Redirect(http.StatusMovedPermanently, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Response().Header().Set(echo.HeaderContentType, mimeType)
|
||||||
|
|
||||||
|
if c.Request().Method == "HEAD" {
|
||||||
|
return c.Blob(http.StatusOK, "application/data", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Stream(http.StatusOK, "application/data", file)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *FSHandler) PutFile(c echo.Context) error {
|
||||||
|
path := util.PathWildcardParam(c)
|
||||||
|
|
||||||
|
c.Response().Header().Del(echo.HeaderContentType)
|
||||||
|
|
||||||
|
req := c.Request()
|
||||||
|
|
||||||
|
_, created, err := h.fs.Filesystem.Store(path, req.Body)
|
||||||
|
if err != nil {
|
||||||
|
return api.Err(http.StatusBadRequest, "%s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.fs.Cache != nil {
|
||||||
|
h.fs.Cache.Delete(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Response().Header().Set("Content-Location", req.URL.RequestURI())
|
||||||
|
|
||||||
|
if created {
|
||||||
|
return c.String(http.StatusCreated, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.NoContent(http.StatusNoContent)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *FSHandler) DeleteFile(c echo.Context) error {
|
||||||
|
path := util.PathWildcardParam(c)
|
||||||
|
|
||||||
|
c.Response().Header().Del(echo.HeaderContentType)
|
||||||
|
|
||||||
|
size := h.fs.Filesystem.Delete(path)
|
||||||
|
|
||||||
|
if size < 0 {
|
||||||
|
return api.Err(http.StatusNotFound, "File not found", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.fs.Cache != nil {
|
||||||
|
h.fs.Cache.Delete(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.String(http.StatusOK, "Deleted: "+path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *FSHandler) ListFiles(c echo.Context) error {
|
||||||
|
pattern := util.DefaultQuery(c, "glob", "")
|
||||||
|
sortby := util.DefaultQuery(c, "sort", "none")
|
||||||
|
order := util.DefaultQuery(c, "order", "asc")
|
||||||
|
|
||||||
|
files := h.fs.Filesystem.List(pattern)
|
||||||
|
|
||||||
|
var sortFunc func(i, j int) bool
|
||||||
|
|
||||||
|
switch sortby {
|
||||||
|
case "name":
|
||||||
|
if order == "desc" {
|
||||||
|
sortFunc = func(i, j int) bool { return files[i].Name() > files[j].Name() }
|
||||||
|
} else {
|
||||||
|
sortFunc = func(i, j int) bool { return files[i].Name() < files[j].Name() }
|
||||||
|
}
|
||||||
|
case "size":
|
||||||
|
if order == "desc" {
|
||||||
|
sortFunc = func(i, j int) bool { return files[i].Size() > files[j].Size() }
|
||||||
|
} else {
|
||||||
|
sortFunc = func(i, j int) bool { return files[i].Size() < files[j].Size() }
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if order == "asc" {
|
||||||
|
sortFunc = func(i, j int) bool { return files[i].ModTime().Before(files[j].ModTime()) }
|
||||||
|
} else {
|
||||||
|
sortFunc = func(i, j int) bool { return files[i].ModTime().After(files[j].ModTime()) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(files, sortFunc)
|
||||||
|
|
||||||
|
var fileinfos []api.FileInfo = make([]api.FileInfo, len(files))
|
||||||
|
|
||||||
|
for i, f := range files {
|
||||||
|
fileinfos[i] = api.FileInfo{
|
||||||
|
Name: f.Name(),
|
||||||
|
Size: f.Size(),
|
||||||
|
LastMod: f.ModTime().Unix(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, fileinfos)
|
||||||
|
}
|
||||||
|
|
@ -1,130 +0,0 @@
|
||||||
package handler
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/datarhei/core/v16/http/api"
|
|
||||||
"github.com/datarhei/core/v16/http/handler/util"
|
|
||||||
"github.com/datarhei/core/v16/io/fs"
|
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
|
||||||
)
|
|
||||||
|
|
||||||
// The MemFSHandler type provides handlers for manipulating a filesystem
|
|
||||||
type MemFSHandler struct {
|
|
||||||
filesystem fs.Filesystem
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMemFS return a new MemFS type. You have to provide a filesystem to act on.
|
|
||||||
func NewMemFS(fs fs.Filesystem) *MemFSHandler {
|
|
||||||
return &MemFSHandler{
|
|
||||||
filesystem: fs,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFile returns the file at the given path
|
|
||||||
// @Summary Fetch a file from the memory filesystem
|
|
||||||
// @Description Fetch a file from the memory filesystem
|
|
||||||
// @ID memfs-get-file
|
|
||||||
// @Produce application/data
|
|
||||||
// @Produce json
|
|
||||||
// @Param path path string true "Path to file"
|
|
||||||
// @Success 200 {file} byte
|
|
||||||
// @Success 301 {string} string
|
|
||||||
// @Failure 404 {object} api.Error
|
|
||||||
// @Router /memfs/{path} [get]
|
|
||||||
func (h *MemFSHandler) GetFile(c echo.Context) error {
|
|
||||||
path := util.PathWildcardParam(c)
|
|
||||||
|
|
||||||
mimeType := c.Response().Header().Get(echo.HeaderContentType)
|
|
||||||
c.Response().Header().Del(echo.HeaderContentType)
|
|
||||||
|
|
||||||
file := h.filesystem.Open(path)
|
|
||||||
if file == nil {
|
|
||||||
return api.Err(http.StatusNotFound, "File not found", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
stat, _ := file.Stat()
|
|
||||||
|
|
||||||
c.Response().Header().Set("Last-Modified", stat.ModTime().UTC().Format("Mon, 02 Jan 2006 15:04:05 GMT"))
|
|
||||||
|
|
||||||
if path, ok := stat.IsLink(); ok {
|
|
||||||
path = filepath.Clean("/" + path)
|
|
||||||
|
|
||||||
if path[0] == '/' {
|
|
||||||
path = path[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Redirect(http.StatusMovedPermanently, path)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Response().Header().Set(echo.HeaderContentType, mimeType)
|
|
||||||
|
|
||||||
if c.Request().Method == "HEAD" {
|
|
||||||
return c.Blob(http.StatusOK, "application/data", nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Stream(http.StatusOK, "application/data", file)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PutFile adds or overwrites a file at the given path
|
|
||||||
// @Summary Add a file to the memory filesystem
|
|
||||||
// @Description Writes or overwrites a file on the memory filesystem
|
|
||||||
// @ID memfs-put-file
|
|
||||||
// @Accept application/data
|
|
||||||
// @Produce text/plain
|
|
||||||
// @Produce json
|
|
||||||
// @Param path path string true "Path to file"
|
|
||||||
// @Param data body []byte true "File data"
|
|
||||||
// @Success 201 {string} string
|
|
||||||
// @Success 204 {string} string
|
|
||||||
// @Failure 507 {object} api.Error
|
|
||||||
// @Security BasicAuth
|
|
||||||
// @Router /memfs/{path} [put]
|
|
||||||
func (h *MemFSHandler) PutFile(c echo.Context) error {
|
|
||||||
path := util.PathWildcardParam(c)
|
|
||||||
|
|
||||||
c.Response().Header().Del(echo.HeaderContentType)
|
|
||||||
|
|
||||||
req := c.Request()
|
|
||||||
|
|
||||||
_, created, err := h.filesystem.Store(path, req.Body)
|
|
||||||
if err != nil {
|
|
||||||
return api.Err(http.StatusBadRequest, "%s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
c.Response().Header().Set("Content-Location", req.URL.RequestURI())
|
|
||||||
|
|
||||||
if created {
|
|
||||||
return c.String(http.StatusCreated, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.NoContent(http.StatusNoContent)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DeleteFile removes a file from the filesystem
|
|
||||||
// @Summary Remove a file from the memory filesystem
|
|
||||||
// @Description Remove a file from the memory filesystem
|
|
||||||
// @ID memfs-delete-file
|
|
||||||
// @Produce text/plain
|
|
||||||
// @Param path path string true "Path to file"
|
|
||||||
// @Success 200 {string} string
|
|
||||||
// @Failure 404 {object} api.Error
|
|
||||||
// @Security BasicAuth
|
|
||||||
// @Router /memfs/{path} [delete]
|
|
||||||
func (h *MemFSHandler) DeleteFile(c echo.Context) error {
|
|
||||||
path := util.PathWildcardParam(c)
|
|
||||||
|
|
||||||
c.Response().Header().Del(echo.HeaderContentType)
|
|
||||||
|
|
||||||
size := h.filesystem.Delete(path)
|
|
||||||
|
|
||||||
if size < 0 {
|
|
||||||
return api.Err(http.StatusNotFound, "File not found", path)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.String(http.StatusOK, "Deleted: "+path)
|
|
||||||
}
|
|
||||||
237
http/server.go
237
http/server.go
|
|
@ -29,19 +29,20 @@
|
||||||
package http
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
cfgstore "github.com/datarhei/core/v16/config/store"
|
cfgstore "github.com/datarhei/core/v16/config/store"
|
||||||
"github.com/datarhei/core/v16/http/cache"
|
"github.com/datarhei/core/v16/http/cache"
|
||||||
"github.com/datarhei/core/v16/http/errorhandler"
|
"github.com/datarhei/core/v16/http/errorhandler"
|
||||||
|
"github.com/datarhei/core/v16/http/fs"
|
||||||
"github.com/datarhei/core/v16/http/graph/resolver"
|
"github.com/datarhei/core/v16/http/graph/resolver"
|
||||||
"github.com/datarhei/core/v16/http/handler"
|
"github.com/datarhei/core/v16/http/handler"
|
||||||
api "github.com/datarhei/core/v16/http/handler/api"
|
api "github.com/datarhei/core/v16/http/handler/api"
|
||||||
"github.com/datarhei/core/v16/http/jwt"
|
"github.com/datarhei/core/v16/http/jwt"
|
||||||
"github.com/datarhei/core/v16/http/router"
|
"github.com/datarhei/core/v16/http/router"
|
||||||
"github.com/datarhei/core/v16/http/validator"
|
"github.com/datarhei/core/v16/http/validator"
|
||||||
"github.com/datarhei/core/v16/io/fs"
|
|
||||||
"github.com/datarhei/core/v16/log"
|
"github.com/datarhei/core/v16/log"
|
||||||
"github.com/datarhei/core/v16/monitor"
|
"github.com/datarhei/core/v16/monitor"
|
||||||
"github.com/datarhei/core/v16/net"
|
"github.com/datarhei/core/v16/net"
|
||||||
|
|
@ -79,8 +80,7 @@ type Config struct {
|
||||||
Metrics monitor.HistoryReader
|
Metrics monitor.HistoryReader
|
||||||
Prometheus prometheus.Reader
|
Prometheus prometheus.Reader
|
||||||
MimeTypesFile string
|
MimeTypesFile string
|
||||||
DiskFS fs.Filesystem
|
Filesystems []fs.FS
|
||||||
MemFS MemFSConfig
|
|
||||||
IPLimiter net.IPLimiter
|
IPLimiter net.IPLimiter
|
||||||
Profiling bool
|
Profiling bool
|
||||||
Cors CorsConfig
|
Cors CorsConfig
|
||||||
|
|
@ -94,13 +94,6 @@ type Config struct {
|
||||||
ReadOnly bool
|
ReadOnly bool
|
||||||
}
|
}
|
||||||
|
|
||||||
type MemFSConfig struct {
|
|
||||||
EnableAuth bool
|
|
||||||
Username string
|
|
||||||
Password string
|
|
||||||
Filesystem fs.Filesystem
|
|
||||||
}
|
|
||||||
|
|
||||||
type CorsConfig struct {
|
type CorsConfig struct {
|
||||||
Origins []string
|
Origins []string
|
||||||
}
|
}
|
||||||
|
|
@ -114,8 +107,6 @@ type server struct {
|
||||||
|
|
||||||
handler struct {
|
handler struct {
|
||||||
about *api.AboutHandler
|
about *api.AboutHandler
|
||||||
memfs *handler.MemFSHandler
|
|
||||||
diskfs *handler.DiskFSHandler
|
|
||||||
prometheus *handler.PrometheusHandler
|
prometheus *handler.PrometheusHandler
|
||||||
profiling *handler.ProfilingHandler
|
profiling *handler.ProfilingHandler
|
||||||
ping *handler.PingHandler
|
ping *handler.PingHandler
|
||||||
|
|
@ -127,8 +118,6 @@ type server struct {
|
||||||
log *api.LogHandler
|
log *api.LogHandler
|
||||||
restream *api.RestreamHandler
|
restream *api.RestreamHandler
|
||||||
playout *api.PlayoutHandler
|
playout *api.PlayoutHandler
|
||||||
memfs *api.MemFSHandler
|
|
||||||
diskfs *api.DiskFSHandler
|
|
||||||
rtmp *api.RTMPHandler
|
rtmp *api.RTMPHandler
|
||||||
srt *api.SRTHandler
|
srt *api.SRTHandler
|
||||||
config *api.ConfigHandler
|
config *api.ConfigHandler
|
||||||
|
|
@ -148,18 +137,12 @@ type server struct {
|
||||||
hlsrewrite echo.MiddlewareFunc
|
hlsrewrite echo.MiddlewareFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
memfs struct {
|
|
||||||
enableAuth bool
|
|
||||||
username string
|
|
||||||
password string
|
|
||||||
}
|
|
||||||
|
|
||||||
diskfs fs.Filesystem
|
|
||||||
|
|
||||||
gzip struct {
|
gzip struct {
|
||||||
mimetypes []string
|
mimetypes []string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filesystems map[string]*filesystem
|
||||||
|
|
||||||
router *echo.Echo
|
router *echo.Echo
|
||||||
mimeTypesFile string
|
mimeTypesFile string
|
||||||
profiling bool
|
profiling bool
|
||||||
|
|
@ -167,32 +150,62 @@ type server struct {
|
||||||
readOnly bool
|
readOnly bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type filesystem struct {
|
||||||
|
fs.FS
|
||||||
|
|
||||||
|
handler *handler.FSHandler
|
||||||
|
}
|
||||||
|
|
||||||
func NewServer(config Config) (Server, error) {
|
func NewServer(config Config) (Server, error) {
|
||||||
s := &server{
|
s := &server{
|
||||||
logger: config.Logger,
|
logger: config.Logger,
|
||||||
mimeTypesFile: config.MimeTypesFile,
|
mimeTypesFile: config.MimeTypesFile,
|
||||||
profiling: config.Profiling,
|
profiling: config.Profiling,
|
||||||
diskfs: config.DiskFS,
|
|
||||||
readOnly: config.ReadOnly,
|
readOnly: config.ReadOnly,
|
||||||
}
|
}
|
||||||
|
|
||||||
s.v3handler.diskfs = api.NewDiskFS(
|
s.filesystems = map[string]*filesystem{}
|
||||||
config.DiskFS,
|
|
||||||
config.Cache,
|
|
||||||
)
|
|
||||||
|
|
||||||
s.handler.diskfs = handler.NewDiskFS(
|
corsPrefixes := map[string][]string{
|
||||||
config.DiskFS,
|
"/api": {"*"},
|
||||||
config.Cache,
|
}
|
||||||
)
|
|
||||||
|
|
||||||
s.middleware.hlsrewrite = mwhlsrewrite.NewHLSRewriteWithConfig(mwhlsrewrite.HLSRewriteConfig{
|
for _, fs := range config.Filesystems {
|
||||||
PathPrefix: config.DiskFS.Base(),
|
if _, ok := s.filesystems[fs.Name]; ok {
|
||||||
})
|
return nil, fmt.Errorf("the filesystem name '%s' is already in use", fs.Name)
|
||||||
|
}
|
||||||
|
|
||||||
s.memfs.enableAuth = config.MemFS.EnableAuth
|
if !strings.HasPrefix(fs.Mountpoint, "/") {
|
||||||
s.memfs.username = config.MemFS.Username
|
fs.Mountpoint = "/" + fs.Mountpoint
|
||||||
s.memfs.password = config.MemFS.Password
|
}
|
||||||
|
|
||||||
|
if !strings.HasSuffix(fs.Mountpoint, "/") {
|
||||||
|
fs.Mountpoint = strings.TrimSuffix(fs.Mountpoint, "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := corsPrefixes[fs.Mountpoint]; ok {
|
||||||
|
return nil, fmt.Errorf("the mount point '%s' is already in use (%s)", fs.Mountpoint, fs.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
corsPrefixes[fs.Mountpoint] = config.Cors.Origins
|
||||||
|
|
||||||
|
filesystem := &filesystem{
|
||||||
|
FS: fs,
|
||||||
|
handler: handler.NewFS(fs),
|
||||||
|
}
|
||||||
|
|
||||||
|
s.filesystems[filesystem.Name] = filesystem
|
||||||
|
|
||||||
|
if fs.Filesystem.Type() == "disk" {
|
||||||
|
s.middleware.hlsrewrite = mwhlsrewrite.NewHLSRewriteWithConfig(mwhlsrewrite.HLSRewriteConfig{
|
||||||
|
PathPrefix: fs.Filesystem.Base(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, ok := corsPrefixes["/"]; !ok {
|
||||||
|
return nil, fmt.Errorf("one filesystem must be mounted at /")
|
||||||
|
}
|
||||||
|
|
||||||
if config.Logger == nil {
|
if config.Logger == nil {
|
||||||
s.logger = log.New("HTTP")
|
s.logger = log.New("HTTP")
|
||||||
|
|
@ -224,16 +237,6 @@ func NewServer(config Config) (Server, error) {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.MemFS.Filesystem != nil {
|
|
||||||
s.v3handler.memfs = api.NewMemFS(
|
|
||||||
config.MemFS.Filesystem,
|
|
||||||
)
|
|
||||||
|
|
||||||
s.handler.memfs = handler.NewMemFS(
|
|
||||||
config.MemFS.Filesystem,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.Prometheus != nil {
|
if config.Prometheus != nil {
|
||||||
s.handler.prometheus = handler.NewPrometheus(
|
s.handler.prometheus = handler.NewPrometheus(
|
||||||
config.Prometheus.HTTPHandler(),
|
config.Prometheus.HTTPHandler(),
|
||||||
|
|
@ -292,12 +295,6 @@ func NewServer(config Config) (Server, error) {
|
||||||
Logger: s.logger,
|
Logger: s.logger,
|
||||||
})
|
})
|
||||||
|
|
||||||
if config.Cache != nil {
|
|
||||||
s.middleware.cache = mwcache.NewWithConfig(mwcache.Config{
|
|
||||||
Cache: config.Cache,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
s.v3handler.widget = api.NewWidget(api.WidgetConfig{
|
s.v3handler.widget = api.NewWidget(api.WidgetConfig{
|
||||||
Restream: config.Restream,
|
Restream: config.Restream,
|
||||||
Registry: config.Sessions,
|
Registry: config.Sessions,
|
||||||
|
|
@ -308,11 +305,7 @@ func NewServer(config Config) (Server, error) {
|
||||||
})
|
})
|
||||||
|
|
||||||
if middleware, err := mwcors.NewWithConfig(mwcors.Config{
|
if middleware, err := mwcors.NewWithConfig(mwcors.Config{
|
||||||
Prefixes: map[string][]string{
|
Prefixes: corsPrefixes,
|
||||||
"/": config.Cors.Origins,
|
|
||||||
"/api": {"*"},
|
|
||||||
"/memfs": config.Cors.Origins,
|
|
||||||
},
|
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -437,65 +430,58 @@ func (s *server) setRoutes() {
|
||||||
doc.Use(gzipMiddleware)
|
doc.Use(gzipMiddleware)
|
||||||
doc.GET("", echoSwagger.WrapHandler)
|
doc.GET("", echoSwagger.WrapHandler)
|
||||||
|
|
||||||
// Serve static data
|
// Mount filesystems
|
||||||
fs := s.router.Group("/*")
|
for _, filesystem := range s.filesystems {
|
||||||
fs.Use(mwmime.NewWithConfig(mwmime.Config{
|
// Define a local variable because later in the loop we have a closure
|
||||||
MimeTypesFile: s.mimeTypesFile,
|
filesystem := filesystem
|
||||||
DefaultContentType: "text/html",
|
|
||||||
}))
|
|
||||||
fs.Use(mwgzip.NewWithConfig(mwgzip.Config{
|
|
||||||
Level: mwgzip.BestSpeed,
|
|
||||||
MinLength: 1000,
|
|
||||||
Skipper: mwgzip.ContentTypeSkipper(s.gzip.mimetypes),
|
|
||||||
}))
|
|
||||||
if s.middleware.cache != nil {
|
|
||||||
fs.Use(s.middleware.cache)
|
|
||||||
}
|
|
||||||
fs.Use(s.middleware.hlsrewrite)
|
|
||||||
if s.middleware.session != nil {
|
|
||||||
fs.Use(s.middleware.session)
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.GET("", s.handler.diskfs.GetFile)
|
mountpoint := filesystem.Mountpoint + "/*"
|
||||||
fs.HEAD("", s.handler.diskfs.GetFile)
|
if filesystem.Mountpoint == "/" {
|
||||||
|
mountpoint = "/*"
|
||||||
// Memory FS
|
|
||||||
if s.handler.memfs != nil {
|
|
||||||
memfs := s.router.Group("/memfs/*")
|
|
||||||
memfs.Use(mwmime.NewWithConfig(mwmime.Config{
|
|
||||||
MimeTypesFile: s.mimeTypesFile,
|
|
||||||
DefaultContentType: "application/data",
|
|
||||||
}))
|
|
||||||
memfs.Use(mwgzip.NewWithConfig(mwgzip.Config{
|
|
||||||
Level: mwgzip.BestSpeed,
|
|
||||||
MinLength: 1000,
|
|
||||||
Skipper: mwgzip.ContentTypeSkipper(s.gzip.mimetypes),
|
|
||||||
}))
|
|
||||||
if s.middleware.session != nil {
|
|
||||||
memfs.Use(s.middleware.session)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
memfs.HEAD("", s.handler.memfs.GetFile)
|
fs := s.router.Group(mountpoint)
|
||||||
memfs.GET("", s.handler.memfs.GetFile)
|
fs.Use(mwmime.NewWithConfig(mwmime.Config{
|
||||||
|
MimeTypesFile: s.mimeTypesFile,
|
||||||
|
DefaultContentType: filesystem.DefaultContentType,
|
||||||
|
}))
|
||||||
|
|
||||||
var authmw echo.MiddlewareFunc
|
if filesystem.Gzip {
|
||||||
|
fs.Use(mwgzip.NewWithConfig(mwgzip.Config{
|
||||||
|
Skipper: mwgzip.ContentTypeSkipper(s.gzip.mimetypes),
|
||||||
|
Level: mwgzip.BestSpeed,
|
||||||
|
MinLength: 1000,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
if s.memfs.enableAuth {
|
if filesystem.Cache != nil {
|
||||||
authmw = middleware.BasicAuth(func(username, password string, c echo.Context) (bool, error) {
|
mwcache := mwcache.NewWithConfig(mwcache.Config{
|
||||||
if username == s.memfs.username && password == s.memfs.password {
|
Cache: filesystem.Cache,
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, nil
|
|
||||||
})
|
})
|
||||||
|
fs.Use(mwcache)
|
||||||
|
}
|
||||||
|
|
||||||
memfs.POST("", s.handler.memfs.PutFile, authmw)
|
fs.GET("", filesystem.handler.GetFile)
|
||||||
memfs.PUT("", s.handler.memfs.PutFile, authmw)
|
fs.HEAD("", filesystem.handler.GetFile)
|
||||||
memfs.DELETE("", s.handler.memfs.DeleteFile, authmw)
|
|
||||||
} else {
|
if filesystem.AllowWrite {
|
||||||
memfs.POST("", s.handler.memfs.PutFile)
|
if filesystem.EnableAuth {
|
||||||
memfs.PUT("", s.handler.memfs.PutFile)
|
authmw := middleware.BasicAuth(func(username, password string, c echo.Context) (bool, error) {
|
||||||
memfs.DELETE("", s.handler.memfs.DeleteFile)
|
if username == filesystem.Username && password == filesystem.Password {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
fs.POST("", filesystem.handler.PutFile, authmw)
|
||||||
|
fs.PUT("", filesystem.handler.PutFile, authmw)
|
||||||
|
fs.DELETE("", filesystem.handler.DeleteFile, authmw)
|
||||||
|
} else {
|
||||||
|
fs.POST("", filesystem.handler.PutFile)
|
||||||
|
fs.PUT("", filesystem.handler.PutFile)
|
||||||
|
fs.DELETE("", filesystem.handler.DeleteFile)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -593,32 +579,33 @@ func (s *server) setRoutesV3(v3 *echo.Group) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// v3 Memory FS
|
// v3 Filesystems
|
||||||
if s.v3handler.memfs != nil {
|
fshandlers := map[string]api.FSConfig{}
|
||||||
v3.GET("/fs/mem", s.v3handler.memfs.ListFiles)
|
for _, fs := range s.filesystems {
|
||||||
v3.GET("/fs/mem/*", s.v3handler.memfs.GetFile)
|
fshandlers[fs.Name] = api.FSConfig{
|
||||||
|
Type: fs.Filesystem.Type(),
|
||||||
if !s.readOnly {
|
Mountpoint: fs.Mountpoint,
|
||||||
v3.DELETE("/fs/mem/*", s.v3handler.memfs.DeleteFile)
|
Handler: fs.handler,
|
||||||
v3.PUT("/fs/mem/*", s.v3handler.memfs.PutFile)
|
|
||||||
v3.PATCH("/fs/mem/*", s.v3handler.memfs.PatchFile)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// v3 Disk FS
|
handler := api.NewFS(fshandlers)
|
||||||
v3.GET("/fs/disk", s.v3handler.diskfs.ListFiles)
|
|
||||||
v3.GET("/fs/disk/*", s.v3handler.diskfs.GetFile, mwmime.NewWithConfig(mwmime.Config{
|
v3.GET("/fs", handler.List)
|
||||||
|
|
||||||
|
v3.GET("/fs/:name", handler.ListFiles)
|
||||||
|
v3.GET("/fs/:name/*", handler.GetFile, mwmime.NewWithConfig(mwmime.Config{
|
||||||
MimeTypesFile: s.mimeTypesFile,
|
MimeTypesFile: s.mimeTypesFile,
|
||||||
DefaultContentType: "application/data",
|
DefaultContentType: "application/data",
|
||||||
}))
|
}))
|
||||||
v3.HEAD("/fs/disk/*", s.v3handler.diskfs.GetFile, mwmime.NewWithConfig(mwmime.Config{
|
v3.HEAD("/fs/:name/*", handler.GetFile, mwmime.NewWithConfig(mwmime.Config{
|
||||||
MimeTypesFile: s.mimeTypesFile,
|
MimeTypesFile: s.mimeTypesFile,
|
||||||
DefaultContentType: "application/data",
|
DefaultContentType: "application/data",
|
||||||
}))
|
}))
|
||||||
|
|
||||||
if !s.readOnly {
|
if !s.readOnly {
|
||||||
v3.PUT("/fs/disk/*", s.v3handler.diskfs.PutFile)
|
v3.PUT("/fs/:name/*", handler.PutFile)
|
||||||
v3.DELETE("/fs/disk/*", s.v3handler.diskfs.DeleteFile)
|
v3.DELETE("/fs/:name/*", handler.DeleteFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
// v3 RTMP
|
// v3 RTMP
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,9 @@ import (
|
||||||
// DiskConfig is the config required to create a new disk
|
// DiskConfig is the config required to create a new disk
|
||||||
// filesystem.
|
// filesystem.
|
||||||
type DiskConfig struct {
|
type DiskConfig struct {
|
||||||
|
// Namee is the name of the filesystem
|
||||||
|
Name string
|
||||||
|
|
||||||
// Dir is the path to the directory to observe
|
// Dir is the path to the directory to observe
|
||||||
Dir string
|
Dir string
|
||||||
|
|
||||||
|
|
@ -109,7 +112,8 @@ func (f *diskFile) Read(p []byte) (int, error) {
|
||||||
|
|
||||||
// diskFilesystem implements the Filesystem interface
|
// diskFilesystem implements the Filesystem interface
|
||||||
type diskFilesystem struct {
|
type diskFilesystem struct {
|
||||||
dir string
|
name string
|
||||||
|
dir string
|
||||||
|
|
||||||
// Max. size of the filesystem in bytes as
|
// Max. size of the filesystem in bytes as
|
||||||
// given by the config
|
// given by the config
|
||||||
|
|
@ -127,14 +131,20 @@ type diskFilesystem struct {
|
||||||
// that implements the Filesystem interface
|
// that implements the Filesystem interface
|
||||||
func NewDiskFilesystem(config DiskConfig) (Filesystem, error) {
|
func NewDiskFilesystem(config DiskConfig) (Filesystem, error) {
|
||||||
fs := &diskFilesystem{
|
fs := &diskFilesystem{
|
||||||
|
name: config.Name,
|
||||||
maxSize: config.Size,
|
maxSize: config.Size,
|
||||||
logger: config.Logger,
|
logger: config.Logger,
|
||||||
}
|
}
|
||||||
|
|
||||||
if fs.logger == nil {
|
if fs.logger == nil {
|
||||||
fs.logger = log.New("DiskFS")
|
fs.logger = log.New("")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fs.logger = fs.logger.WithFields(log.Fields{
|
||||||
|
"name": fs.name,
|
||||||
|
"type": "disk",
|
||||||
|
})
|
||||||
|
|
||||||
if err := fs.Rebase(config.Dir); err != nil {
|
if err := fs.Rebase(config.Dir); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -142,6 +152,10 @@ func NewDiskFilesystem(config DiskConfig) (Filesystem, error) {
|
||||||
return fs, nil
|
return fs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fs *diskFilesystem) Name() string {
|
||||||
|
return fs.name
|
||||||
|
}
|
||||||
|
|
||||||
func (fs *diskFilesystem) Base() string {
|
func (fs *diskFilesystem) Base() string {
|
||||||
return fs.dir
|
return fs.dir
|
||||||
}
|
}
|
||||||
|
|
@ -172,6 +186,10 @@ func (fs *diskFilesystem) Rebase(base string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fs *diskFilesystem) Type() string {
|
||||||
|
return "diskfs"
|
||||||
|
}
|
||||||
|
|
||||||
func (fs *diskFilesystem) Size() (int64, int64) {
|
func (fs *diskFilesystem) Size() (int64, int64) {
|
||||||
// This is to cache the size for some time in order not to
|
// This is to cache the size for some time in order not to
|
||||||
// stress the underlying filesystem too much.
|
// stress the underlying filesystem too much.
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,15 @@ func (d *dummyFile) Close() error { return nil }
|
||||||
func (d *dummyFile) Name() string { return "" }
|
func (d *dummyFile) Name() string { return "" }
|
||||||
func (d *dummyFile) Stat() (FileInfo, error) { return &dummyFileInfo{}, nil }
|
func (d *dummyFile) Stat() (FileInfo, error) { return &dummyFileInfo{}, nil }
|
||||||
|
|
||||||
type dummyFilesystem struct{}
|
type dummyFilesystem struct {
|
||||||
|
name string
|
||||||
|
typ string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dummyFilesystem) Name() string { return d.name }
|
||||||
func (d *dummyFilesystem) Base() string { return "/" }
|
func (d *dummyFilesystem) Base() string { return "/" }
|
||||||
func (d *dummyFilesystem) Rebase(string) error { return nil }
|
func (d *dummyFilesystem) Rebase(string) error { return nil }
|
||||||
|
func (d *dummyFilesystem) Type() string { return d.typ }
|
||||||
func (d *dummyFilesystem) Size() (int64, int64) { return 0, -1 }
|
func (d *dummyFilesystem) Size() (int64, int64) { return 0, -1 }
|
||||||
func (d *dummyFilesystem) Resize(int64) {}
|
func (d *dummyFilesystem) Resize(int64) {}
|
||||||
func (d *dummyFilesystem) Files() int64 { return 0 }
|
func (d *dummyFilesystem) Files() int64 { return 0 }
|
||||||
|
|
@ -35,6 +40,9 @@ func (d *dummyFilesystem) DeleteAll() int64 { return
|
||||||
func (d *dummyFilesystem) List(string) []FileInfo { return []FileInfo{} }
|
func (d *dummyFilesystem) List(string) []FileInfo { return []FileInfo{} }
|
||||||
|
|
||||||
// NewDummyFilesystem return a dummy filesystem
|
// NewDummyFilesystem return a dummy filesystem
|
||||||
func NewDummyFilesystem() Filesystem {
|
func NewDummyFilesystem(name, typ string) Filesystem {
|
||||||
return &dummyFilesystem{}
|
return &dummyFilesystem{
|
||||||
|
name: name,
|
||||||
|
typ: typ,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,12 +38,18 @@ type File interface {
|
||||||
|
|
||||||
// Filesystem is an interface that provides access to a filesystem.
|
// Filesystem is an interface that provides access to a filesystem.
|
||||||
type Filesystem interface {
|
type Filesystem interface {
|
||||||
|
// Name returns the name of this filesystem
|
||||||
|
Name() string
|
||||||
|
|
||||||
// Base returns the base path of this filesystem
|
// Base returns the base path of this filesystem
|
||||||
Base() string
|
Base() string
|
||||||
|
|
||||||
// Rebase sets a new base path for this filesystem
|
// Rebase sets a new base path for this filesystem
|
||||||
Rebase(string) error
|
Rebase(string) error
|
||||||
|
|
||||||
|
// Type returns the type of this filesystem
|
||||||
|
Type() string
|
||||||
|
|
||||||
// Size returns the consumed size and capacity of the filesystem in bytes. The
|
// Size returns the consumed size and capacity of the filesystem in bytes. The
|
||||||
// capacity is negative if the filesystem can consume as much space as it can.
|
// capacity is negative if the filesystem can consume as much space as it can.
|
||||||
Size() (int64, int64)
|
Size() (int64, int64)
|
||||||
|
|
@ -67,7 +73,7 @@ type Filesystem interface {
|
||||||
Store(path string, r io.Reader) (int64, bool, error)
|
Store(path string, r io.Reader) (int64, bool, error)
|
||||||
|
|
||||||
// Delete removes a file at the given path from the filesystem. Returns the size of
|
// Delete removes a file at the given path from the filesystem. Returns the size of
|
||||||
// the remove file in bytes. The size is negative if the file doesn't exist.
|
// the removed file in bytes. The size is negative if the file doesn't exist.
|
||||||
Delete(path string) int64
|
Delete(path string) int64
|
||||||
|
|
||||||
// DeleteAll removes all files from the filesystem. Returns the size of the
|
// DeleteAll removes all files from the filesystem. Returns the size of the
|
||||||
|
|
|
||||||
18
io/fs/mem.go
18
io/fs/mem.go
|
|
@ -15,6 +15,9 @@ import (
|
||||||
// MemConfig is the config that is required for creating
|
// MemConfig is the config that is required for creating
|
||||||
// a new memory filesystem.
|
// a new memory filesystem.
|
||||||
type MemConfig struct {
|
type MemConfig struct {
|
||||||
|
// Namee is the name of the filesystem
|
||||||
|
Name string
|
||||||
|
|
||||||
// Base is the base path to be reported for this filesystem
|
// Base is the base path to be reported for this filesystem
|
||||||
Base string
|
Base string
|
||||||
|
|
||||||
|
|
@ -107,6 +110,7 @@ func (f *memFile) Close() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
type memFilesystem struct {
|
type memFilesystem struct {
|
||||||
|
name string
|
||||||
base string
|
base string
|
||||||
|
|
||||||
// Mapping of path to file
|
// Mapping of path to file
|
||||||
|
|
@ -136,6 +140,7 @@ type memFilesystem struct {
|
||||||
// the Filesystem interface.
|
// the Filesystem interface.
|
||||||
func NewMemFilesystem(config MemConfig) Filesystem {
|
func NewMemFilesystem(config MemConfig) Filesystem {
|
||||||
fs := &memFilesystem{
|
fs := &memFilesystem{
|
||||||
|
name: config.Name,
|
||||||
base: config.Base,
|
base: config.Base,
|
||||||
maxSize: config.Size,
|
maxSize: config.Size,
|
||||||
purge: config.Purge,
|
purge: config.Purge,
|
||||||
|
|
@ -143,9 +148,11 @@ func NewMemFilesystem(config MemConfig) Filesystem {
|
||||||
}
|
}
|
||||||
|
|
||||||
if fs.logger == nil {
|
if fs.logger == nil {
|
||||||
fs.logger = log.New("MemFS")
|
fs.logger = log.New("")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fs.logger = fs.logger.WithField("type", "mem")
|
||||||
|
|
||||||
fs.files = make(map[string]*memFile)
|
fs.files = make(map[string]*memFile)
|
||||||
|
|
||||||
fs.dataPool = sync.Pool{
|
fs.dataPool = sync.Pool{
|
||||||
|
|
@ -155,6 +162,7 @@ func NewMemFilesystem(config MemConfig) Filesystem {
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.logger.WithFields(log.Fields{
|
fs.logger.WithFields(log.Fields{
|
||||||
|
"name": fs.name,
|
||||||
"size_bytes": fs.maxSize,
|
"size_bytes": fs.maxSize,
|
||||||
"purge": fs.purge,
|
"purge": fs.purge,
|
||||||
}).Debug().Log("Created")
|
}).Debug().Log("Created")
|
||||||
|
|
@ -162,6 +170,10 @@ func NewMemFilesystem(config MemConfig) Filesystem {
|
||||||
return fs
|
return fs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fs *memFilesystem) Name() string {
|
||||||
|
return fs.name
|
||||||
|
}
|
||||||
|
|
||||||
func (fs *memFilesystem) Base() string {
|
func (fs *memFilesystem) Base() string {
|
||||||
return fs.base
|
return fs.base
|
||||||
}
|
}
|
||||||
|
|
@ -172,6 +184,10 @@ func (fs *memFilesystem) Rebase(base string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fs *memFilesystem) Type() string {
|
||||||
|
return "memfs"
|
||||||
|
}
|
||||||
|
|
||||||
func (fs *memFilesystem) Size() (int64, int64) {
|
func (fs *memFilesystem) Size() (int64, int64) {
|
||||||
fs.filesLock.RLock()
|
fs.filesLock.RLock()
|
||||||
defer fs.filesLock.RUnlock()
|
defer fs.filesLock.RUnlock()
|
||||||
|
|
|
||||||
389
io/fs/s3.go
Normal file
389
io/fs/s3.go
Normal file
|
|
@ -0,0 +1,389 @@
|
||||||
|
package fs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/datarhei/core/v16/glob"
|
||||||
|
"github.com/datarhei/core/v16/log"
|
||||||
|
"github.com/minio/minio-go/v7"
|
||||||
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
||||||
|
)
|
||||||
|
|
||||||
|
type S3Config struct {
|
||||||
|
// Namee is the name of the filesystem
|
||||||
|
Name string
|
||||||
|
Base string
|
||||||
|
Endpoint string
|
||||||
|
AccessKeyID string
|
||||||
|
SecretAccessKey string
|
||||||
|
Region string
|
||||||
|
Bucket string
|
||||||
|
UseSSL bool
|
||||||
|
|
||||||
|
Logger log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
type s3fs struct {
|
||||||
|
name string
|
||||||
|
base string
|
||||||
|
|
||||||
|
endpoint string
|
||||||
|
accessKeyID string
|
||||||
|
secretAccessKey string
|
||||||
|
region string
|
||||||
|
bucket string
|
||||||
|
useSSL bool
|
||||||
|
|
||||||
|
client *minio.Client
|
||||||
|
|
||||||
|
logger log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewS3Filesystem(config S3Config) (Filesystem, error) {
|
||||||
|
fs := &s3fs{
|
||||||
|
name: config.Name,
|
||||||
|
base: config.Base,
|
||||||
|
endpoint: config.Endpoint,
|
||||||
|
accessKeyID: config.AccessKeyID,
|
||||||
|
secretAccessKey: config.SecretAccessKey,
|
||||||
|
region: config.Region,
|
||||||
|
bucket: config.Bucket,
|
||||||
|
useSSL: config.UseSSL,
|
||||||
|
logger: config.Logger,
|
||||||
|
}
|
||||||
|
|
||||||
|
if fs.logger == nil {
|
||||||
|
fs.logger = log.New("")
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := minio.New(fs.endpoint, &minio.Options{
|
||||||
|
Creds: credentials.NewStaticV4(fs.accessKeyID, fs.secretAccessKey, ""),
|
||||||
|
Region: fs.region,
|
||||||
|
Secure: fs.useSSL,
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't connect to s3 endpoint %s: %w", fs.endpoint, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.logger = fs.logger.WithFields(log.Fields{
|
||||||
|
"name": fs.name,
|
||||||
|
"type": "s3",
|
||||||
|
"bucket": fs.bucket,
|
||||||
|
"region": fs.region,
|
||||||
|
"endpoint": fs.endpoint,
|
||||||
|
})
|
||||||
|
|
||||||
|
fs.logger.Debug().Log("Connected")
|
||||||
|
|
||||||
|
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(30*time.Second))
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
exists, err := client.BucketExists(ctx, fs.bucket)
|
||||||
|
if err != nil {
|
||||||
|
fs.logger.WithError(err).Log("Can't access bucket")
|
||||||
|
return nil, fmt.Errorf("can't access bucket %s: %w", fs.bucket, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if exists {
|
||||||
|
fs.logger.Debug().Log("Bucket already exists")
|
||||||
|
} else {
|
||||||
|
fs.logger.Debug().Log("Bucket doesn't exists")
|
||||||
|
err = client.MakeBucket(ctx, fs.bucket, minio.MakeBucketOptions{Region: fs.region})
|
||||||
|
if err != nil {
|
||||||
|
fs.logger.WithError(err).Log("Can't create bucket")
|
||||||
|
return nil, fmt.Errorf("can't create bucket %s: %w", fs.bucket, err)
|
||||||
|
} else {
|
||||||
|
fs.logger.Debug().Log("Bucket created")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.client = client
|
||||||
|
|
||||||
|
return fs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *s3fs) Name() string {
|
||||||
|
return fs.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *s3fs) Base() string {
|
||||||
|
return fs.base
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *s3fs) Rebase(base string) error {
|
||||||
|
fs.base = base
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *s3fs) Type() string {
|
||||||
|
return "s3fs"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *s3fs) Size() (int64, int64) {
|
||||||
|
size := int64(0)
|
||||||
|
|
||||||
|
files := fs.List("")
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
size += file.Size()
|
||||||
|
}
|
||||||
|
|
||||||
|
return size, -1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *s3fs) Resize(size int64) {}
|
||||||
|
|
||||||
|
func (fs *s3fs) Files() int64 {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
ch := fs.client.ListObjects(ctx, fs.bucket, minio.ListObjectsOptions{
|
||||||
|
Recursive: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
nfiles := int64(0)
|
||||||
|
|
||||||
|
for object := range ch {
|
||||||
|
if object.Err != nil {
|
||||||
|
fs.logger.WithError(object.Err).Log("Listing object failed")
|
||||||
|
}
|
||||||
|
nfiles++
|
||||||
|
}
|
||||||
|
|
||||||
|
return nfiles
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *s3fs) Symlink(oldname, newname string) error {
|
||||||
|
return fmt.Errorf("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *s3fs) Open(path string) File {
|
||||||
|
//ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
//defer cancel()
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
object, err := fs.client.GetObject(ctx, fs.bucket, path, minio.GetObjectOptions{})
|
||||||
|
if err != nil {
|
||||||
|
fs.logger.Debug().WithField("key", path).Log("Not found")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
stat, err := object.Stat()
|
||||||
|
if err != nil {
|
||||||
|
fs.logger.Debug().WithField("key", path).Log("Stat failed")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
file := &s3File{
|
||||||
|
data: object,
|
||||||
|
name: stat.Key,
|
||||||
|
size: stat.Size,
|
||||||
|
lastModified: stat.LastModified,
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.logger.Debug().WithField("key", stat.Key).Log("Opened")
|
||||||
|
|
||||||
|
return file
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *s3fs) Store(path string, r io.Reader) (int64, bool, error) {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
overwrite := false
|
||||||
|
|
||||||
|
_, err := fs.client.StatObject(ctx, fs.bucket, path, minio.StatObjectOptions{})
|
||||||
|
if err == nil {
|
||||||
|
overwrite = true
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := fs.client.PutObject(ctx, fs.bucket, path, r, -1, minio.PutObjectOptions{
|
||||||
|
UserMetadata: map[string]string{},
|
||||||
|
UserTags: map[string]string{},
|
||||||
|
Progress: nil,
|
||||||
|
ContentType: "",
|
||||||
|
ContentEncoding: "",
|
||||||
|
ContentDisposition: "",
|
||||||
|
ContentLanguage: "",
|
||||||
|
CacheControl: "",
|
||||||
|
Mode: "",
|
||||||
|
RetainUntilDate: time.Time{},
|
||||||
|
ServerSideEncryption: nil,
|
||||||
|
NumThreads: 0,
|
||||||
|
StorageClass: "",
|
||||||
|
WebsiteRedirectLocation: "",
|
||||||
|
PartSize: 0,
|
||||||
|
LegalHold: "",
|
||||||
|
SendContentMd5: false,
|
||||||
|
DisableContentSha256: false,
|
||||||
|
DisableMultipart: false,
|
||||||
|
Internal: minio.AdvancedPutOptions{},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
fs.logger.WithError(err).WithField("key", path).Log("Failed to store file")
|
||||||
|
return -1, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.logger.Debug().WithFields(log.Fields{
|
||||||
|
"key": path,
|
||||||
|
"overwrite": overwrite,
|
||||||
|
}).Log("Stored")
|
||||||
|
|
||||||
|
return info.Size, overwrite, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *s3fs) Delete(path string) int64 {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
stat, err := fs.client.StatObject(ctx, fs.bucket, path, minio.StatObjectOptions{})
|
||||||
|
if err != nil {
|
||||||
|
fs.logger.Debug().WithField("key", path).Log("Not found")
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
err = fs.client.RemoveObject(ctx, fs.bucket, path, minio.RemoveObjectOptions{
|
||||||
|
GovernanceBypass: true,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
fs.logger.WithError(err).WithField("key", stat.Key).Log("Failed to delete file")
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.logger.Debug().WithField("key", stat.Key).Log("Deleted")
|
||||||
|
|
||||||
|
return stat.Size
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *s3fs) DeleteAll() int64 {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
totalSize := int64(0)
|
||||||
|
|
||||||
|
objectsCh := make(chan minio.ObjectInfo)
|
||||||
|
|
||||||
|
// Send object names that are needed to be removed to objectsCh
|
||||||
|
go func() {
|
||||||
|
defer close(objectsCh)
|
||||||
|
|
||||||
|
for object := range fs.client.ListObjects(ctx, fs.bucket, minio.ListObjectsOptions{
|
||||||
|
Recursive: true,
|
||||||
|
}) {
|
||||||
|
if object.Err != nil {
|
||||||
|
fs.logger.WithError(object.Err).Log("Listing object failed")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
totalSize += object.Size
|
||||||
|
objectsCh <- object
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for err := range fs.client.RemoveObjects(context.Background(), fs.bucket, objectsCh, minio.RemoveObjectsOptions{
|
||||||
|
GovernanceBypass: true,
|
||||||
|
}) {
|
||||||
|
fs.logger.WithError(err.Err).WithField("key", err.ObjectName).Log("Deleting object failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.logger.Debug().Log("Deleted all files")
|
||||||
|
|
||||||
|
return totalSize
|
||||||
|
}
|
||||||
|
|
||||||
|
func (fs *s3fs) List(pattern string) []FileInfo {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
ch := fs.client.ListObjects(ctx, fs.bucket, minio.ListObjectsOptions{
|
||||||
|
WithVersions: false,
|
||||||
|
WithMetadata: false,
|
||||||
|
Prefix: "",
|
||||||
|
Recursive: true,
|
||||||
|
MaxKeys: 0,
|
||||||
|
StartAfter: "",
|
||||||
|
UseV1: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
files := []FileInfo{}
|
||||||
|
|
||||||
|
for object := range ch {
|
||||||
|
if object.Err != nil {
|
||||||
|
fs.logger.WithError(object.Err).Log("Listing object failed")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(pattern) != 0 {
|
||||||
|
if ok, _ := glob.Match(pattern, object.Key, '/'); !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
f := &s3FileInfo{
|
||||||
|
name: object.Key,
|
||||||
|
size: object.Size,
|
||||||
|
lastModified: object.LastModified,
|
||||||
|
}
|
||||||
|
|
||||||
|
files = append(files, f)
|
||||||
|
}
|
||||||
|
|
||||||
|
return files
|
||||||
|
}
|
||||||
|
|
||||||
|
type s3FileInfo struct {
|
||||||
|
name string
|
||||||
|
size int64
|
||||||
|
lastModified time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *s3FileInfo) Name() string {
|
||||||
|
return f.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *s3FileInfo) Size() int64 {
|
||||||
|
return f.size
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *s3FileInfo) ModTime() time.Time {
|
||||||
|
return f.lastModified
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *s3FileInfo) IsLink() (string, bool) {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *s3FileInfo) IsDir() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type s3File struct {
|
||||||
|
data io.ReadCloser
|
||||||
|
name string
|
||||||
|
size int64
|
||||||
|
lastModified time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *s3File) Read(p []byte) (int, error) {
|
||||||
|
return f.data.Read(p)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *s3File) Close() error {
|
||||||
|
return f.data.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *s3File) Name() string {
|
||||||
|
return f.name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *s3File) Stat() (FileInfo, error) {
|
||||||
|
return &s3FileInfo{
|
||||||
|
name: f.name,
|
||||||
|
size: f.size,
|
||||||
|
lastModified: f.lastModified,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
@ -62,6 +62,11 @@ func New(config Config) Filesystem {
|
||||||
rfs.logger = log.New("")
|
rfs.logger = log.New("")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rfs.logger = rfs.logger.WithFields(log.Fields{
|
||||||
|
"name": config.FS.Name(),
|
||||||
|
"type": config.FS.Type(),
|
||||||
|
})
|
||||||
|
|
||||||
rfs.cleanupPatterns = make(map[string][]Pattern)
|
rfs.cleanupPatterns = make(map[string][]Pattern)
|
||||||
|
|
||||||
// already drain the stop
|
// already drain the stop
|
||||||
|
|
|
||||||
|
|
@ -62,8 +62,7 @@ type Config struct {
|
||||||
ID string
|
ID string
|
||||||
Name string
|
Name string
|
||||||
Store store.Store
|
Store store.Store
|
||||||
DiskFS fs.Filesystem
|
Filesystems []fs.Filesystem
|
||||||
MemFS fs.Filesystem
|
|
||||||
Replace replace.Replacer
|
Replace replace.Replacer
|
||||||
FFmpeg ffmpeg.FFmpeg
|
FFmpeg ffmpeg.FFmpeg
|
||||||
MaxProcesses int64
|
MaxProcesses int64
|
||||||
|
|
@ -94,8 +93,8 @@ type restream struct {
|
||||||
maxProc int64
|
maxProc int64
|
||||||
nProc int64
|
nProc int64
|
||||||
fs struct {
|
fs struct {
|
||||||
diskfs rfs.Filesystem
|
list []rfs.Filesystem
|
||||||
memfs rfs.Filesystem
|
diskfs []rfs.Filesystem
|
||||||
stopObserver context.CancelFunc
|
stopObserver context.CancelFunc
|
||||||
}
|
}
|
||||||
replace replace.Replacer
|
replace replace.Replacer
|
||||||
|
|
@ -128,26 +127,18 @@ func New(config Config) (Restreamer, error) {
|
||||||
r.store = store.NewDummyStore(store.DummyConfig{})
|
r.store = store.NewDummyStore(store.DummyConfig{})
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.DiskFS != nil {
|
for _, fs := range config.Filesystems {
|
||||||
r.fs.diskfs = rfs.New(rfs.Config{
|
fs := rfs.New(rfs.Config{
|
||||||
FS: config.DiskFS,
|
FS: fs,
|
||||||
Logger: r.logger.WithComponent("Cleanup").WithField("type", "diskfs"),
|
Logger: r.logger.WithComponent("Cleanup"),
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
r.fs.diskfs = rfs.New(rfs.Config{
|
|
||||||
FS: fs.NewDummyFilesystem(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.MemFS != nil {
|
r.fs.list = append(r.fs.list, fs)
|
||||||
r.fs.memfs = rfs.New(rfs.Config{
|
|
||||||
FS: config.MemFS,
|
// Add the diskfs filesystems also to a separate array. We need it later for input and output validation
|
||||||
Logger: r.logger.WithComponent("Cleanup").WithField("type", "memfs"),
|
if fs.Type() == "diskfs" {
|
||||||
})
|
r.fs.diskfs = append(r.fs.diskfs, fs)
|
||||||
} else {
|
}
|
||||||
r.fs.memfs = rfs.New(rfs.Config{
|
|
||||||
FS: fs.NewDummyFilesystem(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.replace == nil {
|
if r.replace == nil {
|
||||||
|
|
@ -186,12 +177,16 @@ func (r *restream) Start() {
|
||||||
r.setCleanup(id, t.config)
|
r.setCleanup(id, t.config)
|
||||||
}
|
}
|
||||||
|
|
||||||
r.fs.diskfs.Start()
|
|
||||||
r.fs.memfs.Start()
|
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
r.fs.stopObserver = cancel
|
r.fs.stopObserver = cancel
|
||||||
go r.observe(ctx, 10*time.Second)
|
|
||||||
|
for _, fs := range r.fs.list {
|
||||||
|
fs.Start()
|
||||||
|
|
||||||
|
if fs.Type() == "diskfs" {
|
||||||
|
go r.observe(ctx, fs, 10*time.Second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
r.stopOnce = sync.Once{}
|
r.stopOnce = sync.Once{}
|
||||||
})
|
})
|
||||||
|
|
@ -215,14 +210,16 @@ func (r *restream) Stop() {
|
||||||
|
|
||||||
r.fs.stopObserver()
|
r.fs.stopObserver()
|
||||||
|
|
||||||
r.fs.diskfs.Stop()
|
// Stop the cleanup jobs
|
||||||
r.fs.memfs.Stop()
|
for _, fs := range r.fs.list {
|
||||||
|
fs.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
r.startOnce = sync.Once{}
|
r.startOnce = sync.Once{}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *restream) observe(ctx context.Context, interval time.Duration) {
|
func (r *restream) observe(ctx context.Context, fs fs.Filesystem, interval time.Duration) {
|
||||||
ticker := time.NewTicker(interval)
|
ticker := time.NewTicker(interval)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
|
@ -231,14 +228,14 @@ func (r *restream) observe(ctx context.Context, interval time.Duration) {
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
size, limit := r.fs.diskfs.Size()
|
size, limit := fs.Size()
|
||||||
isFull := false
|
isFull := false
|
||||||
if limit > 0 && size >= limit {
|
if limit > 0 && size >= limit {
|
||||||
isFull = true
|
isFull = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if isFull {
|
if isFull {
|
||||||
// Stop all tasks that write to disk
|
// Stop all tasks that write to this filesystem
|
||||||
r.lock.Lock()
|
r.lock.Lock()
|
||||||
for id, t := range r.tasks {
|
for id, t := range r.tasks {
|
||||||
if !t.valid {
|
if !t.valid {
|
||||||
|
|
@ -253,7 +250,7 @@ func (r *restream) observe(ctx context.Context, interval time.Duration) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
r.logger.Warn().Log("Shutting down because disk is full")
|
r.logger.Warn().Log("Shutting down because filesystem is full")
|
||||||
r.stopProcess(id)
|
r.stopProcess(id)
|
||||||
}
|
}
|
||||||
r.lock.Unlock()
|
r.lock.Unlock()
|
||||||
|
|
@ -503,34 +500,50 @@ func (r *restream) createTask(config *app.Config) (*task, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *restream) setCleanup(id string, config *app.Config) {
|
func (r *restream) setCleanup(id string, config *app.Config) {
|
||||||
|
rePrefix := regexp.MustCompile(`^([a-z]+):`)
|
||||||
|
|
||||||
for _, output := range config.Output {
|
for _, output := range config.Output {
|
||||||
for _, c := range output.Cleanup {
|
for _, c := range output.Cleanup {
|
||||||
if strings.HasPrefix(c.Pattern, "memfs:") {
|
matches := rePrefix.FindStringSubmatch(c.Pattern)
|
||||||
r.fs.memfs.SetCleanup(id, []rfs.Pattern{
|
if matches == nil {
|
||||||
{
|
continue
|
||||||
Pattern: strings.TrimPrefix(c.Pattern, "memfs:"),
|
}
|
||||||
MaxFiles: c.MaxFiles,
|
|
||||||
MaxFileAge: time.Duration(c.MaxFileAge) * time.Second,
|
name := matches[1]
|
||||||
PurgeOnDelete: c.PurgeOnDelete,
|
|
||||||
},
|
// Support legacy names
|
||||||
})
|
if name == "diskfs" {
|
||||||
} else if strings.HasPrefix(c.Pattern, "diskfs:") {
|
name = "disk"
|
||||||
r.fs.diskfs.SetCleanup(id, []rfs.Pattern{
|
} else if name == "memfs" {
|
||||||
{
|
name = "mem"
|
||||||
Pattern: strings.TrimPrefix(c.Pattern, "diskfs:"),
|
}
|
||||||
MaxFiles: c.MaxFiles,
|
|
||||||
MaxFileAge: time.Duration(c.MaxFileAge) * time.Second,
|
for _, fs := range r.fs.list {
|
||||||
PurgeOnDelete: c.PurgeOnDelete,
|
if fs.Name() != name {
|
||||||
},
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
pattern := rfs.Pattern{
|
||||||
|
Pattern: rePrefix.ReplaceAllString(c.Pattern, ""),
|
||||||
|
MaxFiles: c.MaxFiles,
|
||||||
|
MaxFileAge: time.Duration(c.MaxFileAge) * time.Second,
|
||||||
|
PurgeOnDelete: c.PurgeOnDelete,
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.SetCleanup(id, []rfs.Pattern{
|
||||||
|
pattern,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *restream) unsetCleanup(id string) {
|
func (r *restream) unsetCleanup(id string) {
|
||||||
r.fs.diskfs.UnsetCleanup(id)
|
for _, fs := range r.fs.list {
|
||||||
r.fs.memfs.UnsetCleanup(id)
|
fs.UnsetCleanup(id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *restream) setPlayoutPorts(t *task) error {
|
func (r *restream) setPlayoutPorts(t *task) error {
|
||||||
|
|
@ -619,9 +632,23 @@ func (r *restream) validateConfig(config *app.Config) (bool, error) {
|
||||||
return false, fmt.Errorf("the address for input '#%s:%s' must not be empty", config.ID, io.ID)
|
return false, fmt.Errorf("the address for input '#%s:%s' must not be empty", config.ID, io.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
io.Address, err = r.validateInputAddress(io.Address, r.fs.diskfs.Base())
|
if len(r.fs.diskfs) != 0 {
|
||||||
if err != nil {
|
maxFails := 0
|
||||||
return false, fmt.Errorf("the address for input '#%s:%s' (%s) is invalid: %w", config.ID, io.ID, io.Address, err)
|
for _, fs := range r.fs.diskfs {
|
||||||
|
io.Address, err = r.validateInputAddress(io.Address, fs.Base())
|
||||||
|
if err != nil {
|
||||||
|
maxFails++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if maxFails == len(r.fs.diskfs) {
|
||||||
|
return false, fmt.Errorf("the address for input '#%s:%s' (%s) is invalid: %w", config.ID, io.ID, io.Address, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
io.Address, err = r.validateInputAddress(io.Address, "/")
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("the address for input '#%s:%s' (%s) is invalid: %w", config.ID, io.ID, io.Address, err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -651,15 +678,33 @@ func (r *restream) validateConfig(config *app.Config) (bool, error) {
|
||||||
return false, fmt.Errorf("the address for output '#%s:%s' must not be empty", config.ID, io.ID)
|
return false, fmt.Errorf("the address for output '#%s:%s' must not be empty", config.ID, io.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
isFile := false
|
if len(r.fs.diskfs) != 0 {
|
||||||
|
maxFails := 0
|
||||||
|
for _, fs := range r.fs.diskfs {
|
||||||
|
isFile := false
|
||||||
|
io.Address, isFile, err = r.validateOutputAddress(io.Address, fs.Base())
|
||||||
|
if err != nil {
|
||||||
|
maxFails++
|
||||||
|
}
|
||||||
|
|
||||||
io.Address, isFile, err = r.validateOutputAddress(io.Address, r.fs.diskfs.Base())
|
if isFile {
|
||||||
if err != nil {
|
hasFiles = true
|
||||||
return false, fmt.Errorf("the address for output '#%s:%s' is invalid: %w", config.ID, io.ID, err)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if isFile {
|
if maxFails == len(r.fs.diskfs) {
|
||||||
hasFiles = true
|
return false, fmt.Errorf("the address for output '#%s:%s' is invalid: %w", config.ID, io.ID, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
isFile := false
|
||||||
|
io.Address, isFile, err = r.validateOutputAddress(io.Address, "/")
|
||||||
|
if err != nil {
|
||||||
|
return false, fmt.Errorf("the address for output '#%s:%s' is invalid: %w", config.ID, io.ID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if isFile {
|
||||||
|
hasFiles = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
21
vendor/github.com/dustin/go-humanize/.travis.yml
generated
vendored
Normal file
21
vendor/github.com/dustin/go-humanize/.travis.yml
generated
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
sudo: false
|
||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- 1.3.x
|
||||||
|
- 1.5.x
|
||||||
|
- 1.6.x
|
||||||
|
- 1.7.x
|
||||||
|
- 1.8.x
|
||||||
|
- 1.9.x
|
||||||
|
- master
|
||||||
|
matrix:
|
||||||
|
allow_failures:
|
||||||
|
- go: master
|
||||||
|
fast_finish: true
|
||||||
|
install:
|
||||||
|
- # Do nothing. This is needed to prevent default install action "go get -t -v ./..." from happening here (we want it to happen inside script step).
|
||||||
|
script:
|
||||||
|
- go get -t -v ./...
|
||||||
|
- diff -u <(echo -n) <(gofmt -d -s .)
|
||||||
|
- go tool vet .
|
||||||
|
- go test -v -race ./...
|
||||||
21
vendor/github.com/dustin/go-humanize/LICENSE
generated
vendored
Normal file
21
vendor/github.com/dustin/go-humanize/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
Copyright (c) 2005-2008 Dustin Sallings <dustin@spy.net>
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
<http://www.opensource.org/licenses/mit-license.php>
|
||||||
124
vendor/github.com/dustin/go-humanize/README.markdown
generated
vendored
Normal file
124
vendor/github.com/dustin/go-humanize/README.markdown
generated
vendored
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
# Humane Units [](https://travis-ci.org/dustin/go-humanize) [](https://godoc.org/github.com/dustin/go-humanize)
|
||||||
|
|
||||||
|
Just a few functions for helping humanize times and sizes.
|
||||||
|
|
||||||
|
`go get` it as `github.com/dustin/go-humanize`, import it as
|
||||||
|
`"github.com/dustin/go-humanize"`, use it as `humanize`.
|
||||||
|
|
||||||
|
See [godoc](https://godoc.org/github.com/dustin/go-humanize) for
|
||||||
|
complete documentation.
|
||||||
|
|
||||||
|
## Sizes
|
||||||
|
|
||||||
|
This lets you take numbers like `82854982` and convert them to useful
|
||||||
|
strings like, `83 MB` or `79 MiB` (whichever you prefer).
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```go
|
||||||
|
fmt.Printf("That file is %s.", humanize.Bytes(82854982)) // That file is 83 MB.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Times
|
||||||
|
|
||||||
|
This lets you take a `time.Time` and spit it out in relative terms.
|
||||||
|
For example, `12 seconds ago` or `3 days from now`.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```go
|
||||||
|
fmt.Printf("This was touched %s.", humanize.Time(someTimeInstance)) // This was touched 7 hours ago.
|
||||||
|
```
|
||||||
|
|
||||||
|
Thanks to Kyle Lemons for the time implementation from an IRC
|
||||||
|
conversation one day. It's pretty neat.
|
||||||
|
|
||||||
|
## Ordinals
|
||||||
|
|
||||||
|
From a [mailing list discussion][odisc] where a user wanted to be able
|
||||||
|
to label ordinals.
|
||||||
|
|
||||||
|
0 -> 0th
|
||||||
|
1 -> 1st
|
||||||
|
2 -> 2nd
|
||||||
|
3 -> 3rd
|
||||||
|
4 -> 4th
|
||||||
|
[...]
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```go
|
||||||
|
fmt.Printf("You're my %s best friend.", humanize.Ordinal(193)) // You are my 193rd best friend.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Commas
|
||||||
|
|
||||||
|
Want to shove commas into numbers? Be my guest.
|
||||||
|
|
||||||
|
0 -> 0
|
||||||
|
100 -> 100
|
||||||
|
1000 -> 1,000
|
||||||
|
1000000000 -> 1,000,000,000
|
||||||
|
-100000 -> -100,000
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```go
|
||||||
|
fmt.Printf("You owe $%s.\n", humanize.Comma(6582491)) // You owe $6,582,491.
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ftoa
|
||||||
|
|
||||||
|
Nicer float64 formatter that removes trailing zeros.
|
||||||
|
|
||||||
|
```go
|
||||||
|
fmt.Printf("%f", 2.24) // 2.240000
|
||||||
|
fmt.Printf("%s", humanize.Ftoa(2.24)) // 2.24
|
||||||
|
fmt.Printf("%f", 2.0) // 2.000000
|
||||||
|
fmt.Printf("%s", humanize.Ftoa(2.0)) // 2
|
||||||
|
```
|
||||||
|
|
||||||
|
## SI notation
|
||||||
|
|
||||||
|
Format numbers with [SI notation][sinotation].
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```go
|
||||||
|
humanize.SI(0.00000000223, "M") // 2.23 nM
|
||||||
|
```
|
||||||
|
|
||||||
|
## English-specific functions
|
||||||
|
|
||||||
|
The following functions are in the `humanize/english` subpackage.
|
||||||
|
|
||||||
|
### Plurals
|
||||||
|
|
||||||
|
Simple English pluralization
|
||||||
|
|
||||||
|
```go
|
||||||
|
english.PluralWord(1, "object", "") // object
|
||||||
|
english.PluralWord(42, "object", "") // objects
|
||||||
|
english.PluralWord(2, "bus", "") // buses
|
||||||
|
english.PluralWord(99, "locus", "loci") // loci
|
||||||
|
|
||||||
|
english.Plural(1, "object", "") // 1 object
|
||||||
|
english.Plural(42, "object", "") // 42 objects
|
||||||
|
english.Plural(2, "bus", "") // 2 buses
|
||||||
|
english.Plural(99, "locus", "loci") // 99 loci
|
||||||
|
```
|
||||||
|
|
||||||
|
### Word series
|
||||||
|
|
||||||
|
Format comma-separated words lists with conjuctions:
|
||||||
|
|
||||||
|
```go
|
||||||
|
english.WordSeries([]string{"foo"}, "and") // foo
|
||||||
|
english.WordSeries([]string{"foo", "bar"}, "and") // foo and bar
|
||||||
|
english.WordSeries([]string{"foo", "bar", "baz"}, "and") // foo, bar and baz
|
||||||
|
|
||||||
|
english.OxfordWordSeries([]string{"foo", "bar", "baz"}, "and") // foo, bar, and baz
|
||||||
|
```
|
||||||
|
|
||||||
|
[odisc]: https://groups.google.com/d/topic/golang-nuts/l8NhI74jl-4/discussion
|
||||||
|
[sinotation]: http://en.wikipedia.org/wiki/Metric_prefix
|
||||||
31
vendor/github.com/dustin/go-humanize/big.go
generated
vendored
Normal file
31
vendor/github.com/dustin/go-humanize/big.go
generated
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
package humanize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
)
|
||||||
|
|
||||||
|
// order of magnitude (to a max order)
|
||||||
|
func oomm(n, b *big.Int, maxmag int) (float64, int) {
|
||||||
|
mag := 0
|
||||||
|
m := &big.Int{}
|
||||||
|
for n.Cmp(b) >= 0 {
|
||||||
|
n.DivMod(n, b, m)
|
||||||
|
mag++
|
||||||
|
if mag == maxmag && maxmag >= 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return float64(n.Int64()) + (float64(m.Int64()) / float64(b.Int64())), mag
|
||||||
|
}
|
||||||
|
|
||||||
|
// total order of magnitude
|
||||||
|
// (same as above, but with no upper limit)
|
||||||
|
func oom(n, b *big.Int) (float64, int) {
|
||||||
|
mag := 0
|
||||||
|
m := &big.Int{}
|
||||||
|
for n.Cmp(b) >= 0 {
|
||||||
|
n.DivMod(n, b, m)
|
||||||
|
mag++
|
||||||
|
}
|
||||||
|
return float64(n.Int64()) + (float64(m.Int64()) / float64(b.Int64())), mag
|
||||||
|
}
|
||||||
173
vendor/github.com/dustin/go-humanize/bigbytes.go
generated
vendored
Normal file
173
vendor/github.com/dustin/go-humanize/bigbytes.go
generated
vendored
Normal file
|
|
@ -0,0 +1,173 @@
|
||||||
|
package humanize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
bigIECExp = big.NewInt(1024)
|
||||||
|
|
||||||
|
// BigByte is one byte in bit.Ints
|
||||||
|
BigByte = big.NewInt(1)
|
||||||
|
// BigKiByte is 1,024 bytes in bit.Ints
|
||||||
|
BigKiByte = (&big.Int{}).Mul(BigByte, bigIECExp)
|
||||||
|
// BigMiByte is 1,024 k bytes in bit.Ints
|
||||||
|
BigMiByte = (&big.Int{}).Mul(BigKiByte, bigIECExp)
|
||||||
|
// BigGiByte is 1,024 m bytes in bit.Ints
|
||||||
|
BigGiByte = (&big.Int{}).Mul(BigMiByte, bigIECExp)
|
||||||
|
// BigTiByte is 1,024 g bytes in bit.Ints
|
||||||
|
BigTiByte = (&big.Int{}).Mul(BigGiByte, bigIECExp)
|
||||||
|
// BigPiByte is 1,024 t bytes in bit.Ints
|
||||||
|
BigPiByte = (&big.Int{}).Mul(BigTiByte, bigIECExp)
|
||||||
|
// BigEiByte is 1,024 p bytes in bit.Ints
|
||||||
|
BigEiByte = (&big.Int{}).Mul(BigPiByte, bigIECExp)
|
||||||
|
// BigZiByte is 1,024 e bytes in bit.Ints
|
||||||
|
BigZiByte = (&big.Int{}).Mul(BigEiByte, bigIECExp)
|
||||||
|
// BigYiByte is 1,024 z bytes in bit.Ints
|
||||||
|
BigYiByte = (&big.Int{}).Mul(BigZiByte, bigIECExp)
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
bigSIExp = big.NewInt(1000)
|
||||||
|
|
||||||
|
// BigSIByte is one SI byte in big.Ints
|
||||||
|
BigSIByte = big.NewInt(1)
|
||||||
|
// BigKByte is 1,000 SI bytes in big.Ints
|
||||||
|
BigKByte = (&big.Int{}).Mul(BigSIByte, bigSIExp)
|
||||||
|
// BigMByte is 1,000 SI k bytes in big.Ints
|
||||||
|
BigMByte = (&big.Int{}).Mul(BigKByte, bigSIExp)
|
||||||
|
// BigGByte is 1,000 SI m bytes in big.Ints
|
||||||
|
BigGByte = (&big.Int{}).Mul(BigMByte, bigSIExp)
|
||||||
|
// BigTByte is 1,000 SI g bytes in big.Ints
|
||||||
|
BigTByte = (&big.Int{}).Mul(BigGByte, bigSIExp)
|
||||||
|
// BigPByte is 1,000 SI t bytes in big.Ints
|
||||||
|
BigPByte = (&big.Int{}).Mul(BigTByte, bigSIExp)
|
||||||
|
// BigEByte is 1,000 SI p bytes in big.Ints
|
||||||
|
BigEByte = (&big.Int{}).Mul(BigPByte, bigSIExp)
|
||||||
|
// BigZByte is 1,000 SI e bytes in big.Ints
|
||||||
|
BigZByte = (&big.Int{}).Mul(BigEByte, bigSIExp)
|
||||||
|
// BigYByte is 1,000 SI z bytes in big.Ints
|
||||||
|
BigYByte = (&big.Int{}).Mul(BigZByte, bigSIExp)
|
||||||
|
)
|
||||||
|
|
||||||
|
var bigBytesSizeTable = map[string]*big.Int{
|
||||||
|
"b": BigByte,
|
||||||
|
"kib": BigKiByte,
|
||||||
|
"kb": BigKByte,
|
||||||
|
"mib": BigMiByte,
|
||||||
|
"mb": BigMByte,
|
||||||
|
"gib": BigGiByte,
|
||||||
|
"gb": BigGByte,
|
||||||
|
"tib": BigTiByte,
|
||||||
|
"tb": BigTByte,
|
||||||
|
"pib": BigPiByte,
|
||||||
|
"pb": BigPByte,
|
||||||
|
"eib": BigEiByte,
|
||||||
|
"eb": BigEByte,
|
||||||
|
"zib": BigZiByte,
|
||||||
|
"zb": BigZByte,
|
||||||
|
"yib": BigYiByte,
|
||||||
|
"yb": BigYByte,
|
||||||
|
// Without suffix
|
||||||
|
"": BigByte,
|
||||||
|
"ki": BigKiByte,
|
||||||
|
"k": BigKByte,
|
||||||
|
"mi": BigMiByte,
|
||||||
|
"m": BigMByte,
|
||||||
|
"gi": BigGiByte,
|
||||||
|
"g": BigGByte,
|
||||||
|
"ti": BigTiByte,
|
||||||
|
"t": BigTByte,
|
||||||
|
"pi": BigPiByte,
|
||||||
|
"p": BigPByte,
|
||||||
|
"ei": BigEiByte,
|
||||||
|
"e": BigEByte,
|
||||||
|
"z": BigZByte,
|
||||||
|
"zi": BigZiByte,
|
||||||
|
"y": BigYByte,
|
||||||
|
"yi": BigYiByte,
|
||||||
|
}
|
||||||
|
|
||||||
|
var ten = big.NewInt(10)
|
||||||
|
|
||||||
|
func humanateBigBytes(s, base *big.Int, sizes []string) string {
|
||||||
|
if s.Cmp(ten) < 0 {
|
||||||
|
return fmt.Sprintf("%d B", s)
|
||||||
|
}
|
||||||
|
c := (&big.Int{}).Set(s)
|
||||||
|
val, mag := oomm(c, base, len(sizes)-1)
|
||||||
|
suffix := sizes[mag]
|
||||||
|
f := "%.0f %s"
|
||||||
|
if val < 10 {
|
||||||
|
f = "%.1f %s"
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf(f, val, suffix)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// BigBytes produces a human readable representation of an SI size.
|
||||||
|
//
|
||||||
|
// See also: ParseBigBytes.
|
||||||
|
//
|
||||||
|
// BigBytes(82854982) -> 83 MB
|
||||||
|
func BigBytes(s *big.Int) string {
|
||||||
|
sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}
|
||||||
|
return humanateBigBytes(s, bigSIExp, sizes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BigIBytes produces a human readable representation of an IEC size.
|
||||||
|
//
|
||||||
|
// See also: ParseBigBytes.
|
||||||
|
//
|
||||||
|
// BigIBytes(82854982) -> 79 MiB
|
||||||
|
func BigIBytes(s *big.Int) string {
|
||||||
|
sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"}
|
||||||
|
return humanateBigBytes(s, bigIECExp, sizes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseBigBytes parses a string representation of bytes into the number
|
||||||
|
// of bytes it represents.
|
||||||
|
//
|
||||||
|
// See also: BigBytes, BigIBytes.
|
||||||
|
//
|
||||||
|
// ParseBigBytes("42 MB") -> 42000000, nil
|
||||||
|
// ParseBigBytes("42 mib") -> 44040192, nil
|
||||||
|
func ParseBigBytes(s string) (*big.Int, error) {
|
||||||
|
lastDigit := 0
|
||||||
|
hasComma := false
|
||||||
|
for _, r := range s {
|
||||||
|
if !(unicode.IsDigit(r) || r == '.' || r == ',') {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if r == ',' {
|
||||||
|
hasComma = true
|
||||||
|
}
|
||||||
|
lastDigit++
|
||||||
|
}
|
||||||
|
|
||||||
|
num := s[:lastDigit]
|
||||||
|
if hasComma {
|
||||||
|
num = strings.Replace(num, ",", "", -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
val := &big.Rat{}
|
||||||
|
_, err := fmt.Sscanf(num, "%f", val)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
|
||||||
|
if m, ok := bigBytesSizeTable[extra]; ok {
|
||||||
|
mv := (&big.Rat{}).SetInt(m)
|
||||||
|
val.Mul(val, mv)
|
||||||
|
rv := &big.Int{}
|
||||||
|
rv.Div(val.Num(), val.Denom())
|
||||||
|
return rv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("unhandled size name: %v", extra)
|
||||||
|
}
|
||||||
143
vendor/github.com/dustin/go-humanize/bytes.go
generated
vendored
Normal file
143
vendor/github.com/dustin/go-humanize/bytes.go
generated
vendored
Normal file
|
|
@ -0,0 +1,143 @@
|
||||||
|
package humanize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IEC Sizes.
|
||||||
|
// kibis of bits
|
||||||
|
const (
|
||||||
|
Byte = 1 << (iota * 10)
|
||||||
|
KiByte
|
||||||
|
MiByte
|
||||||
|
GiByte
|
||||||
|
TiByte
|
||||||
|
PiByte
|
||||||
|
EiByte
|
||||||
|
)
|
||||||
|
|
||||||
|
// SI Sizes.
|
||||||
|
const (
|
||||||
|
IByte = 1
|
||||||
|
KByte = IByte * 1000
|
||||||
|
MByte = KByte * 1000
|
||||||
|
GByte = MByte * 1000
|
||||||
|
TByte = GByte * 1000
|
||||||
|
PByte = TByte * 1000
|
||||||
|
EByte = PByte * 1000
|
||||||
|
)
|
||||||
|
|
||||||
|
var bytesSizeTable = map[string]uint64{
|
||||||
|
"b": Byte,
|
||||||
|
"kib": KiByte,
|
||||||
|
"kb": KByte,
|
||||||
|
"mib": MiByte,
|
||||||
|
"mb": MByte,
|
||||||
|
"gib": GiByte,
|
||||||
|
"gb": GByte,
|
||||||
|
"tib": TiByte,
|
||||||
|
"tb": TByte,
|
||||||
|
"pib": PiByte,
|
||||||
|
"pb": PByte,
|
||||||
|
"eib": EiByte,
|
||||||
|
"eb": EByte,
|
||||||
|
// Without suffix
|
||||||
|
"": Byte,
|
||||||
|
"ki": KiByte,
|
||||||
|
"k": KByte,
|
||||||
|
"mi": MiByte,
|
||||||
|
"m": MByte,
|
||||||
|
"gi": GiByte,
|
||||||
|
"g": GByte,
|
||||||
|
"ti": TiByte,
|
||||||
|
"t": TByte,
|
||||||
|
"pi": PiByte,
|
||||||
|
"p": PByte,
|
||||||
|
"ei": EiByte,
|
||||||
|
"e": EByte,
|
||||||
|
}
|
||||||
|
|
||||||
|
func logn(n, b float64) float64 {
|
||||||
|
return math.Log(n) / math.Log(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func humanateBytes(s uint64, base float64, sizes []string) string {
|
||||||
|
if s < 10 {
|
||||||
|
return fmt.Sprintf("%d B", s)
|
||||||
|
}
|
||||||
|
e := math.Floor(logn(float64(s), base))
|
||||||
|
suffix := sizes[int(e)]
|
||||||
|
val := math.Floor(float64(s)/math.Pow(base, e)*10+0.5) / 10
|
||||||
|
f := "%.0f %s"
|
||||||
|
if val < 10 {
|
||||||
|
f = "%.1f %s"
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf(f, val, suffix)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes produces a human readable representation of an SI size.
|
||||||
|
//
|
||||||
|
// See also: ParseBytes.
|
||||||
|
//
|
||||||
|
// Bytes(82854982) -> 83 MB
|
||||||
|
func Bytes(s uint64) string {
|
||||||
|
sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"}
|
||||||
|
return humanateBytes(s, 1000, sizes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// IBytes produces a human readable representation of an IEC size.
|
||||||
|
//
|
||||||
|
// See also: ParseBytes.
|
||||||
|
//
|
||||||
|
// IBytes(82854982) -> 79 MiB
|
||||||
|
func IBytes(s uint64) string {
|
||||||
|
sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
|
||||||
|
return humanateBytes(s, 1024, sizes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseBytes parses a string representation of bytes into the number
|
||||||
|
// of bytes it represents.
|
||||||
|
//
|
||||||
|
// See Also: Bytes, IBytes.
|
||||||
|
//
|
||||||
|
// ParseBytes("42 MB") -> 42000000, nil
|
||||||
|
// ParseBytes("42 mib") -> 44040192, nil
|
||||||
|
func ParseBytes(s string) (uint64, error) {
|
||||||
|
lastDigit := 0
|
||||||
|
hasComma := false
|
||||||
|
for _, r := range s {
|
||||||
|
if !(unicode.IsDigit(r) || r == '.' || r == ',') {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if r == ',' {
|
||||||
|
hasComma = true
|
||||||
|
}
|
||||||
|
lastDigit++
|
||||||
|
}
|
||||||
|
|
||||||
|
num := s[:lastDigit]
|
||||||
|
if hasComma {
|
||||||
|
num = strings.Replace(num, ",", "", -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := strconv.ParseFloat(num, 64)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
extra := strings.ToLower(strings.TrimSpace(s[lastDigit:]))
|
||||||
|
if m, ok := bytesSizeTable[extra]; ok {
|
||||||
|
f *= float64(m)
|
||||||
|
if f >= math.MaxUint64 {
|
||||||
|
return 0, fmt.Errorf("too large: %v", s)
|
||||||
|
}
|
||||||
|
return uint64(f), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0, fmt.Errorf("unhandled size name: %v", extra)
|
||||||
|
}
|
||||||
116
vendor/github.com/dustin/go-humanize/comma.go
generated
vendored
Normal file
116
vendor/github.com/dustin/go-humanize/comma.go
generated
vendored
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
package humanize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Comma produces a string form of the given number in base 10 with
|
||||||
|
// commas after every three orders of magnitude.
|
||||||
|
//
|
||||||
|
// e.g. Comma(834142) -> 834,142
|
||||||
|
func Comma(v int64) string {
|
||||||
|
sign := ""
|
||||||
|
|
||||||
|
// Min int64 can't be negated to a usable value, so it has to be special cased.
|
||||||
|
if v == math.MinInt64 {
|
||||||
|
return "-9,223,372,036,854,775,808"
|
||||||
|
}
|
||||||
|
|
||||||
|
if v < 0 {
|
||||||
|
sign = "-"
|
||||||
|
v = 0 - v
|
||||||
|
}
|
||||||
|
|
||||||
|
parts := []string{"", "", "", "", "", "", ""}
|
||||||
|
j := len(parts) - 1
|
||||||
|
|
||||||
|
for v > 999 {
|
||||||
|
parts[j] = strconv.FormatInt(v%1000, 10)
|
||||||
|
switch len(parts[j]) {
|
||||||
|
case 2:
|
||||||
|
parts[j] = "0" + parts[j]
|
||||||
|
case 1:
|
||||||
|
parts[j] = "00" + parts[j]
|
||||||
|
}
|
||||||
|
v = v / 1000
|
||||||
|
j--
|
||||||
|
}
|
||||||
|
parts[j] = strconv.Itoa(int(v))
|
||||||
|
return sign + strings.Join(parts[j:], ",")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Commaf produces a string form of the given number in base 10 with
|
||||||
|
// commas after every three orders of magnitude.
|
||||||
|
//
|
||||||
|
// e.g. Commaf(834142.32) -> 834,142.32
|
||||||
|
func Commaf(v float64) string {
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
if v < 0 {
|
||||||
|
buf.Write([]byte{'-'})
|
||||||
|
v = 0 - v
|
||||||
|
}
|
||||||
|
|
||||||
|
comma := []byte{','}
|
||||||
|
|
||||||
|
parts := strings.Split(strconv.FormatFloat(v, 'f', -1, 64), ".")
|
||||||
|
pos := 0
|
||||||
|
if len(parts[0])%3 != 0 {
|
||||||
|
pos += len(parts[0]) % 3
|
||||||
|
buf.WriteString(parts[0][:pos])
|
||||||
|
buf.Write(comma)
|
||||||
|
}
|
||||||
|
for ; pos < len(parts[0]); pos += 3 {
|
||||||
|
buf.WriteString(parts[0][pos : pos+3])
|
||||||
|
buf.Write(comma)
|
||||||
|
}
|
||||||
|
buf.Truncate(buf.Len() - 1)
|
||||||
|
|
||||||
|
if len(parts) > 1 {
|
||||||
|
buf.Write([]byte{'.'})
|
||||||
|
buf.WriteString(parts[1])
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommafWithDigits works like the Commaf but limits the resulting
|
||||||
|
// string to the given number of decimal places.
|
||||||
|
//
|
||||||
|
// e.g. CommafWithDigits(834142.32, 1) -> 834,142.3
|
||||||
|
func CommafWithDigits(f float64, decimals int) string {
|
||||||
|
return stripTrailingDigits(Commaf(f), decimals)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BigComma produces a string form of the given big.Int in base 10
|
||||||
|
// with commas after every three orders of magnitude.
|
||||||
|
func BigComma(b *big.Int) string {
|
||||||
|
sign := ""
|
||||||
|
if b.Sign() < 0 {
|
||||||
|
sign = "-"
|
||||||
|
b.Abs(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
athousand := big.NewInt(1000)
|
||||||
|
c := (&big.Int{}).Set(b)
|
||||||
|
_, m := oom(c, athousand)
|
||||||
|
parts := make([]string, m+1)
|
||||||
|
j := len(parts) - 1
|
||||||
|
|
||||||
|
mod := &big.Int{}
|
||||||
|
for b.Cmp(athousand) >= 0 {
|
||||||
|
b.DivMod(b, athousand, mod)
|
||||||
|
parts[j] = strconv.FormatInt(mod.Int64(), 10)
|
||||||
|
switch len(parts[j]) {
|
||||||
|
case 2:
|
||||||
|
parts[j] = "0" + parts[j]
|
||||||
|
case 1:
|
||||||
|
parts[j] = "00" + parts[j]
|
||||||
|
}
|
||||||
|
j--
|
||||||
|
}
|
||||||
|
parts[j] = strconv.Itoa(int(b.Int64()))
|
||||||
|
return sign + strings.Join(parts[j:], ",")
|
||||||
|
}
|
||||||
40
vendor/github.com/dustin/go-humanize/commaf.go
generated
vendored
Normal file
40
vendor/github.com/dustin/go-humanize/commaf.go
generated
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
// +build go1.6
|
||||||
|
|
||||||
|
package humanize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"math/big"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BigCommaf produces a string form of the given big.Float in base 10
|
||||||
|
// with commas after every three orders of magnitude.
|
||||||
|
func BigCommaf(v *big.Float) string {
|
||||||
|
buf := &bytes.Buffer{}
|
||||||
|
if v.Sign() < 0 {
|
||||||
|
buf.Write([]byte{'-'})
|
||||||
|
v.Abs(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
comma := []byte{','}
|
||||||
|
|
||||||
|
parts := strings.Split(v.Text('f', -1), ".")
|
||||||
|
pos := 0
|
||||||
|
if len(parts[0])%3 != 0 {
|
||||||
|
pos += len(parts[0]) % 3
|
||||||
|
buf.WriteString(parts[0][:pos])
|
||||||
|
buf.Write(comma)
|
||||||
|
}
|
||||||
|
for ; pos < len(parts[0]); pos += 3 {
|
||||||
|
buf.WriteString(parts[0][pos : pos+3])
|
||||||
|
buf.Write(comma)
|
||||||
|
}
|
||||||
|
buf.Truncate(buf.Len() - 1)
|
||||||
|
|
||||||
|
if len(parts) > 1 {
|
||||||
|
buf.Write([]byte{'.'})
|
||||||
|
buf.WriteString(parts[1])
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
46
vendor/github.com/dustin/go-humanize/ftoa.go
generated
vendored
Normal file
46
vendor/github.com/dustin/go-humanize/ftoa.go
generated
vendored
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
package humanize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func stripTrailingZeros(s string) string {
|
||||||
|
offset := len(s) - 1
|
||||||
|
for offset > 0 {
|
||||||
|
if s[offset] == '.' {
|
||||||
|
offset--
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if s[offset] != '0' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
offset--
|
||||||
|
}
|
||||||
|
return s[:offset+1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func stripTrailingDigits(s string, digits int) string {
|
||||||
|
if i := strings.Index(s, "."); i >= 0 {
|
||||||
|
if digits <= 0 {
|
||||||
|
return s[:i]
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
if i+digits >= len(s) {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
return s[:i+digits]
|
||||||
|
}
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ftoa converts a float to a string with no trailing zeros.
|
||||||
|
func Ftoa(num float64) string {
|
||||||
|
return stripTrailingZeros(strconv.FormatFloat(num, 'f', 6, 64))
|
||||||
|
}
|
||||||
|
|
||||||
|
// FtoaWithDigits converts a float to a string but limits the resulting string
|
||||||
|
// to the given number of decimal places, and no trailing zeros.
|
||||||
|
func FtoaWithDigits(num float64, digits int) string {
|
||||||
|
return stripTrailingZeros(stripTrailingDigits(strconv.FormatFloat(num, 'f', 6, 64), digits))
|
||||||
|
}
|
||||||
8
vendor/github.com/dustin/go-humanize/humanize.go
generated
vendored
Normal file
8
vendor/github.com/dustin/go-humanize/humanize.go
generated
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
/*
|
||||||
|
Package humanize converts boring ugly numbers to human-friendly strings and back.
|
||||||
|
|
||||||
|
Durations can be turned into strings such as "3 days ago", numbers
|
||||||
|
representing sizes like 82854982 into useful strings like, "83 MB" or
|
||||||
|
"79 MiB" (whichever you prefer).
|
||||||
|
*/
|
||||||
|
package humanize
|
||||||
192
vendor/github.com/dustin/go-humanize/number.go
generated
vendored
Normal file
192
vendor/github.com/dustin/go-humanize/number.go
generated
vendored
Normal file
|
|
@ -0,0 +1,192 @@
|
||||||
|
package humanize
|
||||||
|
|
||||||
|
/*
|
||||||
|
Slightly adapted from the source to fit go-humanize.
|
||||||
|
|
||||||
|
Author: https://github.com/gorhill
|
||||||
|
Source: https://gist.github.com/gorhill/5285193
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
renderFloatPrecisionMultipliers = [...]float64{
|
||||||
|
1,
|
||||||
|
10,
|
||||||
|
100,
|
||||||
|
1000,
|
||||||
|
10000,
|
||||||
|
100000,
|
||||||
|
1000000,
|
||||||
|
10000000,
|
||||||
|
100000000,
|
||||||
|
1000000000,
|
||||||
|
}
|
||||||
|
|
||||||
|
renderFloatPrecisionRounders = [...]float64{
|
||||||
|
0.5,
|
||||||
|
0.05,
|
||||||
|
0.005,
|
||||||
|
0.0005,
|
||||||
|
0.00005,
|
||||||
|
0.000005,
|
||||||
|
0.0000005,
|
||||||
|
0.00000005,
|
||||||
|
0.000000005,
|
||||||
|
0.0000000005,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// FormatFloat produces a formatted number as string based on the following user-specified criteria:
|
||||||
|
// * thousands separator
|
||||||
|
// * decimal separator
|
||||||
|
// * decimal precision
|
||||||
|
//
|
||||||
|
// Usage: s := RenderFloat(format, n)
|
||||||
|
// The format parameter tells how to render the number n.
|
||||||
|
//
|
||||||
|
// See examples: http://play.golang.org/p/LXc1Ddm1lJ
|
||||||
|
//
|
||||||
|
// Examples of format strings, given n = 12345.6789:
|
||||||
|
// "#,###.##" => "12,345.67"
|
||||||
|
// "#,###." => "12,345"
|
||||||
|
// "#,###" => "12345,678"
|
||||||
|
// "#\u202F###,##" => "12 345,68"
|
||||||
|
// "#.###,###### => 12.345,678900
|
||||||
|
// "" (aka default format) => 12,345.67
|
||||||
|
//
|
||||||
|
// The highest precision allowed is 9 digits after the decimal symbol.
|
||||||
|
// There is also a version for integer number, FormatInteger(),
|
||||||
|
// which is convenient for calls within template.
|
||||||
|
func FormatFloat(format string, n float64) string {
|
||||||
|
// Special cases:
|
||||||
|
// NaN = "NaN"
|
||||||
|
// +Inf = "+Infinity"
|
||||||
|
// -Inf = "-Infinity"
|
||||||
|
if math.IsNaN(n) {
|
||||||
|
return "NaN"
|
||||||
|
}
|
||||||
|
if n > math.MaxFloat64 {
|
||||||
|
return "Infinity"
|
||||||
|
}
|
||||||
|
if n < -math.MaxFloat64 {
|
||||||
|
return "-Infinity"
|
||||||
|
}
|
||||||
|
|
||||||
|
// default format
|
||||||
|
precision := 2
|
||||||
|
decimalStr := "."
|
||||||
|
thousandStr := ","
|
||||||
|
positiveStr := ""
|
||||||
|
negativeStr := "-"
|
||||||
|
|
||||||
|
if len(format) > 0 {
|
||||||
|
format := []rune(format)
|
||||||
|
|
||||||
|
// If there is an explicit format directive,
|
||||||
|
// then default values are these:
|
||||||
|
precision = 9
|
||||||
|
thousandStr = ""
|
||||||
|
|
||||||
|
// collect indices of meaningful formatting directives
|
||||||
|
formatIndx := []int{}
|
||||||
|
for i, char := range format {
|
||||||
|
if char != '#' && char != '0' {
|
||||||
|
formatIndx = append(formatIndx, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(formatIndx) > 0 {
|
||||||
|
// Directive at index 0:
|
||||||
|
// Must be a '+'
|
||||||
|
// Raise an error if not the case
|
||||||
|
// index: 0123456789
|
||||||
|
// +0.000,000
|
||||||
|
// +000,000.0
|
||||||
|
// +0000.00
|
||||||
|
// +0000
|
||||||
|
if formatIndx[0] == 0 {
|
||||||
|
if format[formatIndx[0]] != '+' {
|
||||||
|
panic("RenderFloat(): invalid positive sign directive")
|
||||||
|
}
|
||||||
|
positiveStr = "+"
|
||||||
|
formatIndx = formatIndx[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Two directives:
|
||||||
|
// First is thousands separator
|
||||||
|
// Raise an error if not followed by 3-digit
|
||||||
|
// 0123456789
|
||||||
|
// 0.000,000
|
||||||
|
// 000,000.00
|
||||||
|
if len(formatIndx) == 2 {
|
||||||
|
if (formatIndx[1] - formatIndx[0]) != 4 {
|
||||||
|
panic("RenderFloat(): thousands separator directive must be followed by 3 digit-specifiers")
|
||||||
|
}
|
||||||
|
thousandStr = string(format[formatIndx[0]])
|
||||||
|
formatIndx = formatIndx[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// One directive:
|
||||||
|
// Directive is decimal separator
|
||||||
|
// The number of digit-specifier following the separator indicates wanted precision
|
||||||
|
// 0123456789
|
||||||
|
// 0.00
|
||||||
|
// 000,0000
|
||||||
|
if len(formatIndx) == 1 {
|
||||||
|
decimalStr = string(format[formatIndx[0]])
|
||||||
|
precision = len(format) - formatIndx[0] - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate sign part
|
||||||
|
var signStr string
|
||||||
|
if n >= 0.000000001 {
|
||||||
|
signStr = positiveStr
|
||||||
|
} else if n <= -0.000000001 {
|
||||||
|
signStr = negativeStr
|
||||||
|
n = -n
|
||||||
|
} else {
|
||||||
|
signStr = ""
|
||||||
|
n = 0.0
|
||||||
|
}
|
||||||
|
|
||||||
|
// split number into integer and fractional parts
|
||||||
|
intf, fracf := math.Modf(n + renderFloatPrecisionRounders[precision])
|
||||||
|
|
||||||
|
// generate integer part string
|
||||||
|
intStr := strconv.FormatInt(int64(intf), 10)
|
||||||
|
|
||||||
|
// add thousand separator if required
|
||||||
|
if len(thousandStr) > 0 {
|
||||||
|
for i := len(intStr); i > 3; {
|
||||||
|
i -= 3
|
||||||
|
intStr = intStr[:i] + thousandStr + intStr[i:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no fractional part, we can leave now
|
||||||
|
if precision == 0 {
|
||||||
|
return signStr + intStr
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate fractional part
|
||||||
|
fracStr := strconv.Itoa(int(fracf * renderFloatPrecisionMultipliers[precision]))
|
||||||
|
// may need padding
|
||||||
|
if len(fracStr) < precision {
|
||||||
|
fracStr = "000000000000000"[:precision-len(fracStr)] + fracStr
|
||||||
|
}
|
||||||
|
|
||||||
|
return signStr + intStr + decimalStr + fracStr
|
||||||
|
}
|
||||||
|
|
||||||
|
// FormatInteger produces a formatted number as string.
|
||||||
|
// See FormatFloat.
|
||||||
|
func FormatInteger(format string, n int) string {
|
||||||
|
return FormatFloat(format, float64(n))
|
||||||
|
}
|
||||||
25
vendor/github.com/dustin/go-humanize/ordinals.go
generated
vendored
Normal file
25
vendor/github.com/dustin/go-humanize/ordinals.go
generated
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
package humanize
|
||||||
|
|
||||||
|
import "strconv"
|
||||||
|
|
||||||
|
// Ordinal gives you the input number in a rank/ordinal format.
|
||||||
|
//
|
||||||
|
// Ordinal(3) -> 3rd
|
||||||
|
func Ordinal(x int) string {
|
||||||
|
suffix := "th"
|
||||||
|
switch x % 10 {
|
||||||
|
case 1:
|
||||||
|
if x%100 != 11 {
|
||||||
|
suffix = "st"
|
||||||
|
}
|
||||||
|
case 2:
|
||||||
|
if x%100 != 12 {
|
||||||
|
suffix = "nd"
|
||||||
|
}
|
||||||
|
case 3:
|
||||||
|
if x%100 != 13 {
|
||||||
|
suffix = "rd"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strconv.Itoa(x) + suffix
|
||||||
|
}
|
||||||
123
vendor/github.com/dustin/go-humanize/si.go
generated
vendored
Normal file
123
vendor/github.com/dustin/go-humanize/si.go
generated
vendored
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
package humanize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"math"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
var siPrefixTable = map[float64]string{
|
||||||
|
-24: "y", // yocto
|
||||||
|
-21: "z", // zepto
|
||||||
|
-18: "a", // atto
|
||||||
|
-15: "f", // femto
|
||||||
|
-12: "p", // pico
|
||||||
|
-9: "n", // nano
|
||||||
|
-6: "µ", // micro
|
||||||
|
-3: "m", // milli
|
||||||
|
0: "",
|
||||||
|
3: "k", // kilo
|
||||||
|
6: "M", // mega
|
||||||
|
9: "G", // giga
|
||||||
|
12: "T", // tera
|
||||||
|
15: "P", // peta
|
||||||
|
18: "E", // exa
|
||||||
|
21: "Z", // zetta
|
||||||
|
24: "Y", // yotta
|
||||||
|
}
|
||||||
|
|
||||||
|
var revSIPrefixTable = revfmap(siPrefixTable)
|
||||||
|
|
||||||
|
// revfmap reverses the map and precomputes the power multiplier
|
||||||
|
func revfmap(in map[float64]string) map[string]float64 {
|
||||||
|
rv := map[string]float64{}
|
||||||
|
for k, v := range in {
|
||||||
|
rv[v] = math.Pow(10, k)
|
||||||
|
}
|
||||||
|
return rv
|
||||||
|
}
|
||||||
|
|
||||||
|
var riParseRegex *regexp.Regexp
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
ri := `^([\-0-9.]+)\s?([`
|
||||||
|
for _, v := range siPrefixTable {
|
||||||
|
ri += v
|
||||||
|
}
|
||||||
|
ri += `]?)(.*)`
|
||||||
|
|
||||||
|
riParseRegex = regexp.MustCompile(ri)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ComputeSI finds the most appropriate SI prefix for the given number
|
||||||
|
// and returns the prefix along with the value adjusted to be within
|
||||||
|
// that prefix.
|
||||||
|
//
|
||||||
|
// See also: SI, ParseSI.
|
||||||
|
//
|
||||||
|
// e.g. ComputeSI(2.2345e-12) -> (2.2345, "p")
|
||||||
|
func ComputeSI(input float64) (float64, string) {
|
||||||
|
if input == 0 {
|
||||||
|
return 0, ""
|
||||||
|
}
|
||||||
|
mag := math.Abs(input)
|
||||||
|
exponent := math.Floor(logn(mag, 10))
|
||||||
|
exponent = math.Floor(exponent/3) * 3
|
||||||
|
|
||||||
|
value := mag / math.Pow(10, exponent)
|
||||||
|
|
||||||
|
// Handle special case where value is exactly 1000.0
|
||||||
|
// Should return 1 M instead of 1000 k
|
||||||
|
if value == 1000.0 {
|
||||||
|
exponent += 3
|
||||||
|
value = mag / math.Pow(10, exponent)
|
||||||
|
}
|
||||||
|
|
||||||
|
value = math.Copysign(value, input)
|
||||||
|
|
||||||
|
prefix := siPrefixTable[exponent]
|
||||||
|
return value, prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
// SI returns a string with default formatting.
|
||||||
|
//
|
||||||
|
// SI uses Ftoa to format float value, removing trailing zeros.
|
||||||
|
//
|
||||||
|
// See also: ComputeSI, ParseSI.
|
||||||
|
//
|
||||||
|
// e.g. SI(1000000, "B") -> 1 MB
|
||||||
|
// e.g. SI(2.2345e-12, "F") -> 2.2345 pF
|
||||||
|
func SI(input float64, unit string) string {
|
||||||
|
value, prefix := ComputeSI(input)
|
||||||
|
return Ftoa(value) + " " + prefix + unit
|
||||||
|
}
|
||||||
|
|
||||||
|
// SIWithDigits works like SI but limits the resulting string to the
|
||||||
|
// given number of decimal places.
|
||||||
|
//
|
||||||
|
// e.g. SIWithDigits(1000000, 0, "B") -> 1 MB
|
||||||
|
// e.g. SIWithDigits(2.2345e-12, 2, "F") -> 2.23 pF
|
||||||
|
func SIWithDigits(input float64, decimals int, unit string) string {
|
||||||
|
value, prefix := ComputeSI(input)
|
||||||
|
return FtoaWithDigits(value, decimals) + " " + prefix + unit
|
||||||
|
}
|
||||||
|
|
||||||
|
var errInvalid = errors.New("invalid input")
|
||||||
|
|
||||||
|
// ParseSI parses an SI string back into the number and unit.
|
||||||
|
//
|
||||||
|
// See also: SI, ComputeSI.
|
||||||
|
//
|
||||||
|
// e.g. ParseSI("2.2345 pF") -> (2.2345e-12, "F", nil)
|
||||||
|
func ParseSI(input string) (float64, string, error) {
|
||||||
|
found := riParseRegex.FindStringSubmatch(input)
|
||||||
|
if len(found) != 4 {
|
||||||
|
return 0, "", errInvalid
|
||||||
|
}
|
||||||
|
mag := revSIPrefixTable[found[2]]
|
||||||
|
unit := found[3]
|
||||||
|
|
||||||
|
base, err := strconv.ParseFloat(found[1], 64)
|
||||||
|
return base * mag, unit, err
|
||||||
|
}
|
||||||
117
vendor/github.com/dustin/go-humanize/times.go
generated
vendored
Normal file
117
vendor/github.com/dustin/go-humanize/times.go
generated
vendored
Normal file
|
|
@ -0,0 +1,117 @@
|
||||||
|
package humanize
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Seconds-based time units
|
||||||
|
const (
|
||||||
|
Day = 24 * time.Hour
|
||||||
|
Week = 7 * Day
|
||||||
|
Month = 30 * Day
|
||||||
|
Year = 12 * Month
|
||||||
|
LongTime = 37 * Year
|
||||||
|
)
|
||||||
|
|
||||||
|
// Time formats a time into a relative string.
|
||||||
|
//
|
||||||
|
// Time(someT) -> "3 weeks ago"
|
||||||
|
func Time(then time.Time) string {
|
||||||
|
return RelTime(then, time.Now(), "ago", "from now")
|
||||||
|
}
|
||||||
|
|
||||||
|
// A RelTimeMagnitude struct contains a relative time point at which
|
||||||
|
// the relative format of time will switch to a new format string. A
|
||||||
|
// slice of these in ascending order by their "D" field is passed to
|
||||||
|
// CustomRelTime to format durations.
|
||||||
|
//
|
||||||
|
// The Format field is a string that may contain a "%s" which will be
|
||||||
|
// replaced with the appropriate signed label (e.g. "ago" or "from
|
||||||
|
// now") and a "%d" that will be replaced by the quantity.
|
||||||
|
//
|
||||||
|
// The DivBy field is the amount of time the time difference must be
|
||||||
|
// divided by in order to display correctly.
|
||||||
|
//
|
||||||
|
// e.g. if D is 2*time.Minute and you want to display "%d minutes %s"
|
||||||
|
// DivBy should be time.Minute so whatever the duration is will be
|
||||||
|
// expressed in minutes.
|
||||||
|
type RelTimeMagnitude struct {
|
||||||
|
D time.Duration
|
||||||
|
Format string
|
||||||
|
DivBy time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultMagnitudes = []RelTimeMagnitude{
|
||||||
|
{time.Second, "now", time.Second},
|
||||||
|
{2 * time.Second, "1 second %s", 1},
|
||||||
|
{time.Minute, "%d seconds %s", time.Second},
|
||||||
|
{2 * time.Minute, "1 minute %s", 1},
|
||||||
|
{time.Hour, "%d minutes %s", time.Minute},
|
||||||
|
{2 * time.Hour, "1 hour %s", 1},
|
||||||
|
{Day, "%d hours %s", time.Hour},
|
||||||
|
{2 * Day, "1 day %s", 1},
|
||||||
|
{Week, "%d days %s", Day},
|
||||||
|
{2 * Week, "1 week %s", 1},
|
||||||
|
{Month, "%d weeks %s", Week},
|
||||||
|
{2 * Month, "1 month %s", 1},
|
||||||
|
{Year, "%d months %s", Month},
|
||||||
|
{18 * Month, "1 year %s", 1},
|
||||||
|
{2 * Year, "2 years %s", 1},
|
||||||
|
{LongTime, "%d years %s", Year},
|
||||||
|
{math.MaxInt64, "a long while %s", 1},
|
||||||
|
}
|
||||||
|
|
||||||
|
// RelTime formats a time into a relative string.
|
||||||
|
//
|
||||||
|
// It takes two times and two labels. In addition to the generic time
|
||||||
|
// delta string (e.g. 5 minutes), the labels are used applied so that
|
||||||
|
// the label corresponding to the smaller time is applied.
|
||||||
|
//
|
||||||
|
// RelTime(timeInPast, timeInFuture, "earlier", "later") -> "3 weeks earlier"
|
||||||
|
func RelTime(a, b time.Time, albl, blbl string) string {
|
||||||
|
return CustomRelTime(a, b, albl, blbl, defaultMagnitudes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CustomRelTime formats a time into a relative string.
|
||||||
|
//
|
||||||
|
// It takes two times two labels and a table of relative time formats.
|
||||||
|
// In addition to the generic time delta string (e.g. 5 minutes), the
|
||||||
|
// labels are used applied so that the label corresponding to the
|
||||||
|
// smaller time is applied.
|
||||||
|
func CustomRelTime(a, b time.Time, albl, blbl string, magnitudes []RelTimeMagnitude) string {
|
||||||
|
lbl := albl
|
||||||
|
diff := b.Sub(a)
|
||||||
|
|
||||||
|
if a.After(b) {
|
||||||
|
lbl = blbl
|
||||||
|
diff = a.Sub(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
n := sort.Search(len(magnitudes), func(i int) bool {
|
||||||
|
return magnitudes[i].D > diff
|
||||||
|
})
|
||||||
|
|
||||||
|
if n >= len(magnitudes) {
|
||||||
|
n = len(magnitudes) - 1
|
||||||
|
}
|
||||||
|
mag := magnitudes[n]
|
||||||
|
args := []interface{}{}
|
||||||
|
escaped := false
|
||||||
|
for _, ch := range mag.Format {
|
||||||
|
if escaped {
|
||||||
|
switch ch {
|
||||||
|
case 's':
|
||||||
|
args = append(args, lbl)
|
||||||
|
case 'd':
|
||||||
|
args = append(args, diff/mag.DivBy)
|
||||||
|
}
|
||||||
|
escaped = false
|
||||||
|
} else {
|
||||||
|
escaped = ch == '%'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Sprintf(mag.Format, args...)
|
||||||
|
}
|
||||||
3
vendor/github.com/json-iterator/go/.codecov.yml
generated
vendored
Normal file
3
vendor/github.com/json-iterator/go/.codecov.yml
generated
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
ignore:
|
||||||
|
- "output_tests/.*"
|
||||||
|
|
||||||
4
vendor/github.com/json-iterator/go/.gitignore
generated
vendored
Normal file
4
vendor/github.com/json-iterator/go/.gitignore
generated
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
/vendor
|
||||||
|
/bug_test.go
|
||||||
|
/coverage.txt
|
||||||
|
/.idea
|
||||||
14
vendor/github.com/json-iterator/go/.travis.yml
generated
vendored
Normal file
14
vendor/github.com/json-iterator/go/.travis.yml
generated
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
language: go
|
||||||
|
|
||||||
|
go:
|
||||||
|
- 1.8.x
|
||||||
|
- 1.x
|
||||||
|
|
||||||
|
before_install:
|
||||||
|
- go get -t -v ./...
|
||||||
|
|
||||||
|
script:
|
||||||
|
- ./test.sh
|
||||||
|
|
||||||
|
after_success:
|
||||||
|
- bash <(curl -s https://codecov.io/bash)
|
||||||
21
vendor/github.com/json-iterator/go/Gopkg.lock
generated
vendored
Normal file
21
vendor/github.com/json-iterator/go/Gopkg.lock
generated
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||||
|
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/modern-go/concurrent"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "e0a39a4cb4216ea8db28e22a69f4ec25610d513a"
|
||||||
|
version = "1.0.0"
|
||||||
|
|
||||||
|
[[projects]]
|
||||||
|
name = "github.com/modern-go/reflect2"
|
||||||
|
packages = ["."]
|
||||||
|
revision = "4b7aa43c6742a2c18fdef89dd197aaae7dac7ccd"
|
||||||
|
version = "1.0.1"
|
||||||
|
|
||||||
|
[solve-meta]
|
||||||
|
analyzer-name = "dep"
|
||||||
|
analyzer-version = 1
|
||||||
|
inputs-digest = "ea54a775e5a354cb015502d2e7aa4b74230fc77e894f34a838b268c25ec8eeb8"
|
||||||
|
solver-name = "gps-cdcl"
|
||||||
|
solver-version = 1
|
||||||
26
vendor/github.com/json-iterator/go/Gopkg.toml
generated
vendored
Normal file
26
vendor/github.com/json-iterator/go/Gopkg.toml
generated
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
# Gopkg.toml example
|
||||||
|
#
|
||||||
|
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||||
|
# for detailed Gopkg.toml documentation.
|
||||||
|
#
|
||||||
|
# required = ["github.com/user/thing/cmd/thing"]
|
||||||
|
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||||
|
#
|
||||||
|
# [[constraint]]
|
||||||
|
# name = "github.com/user/project"
|
||||||
|
# version = "1.0.0"
|
||||||
|
#
|
||||||
|
# [[constraint]]
|
||||||
|
# name = "github.com/user/project2"
|
||||||
|
# branch = "dev"
|
||||||
|
# source = "github.com/myfork/project2"
|
||||||
|
#
|
||||||
|
# [[override]]
|
||||||
|
# name = "github.com/x/y"
|
||||||
|
# version = "2.4.0"
|
||||||
|
|
||||||
|
ignored = ["github.com/davecgh/go-spew*","github.com/google/gofuzz*","github.com/stretchr/testify*"]
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/modern-go/reflect2"
|
||||||
|
version = "1.0.1"
|
||||||
21
vendor/github.com/json-iterator/go/LICENSE
generated
vendored
Normal file
21
vendor/github.com/json-iterator/go/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2016 json-iterator
|
||||||
|
|
||||||
|
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.
|
||||||
85
vendor/github.com/json-iterator/go/README.md
generated
vendored
Normal file
85
vendor/github.com/json-iterator/go/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
[](https://sourcegraph.com/github.com/json-iterator/go?badge)
|
||||||
|
[](https://pkg.go.dev/github.com/json-iterator/go)
|
||||||
|
[](https://travis-ci.org/json-iterator/go)
|
||||||
|
[](https://codecov.io/gh/json-iterator/go)
|
||||||
|
[](https://goreportcard.com/report/github.com/json-iterator/go)
|
||||||
|
[](https://raw.githubusercontent.com/json-iterator/go/master/LICENSE)
|
||||||
|
[](https://gitter.im/json-iterator/Lobby)
|
||||||
|
|
||||||
|
A high-performance 100% compatible drop-in replacement of "encoding/json"
|
||||||
|
|
||||||
|
# Benchmark
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
Source code: https://github.com/json-iterator/go-benchmark/blob/master/src/github.com/json-iterator/go-benchmark/benchmark_medium_payload_test.go
|
||||||
|
|
||||||
|
Raw Result (easyjson requires static code generation)
|
||||||
|
|
||||||
|
| | ns/op | allocation bytes | allocation times |
|
||||||
|
| --------------- | ----------- | ---------------- | ---------------- |
|
||||||
|
| std decode | 35510 ns/op | 1960 B/op | 99 allocs/op |
|
||||||
|
| easyjson decode | 8499 ns/op | 160 B/op | 4 allocs/op |
|
||||||
|
| jsoniter decode | 5623 ns/op | 160 B/op | 3 allocs/op |
|
||||||
|
| std encode | 2213 ns/op | 712 B/op | 5 allocs/op |
|
||||||
|
| easyjson encode | 883 ns/op | 576 B/op | 3 allocs/op |
|
||||||
|
| jsoniter encode | 837 ns/op | 384 B/op | 4 allocs/op |
|
||||||
|
|
||||||
|
Always benchmark with your own workload.
|
||||||
|
The result depends heavily on the data input.
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
100% compatibility with standard lib
|
||||||
|
|
||||||
|
Replace
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "encoding/json"
|
||||||
|
json.Marshal(&data)
|
||||||
|
```
|
||||||
|
|
||||||
|
with
|
||||||
|
|
||||||
|
```go
|
||||||
|
import jsoniter "github.com/json-iterator/go"
|
||||||
|
|
||||||
|
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||||
|
json.Marshal(&data)
|
||||||
|
```
|
||||||
|
|
||||||
|
Replace
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "encoding/json"
|
||||||
|
json.Unmarshal(input, &data)
|
||||||
|
```
|
||||||
|
|
||||||
|
with
|
||||||
|
|
||||||
|
```go
|
||||||
|
import jsoniter "github.com/json-iterator/go"
|
||||||
|
|
||||||
|
var json = jsoniter.ConfigCompatibleWithStandardLibrary
|
||||||
|
json.Unmarshal(input, &data)
|
||||||
|
```
|
||||||
|
|
||||||
|
[More documentation](http://jsoniter.com/migrate-from-go-std.html)
|
||||||
|
|
||||||
|
# How to get
|
||||||
|
|
||||||
|
```
|
||||||
|
go get github.com/json-iterator/go
|
||||||
|
```
|
||||||
|
|
||||||
|
# Contribution Welcomed !
|
||||||
|
|
||||||
|
Contributors
|
||||||
|
|
||||||
|
- [thockin](https://github.com/thockin)
|
||||||
|
- [mattn](https://github.com/mattn)
|
||||||
|
- [cch123](https://github.com/cch123)
|
||||||
|
- [Oleg Shaldybin](https://github.com/olegshaldybin)
|
||||||
|
- [Jason Toffaletti](https://github.com/toffaletti)
|
||||||
|
|
||||||
|
Report issue or pull request, or email taowen@gmail.com, or [](https://gitter.im/json-iterator/Lobby)
|
||||||
150
vendor/github.com/json-iterator/go/adapter.go
generated
vendored
Normal file
150
vendor/github.com/json-iterator/go/adapter.go
generated
vendored
Normal file
|
|
@ -0,0 +1,150 @@
|
||||||
|
package jsoniter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RawMessage to make replace json with jsoniter
|
||||||
|
type RawMessage []byte
|
||||||
|
|
||||||
|
// Unmarshal adapts to json/encoding Unmarshal API
|
||||||
|
//
|
||||||
|
// Unmarshal parses the JSON-encoded data and stores the result in the value pointed to by v.
|
||||||
|
// Refer to https://godoc.org/encoding/json#Unmarshal for more information
|
||||||
|
func Unmarshal(data []byte, v interface{}) error {
|
||||||
|
return ConfigDefault.Unmarshal(data, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalFromString is a convenient method to read from string instead of []byte
|
||||||
|
func UnmarshalFromString(str string, v interface{}) error {
|
||||||
|
return ConfigDefault.UnmarshalFromString(str, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get quick method to get value from deeply nested JSON structure
|
||||||
|
func Get(data []byte, path ...interface{}) Any {
|
||||||
|
return ConfigDefault.Get(data, path...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal adapts to json/encoding Marshal API
|
||||||
|
//
|
||||||
|
// Marshal returns the JSON encoding of v, adapts to json/encoding Marshal API
|
||||||
|
// Refer to https://godoc.org/encoding/json#Marshal for more information
|
||||||
|
func Marshal(v interface{}) ([]byte, error) {
|
||||||
|
return ConfigDefault.Marshal(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalIndent same as json.MarshalIndent. Prefix is not supported.
|
||||||
|
func MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) {
|
||||||
|
return ConfigDefault.MarshalIndent(v, prefix, indent)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalToString convenient method to write as string instead of []byte
|
||||||
|
func MarshalToString(v interface{}) (string, error) {
|
||||||
|
return ConfigDefault.MarshalToString(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDecoder adapts to json/stream NewDecoder API.
|
||||||
|
//
|
||||||
|
// NewDecoder returns a new decoder that reads from r.
|
||||||
|
//
|
||||||
|
// Instead of a json/encoding Decoder, an Decoder is returned
|
||||||
|
// Refer to https://godoc.org/encoding/json#NewDecoder for more information
|
||||||
|
func NewDecoder(reader io.Reader) *Decoder {
|
||||||
|
return ConfigDefault.NewDecoder(reader)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decoder reads and decodes JSON values from an input stream.
|
||||||
|
// Decoder provides identical APIs with json/stream Decoder (Token() and UseNumber() are in progress)
|
||||||
|
type Decoder struct {
|
||||||
|
iter *Iterator
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode decode JSON into interface{}
|
||||||
|
func (adapter *Decoder) Decode(obj interface{}) error {
|
||||||
|
if adapter.iter.head == adapter.iter.tail && adapter.iter.reader != nil {
|
||||||
|
if !adapter.iter.loadMore() {
|
||||||
|
return io.EOF
|
||||||
|
}
|
||||||
|
}
|
||||||
|
adapter.iter.ReadVal(obj)
|
||||||
|
err := adapter.iter.Error
|
||||||
|
if err == io.EOF {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return adapter.iter.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// More is there more?
|
||||||
|
func (adapter *Decoder) More() bool {
|
||||||
|
iter := adapter.iter
|
||||||
|
if iter.Error != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
c := iter.nextToken()
|
||||||
|
if c == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
iter.unreadByte()
|
||||||
|
return c != ']' && c != '}'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buffered remaining buffer
|
||||||
|
func (adapter *Decoder) Buffered() io.Reader {
|
||||||
|
remaining := adapter.iter.buf[adapter.iter.head:adapter.iter.tail]
|
||||||
|
return bytes.NewReader(remaining)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UseNumber causes the Decoder to unmarshal a number into an interface{} as a
|
||||||
|
// Number instead of as a float64.
|
||||||
|
func (adapter *Decoder) UseNumber() {
|
||||||
|
cfg := adapter.iter.cfg.configBeforeFrozen
|
||||||
|
cfg.UseNumber = true
|
||||||
|
adapter.iter.cfg = cfg.frozeWithCacheReuse(adapter.iter.cfg.extraExtensions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisallowUnknownFields causes the Decoder to return an error when the destination
|
||||||
|
// is a struct and the input contains object keys which do not match any
|
||||||
|
// non-ignored, exported fields in the destination.
|
||||||
|
func (adapter *Decoder) DisallowUnknownFields() {
|
||||||
|
cfg := adapter.iter.cfg.configBeforeFrozen
|
||||||
|
cfg.DisallowUnknownFields = true
|
||||||
|
adapter.iter.cfg = cfg.frozeWithCacheReuse(adapter.iter.cfg.extraExtensions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewEncoder same as json.NewEncoder
|
||||||
|
func NewEncoder(writer io.Writer) *Encoder {
|
||||||
|
return ConfigDefault.NewEncoder(writer)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encoder same as json.Encoder
|
||||||
|
type Encoder struct {
|
||||||
|
stream *Stream
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode encode interface{} as JSON to io.Writer
|
||||||
|
func (adapter *Encoder) Encode(val interface{}) error {
|
||||||
|
adapter.stream.WriteVal(val)
|
||||||
|
adapter.stream.WriteRaw("\n")
|
||||||
|
adapter.stream.Flush()
|
||||||
|
return adapter.stream.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetIndent set the indention. Prefix is not supported
|
||||||
|
func (adapter *Encoder) SetIndent(prefix, indent string) {
|
||||||
|
config := adapter.stream.cfg.configBeforeFrozen
|
||||||
|
config.IndentionStep = len(indent)
|
||||||
|
adapter.stream.cfg = config.frozeWithCacheReuse(adapter.stream.cfg.extraExtensions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetEscapeHTML escape html by default, set to false to disable
|
||||||
|
func (adapter *Encoder) SetEscapeHTML(escapeHTML bool) {
|
||||||
|
config := adapter.stream.cfg.configBeforeFrozen
|
||||||
|
config.EscapeHTML = escapeHTML
|
||||||
|
adapter.stream.cfg = config.frozeWithCacheReuse(adapter.stream.cfg.extraExtensions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid reports whether data is a valid JSON encoding.
|
||||||
|
func Valid(data []byte) bool {
|
||||||
|
return ConfigDefault.Valid(data)
|
||||||
|
}
|
||||||
325
vendor/github.com/json-iterator/go/any.go
generated
vendored
Normal file
325
vendor/github.com/json-iterator/go/any.go
generated
vendored
Normal file
|
|
@ -0,0 +1,325 @@
|
||||||
|
package jsoniter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/modern-go/reflect2"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Any generic object representation.
|
||||||
|
// The lazy json implementation holds []byte and parse lazily.
|
||||||
|
type Any interface {
|
||||||
|
LastError() error
|
||||||
|
ValueType() ValueType
|
||||||
|
MustBeValid() Any
|
||||||
|
ToBool() bool
|
||||||
|
ToInt() int
|
||||||
|
ToInt32() int32
|
||||||
|
ToInt64() int64
|
||||||
|
ToUint() uint
|
||||||
|
ToUint32() uint32
|
||||||
|
ToUint64() uint64
|
||||||
|
ToFloat32() float32
|
||||||
|
ToFloat64() float64
|
||||||
|
ToString() string
|
||||||
|
ToVal(val interface{})
|
||||||
|
Get(path ...interface{}) Any
|
||||||
|
Size() int
|
||||||
|
Keys() []string
|
||||||
|
GetInterface() interface{}
|
||||||
|
WriteTo(stream *Stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
type baseAny struct{}
|
||||||
|
|
||||||
|
func (any *baseAny) Get(path ...interface{}) Any {
|
||||||
|
return &invalidAny{baseAny{}, fmt.Errorf("GetIndex %v from simple value", path)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *baseAny) Size() int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *baseAny) Keys() []string {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *baseAny) ToVal(obj interface{}) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrapInt32 turn int32 into Any interface
|
||||||
|
func WrapInt32(val int32) Any {
|
||||||
|
return &int32Any{baseAny{}, val}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrapInt64 turn int64 into Any interface
|
||||||
|
func WrapInt64(val int64) Any {
|
||||||
|
return &int64Any{baseAny{}, val}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrapUint32 turn uint32 into Any interface
|
||||||
|
func WrapUint32(val uint32) Any {
|
||||||
|
return &uint32Any{baseAny{}, val}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrapUint64 turn uint64 into Any interface
|
||||||
|
func WrapUint64(val uint64) Any {
|
||||||
|
return &uint64Any{baseAny{}, val}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrapFloat64 turn float64 into Any interface
|
||||||
|
func WrapFloat64(val float64) Any {
|
||||||
|
return &floatAny{baseAny{}, val}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WrapString turn string into Any interface
|
||||||
|
func WrapString(val string) Any {
|
||||||
|
return &stringAny{baseAny{}, val}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrap turn a go object into Any interface
|
||||||
|
func Wrap(val interface{}) Any {
|
||||||
|
if val == nil {
|
||||||
|
return &nilAny{}
|
||||||
|
}
|
||||||
|
asAny, isAny := val.(Any)
|
||||||
|
if isAny {
|
||||||
|
return asAny
|
||||||
|
}
|
||||||
|
typ := reflect2.TypeOf(val)
|
||||||
|
switch typ.Kind() {
|
||||||
|
case reflect.Slice:
|
||||||
|
return wrapArray(val)
|
||||||
|
case reflect.Struct:
|
||||||
|
return wrapStruct(val)
|
||||||
|
case reflect.Map:
|
||||||
|
return wrapMap(val)
|
||||||
|
case reflect.String:
|
||||||
|
return WrapString(val.(string))
|
||||||
|
case reflect.Int:
|
||||||
|
if strconv.IntSize == 32 {
|
||||||
|
return WrapInt32(int32(val.(int)))
|
||||||
|
}
|
||||||
|
return WrapInt64(int64(val.(int)))
|
||||||
|
case reflect.Int8:
|
||||||
|
return WrapInt32(int32(val.(int8)))
|
||||||
|
case reflect.Int16:
|
||||||
|
return WrapInt32(int32(val.(int16)))
|
||||||
|
case reflect.Int32:
|
||||||
|
return WrapInt32(val.(int32))
|
||||||
|
case reflect.Int64:
|
||||||
|
return WrapInt64(val.(int64))
|
||||||
|
case reflect.Uint:
|
||||||
|
if strconv.IntSize == 32 {
|
||||||
|
return WrapUint32(uint32(val.(uint)))
|
||||||
|
}
|
||||||
|
return WrapUint64(uint64(val.(uint)))
|
||||||
|
case reflect.Uintptr:
|
||||||
|
if ptrSize == 32 {
|
||||||
|
return WrapUint32(uint32(val.(uintptr)))
|
||||||
|
}
|
||||||
|
return WrapUint64(uint64(val.(uintptr)))
|
||||||
|
case reflect.Uint8:
|
||||||
|
return WrapUint32(uint32(val.(uint8)))
|
||||||
|
case reflect.Uint16:
|
||||||
|
return WrapUint32(uint32(val.(uint16)))
|
||||||
|
case reflect.Uint32:
|
||||||
|
return WrapUint32(uint32(val.(uint32)))
|
||||||
|
case reflect.Uint64:
|
||||||
|
return WrapUint64(val.(uint64))
|
||||||
|
case reflect.Float32:
|
||||||
|
return WrapFloat64(float64(val.(float32)))
|
||||||
|
case reflect.Float64:
|
||||||
|
return WrapFloat64(val.(float64))
|
||||||
|
case reflect.Bool:
|
||||||
|
if val.(bool) == true {
|
||||||
|
return &trueAny{}
|
||||||
|
}
|
||||||
|
return &falseAny{}
|
||||||
|
}
|
||||||
|
return &invalidAny{baseAny{}, fmt.Errorf("unsupported type: %v", typ)}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadAny read next JSON element as an Any object. It is a better json.RawMessage.
|
||||||
|
func (iter *Iterator) ReadAny() Any {
|
||||||
|
return iter.readAny()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *Iterator) readAny() Any {
|
||||||
|
c := iter.nextToken()
|
||||||
|
switch c {
|
||||||
|
case '"':
|
||||||
|
iter.unreadByte()
|
||||||
|
return &stringAny{baseAny{}, iter.ReadString()}
|
||||||
|
case 'n':
|
||||||
|
iter.skipThreeBytes('u', 'l', 'l') // null
|
||||||
|
return &nilAny{}
|
||||||
|
case 't':
|
||||||
|
iter.skipThreeBytes('r', 'u', 'e') // true
|
||||||
|
return &trueAny{}
|
||||||
|
case 'f':
|
||||||
|
iter.skipFourBytes('a', 'l', 's', 'e') // false
|
||||||
|
return &falseAny{}
|
||||||
|
case '{':
|
||||||
|
return iter.readObjectAny()
|
||||||
|
case '[':
|
||||||
|
return iter.readArrayAny()
|
||||||
|
case '-':
|
||||||
|
return iter.readNumberAny(false)
|
||||||
|
case 0:
|
||||||
|
return &invalidAny{baseAny{}, errors.New("input is empty")}
|
||||||
|
default:
|
||||||
|
return iter.readNumberAny(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *Iterator) readNumberAny(positive bool) Any {
|
||||||
|
iter.startCapture(iter.head - 1)
|
||||||
|
iter.skipNumber()
|
||||||
|
lazyBuf := iter.stopCapture()
|
||||||
|
return &numberLazyAny{baseAny{}, iter.cfg, lazyBuf, nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *Iterator) readObjectAny() Any {
|
||||||
|
iter.startCapture(iter.head - 1)
|
||||||
|
iter.skipObject()
|
||||||
|
lazyBuf := iter.stopCapture()
|
||||||
|
return &objectLazyAny{baseAny{}, iter.cfg, lazyBuf, nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *Iterator) readArrayAny() Any {
|
||||||
|
iter.startCapture(iter.head - 1)
|
||||||
|
iter.skipArray()
|
||||||
|
lazyBuf := iter.stopCapture()
|
||||||
|
return &arrayLazyAny{baseAny{}, iter.cfg, lazyBuf, nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
func locateObjectField(iter *Iterator, target string) []byte {
|
||||||
|
var found []byte
|
||||||
|
iter.ReadObjectCB(func(iter *Iterator, field string) bool {
|
||||||
|
if field == target {
|
||||||
|
found = iter.SkipAndReturnBytes()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
iter.Skip()
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
|
||||||
|
func locateArrayElement(iter *Iterator, target int) []byte {
|
||||||
|
var found []byte
|
||||||
|
n := 0
|
||||||
|
iter.ReadArrayCB(func(iter *Iterator) bool {
|
||||||
|
if n == target {
|
||||||
|
found = iter.SkipAndReturnBytes()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
iter.Skip()
|
||||||
|
n++
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
|
||||||
|
func locatePath(iter *Iterator, path []interface{}) Any {
|
||||||
|
for i, pathKeyObj := range path {
|
||||||
|
switch pathKey := pathKeyObj.(type) {
|
||||||
|
case string:
|
||||||
|
valueBytes := locateObjectField(iter, pathKey)
|
||||||
|
if valueBytes == nil {
|
||||||
|
return newInvalidAny(path[i:])
|
||||||
|
}
|
||||||
|
iter.ResetBytes(valueBytes)
|
||||||
|
case int:
|
||||||
|
valueBytes := locateArrayElement(iter, pathKey)
|
||||||
|
if valueBytes == nil {
|
||||||
|
return newInvalidAny(path[i:])
|
||||||
|
}
|
||||||
|
iter.ResetBytes(valueBytes)
|
||||||
|
case int32:
|
||||||
|
if '*' == pathKey {
|
||||||
|
return iter.readAny().Get(path[i:]...)
|
||||||
|
}
|
||||||
|
return newInvalidAny(path[i:])
|
||||||
|
default:
|
||||||
|
return newInvalidAny(path[i:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if iter.Error != nil && iter.Error != io.EOF {
|
||||||
|
return &invalidAny{baseAny{}, iter.Error}
|
||||||
|
}
|
||||||
|
return iter.readAny()
|
||||||
|
}
|
||||||
|
|
||||||
|
var anyType = reflect2.TypeOfPtr((*Any)(nil)).Elem()
|
||||||
|
|
||||||
|
func createDecoderOfAny(ctx *ctx, typ reflect2.Type) ValDecoder {
|
||||||
|
if typ == anyType {
|
||||||
|
return &directAnyCodec{}
|
||||||
|
}
|
||||||
|
if typ.Implements(anyType) {
|
||||||
|
return &anyCodec{
|
||||||
|
valType: typ,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createEncoderOfAny(ctx *ctx, typ reflect2.Type) ValEncoder {
|
||||||
|
if typ == anyType {
|
||||||
|
return &directAnyCodec{}
|
||||||
|
}
|
||||||
|
if typ.Implements(anyType) {
|
||||||
|
return &anyCodec{
|
||||||
|
valType: typ,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type anyCodec struct {
|
||||||
|
valType reflect2.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *anyCodec) Decode(ptr unsafe.Pointer, iter *Iterator) {
|
||||||
|
panic("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *anyCodec) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
obj := codec.valType.UnsafeIndirect(ptr)
|
||||||
|
any := obj.(Any)
|
||||||
|
any.WriteTo(stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *anyCodec) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
obj := codec.valType.UnsafeIndirect(ptr)
|
||||||
|
any := obj.(Any)
|
||||||
|
return any.Size() == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type directAnyCodec struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *directAnyCodec) Decode(ptr unsafe.Pointer, iter *Iterator) {
|
||||||
|
*(*Any)(ptr) = iter.readAny()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *directAnyCodec) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
any := *(*Any)(ptr)
|
||||||
|
if any == nil {
|
||||||
|
stream.WriteNil()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
any.WriteTo(stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *directAnyCodec) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
any := *(*Any)(ptr)
|
||||||
|
return any.Size() == 0
|
||||||
|
}
|
||||||
278
vendor/github.com/json-iterator/go/any_array.go
generated
vendored
Normal file
278
vendor/github.com/json-iterator/go/any_array.go
generated
vendored
Normal file
|
|
@ -0,0 +1,278 @@
|
||||||
|
package jsoniter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
type arrayLazyAny struct {
|
||||||
|
baseAny
|
||||||
|
cfg *frozenConfig
|
||||||
|
buf []byte
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *arrayLazyAny) ValueType() ValueType {
|
||||||
|
return ArrayValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *arrayLazyAny) MustBeValid() Any {
|
||||||
|
return any
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *arrayLazyAny) LastError() error {
|
||||||
|
return any.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *arrayLazyAny) ToBool() bool {
|
||||||
|
iter := any.cfg.BorrowIterator(any.buf)
|
||||||
|
defer any.cfg.ReturnIterator(iter)
|
||||||
|
return iter.ReadArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *arrayLazyAny) ToInt() int {
|
||||||
|
if any.ToBool() {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *arrayLazyAny) ToInt32() int32 {
|
||||||
|
if any.ToBool() {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *arrayLazyAny) ToInt64() int64 {
|
||||||
|
if any.ToBool() {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *arrayLazyAny) ToUint() uint {
|
||||||
|
if any.ToBool() {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *arrayLazyAny) ToUint32() uint32 {
|
||||||
|
if any.ToBool() {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *arrayLazyAny) ToUint64() uint64 {
|
||||||
|
if any.ToBool() {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *arrayLazyAny) ToFloat32() float32 {
|
||||||
|
if any.ToBool() {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *arrayLazyAny) ToFloat64() float64 {
|
||||||
|
if any.ToBool() {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *arrayLazyAny) ToString() string {
|
||||||
|
return *(*string)(unsafe.Pointer(&any.buf))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *arrayLazyAny) ToVal(val interface{}) {
|
||||||
|
iter := any.cfg.BorrowIterator(any.buf)
|
||||||
|
defer any.cfg.ReturnIterator(iter)
|
||||||
|
iter.ReadVal(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *arrayLazyAny) Get(path ...interface{}) Any {
|
||||||
|
if len(path) == 0 {
|
||||||
|
return any
|
||||||
|
}
|
||||||
|
switch firstPath := path[0].(type) {
|
||||||
|
case int:
|
||||||
|
iter := any.cfg.BorrowIterator(any.buf)
|
||||||
|
defer any.cfg.ReturnIterator(iter)
|
||||||
|
valueBytes := locateArrayElement(iter, firstPath)
|
||||||
|
if valueBytes == nil {
|
||||||
|
return newInvalidAny(path)
|
||||||
|
}
|
||||||
|
iter.ResetBytes(valueBytes)
|
||||||
|
return locatePath(iter, path[1:])
|
||||||
|
case int32:
|
||||||
|
if '*' == firstPath {
|
||||||
|
iter := any.cfg.BorrowIterator(any.buf)
|
||||||
|
defer any.cfg.ReturnIterator(iter)
|
||||||
|
arr := make([]Any, 0)
|
||||||
|
iter.ReadArrayCB(func(iter *Iterator) bool {
|
||||||
|
found := iter.readAny().Get(path[1:]...)
|
||||||
|
if found.ValueType() != InvalidValue {
|
||||||
|
arr = append(arr, found)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return wrapArray(arr)
|
||||||
|
}
|
||||||
|
return newInvalidAny(path)
|
||||||
|
default:
|
||||||
|
return newInvalidAny(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *arrayLazyAny) Size() int {
|
||||||
|
size := 0
|
||||||
|
iter := any.cfg.BorrowIterator(any.buf)
|
||||||
|
defer any.cfg.ReturnIterator(iter)
|
||||||
|
iter.ReadArrayCB(func(iter *Iterator) bool {
|
||||||
|
size++
|
||||||
|
iter.Skip()
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *arrayLazyAny) WriteTo(stream *Stream) {
|
||||||
|
stream.Write(any.buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *arrayLazyAny) GetInterface() interface{} {
|
||||||
|
iter := any.cfg.BorrowIterator(any.buf)
|
||||||
|
defer any.cfg.ReturnIterator(iter)
|
||||||
|
return iter.Read()
|
||||||
|
}
|
||||||
|
|
||||||
|
type arrayAny struct {
|
||||||
|
baseAny
|
||||||
|
val reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapArray(val interface{}) *arrayAny {
|
||||||
|
return &arrayAny{baseAny{}, reflect.ValueOf(val)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *arrayAny) ValueType() ValueType {
|
||||||
|
return ArrayValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *arrayAny) MustBeValid() Any {
|
||||||
|
return any
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *arrayAny) LastError() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *arrayAny) ToBool() bool {
|
||||||
|
return any.val.Len() != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *arrayAny) ToInt() int {
|
||||||
|
if any.val.Len() == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *arrayAny) ToInt32() int32 {
|
||||||
|
if any.val.Len() == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *arrayAny) ToInt64() int64 {
|
||||||
|
if any.val.Len() == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *arrayAny) ToUint() uint {
|
||||||
|
if any.val.Len() == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *arrayAny) ToUint32() uint32 {
|
||||||
|
if any.val.Len() == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *arrayAny) ToUint64() uint64 {
|
||||||
|
if any.val.Len() == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *arrayAny) ToFloat32() float32 {
|
||||||
|
if any.val.Len() == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *arrayAny) ToFloat64() float64 {
|
||||||
|
if any.val.Len() == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *arrayAny) ToString() string {
|
||||||
|
str, _ := MarshalToString(any.val.Interface())
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *arrayAny) Get(path ...interface{}) Any {
|
||||||
|
if len(path) == 0 {
|
||||||
|
return any
|
||||||
|
}
|
||||||
|
switch firstPath := path[0].(type) {
|
||||||
|
case int:
|
||||||
|
if firstPath < 0 || firstPath >= any.val.Len() {
|
||||||
|
return newInvalidAny(path)
|
||||||
|
}
|
||||||
|
return Wrap(any.val.Index(firstPath).Interface())
|
||||||
|
case int32:
|
||||||
|
if '*' == firstPath {
|
||||||
|
mappedAll := make([]Any, 0)
|
||||||
|
for i := 0; i < any.val.Len(); i++ {
|
||||||
|
mapped := Wrap(any.val.Index(i).Interface()).Get(path[1:]...)
|
||||||
|
if mapped.ValueType() != InvalidValue {
|
||||||
|
mappedAll = append(mappedAll, mapped)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return wrapArray(mappedAll)
|
||||||
|
}
|
||||||
|
return newInvalidAny(path)
|
||||||
|
default:
|
||||||
|
return newInvalidAny(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *arrayAny) Size() int {
|
||||||
|
return any.val.Len()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *arrayAny) WriteTo(stream *Stream) {
|
||||||
|
stream.WriteVal(any.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *arrayAny) GetInterface() interface{} {
|
||||||
|
return any.val.Interface()
|
||||||
|
}
|
||||||
137
vendor/github.com/json-iterator/go/any_bool.go
generated
vendored
Normal file
137
vendor/github.com/json-iterator/go/any_bool.go
generated
vendored
Normal file
|
|
@ -0,0 +1,137 @@
|
||||||
|
package jsoniter
|
||||||
|
|
||||||
|
type trueAny struct {
|
||||||
|
baseAny
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *trueAny) LastError() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *trueAny) ToBool() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *trueAny) ToInt() int {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *trueAny) ToInt32() int32 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *trueAny) ToInt64() int64 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *trueAny) ToUint() uint {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *trueAny) ToUint32() uint32 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *trueAny) ToUint64() uint64 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *trueAny) ToFloat32() float32 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *trueAny) ToFloat64() float64 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *trueAny) ToString() string {
|
||||||
|
return "true"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *trueAny) WriteTo(stream *Stream) {
|
||||||
|
stream.WriteTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *trueAny) Parse() *Iterator {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *trueAny) GetInterface() interface{} {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *trueAny) ValueType() ValueType {
|
||||||
|
return BoolValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *trueAny) MustBeValid() Any {
|
||||||
|
return any
|
||||||
|
}
|
||||||
|
|
||||||
|
type falseAny struct {
|
||||||
|
baseAny
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *falseAny) LastError() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *falseAny) ToBool() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *falseAny) ToInt() int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *falseAny) ToInt32() int32 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *falseAny) ToInt64() int64 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *falseAny) ToUint() uint {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *falseAny) ToUint32() uint32 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *falseAny) ToUint64() uint64 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *falseAny) ToFloat32() float32 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *falseAny) ToFloat64() float64 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *falseAny) ToString() string {
|
||||||
|
return "false"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *falseAny) WriteTo(stream *Stream) {
|
||||||
|
stream.WriteFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *falseAny) Parse() *Iterator {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *falseAny) GetInterface() interface{} {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *falseAny) ValueType() ValueType {
|
||||||
|
return BoolValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *falseAny) MustBeValid() Any {
|
||||||
|
return any
|
||||||
|
}
|
||||||
83
vendor/github.com/json-iterator/go/any_float.go
generated
vendored
Normal file
83
vendor/github.com/json-iterator/go/any_float.go
generated
vendored
Normal file
|
|
@ -0,0 +1,83 @@
|
||||||
|
package jsoniter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type floatAny struct {
|
||||||
|
baseAny
|
||||||
|
val float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *floatAny) Parse() *Iterator {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *floatAny) ValueType() ValueType {
|
||||||
|
return NumberValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *floatAny) MustBeValid() Any {
|
||||||
|
return any
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *floatAny) LastError() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *floatAny) ToBool() bool {
|
||||||
|
return any.ToFloat64() != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *floatAny) ToInt() int {
|
||||||
|
return int(any.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *floatAny) ToInt32() int32 {
|
||||||
|
return int32(any.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *floatAny) ToInt64() int64 {
|
||||||
|
return int64(any.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *floatAny) ToUint() uint {
|
||||||
|
if any.val > 0 {
|
||||||
|
return uint(any.val)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *floatAny) ToUint32() uint32 {
|
||||||
|
if any.val > 0 {
|
||||||
|
return uint32(any.val)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *floatAny) ToUint64() uint64 {
|
||||||
|
if any.val > 0 {
|
||||||
|
return uint64(any.val)
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *floatAny) ToFloat32() float32 {
|
||||||
|
return float32(any.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *floatAny) ToFloat64() float64 {
|
||||||
|
return any.val
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *floatAny) ToString() string {
|
||||||
|
return strconv.FormatFloat(any.val, 'E', -1, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *floatAny) WriteTo(stream *Stream) {
|
||||||
|
stream.WriteFloat64(any.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *floatAny) GetInterface() interface{} {
|
||||||
|
return any.val
|
||||||
|
}
|
||||||
74
vendor/github.com/json-iterator/go/any_int32.go
generated
vendored
Normal file
74
vendor/github.com/json-iterator/go/any_int32.go
generated
vendored
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
package jsoniter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type int32Any struct {
|
||||||
|
baseAny
|
||||||
|
val int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *int32Any) LastError() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *int32Any) ValueType() ValueType {
|
||||||
|
return NumberValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *int32Any) MustBeValid() Any {
|
||||||
|
return any
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *int32Any) ToBool() bool {
|
||||||
|
return any.val != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *int32Any) ToInt() int {
|
||||||
|
return int(any.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *int32Any) ToInt32() int32 {
|
||||||
|
return any.val
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *int32Any) ToInt64() int64 {
|
||||||
|
return int64(any.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *int32Any) ToUint() uint {
|
||||||
|
return uint(any.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *int32Any) ToUint32() uint32 {
|
||||||
|
return uint32(any.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *int32Any) ToUint64() uint64 {
|
||||||
|
return uint64(any.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *int32Any) ToFloat32() float32 {
|
||||||
|
return float32(any.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *int32Any) ToFloat64() float64 {
|
||||||
|
return float64(any.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *int32Any) ToString() string {
|
||||||
|
return strconv.FormatInt(int64(any.val), 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *int32Any) WriteTo(stream *Stream) {
|
||||||
|
stream.WriteInt32(any.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *int32Any) Parse() *Iterator {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *int32Any) GetInterface() interface{} {
|
||||||
|
return any.val
|
||||||
|
}
|
||||||
74
vendor/github.com/json-iterator/go/any_int64.go
generated
vendored
Normal file
74
vendor/github.com/json-iterator/go/any_int64.go
generated
vendored
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
package jsoniter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type int64Any struct {
|
||||||
|
baseAny
|
||||||
|
val int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *int64Any) LastError() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *int64Any) ValueType() ValueType {
|
||||||
|
return NumberValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *int64Any) MustBeValid() Any {
|
||||||
|
return any
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *int64Any) ToBool() bool {
|
||||||
|
return any.val != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *int64Any) ToInt() int {
|
||||||
|
return int(any.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *int64Any) ToInt32() int32 {
|
||||||
|
return int32(any.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *int64Any) ToInt64() int64 {
|
||||||
|
return any.val
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *int64Any) ToUint() uint {
|
||||||
|
return uint(any.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *int64Any) ToUint32() uint32 {
|
||||||
|
return uint32(any.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *int64Any) ToUint64() uint64 {
|
||||||
|
return uint64(any.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *int64Any) ToFloat32() float32 {
|
||||||
|
return float32(any.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *int64Any) ToFloat64() float64 {
|
||||||
|
return float64(any.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *int64Any) ToString() string {
|
||||||
|
return strconv.FormatInt(any.val, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *int64Any) WriteTo(stream *Stream) {
|
||||||
|
stream.WriteInt64(any.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *int64Any) Parse() *Iterator {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *int64Any) GetInterface() interface{} {
|
||||||
|
return any.val
|
||||||
|
}
|
||||||
82
vendor/github.com/json-iterator/go/any_invalid.go
generated
vendored
Normal file
82
vendor/github.com/json-iterator/go/any_invalid.go
generated
vendored
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
package jsoniter
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
type invalidAny struct {
|
||||||
|
baseAny
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func newInvalidAny(path []interface{}) *invalidAny {
|
||||||
|
return &invalidAny{baseAny{}, fmt.Errorf("%v not found", path)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *invalidAny) LastError() error {
|
||||||
|
return any.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *invalidAny) ValueType() ValueType {
|
||||||
|
return InvalidValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *invalidAny) MustBeValid() Any {
|
||||||
|
panic(any.err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *invalidAny) ToBool() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *invalidAny) ToInt() int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *invalidAny) ToInt32() int32 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *invalidAny) ToInt64() int64 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *invalidAny) ToUint() uint {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *invalidAny) ToUint32() uint32 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *invalidAny) ToUint64() uint64 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *invalidAny) ToFloat32() float32 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *invalidAny) ToFloat64() float64 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *invalidAny) ToString() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *invalidAny) WriteTo(stream *Stream) {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *invalidAny) Get(path ...interface{}) Any {
|
||||||
|
if any.err == nil {
|
||||||
|
return &invalidAny{baseAny{}, fmt.Errorf("get %v from invalid", path)}
|
||||||
|
}
|
||||||
|
return &invalidAny{baseAny{}, fmt.Errorf("%v, get %v from invalid", any.err, path)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *invalidAny) Parse() *Iterator {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *invalidAny) GetInterface() interface{} {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
69
vendor/github.com/json-iterator/go/any_nil.go
generated
vendored
Normal file
69
vendor/github.com/json-iterator/go/any_nil.go
generated
vendored
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
package jsoniter
|
||||||
|
|
||||||
|
type nilAny struct {
|
||||||
|
baseAny
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *nilAny) LastError() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *nilAny) ValueType() ValueType {
|
||||||
|
return NilValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *nilAny) MustBeValid() Any {
|
||||||
|
return any
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *nilAny) ToBool() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *nilAny) ToInt() int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *nilAny) ToInt32() int32 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *nilAny) ToInt64() int64 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *nilAny) ToUint() uint {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *nilAny) ToUint32() uint32 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *nilAny) ToUint64() uint64 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *nilAny) ToFloat32() float32 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *nilAny) ToFloat64() float64 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *nilAny) ToString() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *nilAny) WriteTo(stream *Stream) {
|
||||||
|
stream.WriteNil()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *nilAny) Parse() *Iterator {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *nilAny) GetInterface() interface{} {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
123
vendor/github.com/json-iterator/go/any_number.go
generated
vendored
Normal file
123
vendor/github.com/json-iterator/go/any_number.go
generated
vendored
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
package jsoniter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
type numberLazyAny struct {
|
||||||
|
baseAny
|
||||||
|
cfg *frozenConfig
|
||||||
|
buf []byte
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *numberLazyAny) ValueType() ValueType {
|
||||||
|
return NumberValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *numberLazyAny) MustBeValid() Any {
|
||||||
|
return any
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *numberLazyAny) LastError() error {
|
||||||
|
return any.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *numberLazyAny) ToBool() bool {
|
||||||
|
return any.ToFloat64() != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *numberLazyAny) ToInt() int {
|
||||||
|
iter := any.cfg.BorrowIterator(any.buf)
|
||||||
|
defer any.cfg.ReturnIterator(iter)
|
||||||
|
val := iter.ReadInt()
|
||||||
|
if iter.Error != nil && iter.Error != io.EOF {
|
||||||
|
any.err = iter.Error
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *numberLazyAny) ToInt32() int32 {
|
||||||
|
iter := any.cfg.BorrowIterator(any.buf)
|
||||||
|
defer any.cfg.ReturnIterator(iter)
|
||||||
|
val := iter.ReadInt32()
|
||||||
|
if iter.Error != nil && iter.Error != io.EOF {
|
||||||
|
any.err = iter.Error
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *numberLazyAny) ToInt64() int64 {
|
||||||
|
iter := any.cfg.BorrowIterator(any.buf)
|
||||||
|
defer any.cfg.ReturnIterator(iter)
|
||||||
|
val := iter.ReadInt64()
|
||||||
|
if iter.Error != nil && iter.Error != io.EOF {
|
||||||
|
any.err = iter.Error
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *numberLazyAny) ToUint() uint {
|
||||||
|
iter := any.cfg.BorrowIterator(any.buf)
|
||||||
|
defer any.cfg.ReturnIterator(iter)
|
||||||
|
val := iter.ReadUint()
|
||||||
|
if iter.Error != nil && iter.Error != io.EOF {
|
||||||
|
any.err = iter.Error
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *numberLazyAny) ToUint32() uint32 {
|
||||||
|
iter := any.cfg.BorrowIterator(any.buf)
|
||||||
|
defer any.cfg.ReturnIterator(iter)
|
||||||
|
val := iter.ReadUint32()
|
||||||
|
if iter.Error != nil && iter.Error != io.EOF {
|
||||||
|
any.err = iter.Error
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *numberLazyAny) ToUint64() uint64 {
|
||||||
|
iter := any.cfg.BorrowIterator(any.buf)
|
||||||
|
defer any.cfg.ReturnIterator(iter)
|
||||||
|
val := iter.ReadUint64()
|
||||||
|
if iter.Error != nil && iter.Error != io.EOF {
|
||||||
|
any.err = iter.Error
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *numberLazyAny) ToFloat32() float32 {
|
||||||
|
iter := any.cfg.BorrowIterator(any.buf)
|
||||||
|
defer any.cfg.ReturnIterator(iter)
|
||||||
|
val := iter.ReadFloat32()
|
||||||
|
if iter.Error != nil && iter.Error != io.EOF {
|
||||||
|
any.err = iter.Error
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *numberLazyAny) ToFloat64() float64 {
|
||||||
|
iter := any.cfg.BorrowIterator(any.buf)
|
||||||
|
defer any.cfg.ReturnIterator(iter)
|
||||||
|
val := iter.ReadFloat64()
|
||||||
|
if iter.Error != nil && iter.Error != io.EOF {
|
||||||
|
any.err = iter.Error
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *numberLazyAny) ToString() string {
|
||||||
|
return *(*string)(unsafe.Pointer(&any.buf))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *numberLazyAny) WriteTo(stream *Stream) {
|
||||||
|
stream.Write(any.buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *numberLazyAny) GetInterface() interface{} {
|
||||||
|
iter := any.cfg.BorrowIterator(any.buf)
|
||||||
|
defer any.cfg.ReturnIterator(iter)
|
||||||
|
return iter.Read()
|
||||||
|
}
|
||||||
374
vendor/github.com/json-iterator/go/any_object.go
generated
vendored
Normal file
374
vendor/github.com/json-iterator/go/any_object.go
generated
vendored
Normal file
|
|
@ -0,0 +1,374 @@
|
||||||
|
package jsoniter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
type objectLazyAny struct {
|
||||||
|
baseAny
|
||||||
|
cfg *frozenConfig
|
||||||
|
buf []byte
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *objectLazyAny) ValueType() ValueType {
|
||||||
|
return ObjectValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *objectLazyAny) MustBeValid() Any {
|
||||||
|
return any
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *objectLazyAny) LastError() error {
|
||||||
|
return any.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *objectLazyAny) ToBool() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *objectLazyAny) ToInt() int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *objectLazyAny) ToInt32() int32 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *objectLazyAny) ToInt64() int64 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *objectLazyAny) ToUint() uint {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *objectLazyAny) ToUint32() uint32 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *objectLazyAny) ToUint64() uint64 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *objectLazyAny) ToFloat32() float32 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *objectLazyAny) ToFloat64() float64 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *objectLazyAny) ToString() string {
|
||||||
|
return *(*string)(unsafe.Pointer(&any.buf))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *objectLazyAny) ToVal(obj interface{}) {
|
||||||
|
iter := any.cfg.BorrowIterator(any.buf)
|
||||||
|
defer any.cfg.ReturnIterator(iter)
|
||||||
|
iter.ReadVal(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *objectLazyAny) Get(path ...interface{}) Any {
|
||||||
|
if len(path) == 0 {
|
||||||
|
return any
|
||||||
|
}
|
||||||
|
switch firstPath := path[0].(type) {
|
||||||
|
case string:
|
||||||
|
iter := any.cfg.BorrowIterator(any.buf)
|
||||||
|
defer any.cfg.ReturnIterator(iter)
|
||||||
|
valueBytes := locateObjectField(iter, firstPath)
|
||||||
|
if valueBytes == nil {
|
||||||
|
return newInvalidAny(path)
|
||||||
|
}
|
||||||
|
iter.ResetBytes(valueBytes)
|
||||||
|
return locatePath(iter, path[1:])
|
||||||
|
case int32:
|
||||||
|
if '*' == firstPath {
|
||||||
|
mappedAll := map[string]Any{}
|
||||||
|
iter := any.cfg.BorrowIterator(any.buf)
|
||||||
|
defer any.cfg.ReturnIterator(iter)
|
||||||
|
iter.ReadMapCB(func(iter *Iterator, field string) bool {
|
||||||
|
mapped := locatePath(iter, path[1:])
|
||||||
|
if mapped.ValueType() != InvalidValue {
|
||||||
|
mappedAll[field] = mapped
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return wrapMap(mappedAll)
|
||||||
|
}
|
||||||
|
return newInvalidAny(path)
|
||||||
|
default:
|
||||||
|
return newInvalidAny(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *objectLazyAny) Keys() []string {
|
||||||
|
keys := []string{}
|
||||||
|
iter := any.cfg.BorrowIterator(any.buf)
|
||||||
|
defer any.cfg.ReturnIterator(iter)
|
||||||
|
iter.ReadMapCB(func(iter *Iterator, field string) bool {
|
||||||
|
iter.Skip()
|
||||||
|
keys = append(keys, field)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *objectLazyAny) Size() int {
|
||||||
|
size := 0
|
||||||
|
iter := any.cfg.BorrowIterator(any.buf)
|
||||||
|
defer any.cfg.ReturnIterator(iter)
|
||||||
|
iter.ReadObjectCB(func(iter *Iterator, field string) bool {
|
||||||
|
iter.Skip()
|
||||||
|
size++
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *objectLazyAny) WriteTo(stream *Stream) {
|
||||||
|
stream.Write(any.buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *objectLazyAny) GetInterface() interface{} {
|
||||||
|
iter := any.cfg.BorrowIterator(any.buf)
|
||||||
|
defer any.cfg.ReturnIterator(iter)
|
||||||
|
return iter.Read()
|
||||||
|
}
|
||||||
|
|
||||||
|
type objectAny struct {
|
||||||
|
baseAny
|
||||||
|
err error
|
||||||
|
val reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapStruct(val interface{}) *objectAny {
|
||||||
|
return &objectAny{baseAny{}, nil, reflect.ValueOf(val)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *objectAny) ValueType() ValueType {
|
||||||
|
return ObjectValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *objectAny) MustBeValid() Any {
|
||||||
|
return any
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *objectAny) Parse() *Iterator {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *objectAny) LastError() error {
|
||||||
|
return any.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *objectAny) ToBool() bool {
|
||||||
|
return any.val.NumField() != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *objectAny) ToInt() int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *objectAny) ToInt32() int32 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *objectAny) ToInt64() int64 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *objectAny) ToUint() uint {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *objectAny) ToUint32() uint32 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *objectAny) ToUint64() uint64 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *objectAny) ToFloat32() float32 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *objectAny) ToFloat64() float64 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *objectAny) ToString() string {
|
||||||
|
str, err := MarshalToString(any.val.Interface())
|
||||||
|
any.err = err
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *objectAny) Get(path ...interface{}) Any {
|
||||||
|
if len(path) == 0 {
|
||||||
|
return any
|
||||||
|
}
|
||||||
|
switch firstPath := path[0].(type) {
|
||||||
|
case string:
|
||||||
|
field := any.val.FieldByName(firstPath)
|
||||||
|
if !field.IsValid() {
|
||||||
|
return newInvalidAny(path)
|
||||||
|
}
|
||||||
|
return Wrap(field.Interface())
|
||||||
|
case int32:
|
||||||
|
if '*' == firstPath {
|
||||||
|
mappedAll := map[string]Any{}
|
||||||
|
for i := 0; i < any.val.NumField(); i++ {
|
||||||
|
field := any.val.Field(i)
|
||||||
|
if field.CanInterface() {
|
||||||
|
mapped := Wrap(field.Interface()).Get(path[1:]...)
|
||||||
|
if mapped.ValueType() != InvalidValue {
|
||||||
|
mappedAll[any.val.Type().Field(i).Name] = mapped
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return wrapMap(mappedAll)
|
||||||
|
}
|
||||||
|
return newInvalidAny(path)
|
||||||
|
default:
|
||||||
|
return newInvalidAny(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *objectAny) Keys() []string {
|
||||||
|
keys := make([]string, 0, any.val.NumField())
|
||||||
|
for i := 0; i < any.val.NumField(); i++ {
|
||||||
|
keys = append(keys, any.val.Type().Field(i).Name)
|
||||||
|
}
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *objectAny) Size() int {
|
||||||
|
return any.val.NumField()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *objectAny) WriteTo(stream *Stream) {
|
||||||
|
stream.WriteVal(any.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *objectAny) GetInterface() interface{} {
|
||||||
|
return any.val.Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
type mapAny struct {
|
||||||
|
baseAny
|
||||||
|
err error
|
||||||
|
val reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapMap(val interface{}) *mapAny {
|
||||||
|
return &mapAny{baseAny{}, nil, reflect.ValueOf(val)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *mapAny) ValueType() ValueType {
|
||||||
|
return ObjectValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *mapAny) MustBeValid() Any {
|
||||||
|
return any
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *mapAny) Parse() *Iterator {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *mapAny) LastError() error {
|
||||||
|
return any.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *mapAny) ToBool() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *mapAny) ToInt() int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *mapAny) ToInt32() int32 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *mapAny) ToInt64() int64 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *mapAny) ToUint() uint {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *mapAny) ToUint32() uint32 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *mapAny) ToUint64() uint64 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *mapAny) ToFloat32() float32 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *mapAny) ToFloat64() float64 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *mapAny) ToString() string {
|
||||||
|
str, err := MarshalToString(any.val.Interface())
|
||||||
|
any.err = err
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *mapAny) Get(path ...interface{}) Any {
|
||||||
|
if len(path) == 0 {
|
||||||
|
return any
|
||||||
|
}
|
||||||
|
switch firstPath := path[0].(type) {
|
||||||
|
case int32:
|
||||||
|
if '*' == firstPath {
|
||||||
|
mappedAll := map[string]Any{}
|
||||||
|
for _, key := range any.val.MapKeys() {
|
||||||
|
keyAsStr := key.String()
|
||||||
|
element := Wrap(any.val.MapIndex(key).Interface())
|
||||||
|
mapped := element.Get(path[1:]...)
|
||||||
|
if mapped.ValueType() != InvalidValue {
|
||||||
|
mappedAll[keyAsStr] = mapped
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return wrapMap(mappedAll)
|
||||||
|
}
|
||||||
|
return newInvalidAny(path)
|
||||||
|
default:
|
||||||
|
value := any.val.MapIndex(reflect.ValueOf(firstPath))
|
||||||
|
if !value.IsValid() {
|
||||||
|
return newInvalidAny(path)
|
||||||
|
}
|
||||||
|
return Wrap(value.Interface())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *mapAny) Keys() []string {
|
||||||
|
keys := make([]string, 0, any.val.Len())
|
||||||
|
for _, key := range any.val.MapKeys() {
|
||||||
|
keys = append(keys, key.String())
|
||||||
|
}
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *mapAny) Size() int {
|
||||||
|
return any.val.Len()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *mapAny) WriteTo(stream *Stream) {
|
||||||
|
stream.WriteVal(any.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *mapAny) GetInterface() interface{} {
|
||||||
|
return any.val.Interface()
|
||||||
|
}
|
||||||
166
vendor/github.com/json-iterator/go/any_str.go
generated
vendored
Normal file
166
vendor/github.com/json-iterator/go/any_str.go
generated
vendored
Normal file
|
|
@ -0,0 +1,166 @@
|
||||||
|
package jsoniter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stringAny struct {
|
||||||
|
baseAny
|
||||||
|
val string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *stringAny) Get(path ...interface{}) Any {
|
||||||
|
if len(path) == 0 {
|
||||||
|
return any
|
||||||
|
}
|
||||||
|
return &invalidAny{baseAny{}, fmt.Errorf("GetIndex %v from simple value", path)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *stringAny) Parse() *Iterator {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *stringAny) ValueType() ValueType {
|
||||||
|
return StringValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *stringAny) MustBeValid() Any {
|
||||||
|
return any
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *stringAny) LastError() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *stringAny) ToBool() bool {
|
||||||
|
str := any.ToString()
|
||||||
|
if str == "0" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, c := range str {
|
||||||
|
switch c {
|
||||||
|
case ' ', '\n', '\r', '\t':
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *stringAny) ToInt() int {
|
||||||
|
return int(any.ToInt64())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *stringAny) ToInt32() int32 {
|
||||||
|
return int32(any.ToInt64())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *stringAny) ToInt64() int64 {
|
||||||
|
if any.val == "" {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
flag := 1
|
||||||
|
startPos := 0
|
||||||
|
if any.val[0] == '+' || any.val[0] == '-' {
|
||||||
|
startPos = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if any.val[0] == '-' {
|
||||||
|
flag = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
endPos := startPos
|
||||||
|
for i := startPos; i < len(any.val); i++ {
|
||||||
|
if any.val[i] >= '0' && any.val[i] <= '9' {
|
||||||
|
endPos = i + 1
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parsed, _ := strconv.ParseInt(any.val[startPos:endPos], 10, 64)
|
||||||
|
return int64(flag) * parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *stringAny) ToUint() uint {
|
||||||
|
return uint(any.ToUint64())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *stringAny) ToUint32() uint32 {
|
||||||
|
return uint32(any.ToUint64())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *stringAny) ToUint64() uint64 {
|
||||||
|
if any.val == "" {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
startPos := 0
|
||||||
|
|
||||||
|
if any.val[0] == '-' {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if any.val[0] == '+' {
|
||||||
|
startPos = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
endPos := startPos
|
||||||
|
for i := startPos; i < len(any.val); i++ {
|
||||||
|
if any.val[i] >= '0' && any.val[i] <= '9' {
|
||||||
|
endPos = i + 1
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parsed, _ := strconv.ParseUint(any.val[startPos:endPos], 10, 64)
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *stringAny) ToFloat32() float32 {
|
||||||
|
return float32(any.ToFloat64())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *stringAny) ToFloat64() float64 {
|
||||||
|
if len(any.val) == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// first char invalid
|
||||||
|
if any.val[0] != '+' && any.val[0] != '-' && (any.val[0] > '9' || any.val[0] < '0') {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// extract valid num expression from string
|
||||||
|
// eg 123true => 123, -12.12xxa => -12.12
|
||||||
|
endPos := 1
|
||||||
|
for i := 1; i < len(any.val); i++ {
|
||||||
|
if any.val[i] == '.' || any.val[i] == 'e' || any.val[i] == 'E' || any.val[i] == '+' || any.val[i] == '-' {
|
||||||
|
endPos = i + 1
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// end position is the first char which is not digit
|
||||||
|
if any.val[i] >= '0' && any.val[i] <= '9' {
|
||||||
|
endPos = i + 1
|
||||||
|
} else {
|
||||||
|
endPos = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
parsed, _ := strconv.ParseFloat(any.val[:endPos], 64)
|
||||||
|
return parsed
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *stringAny) ToString() string {
|
||||||
|
return any.val
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *stringAny) WriteTo(stream *Stream) {
|
||||||
|
stream.WriteString(any.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *stringAny) GetInterface() interface{} {
|
||||||
|
return any.val
|
||||||
|
}
|
||||||
74
vendor/github.com/json-iterator/go/any_uint32.go
generated
vendored
Normal file
74
vendor/github.com/json-iterator/go/any_uint32.go
generated
vendored
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
package jsoniter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type uint32Any struct {
|
||||||
|
baseAny
|
||||||
|
val uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *uint32Any) LastError() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *uint32Any) ValueType() ValueType {
|
||||||
|
return NumberValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *uint32Any) MustBeValid() Any {
|
||||||
|
return any
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *uint32Any) ToBool() bool {
|
||||||
|
return any.val != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *uint32Any) ToInt() int {
|
||||||
|
return int(any.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *uint32Any) ToInt32() int32 {
|
||||||
|
return int32(any.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *uint32Any) ToInt64() int64 {
|
||||||
|
return int64(any.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *uint32Any) ToUint() uint {
|
||||||
|
return uint(any.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *uint32Any) ToUint32() uint32 {
|
||||||
|
return any.val
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *uint32Any) ToUint64() uint64 {
|
||||||
|
return uint64(any.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *uint32Any) ToFloat32() float32 {
|
||||||
|
return float32(any.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *uint32Any) ToFloat64() float64 {
|
||||||
|
return float64(any.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *uint32Any) ToString() string {
|
||||||
|
return strconv.FormatInt(int64(any.val), 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *uint32Any) WriteTo(stream *Stream) {
|
||||||
|
stream.WriteUint32(any.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *uint32Any) Parse() *Iterator {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *uint32Any) GetInterface() interface{} {
|
||||||
|
return any.val
|
||||||
|
}
|
||||||
74
vendor/github.com/json-iterator/go/any_uint64.go
generated
vendored
Normal file
74
vendor/github.com/json-iterator/go/any_uint64.go
generated
vendored
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
package jsoniter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type uint64Any struct {
|
||||||
|
baseAny
|
||||||
|
val uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *uint64Any) LastError() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *uint64Any) ValueType() ValueType {
|
||||||
|
return NumberValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *uint64Any) MustBeValid() Any {
|
||||||
|
return any
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *uint64Any) ToBool() bool {
|
||||||
|
return any.val != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *uint64Any) ToInt() int {
|
||||||
|
return int(any.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *uint64Any) ToInt32() int32 {
|
||||||
|
return int32(any.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *uint64Any) ToInt64() int64 {
|
||||||
|
return int64(any.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *uint64Any) ToUint() uint {
|
||||||
|
return uint(any.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *uint64Any) ToUint32() uint32 {
|
||||||
|
return uint32(any.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *uint64Any) ToUint64() uint64 {
|
||||||
|
return any.val
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *uint64Any) ToFloat32() float32 {
|
||||||
|
return float32(any.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *uint64Any) ToFloat64() float64 {
|
||||||
|
return float64(any.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *uint64Any) ToString() string {
|
||||||
|
return strconv.FormatUint(any.val, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *uint64Any) WriteTo(stream *Stream) {
|
||||||
|
stream.WriteUint64(any.val)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *uint64Any) Parse() *Iterator {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (any *uint64Any) GetInterface() interface{} {
|
||||||
|
return any.val
|
||||||
|
}
|
||||||
12
vendor/github.com/json-iterator/go/build.sh
generated
vendored
Normal file
12
vendor/github.com/json-iterator/go/build.sh
generated
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
set -x
|
||||||
|
|
||||||
|
if [ ! -d /tmp/build-golang/src/github.com/json-iterator ]; then
|
||||||
|
mkdir -p /tmp/build-golang/src/github.com/json-iterator
|
||||||
|
ln -s $PWD /tmp/build-golang/src/github.com/json-iterator/go
|
||||||
|
fi
|
||||||
|
export GOPATH=/tmp/build-golang
|
||||||
|
go get -u github.com/golang/dep/cmd/dep
|
||||||
|
cd /tmp/build-golang/src/github.com/json-iterator/go
|
||||||
|
exec $GOPATH/bin/dep ensure -update
|
||||||
375
vendor/github.com/json-iterator/go/config.go
generated
vendored
Normal file
375
vendor/github.com/json-iterator/go/config.go
generated
vendored
Normal file
|
|
@ -0,0 +1,375 @@
|
||||||
|
package jsoniter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"sync"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/modern-go/concurrent"
|
||||||
|
"github.com/modern-go/reflect2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config customize how the API should behave.
|
||||||
|
// The API is created from Config by Froze.
|
||||||
|
type Config struct {
|
||||||
|
IndentionStep int
|
||||||
|
MarshalFloatWith6Digits bool
|
||||||
|
EscapeHTML bool
|
||||||
|
SortMapKeys bool
|
||||||
|
UseNumber bool
|
||||||
|
DisallowUnknownFields bool
|
||||||
|
TagKey string
|
||||||
|
OnlyTaggedField bool
|
||||||
|
ValidateJsonRawMessage bool
|
||||||
|
ObjectFieldMustBeSimpleString bool
|
||||||
|
CaseSensitive bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// API the public interface of this package.
|
||||||
|
// Primary Marshal and Unmarshal.
|
||||||
|
type API interface {
|
||||||
|
IteratorPool
|
||||||
|
StreamPool
|
||||||
|
MarshalToString(v interface{}) (string, error)
|
||||||
|
Marshal(v interface{}) ([]byte, error)
|
||||||
|
MarshalIndent(v interface{}, prefix, indent string) ([]byte, error)
|
||||||
|
UnmarshalFromString(str string, v interface{}) error
|
||||||
|
Unmarshal(data []byte, v interface{}) error
|
||||||
|
Get(data []byte, path ...interface{}) Any
|
||||||
|
NewEncoder(writer io.Writer) *Encoder
|
||||||
|
NewDecoder(reader io.Reader) *Decoder
|
||||||
|
Valid(data []byte) bool
|
||||||
|
RegisterExtension(extension Extension)
|
||||||
|
DecoderOf(typ reflect2.Type) ValDecoder
|
||||||
|
EncoderOf(typ reflect2.Type) ValEncoder
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfigDefault the default API
|
||||||
|
var ConfigDefault = Config{
|
||||||
|
EscapeHTML: true,
|
||||||
|
}.Froze()
|
||||||
|
|
||||||
|
// ConfigCompatibleWithStandardLibrary tries to be 100% compatible with standard library behavior
|
||||||
|
var ConfigCompatibleWithStandardLibrary = Config{
|
||||||
|
EscapeHTML: true,
|
||||||
|
SortMapKeys: true,
|
||||||
|
ValidateJsonRawMessage: true,
|
||||||
|
}.Froze()
|
||||||
|
|
||||||
|
// ConfigFastest marshals float with only 6 digits precision
|
||||||
|
var ConfigFastest = Config{
|
||||||
|
EscapeHTML: false,
|
||||||
|
MarshalFloatWith6Digits: true, // will lose precession
|
||||||
|
ObjectFieldMustBeSimpleString: true, // do not unescape object field
|
||||||
|
}.Froze()
|
||||||
|
|
||||||
|
type frozenConfig struct {
|
||||||
|
configBeforeFrozen Config
|
||||||
|
sortMapKeys bool
|
||||||
|
indentionStep int
|
||||||
|
objectFieldMustBeSimpleString bool
|
||||||
|
onlyTaggedField bool
|
||||||
|
disallowUnknownFields bool
|
||||||
|
decoderCache *concurrent.Map
|
||||||
|
encoderCache *concurrent.Map
|
||||||
|
encoderExtension Extension
|
||||||
|
decoderExtension Extension
|
||||||
|
extraExtensions []Extension
|
||||||
|
streamPool *sync.Pool
|
||||||
|
iteratorPool *sync.Pool
|
||||||
|
caseSensitive bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *frozenConfig) initCache() {
|
||||||
|
cfg.decoderCache = concurrent.NewMap()
|
||||||
|
cfg.encoderCache = concurrent.NewMap()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *frozenConfig) addDecoderToCache(cacheKey uintptr, decoder ValDecoder) {
|
||||||
|
cfg.decoderCache.Store(cacheKey, decoder)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *frozenConfig) addEncoderToCache(cacheKey uintptr, encoder ValEncoder) {
|
||||||
|
cfg.encoderCache.Store(cacheKey, encoder)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *frozenConfig) getDecoderFromCache(cacheKey uintptr) ValDecoder {
|
||||||
|
decoder, found := cfg.decoderCache.Load(cacheKey)
|
||||||
|
if found {
|
||||||
|
return decoder.(ValDecoder)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *frozenConfig) getEncoderFromCache(cacheKey uintptr) ValEncoder {
|
||||||
|
encoder, found := cfg.encoderCache.Load(cacheKey)
|
||||||
|
if found {
|
||||||
|
return encoder.(ValEncoder)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var cfgCache = concurrent.NewMap()
|
||||||
|
|
||||||
|
func getFrozenConfigFromCache(cfg Config) *frozenConfig {
|
||||||
|
obj, found := cfgCache.Load(cfg)
|
||||||
|
if found {
|
||||||
|
return obj.(*frozenConfig)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addFrozenConfigToCache(cfg Config, frozenConfig *frozenConfig) {
|
||||||
|
cfgCache.Store(cfg, frozenConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Froze forge API from config
|
||||||
|
func (cfg Config) Froze() API {
|
||||||
|
api := &frozenConfig{
|
||||||
|
sortMapKeys: cfg.SortMapKeys,
|
||||||
|
indentionStep: cfg.IndentionStep,
|
||||||
|
objectFieldMustBeSimpleString: cfg.ObjectFieldMustBeSimpleString,
|
||||||
|
onlyTaggedField: cfg.OnlyTaggedField,
|
||||||
|
disallowUnknownFields: cfg.DisallowUnknownFields,
|
||||||
|
caseSensitive: cfg.CaseSensitive,
|
||||||
|
}
|
||||||
|
api.streamPool = &sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return NewStream(api, nil, 512)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
api.iteratorPool = &sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return NewIterator(api)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
api.initCache()
|
||||||
|
encoderExtension := EncoderExtension{}
|
||||||
|
decoderExtension := DecoderExtension{}
|
||||||
|
if cfg.MarshalFloatWith6Digits {
|
||||||
|
api.marshalFloatWith6Digits(encoderExtension)
|
||||||
|
}
|
||||||
|
if cfg.EscapeHTML {
|
||||||
|
api.escapeHTML(encoderExtension)
|
||||||
|
}
|
||||||
|
if cfg.UseNumber {
|
||||||
|
api.useNumber(decoderExtension)
|
||||||
|
}
|
||||||
|
if cfg.ValidateJsonRawMessage {
|
||||||
|
api.validateJsonRawMessage(encoderExtension)
|
||||||
|
}
|
||||||
|
api.encoderExtension = encoderExtension
|
||||||
|
api.decoderExtension = decoderExtension
|
||||||
|
api.configBeforeFrozen = cfg
|
||||||
|
return api
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg Config) frozeWithCacheReuse(extraExtensions []Extension) *frozenConfig {
|
||||||
|
api := getFrozenConfigFromCache(cfg)
|
||||||
|
if api != nil {
|
||||||
|
return api
|
||||||
|
}
|
||||||
|
api = cfg.Froze().(*frozenConfig)
|
||||||
|
for _, extension := range extraExtensions {
|
||||||
|
api.RegisterExtension(extension)
|
||||||
|
}
|
||||||
|
addFrozenConfigToCache(cfg, api)
|
||||||
|
return api
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *frozenConfig) validateJsonRawMessage(extension EncoderExtension) {
|
||||||
|
encoder := &funcEncoder{func(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
rawMessage := *(*json.RawMessage)(ptr)
|
||||||
|
iter := cfg.BorrowIterator([]byte(rawMessage))
|
||||||
|
defer cfg.ReturnIterator(iter)
|
||||||
|
iter.Read()
|
||||||
|
if iter.Error != nil && iter.Error != io.EOF {
|
||||||
|
stream.WriteRaw("null")
|
||||||
|
} else {
|
||||||
|
stream.WriteRaw(string(rawMessage))
|
||||||
|
}
|
||||||
|
}, func(ptr unsafe.Pointer) bool {
|
||||||
|
return len(*((*json.RawMessage)(ptr))) == 0
|
||||||
|
}}
|
||||||
|
extension[reflect2.TypeOfPtr((*json.RawMessage)(nil)).Elem()] = encoder
|
||||||
|
extension[reflect2.TypeOfPtr((*RawMessage)(nil)).Elem()] = encoder
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *frozenConfig) useNumber(extension DecoderExtension) {
|
||||||
|
extension[reflect2.TypeOfPtr((*interface{})(nil)).Elem()] = &funcDecoder{func(ptr unsafe.Pointer, iter *Iterator) {
|
||||||
|
exitingValue := *((*interface{})(ptr))
|
||||||
|
if exitingValue != nil && reflect.TypeOf(exitingValue).Kind() == reflect.Ptr {
|
||||||
|
iter.ReadVal(exitingValue)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if iter.WhatIsNext() == NumberValue {
|
||||||
|
*((*interface{})(ptr)) = json.Number(iter.readNumberAsString())
|
||||||
|
} else {
|
||||||
|
*((*interface{})(ptr)) = iter.Read()
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
func (cfg *frozenConfig) getTagKey() string {
|
||||||
|
tagKey := cfg.configBeforeFrozen.TagKey
|
||||||
|
if tagKey == "" {
|
||||||
|
return "json"
|
||||||
|
}
|
||||||
|
return tagKey
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *frozenConfig) RegisterExtension(extension Extension) {
|
||||||
|
cfg.extraExtensions = append(cfg.extraExtensions, extension)
|
||||||
|
copied := cfg.configBeforeFrozen
|
||||||
|
cfg.configBeforeFrozen = copied
|
||||||
|
}
|
||||||
|
|
||||||
|
type lossyFloat32Encoder struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *lossyFloat32Encoder) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
stream.WriteFloat32Lossy(*((*float32)(ptr)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *lossyFloat32Encoder) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
return *((*float32)(ptr)) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type lossyFloat64Encoder struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *lossyFloat64Encoder) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
stream.WriteFloat64Lossy(*((*float64)(ptr)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *lossyFloat64Encoder) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
return *((*float64)(ptr)) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnableLossyFloatMarshalling keeps 10**(-6) precision
|
||||||
|
// for float variables for better performance.
|
||||||
|
func (cfg *frozenConfig) marshalFloatWith6Digits(extension EncoderExtension) {
|
||||||
|
// for better performance
|
||||||
|
extension[reflect2.TypeOfPtr((*float32)(nil)).Elem()] = &lossyFloat32Encoder{}
|
||||||
|
extension[reflect2.TypeOfPtr((*float64)(nil)).Elem()] = &lossyFloat64Encoder{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type htmlEscapedStringEncoder struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *htmlEscapedStringEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
str := *((*string)(ptr))
|
||||||
|
stream.WriteStringWithHTMLEscaped(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *htmlEscapedStringEncoder) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
return *((*string)(ptr)) == ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *frozenConfig) escapeHTML(encoderExtension EncoderExtension) {
|
||||||
|
encoderExtension[reflect2.TypeOfPtr((*string)(nil)).Elem()] = &htmlEscapedStringEncoder{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *frozenConfig) cleanDecoders() {
|
||||||
|
typeDecoders = map[string]ValDecoder{}
|
||||||
|
fieldDecoders = map[string]ValDecoder{}
|
||||||
|
*cfg = *(cfg.configBeforeFrozen.Froze().(*frozenConfig))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *frozenConfig) cleanEncoders() {
|
||||||
|
typeEncoders = map[string]ValEncoder{}
|
||||||
|
fieldEncoders = map[string]ValEncoder{}
|
||||||
|
*cfg = *(cfg.configBeforeFrozen.Froze().(*frozenConfig))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *frozenConfig) MarshalToString(v interface{}) (string, error) {
|
||||||
|
stream := cfg.BorrowStream(nil)
|
||||||
|
defer cfg.ReturnStream(stream)
|
||||||
|
stream.WriteVal(v)
|
||||||
|
if stream.Error != nil {
|
||||||
|
return "", stream.Error
|
||||||
|
}
|
||||||
|
return string(stream.Buffer()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *frozenConfig) Marshal(v interface{}) ([]byte, error) {
|
||||||
|
stream := cfg.BorrowStream(nil)
|
||||||
|
defer cfg.ReturnStream(stream)
|
||||||
|
stream.WriteVal(v)
|
||||||
|
if stream.Error != nil {
|
||||||
|
return nil, stream.Error
|
||||||
|
}
|
||||||
|
result := stream.Buffer()
|
||||||
|
copied := make([]byte, len(result))
|
||||||
|
copy(copied, result)
|
||||||
|
return copied, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *frozenConfig) MarshalIndent(v interface{}, prefix, indent string) ([]byte, error) {
|
||||||
|
if prefix != "" {
|
||||||
|
panic("prefix is not supported")
|
||||||
|
}
|
||||||
|
for _, r := range indent {
|
||||||
|
if r != ' ' {
|
||||||
|
panic("indent can only be space")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
newCfg := cfg.configBeforeFrozen
|
||||||
|
newCfg.IndentionStep = len(indent)
|
||||||
|
return newCfg.frozeWithCacheReuse(cfg.extraExtensions).Marshal(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *frozenConfig) UnmarshalFromString(str string, v interface{}) error {
|
||||||
|
data := []byte(str)
|
||||||
|
iter := cfg.BorrowIterator(data)
|
||||||
|
defer cfg.ReturnIterator(iter)
|
||||||
|
iter.ReadVal(v)
|
||||||
|
c := iter.nextToken()
|
||||||
|
if c == 0 {
|
||||||
|
if iter.Error == io.EOF {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return iter.Error
|
||||||
|
}
|
||||||
|
iter.ReportError("Unmarshal", "there are bytes left after unmarshal")
|
||||||
|
return iter.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *frozenConfig) Get(data []byte, path ...interface{}) Any {
|
||||||
|
iter := cfg.BorrowIterator(data)
|
||||||
|
defer cfg.ReturnIterator(iter)
|
||||||
|
return locatePath(iter, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *frozenConfig) Unmarshal(data []byte, v interface{}) error {
|
||||||
|
iter := cfg.BorrowIterator(data)
|
||||||
|
defer cfg.ReturnIterator(iter)
|
||||||
|
iter.ReadVal(v)
|
||||||
|
c := iter.nextToken()
|
||||||
|
if c == 0 {
|
||||||
|
if iter.Error == io.EOF {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return iter.Error
|
||||||
|
}
|
||||||
|
iter.ReportError("Unmarshal", "there are bytes left after unmarshal")
|
||||||
|
return iter.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *frozenConfig) NewEncoder(writer io.Writer) *Encoder {
|
||||||
|
stream := NewStream(cfg, writer, 512)
|
||||||
|
return &Encoder{stream}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *frozenConfig) NewDecoder(reader io.Reader) *Decoder {
|
||||||
|
iter := Parse(cfg, reader, 512)
|
||||||
|
return &Decoder{iter}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *frozenConfig) Valid(data []byte) bool {
|
||||||
|
iter := cfg.BorrowIterator(data)
|
||||||
|
defer cfg.ReturnIterator(iter)
|
||||||
|
iter.Skip()
|
||||||
|
return iter.Error == nil
|
||||||
|
}
|
||||||
7
vendor/github.com/json-iterator/go/fuzzy_mode_convert_table.md
generated
vendored
Normal file
7
vendor/github.com/json-iterator/go/fuzzy_mode_convert_table.md
generated
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
| json type \ dest type | bool | int | uint | float |string|
|
||||||
|
| --- | --- | --- | --- |--|--|
|
||||||
|
| number | positive => true <br/> negative => true <br/> zero => false| 23.2 => 23 <br/> -32.1 => -32| 12.1 => 12 <br/> -12.1 => 0|as normal|same as origin|
|
||||||
|
| string | empty string => false <br/> string "0" => false <br/> other strings => true | "123.32" => 123 <br/> "-123.4" => -123 <br/> "123.23xxxw" => 123 <br/> "abcde12" => 0 <br/> "-32.1" => -32| 13.2 => 13 <br/> -1.1 => 0 |12.1 => 12.1 <br/> -12.3 => -12.3<br/> 12.4xxa => 12.4 <br/> +1.1e2 =>110 |same as origin|
|
||||||
|
| bool | true => true <br/> false => false| true => 1 <br/> false => 0 | true => 1 <br/> false => 0 |true => 1 <br/>false => 0|true => "true" <br/> false => "false"|
|
||||||
|
| object | true | 0 | 0 |0|originnal json|
|
||||||
|
| array | empty array => false <br/> nonempty array => true| [] => 0 <br/> [1,2] => 1 | [] => 0 <br/> [1,2] => 1 |[] => 0<br/>[1,2] => 1|original json|
|
||||||
349
vendor/github.com/json-iterator/go/iter.go
generated
vendored
Normal file
349
vendor/github.com/json-iterator/go/iter.go
generated
vendored
Normal file
|
|
@ -0,0 +1,349 @@
|
||||||
|
package jsoniter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ValueType the type for JSON element
|
||||||
|
type ValueType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// InvalidValue invalid JSON element
|
||||||
|
InvalidValue ValueType = iota
|
||||||
|
// StringValue JSON element "string"
|
||||||
|
StringValue
|
||||||
|
// NumberValue JSON element 100 or 0.10
|
||||||
|
NumberValue
|
||||||
|
// NilValue JSON element null
|
||||||
|
NilValue
|
||||||
|
// BoolValue JSON element true or false
|
||||||
|
BoolValue
|
||||||
|
// ArrayValue JSON element []
|
||||||
|
ArrayValue
|
||||||
|
// ObjectValue JSON element {}
|
||||||
|
ObjectValue
|
||||||
|
)
|
||||||
|
|
||||||
|
var hexDigits []byte
|
||||||
|
var valueTypes []ValueType
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
hexDigits = make([]byte, 256)
|
||||||
|
for i := 0; i < len(hexDigits); i++ {
|
||||||
|
hexDigits[i] = 255
|
||||||
|
}
|
||||||
|
for i := '0'; i <= '9'; i++ {
|
||||||
|
hexDigits[i] = byte(i - '0')
|
||||||
|
}
|
||||||
|
for i := 'a'; i <= 'f'; i++ {
|
||||||
|
hexDigits[i] = byte((i - 'a') + 10)
|
||||||
|
}
|
||||||
|
for i := 'A'; i <= 'F'; i++ {
|
||||||
|
hexDigits[i] = byte((i - 'A') + 10)
|
||||||
|
}
|
||||||
|
valueTypes = make([]ValueType, 256)
|
||||||
|
for i := 0; i < len(valueTypes); i++ {
|
||||||
|
valueTypes[i] = InvalidValue
|
||||||
|
}
|
||||||
|
valueTypes['"'] = StringValue
|
||||||
|
valueTypes['-'] = NumberValue
|
||||||
|
valueTypes['0'] = NumberValue
|
||||||
|
valueTypes['1'] = NumberValue
|
||||||
|
valueTypes['2'] = NumberValue
|
||||||
|
valueTypes['3'] = NumberValue
|
||||||
|
valueTypes['4'] = NumberValue
|
||||||
|
valueTypes['5'] = NumberValue
|
||||||
|
valueTypes['6'] = NumberValue
|
||||||
|
valueTypes['7'] = NumberValue
|
||||||
|
valueTypes['8'] = NumberValue
|
||||||
|
valueTypes['9'] = NumberValue
|
||||||
|
valueTypes['t'] = BoolValue
|
||||||
|
valueTypes['f'] = BoolValue
|
||||||
|
valueTypes['n'] = NilValue
|
||||||
|
valueTypes['['] = ArrayValue
|
||||||
|
valueTypes['{'] = ObjectValue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterator is a io.Reader like object, with JSON specific read functions.
|
||||||
|
// Error is not returned as return value, but stored as Error member on this iterator instance.
|
||||||
|
type Iterator struct {
|
||||||
|
cfg *frozenConfig
|
||||||
|
reader io.Reader
|
||||||
|
buf []byte
|
||||||
|
head int
|
||||||
|
tail int
|
||||||
|
depth int
|
||||||
|
captureStartedAt int
|
||||||
|
captured []byte
|
||||||
|
Error error
|
||||||
|
Attachment interface{} // open for customized decoder
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIterator creates an empty Iterator instance
|
||||||
|
func NewIterator(cfg API) *Iterator {
|
||||||
|
return &Iterator{
|
||||||
|
cfg: cfg.(*frozenConfig),
|
||||||
|
reader: nil,
|
||||||
|
buf: nil,
|
||||||
|
head: 0,
|
||||||
|
tail: 0,
|
||||||
|
depth: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse creates an Iterator instance from io.Reader
|
||||||
|
func Parse(cfg API, reader io.Reader, bufSize int) *Iterator {
|
||||||
|
return &Iterator{
|
||||||
|
cfg: cfg.(*frozenConfig),
|
||||||
|
reader: reader,
|
||||||
|
buf: make([]byte, bufSize),
|
||||||
|
head: 0,
|
||||||
|
tail: 0,
|
||||||
|
depth: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseBytes creates an Iterator instance from byte array
|
||||||
|
func ParseBytes(cfg API, input []byte) *Iterator {
|
||||||
|
return &Iterator{
|
||||||
|
cfg: cfg.(*frozenConfig),
|
||||||
|
reader: nil,
|
||||||
|
buf: input,
|
||||||
|
head: 0,
|
||||||
|
tail: len(input),
|
||||||
|
depth: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseString creates an Iterator instance from string
|
||||||
|
func ParseString(cfg API, input string) *Iterator {
|
||||||
|
return ParseBytes(cfg, []byte(input))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pool returns a pool can provide more iterator with same configuration
|
||||||
|
func (iter *Iterator) Pool() IteratorPool {
|
||||||
|
return iter.cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset reuse iterator instance by specifying another reader
|
||||||
|
func (iter *Iterator) Reset(reader io.Reader) *Iterator {
|
||||||
|
iter.reader = reader
|
||||||
|
iter.head = 0
|
||||||
|
iter.tail = 0
|
||||||
|
iter.depth = 0
|
||||||
|
return iter
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetBytes reuse iterator instance by specifying another byte array as input
|
||||||
|
func (iter *Iterator) ResetBytes(input []byte) *Iterator {
|
||||||
|
iter.reader = nil
|
||||||
|
iter.buf = input
|
||||||
|
iter.head = 0
|
||||||
|
iter.tail = len(input)
|
||||||
|
iter.depth = 0
|
||||||
|
return iter
|
||||||
|
}
|
||||||
|
|
||||||
|
// WhatIsNext gets ValueType of relatively next json element
|
||||||
|
func (iter *Iterator) WhatIsNext() ValueType {
|
||||||
|
valueType := valueTypes[iter.nextToken()]
|
||||||
|
iter.unreadByte()
|
||||||
|
return valueType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *Iterator) skipWhitespacesWithoutLoadMore() bool {
|
||||||
|
for i := iter.head; i < iter.tail; i++ {
|
||||||
|
c := iter.buf[i]
|
||||||
|
switch c {
|
||||||
|
case ' ', '\n', '\t', '\r':
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
iter.head = i
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *Iterator) isObjectEnd() bool {
|
||||||
|
c := iter.nextToken()
|
||||||
|
if c == ',' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if c == '}' {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
iter.ReportError("isObjectEnd", "object ended prematurely, unexpected char "+string([]byte{c}))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *Iterator) nextToken() byte {
|
||||||
|
// a variation of skip whitespaces, returning the next non-whitespace token
|
||||||
|
for {
|
||||||
|
for i := iter.head; i < iter.tail; i++ {
|
||||||
|
c := iter.buf[i]
|
||||||
|
switch c {
|
||||||
|
case ' ', '\n', '\t', '\r':
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
iter.head = i + 1
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
if !iter.loadMore() {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReportError record a error in iterator instance with current position.
|
||||||
|
func (iter *Iterator) ReportError(operation string, msg string) {
|
||||||
|
if iter.Error != nil {
|
||||||
|
if iter.Error != io.EOF {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
peekStart := iter.head - 10
|
||||||
|
if peekStart < 0 {
|
||||||
|
peekStart = 0
|
||||||
|
}
|
||||||
|
peekEnd := iter.head + 10
|
||||||
|
if peekEnd > iter.tail {
|
||||||
|
peekEnd = iter.tail
|
||||||
|
}
|
||||||
|
parsing := string(iter.buf[peekStart:peekEnd])
|
||||||
|
contextStart := iter.head - 50
|
||||||
|
if contextStart < 0 {
|
||||||
|
contextStart = 0
|
||||||
|
}
|
||||||
|
contextEnd := iter.head + 50
|
||||||
|
if contextEnd > iter.tail {
|
||||||
|
contextEnd = iter.tail
|
||||||
|
}
|
||||||
|
context := string(iter.buf[contextStart:contextEnd])
|
||||||
|
iter.Error = fmt.Errorf("%s: %s, error found in #%v byte of ...|%s|..., bigger context ...|%s|...",
|
||||||
|
operation, msg, iter.head-peekStart, parsing, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CurrentBuffer gets current buffer as string for debugging purpose
|
||||||
|
func (iter *Iterator) CurrentBuffer() string {
|
||||||
|
peekStart := iter.head - 10
|
||||||
|
if peekStart < 0 {
|
||||||
|
peekStart = 0
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("parsing #%v byte, around ...|%s|..., whole buffer ...|%s|...", iter.head,
|
||||||
|
string(iter.buf[peekStart:iter.head]), string(iter.buf[0:iter.tail]))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *Iterator) readByte() (ret byte) {
|
||||||
|
if iter.head == iter.tail {
|
||||||
|
if iter.loadMore() {
|
||||||
|
ret = iter.buf[iter.head]
|
||||||
|
iter.head++
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
ret = iter.buf[iter.head]
|
||||||
|
iter.head++
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *Iterator) loadMore() bool {
|
||||||
|
if iter.reader == nil {
|
||||||
|
if iter.Error == nil {
|
||||||
|
iter.head = iter.tail
|
||||||
|
iter.Error = io.EOF
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if iter.captured != nil {
|
||||||
|
iter.captured = append(iter.captured,
|
||||||
|
iter.buf[iter.captureStartedAt:iter.tail]...)
|
||||||
|
iter.captureStartedAt = 0
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
n, err := iter.reader.Read(iter.buf)
|
||||||
|
if n == 0 {
|
||||||
|
if err != nil {
|
||||||
|
if iter.Error == nil {
|
||||||
|
iter.Error = err
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
iter.head = 0
|
||||||
|
iter.tail = n
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *Iterator) unreadByte() {
|
||||||
|
if iter.Error != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
iter.head--
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read read the next JSON element as generic interface{}.
|
||||||
|
func (iter *Iterator) Read() interface{} {
|
||||||
|
valueType := iter.WhatIsNext()
|
||||||
|
switch valueType {
|
||||||
|
case StringValue:
|
||||||
|
return iter.ReadString()
|
||||||
|
case NumberValue:
|
||||||
|
if iter.cfg.configBeforeFrozen.UseNumber {
|
||||||
|
return json.Number(iter.readNumberAsString())
|
||||||
|
}
|
||||||
|
return iter.ReadFloat64()
|
||||||
|
case NilValue:
|
||||||
|
iter.skipFourBytes('n', 'u', 'l', 'l')
|
||||||
|
return nil
|
||||||
|
case BoolValue:
|
||||||
|
return iter.ReadBool()
|
||||||
|
case ArrayValue:
|
||||||
|
arr := []interface{}{}
|
||||||
|
iter.ReadArrayCB(func(iter *Iterator) bool {
|
||||||
|
var elem interface{}
|
||||||
|
iter.ReadVal(&elem)
|
||||||
|
arr = append(arr, elem)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return arr
|
||||||
|
case ObjectValue:
|
||||||
|
obj := map[string]interface{}{}
|
||||||
|
iter.ReadMapCB(func(Iter *Iterator, field string) bool {
|
||||||
|
var elem interface{}
|
||||||
|
iter.ReadVal(&elem)
|
||||||
|
obj[field] = elem
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return obj
|
||||||
|
default:
|
||||||
|
iter.ReportError("Read", fmt.Sprintf("unexpected value type: %v", valueType))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// limit maximum depth of nesting, as allowed by https://tools.ietf.org/html/rfc7159#section-9
|
||||||
|
const maxDepth = 10000
|
||||||
|
|
||||||
|
func (iter *Iterator) incrementDepth() (success bool) {
|
||||||
|
iter.depth++
|
||||||
|
if iter.depth <= maxDepth {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
iter.ReportError("incrementDepth", "exceeded max depth")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *Iterator) decrementDepth() (success bool) {
|
||||||
|
iter.depth--
|
||||||
|
if iter.depth >= 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
iter.ReportError("decrementDepth", "unexpected negative nesting")
|
||||||
|
return false
|
||||||
|
}
|
||||||
64
vendor/github.com/json-iterator/go/iter_array.go
generated
vendored
Normal file
64
vendor/github.com/json-iterator/go/iter_array.go
generated
vendored
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
package jsoniter
|
||||||
|
|
||||||
|
// ReadArray read array element, tells if the array has more element to read.
|
||||||
|
func (iter *Iterator) ReadArray() (ret bool) {
|
||||||
|
c := iter.nextToken()
|
||||||
|
switch c {
|
||||||
|
case 'n':
|
||||||
|
iter.skipThreeBytes('u', 'l', 'l')
|
||||||
|
return false // null
|
||||||
|
case '[':
|
||||||
|
c = iter.nextToken()
|
||||||
|
if c != ']' {
|
||||||
|
iter.unreadByte()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
case ']':
|
||||||
|
return false
|
||||||
|
case ',':
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
iter.ReportError("ReadArray", "expect [ or , or ] or n, but found "+string([]byte{c}))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadArrayCB read array with callback
|
||||||
|
func (iter *Iterator) ReadArrayCB(callback func(*Iterator) bool) (ret bool) {
|
||||||
|
c := iter.nextToken()
|
||||||
|
if c == '[' {
|
||||||
|
if !iter.incrementDepth() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
c = iter.nextToken()
|
||||||
|
if c != ']' {
|
||||||
|
iter.unreadByte()
|
||||||
|
if !callback(iter) {
|
||||||
|
iter.decrementDepth()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
c = iter.nextToken()
|
||||||
|
for c == ',' {
|
||||||
|
if !callback(iter) {
|
||||||
|
iter.decrementDepth()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
c = iter.nextToken()
|
||||||
|
}
|
||||||
|
if c != ']' {
|
||||||
|
iter.ReportError("ReadArrayCB", "expect ] in the end, but found "+string([]byte{c}))
|
||||||
|
iter.decrementDepth()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return iter.decrementDepth()
|
||||||
|
}
|
||||||
|
return iter.decrementDepth()
|
||||||
|
}
|
||||||
|
if c == 'n' {
|
||||||
|
iter.skipThreeBytes('u', 'l', 'l')
|
||||||
|
return true // null
|
||||||
|
}
|
||||||
|
iter.ReportError("ReadArrayCB", "expect [ or n, but found "+string([]byte{c}))
|
||||||
|
return false
|
||||||
|
}
|
||||||
342
vendor/github.com/json-iterator/go/iter_float.go
generated
vendored
Normal file
342
vendor/github.com/json-iterator/go/iter_float.go
generated
vendored
Normal file
|
|
@ -0,0 +1,342 @@
|
||||||
|
package jsoniter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"math/big"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
var floatDigits []int8
|
||||||
|
|
||||||
|
const invalidCharForNumber = int8(-1)
|
||||||
|
const endOfNumber = int8(-2)
|
||||||
|
const dotInNumber = int8(-3)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
floatDigits = make([]int8, 256)
|
||||||
|
for i := 0; i < len(floatDigits); i++ {
|
||||||
|
floatDigits[i] = invalidCharForNumber
|
||||||
|
}
|
||||||
|
for i := int8('0'); i <= int8('9'); i++ {
|
||||||
|
floatDigits[i] = i - int8('0')
|
||||||
|
}
|
||||||
|
floatDigits[','] = endOfNumber
|
||||||
|
floatDigits[']'] = endOfNumber
|
||||||
|
floatDigits['}'] = endOfNumber
|
||||||
|
floatDigits[' '] = endOfNumber
|
||||||
|
floatDigits['\t'] = endOfNumber
|
||||||
|
floatDigits['\n'] = endOfNumber
|
||||||
|
floatDigits['.'] = dotInNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadBigFloat read big.Float
|
||||||
|
func (iter *Iterator) ReadBigFloat() (ret *big.Float) {
|
||||||
|
str := iter.readNumberAsString()
|
||||||
|
if iter.Error != nil && iter.Error != io.EOF {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
prec := 64
|
||||||
|
if len(str) > prec {
|
||||||
|
prec = len(str)
|
||||||
|
}
|
||||||
|
val, _, err := big.ParseFloat(str, 10, uint(prec), big.ToZero)
|
||||||
|
if err != nil {
|
||||||
|
iter.Error = err
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadBigInt read big.Int
|
||||||
|
func (iter *Iterator) ReadBigInt() (ret *big.Int) {
|
||||||
|
str := iter.readNumberAsString()
|
||||||
|
if iter.Error != nil && iter.Error != io.EOF {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ret = big.NewInt(0)
|
||||||
|
var success bool
|
||||||
|
ret, success = ret.SetString(str, 10)
|
||||||
|
if !success {
|
||||||
|
iter.ReportError("ReadBigInt", "invalid big int")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
//ReadFloat32 read float32
|
||||||
|
func (iter *Iterator) ReadFloat32() (ret float32) {
|
||||||
|
c := iter.nextToken()
|
||||||
|
if c == '-' {
|
||||||
|
return -iter.readPositiveFloat32()
|
||||||
|
}
|
||||||
|
iter.unreadByte()
|
||||||
|
return iter.readPositiveFloat32()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *Iterator) readPositiveFloat32() (ret float32) {
|
||||||
|
i := iter.head
|
||||||
|
// first char
|
||||||
|
if i == iter.tail {
|
||||||
|
return iter.readFloat32SlowPath()
|
||||||
|
}
|
||||||
|
c := iter.buf[i]
|
||||||
|
i++
|
||||||
|
ind := floatDigits[c]
|
||||||
|
switch ind {
|
||||||
|
case invalidCharForNumber:
|
||||||
|
return iter.readFloat32SlowPath()
|
||||||
|
case endOfNumber:
|
||||||
|
iter.ReportError("readFloat32", "empty number")
|
||||||
|
return
|
||||||
|
case dotInNumber:
|
||||||
|
iter.ReportError("readFloat32", "leading dot is invalid")
|
||||||
|
return
|
||||||
|
case 0:
|
||||||
|
if i == iter.tail {
|
||||||
|
return iter.readFloat32SlowPath()
|
||||||
|
}
|
||||||
|
c = iter.buf[i]
|
||||||
|
switch c {
|
||||||
|
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||||
|
iter.ReportError("readFloat32", "leading zero is invalid")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value := uint64(ind)
|
||||||
|
// chars before dot
|
||||||
|
non_decimal_loop:
|
||||||
|
for ; i < iter.tail; i++ {
|
||||||
|
c = iter.buf[i]
|
||||||
|
ind := floatDigits[c]
|
||||||
|
switch ind {
|
||||||
|
case invalidCharForNumber:
|
||||||
|
return iter.readFloat32SlowPath()
|
||||||
|
case endOfNumber:
|
||||||
|
iter.head = i
|
||||||
|
return float32(value)
|
||||||
|
case dotInNumber:
|
||||||
|
break non_decimal_loop
|
||||||
|
}
|
||||||
|
if value > uint64SafeToMultiple10 {
|
||||||
|
return iter.readFloat32SlowPath()
|
||||||
|
}
|
||||||
|
value = (value << 3) + (value << 1) + uint64(ind) // value = value * 10 + ind;
|
||||||
|
}
|
||||||
|
// chars after dot
|
||||||
|
if c == '.' {
|
||||||
|
i++
|
||||||
|
decimalPlaces := 0
|
||||||
|
if i == iter.tail {
|
||||||
|
return iter.readFloat32SlowPath()
|
||||||
|
}
|
||||||
|
for ; i < iter.tail; i++ {
|
||||||
|
c = iter.buf[i]
|
||||||
|
ind := floatDigits[c]
|
||||||
|
switch ind {
|
||||||
|
case endOfNumber:
|
||||||
|
if decimalPlaces > 0 && decimalPlaces < len(pow10) {
|
||||||
|
iter.head = i
|
||||||
|
return float32(float64(value) / float64(pow10[decimalPlaces]))
|
||||||
|
}
|
||||||
|
// too many decimal places
|
||||||
|
return iter.readFloat32SlowPath()
|
||||||
|
case invalidCharForNumber, dotInNumber:
|
||||||
|
return iter.readFloat32SlowPath()
|
||||||
|
}
|
||||||
|
decimalPlaces++
|
||||||
|
if value > uint64SafeToMultiple10 {
|
||||||
|
return iter.readFloat32SlowPath()
|
||||||
|
}
|
||||||
|
value = (value << 3) + (value << 1) + uint64(ind)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return iter.readFloat32SlowPath()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *Iterator) readNumberAsString() (ret string) {
|
||||||
|
strBuf := [16]byte{}
|
||||||
|
str := strBuf[0:0]
|
||||||
|
load_loop:
|
||||||
|
for {
|
||||||
|
for i := iter.head; i < iter.tail; i++ {
|
||||||
|
c := iter.buf[i]
|
||||||
|
switch c {
|
||||||
|
case '+', '-', '.', 'e', 'E', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||||
|
str = append(str, c)
|
||||||
|
continue
|
||||||
|
default:
|
||||||
|
iter.head = i
|
||||||
|
break load_loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !iter.loadMore() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if iter.Error != nil && iter.Error != io.EOF {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(str) == 0 {
|
||||||
|
iter.ReportError("readNumberAsString", "invalid number")
|
||||||
|
}
|
||||||
|
return *(*string)(unsafe.Pointer(&str))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *Iterator) readFloat32SlowPath() (ret float32) {
|
||||||
|
str := iter.readNumberAsString()
|
||||||
|
if iter.Error != nil && iter.Error != io.EOF {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
errMsg := validateFloat(str)
|
||||||
|
if errMsg != "" {
|
||||||
|
iter.ReportError("readFloat32SlowPath", errMsg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val, err := strconv.ParseFloat(str, 32)
|
||||||
|
if err != nil {
|
||||||
|
iter.Error = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return float32(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadFloat64 read float64
|
||||||
|
func (iter *Iterator) ReadFloat64() (ret float64) {
|
||||||
|
c := iter.nextToken()
|
||||||
|
if c == '-' {
|
||||||
|
return -iter.readPositiveFloat64()
|
||||||
|
}
|
||||||
|
iter.unreadByte()
|
||||||
|
return iter.readPositiveFloat64()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *Iterator) readPositiveFloat64() (ret float64) {
|
||||||
|
i := iter.head
|
||||||
|
// first char
|
||||||
|
if i == iter.tail {
|
||||||
|
return iter.readFloat64SlowPath()
|
||||||
|
}
|
||||||
|
c := iter.buf[i]
|
||||||
|
i++
|
||||||
|
ind := floatDigits[c]
|
||||||
|
switch ind {
|
||||||
|
case invalidCharForNumber:
|
||||||
|
return iter.readFloat64SlowPath()
|
||||||
|
case endOfNumber:
|
||||||
|
iter.ReportError("readFloat64", "empty number")
|
||||||
|
return
|
||||||
|
case dotInNumber:
|
||||||
|
iter.ReportError("readFloat64", "leading dot is invalid")
|
||||||
|
return
|
||||||
|
case 0:
|
||||||
|
if i == iter.tail {
|
||||||
|
return iter.readFloat64SlowPath()
|
||||||
|
}
|
||||||
|
c = iter.buf[i]
|
||||||
|
switch c {
|
||||||
|
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||||
|
iter.ReportError("readFloat64", "leading zero is invalid")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value := uint64(ind)
|
||||||
|
// chars before dot
|
||||||
|
non_decimal_loop:
|
||||||
|
for ; i < iter.tail; i++ {
|
||||||
|
c = iter.buf[i]
|
||||||
|
ind := floatDigits[c]
|
||||||
|
switch ind {
|
||||||
|
case invalidCharForNumber:
|
||||||
|
return iter.readFloat64SlowPath()
|
||||||
|
case endOfNumber:
|
||||||
|
iter.head = i
|
||||||
|
return float64(value)
|
||||||
|
case dotInNumber:
|
||||||
|
break non_decimal_loop
|
||||||
|
}
|
||||||
|
if value > uint64SafeToMultiple10 {
|
||||||
|
return iter.readFloat64SlowPath()
|
||||||
|
}
|
||||||
|
value = (value << 3) + (value << 1) + uint64(ind) // value = value * 10 + ind;
|
||||||
|
}
|
||||||
|
// chars after dot
|
||||||
|
if c == '.' {
|
||||||
|
i++
|
||||||
|
decimalPlaces := 0
|
||||||
|
if i == iter.tail {
|
||||||
|
return iter.readFloat64SlowPath()
|
||||||
|
}
|
||||||
|
for ; i < iter.tail; i++ {
|
||||||
|
c = iter.buf[i]
|
||||||
|
ind := floatDigits[c]
|
||||||
|
switch ind {
|
||||||
|
case endOfNumber:
|
||||||
|
if decimalPlaces > 0 && decimalPlaces < len(pow10) {
|
||||||
|
iter.head = i
|
||||||
|
return float64(value) / float64(pow10[decimalPlaces])
|
||||||
|
}
|
||||||
|
// too many decimal places
|
||||||
|
return iter.readFloat64SlowPath()
|
||||||
|
case invalidCharForNumber, dotInNumber:
|
||||||
|
return iter.readFloat64SlowPath()
|
||||||
|
}
|
||||||
|
decimalPlaces++
|
||||||
|
if value > uint64SafeToMultiple10 {
|
||||||
|
return iter.readFloat64SlowPath()
|
||||||
|
}
|
||||||
|
value = (value << 3) + (value << 1) + uint64(ind)
|
||||||
|
if value > maxFloat64 {
|
||||||
|
return iter.readFloat64SlowPath()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return iter.readFloat64SlowPath()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *Iterator) readFloat64SlowPath() (ret float64) {
|
||||||
|
str := iter.readNumberAsString()
|
||||||
|
if iter.Error != nil && iter.Error != io.EOF {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
errMsg := validateFloat(str)
|
||||||
|
if errMsg != "" {
|
||||||
|
iter.ReportError("readFloat64SlowPath", errMsg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
val, err := strconv.ParseFloat(str, 64)
|
||||||
|
if err != nil {
|
||||||
|
iter.Error = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateFloat(str string) string {
|
||||||
|
// strconv.ParseFloat is not validating `1.` or `1.e1`
|
||||||
|
if len(str) == 0 {
|
||||||
|
return "empty number"
|
||||||
|
}
|
||||||
|
if str[0] == '-' {
|
||||||
|
return "-- is not valid"
|
||||||
|
}
|
||||||
|
dotPos := strings.IndexByte(str, '.')
|
||||||
|
if dotPos != -1 {
|
||||||
|
if dotPos == len(str)-1 {
|
||||||
|
return "dot can not be last character"
|
||||||
|
}
|
||||||
|
switch str[dotPos+1] {
|
||||||
|
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||||
|
default:
|
||||||
|
return "missing digit after dot"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadNumber read json.Number
|
||||||
|
func (iter *Iterator) ReadNumber() (ret json.Number) {
|
||||||
|
return json.Number(iter.readNumberAsString())
|
||||||
|
}
|
||||||
346
vendor/github.com/json-iterator/go/iter_int.go
generated
vendored
Normal file
346
vendor/github.com/json-iterator/go/iter_int.go
generated
vendored
Normal file
|
|
@ -0,0 +1,346 @@
|
||||||
|
package jsoniter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
var intDigits []int8
|
||||||
|
|
||||||
|
const uint32SafeToMultiply10 = uint32(0xffffffff)/10 - 1
|
||||||
|
const uint64SafeToMultiple10 = uint64(0xffffffffffffffff)/10 - 1
|
||||||
|
const maxFloat64 = 1<<53 - 1
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
intDigits = make([]int8, 256)
|
||||||
|
for i := 0; i < len(intDigits); i++ {
|
||||||
|
intDigits[i] = invalidCharForNumber
|
||||||
|
}
|
||||||
|
for i := int8('0'); i <= int8('9'); i++ {
|
||||||
|
intDigits[i] = i - int8('0')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadUint read uint
|
||||||
|
func (iter *Iterator) ReadUint() uint {
|
||||||
|
if strconv.IntSize == 32 {
|
||||||
|
return uint(iter.ReadUint32())
|
||||||
|
}
|
||||||
|
return uint(iter.ReadUint64())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadInt read int
|
||||||
|
func (iter *Iterator) ReadInt() int {
|
||||||
|
if strconv.IntSize == 32 {
|
||||||
|
return int(iter.ReadInt32())
|
||||||
|
}
|
||||||
|
return int(iter.ReadInt64())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadInt8 read int8
|
||||||
|
func (iter *Iterator) ReadInt8() (ret int8) {
|
||||||
|
c := iter.nextToken()
|
||||||
|
if c == '-' {
|
||||||
|
val := iter.readUint32(iter.readByte())
|
||||||
|
if val > math.MaxInt8+1 {
|
||||||
|
iter.ReportError("ReadInt8", "overflow: "+strconv.FormatInt(int64(val), 10))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return -int8(val)
|
||||||
|
}
|
||||||
|
val := iter.readUint32(c)
|
||||||
|
if val > math.MaxInt8 {
|
||||||
|
iter.ReportError("ReadInt8", "overflow: "+strconv.FormatInt(int64(val), 10))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return int8(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadUint8 read uint8
|
||||||
|
func (iter *Iterator) ReadUint8() (ret uint8) {
|
||||||
|
val := iter.readUint32(iter.nextToken())
|
||||||
|
if val > math.MaxUint8 {
|
||||||
|
iter.ReportError("ReadUint8", "overflow: "+strconv.FormatInt(int64(val), 10))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return uint8(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadInt16 read int16
|
||||||
|
func (iter *Iterator) ReadInt16() (ret int16) {
|
||||||
|
c := iter.nextToken()
|
||||||
|
if c == '-' {
|
||||||
|
val := iter.readUint32(iter.readByte())
|
||||||
|
if val > math.MaxInt16+1 {
|
||||||
|
iter.ReportError("ReadInt16", "overflow: "+strconv.FormatInt(int64(val), 10))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return -int16(val)
|
||||||
|
}
|
||||||
|
val := iter.readUint32(c)
|
||||||
|
if val > math.MaxInt16 {
|
||||||
|
iter.ReportError("ReadInt16", "overflow: "+strconv.FormatInt(int64(val), 10))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return int16(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadUint16 read uint16
|
||||||
|
func (iter *Iterator) ReadUint16() (ret uint16) {
|
||||||
|
val := iter.readUint32(iter.nextToken())
|
||||||
|
if val > math.MaxUint16 {
|
||||||
|
iter.ReportError("ReadUint16", "overflow: "+strconv.FormatInt(int64(val), 10))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return uint16(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadInt32 read int32
|
||||||
|
func (iter *Iterator) ReadInt32() (ret int32) {
|
||||||
|
c := iter.nextToken()
|
||||||
|
if c == '-' {
|
||||||
|
val := iter.readUint32(iter.readByte())
|
||||||
|
if val > math.MaxInt32+1 {
|
||||||
|
iter.ReportError("ReadInt32", "overflow: "+strconv.FormatInt(int64(val), 10))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return -int32(val)
|
||||||
|
}
|
||||||
|
val := iter.readUint32(c)
|
||||||
|
if val > math.MaxInt32 {
|
||||||
|
iter.ReportError("ReadInt32", "overflow: "+strconv.FormatInt(int64(val), 10))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return int32(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadUint32 read uint32
|
||||||
|
func (iter *Iterator) ReadUint32() (ret uint32) {
|
||||||
|
return iter.readUint32(iter.nextToken())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *Iterator) readUint32(c byte) (ret uint32) {
|
||||||
|
ind := intDigits[c]
|
||||||
|
if ind == 0 {
|
||||||
|
iter.assertInteger()
|
||||||
|
return 0 // single zero
|
||||||
|
}
|
||||||
|
if ind == invalidCharForNumber {
|
||||||
|
iter.ReportError("readUint32", "unexpected character: "+string([]byte{byte(ind)}))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
value := uint32(ind)
|
||||||
|
if iter.tail-iter.head > 10 {
|
||||||
|
i := iter.head
|
||||||
|
ind2 := intDigits[iter.buf[i]]
|
||||||
|
if ind2 == invalidCharForNumber {
|
||||||
|
iter.head = i
|
||||||
|
iter.assertInteger()
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
ind3 := intDigits[iter.buf[i]]
|
||||||
|
if ind3 == invalidCharForNumber {
|
||||||
|
iter.head = i
|
||||||
|
iter.assertInteger()
|
||||||
|
return value*10 + uint32(ind2)
|
||||||
|
}
|
||||||
|
//iter.head = i + 1
|
||||||
|
//value = value * 100 + uint32(ind2) * 10 + uint32(ind3)
|
||||||
|
i++
|
||||||
|
ind4 := intDigits[iter.buf[i]]
|
||||||
|
if ind4 == invalidCharForNumber {
|
||||||
|
iter.head = i
|
||||||
|
iter.assertInteger()
|
||||||
|
return value*100 + uint32(ind2)*10 + uint32(ind3)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
ind5 := intDigits[iter.buf[i]]
|
||||||
|
if ind5 == invalidCharForNumber {
|
||||||
|
iter.head = i
|
||||||
|
iter.assertInteger()
|
||||||
|
return value*1000 + uint32(ind2)*100 + uint32(ind3)*10 + uint32(ind4)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
ind6 := intDigits[iter.buf[i]]
|
||||||
|
if ind6 == invalidCharForNumber {
|
||||||
|
iter.head = i
|
||||||
|
iter.assertInteger()
|
||||||
|
return value*10000 + uint32(ind2)*1000 + uint32(ind3)*100 + uint32(ind4)*10 + uint32(ind5)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
ind7 := intDigits[iter.buf[i]]
|
||||||
|
if ind7 == invalidCharForNumber {
|
||||||
|
iter.head = i
|
||||||
|
iter.assertInteger()
|
||||||
|
return value*100000 + uint32(ind2)*10000 + uint32(ind3)*1000 + uint32(ind4)*100 + uint32(ind5)*10 + uint32(ind6)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
ind8 := intDigits[iter.buf[i]]
|
||||||
|
if ind8 == invalidCharForNumber {
|
||||||
|
iter.head = i
|
||||||
|
iter.assertInteger()
|
||||||
|
return value*1000000 + uint32(ind2)*100000 + uint32(ind3)*10000 + uint32(ind4)*1000 + uint32(ind5)*100 + uint32(ind6)*10 + uint32(ind7)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
ind9 := intDigits[iter.buf[i]]
|
||||||
|
value = value*10000000 + uint32(ind2)*1000000 + uint32(ind3)*100000 + uint32(ind4)*10000 + uint32(ind5)*1000 + uint32(ind6)*100 + uint32(ind7)*10 + uint32(ind8)
|
||||||
|
iter.head = i
|
||||||
|
if ind9 == invalidCharForNumber {
|
||||||
|
iter.assertInteger()
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
for i := iter.head; i < iter.tail; i++ {
|
||||||
|
ind = intDigits[iter.buf[i]]
|
||||||
|
if ind == invalidCharForNumber {
|
||||||
|
iter.head = i
|
||||||
|
iter.assertInteger()
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
if value > uint32SafeToMultiply10 {
|
||||||
|
value2 := (value << 3) + (value << 1) + uint32(ind)
|
||||||
|
if value2 < value {
|
||||||
|
iter.ReportError("readUint32", "overflow")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
value = value2
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
value = (value << 3) + (value << 1) + uint32(ind)
|
||||||
|
}
|
||||||
|
if !iter.loadMore() {
|
||||||
|
iter.assertInteger()
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadInt64 read int64
|
||||||
|
func (iter *Iterator) ReadInt64() (ret int64) {
|
||||||
|
c := iter.nextToken()
|
||||||
|
if c == '-' {
|
||||||
|
val := iter.readUint64(iter.readByte())
|
||||||
|
if val > math.MaxInt64+1 {
|
||||||
|
iter.ReportError("ReadInt64", "overflow: "+strconv.FormatUint(uint64(val), 10))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return -int64(val)
|
||||||
|
}
|
||||||
|
val := iter.readUint64(c)
|
||||||
|
if val > math.MaxInt64 {
|
||||||
|
iter.ReportError("ReadInt64", "overflow: "+strconv.FormatUint(uint64(val), 10))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return int64(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadUint64 read uint64
|
||||||
|
func (iter *Iterator) ReadUint64() uint64 {
|
||||||
|
return iter.readUint64(iter.nextToken())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *Iterator) readUint64(c byte) (ret uint64) {
|
||||||
|
ind := intDigits[c]
|
||||||
|
if ind == 0 {
|
||||||
|
iter.assertInteger()
|
||||||
|
return 0 // single zero
|
||||||
|
}
|
||||||
|
if ind == invalidCharForNumber {
|
||||||
|
iter.ReportError("readUint64", "unexpected character: "+string([]byte{byte(ind)}))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
value := uint64(ind)
|
||||||
|
if iter.tail-iter.head > 10 {
|
||||||
|
i := iter.head
|
||||||
|
ind2 := intDigits[iter.buf[i]]
|
||||||
|
if ind2 == invalidCharForNumber {
|
||||||
|
iter.head = i
|
||||||
|
iter.assertInteger()
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
ind3 := intDigits[iter.buf[i]]
|
||||||
|
if ind3 == invalidCharForNumber {
|
||||||
|
iter.head = i
|
||||||
|
iter.assertInteger()
|
||||||
|
return value*10 + uint64(ind2)
|
||||||
|
}
|
||||||
|
//iter.head = i + 1
|
||||||
|
//value = value * 100 + uint32(ind2) * 10 + uint32(ind3)
|
||||||
|
i++
|
||||||
|
ind4 := intDigits[iter.buf[i]]
|
||||||
|
if ind4 == invalidCharForNumber {
|
||||||
|
iter.head = i
|
||||||
|
iter.assertInteger()
|
||||||
|
return value*100 + uint64(ind2)*10 + uint64(ind3)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
ind5 := intDigits[iter.buf[i]]
|
||||||
|
if ind5 == invalidCharForNumber {
|
||||||
|
iter.head = i
|
||||||
|
iter.assertInteger()
|
||||||
|
return value*1000 + uint64(ind2)*100 + uint64(ind3)*10 + uint64(ind4)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
ind6 := intDigits[iter.buf[i]]
|
||||||
|
if ind6 == invalidCharForNumber {
|
||||||
|
iter.head = i
|
||||||
|
iter.assertInteger()
|
||||||
|
return value*10000 + uint64(ind2)*1000 + uint64(ind3)*100 + uint64(ind4)*10 + uint64(ind5)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
ind7 := intDigits[iter.buf[i]]
|
||||||
|
if ind7 == invalidCharForNumber {
|
||||||
|
iter.head = i
|
||||||
|
iter.assertInteger()
|
||||||
|
return value*100000 + uint64(ind2)*10000 + uint64(ind3)*1000 + uint64(ind4)*100 + uint64(ind5)*10 + uint64(ind6)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
ind8 := intDigits[iter.buf[i]]
|
||||||
|
if ind8 == invalidCharForNumber {
|
||||||
|
iter.head = i
|
||||||
|
iter.assertInteger()
|
||||||
|
return value*1000000 + uint64(ind2)*100000 + uint64(ind3)*10000 + uint64(ind4)*1000 + uint64(ind5)*100 + uint64(ind6)*10 + uint64(ind7)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
ind9 := intDigits[iter.buf[i]]
|
||||||
|
value = value*10000000 + uint64(ind2)*1000000 + uint64(ind3)*100000 + uint64(ind4)*10000 + uint64(ind5)*1000 + uint64(ind6)*100 + uint64(ind7)*10 + uint64(ind8)
|
||||||
|
iter.head = i
|
||||||
|
if ind9 == invalidCharForNumber {
|
||||||
|
iter.assertInteger()
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
for i := iter.head; i < iter.tail; i++ {
|
||||||
|
ind = intDigits[iter.buf[i]]
|
||||||
|
if ind == invalidCharForNumber {
|
||||||
|
iter.head = i
|
||||||
|
iter.assertInteger()
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
if value > uint64SafeToMultiple10 {
|
||||||
|
value2 := (value << 3) + (value << 1) + uint64(ind)
|
||||||
|
if value2 < value {
|
||||||
|
iter.ReportError("readUint64", "overflow")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
value = value2
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
value = (value << 3) + (value << 1) + uint64(ind)
|
||||||
|
}
|
||||||
|
if !iter.loadMore() {
|
||||||
|
iter.assertInteger()
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *Iterator) assertInteger() {
|
||||||
|
if iter.head < iter.tail && iter.buf[iter.head] == '.' {
|
||||||
|
iter.ReportError("assertInteger", "can not decode float as int")
|
||||||
|
}
|
||||||
|
}
|
||||||
267
vendor/github.com/json-iterator/go/iter_object.go
generated
vendored
Normal file
267
vendor/github.com/json-iterator/go/iter_object.go
generated
vendored
Normal file
|
|
@ -0,0 +1,267 @@
|
||||||
|
package jsoniter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReadObject read one field from object.
|
||||||
|
// If object ended, returns empty string.
|
||||||
|
// Otherwise, returns the field name.
|
||||||
|
func (iter *Iterator) ReadObject() (ret string) {
|
||||||
|
c := iter.nextToken()
|
||||||
|
switch c {
|
||||||
|
case 'n':
|
||||||
|
iter.skipThreeBytes('u', 'l', 'l')
|
||||||
|
return "" // null
|
||||||
|
case '{':
|
||||||
|
c = iter.nextToken()
|
||||||
|
if c == '"' {
|
||||||
|
iter.unreadByte()
|
||||||
|
field := iter.ReadString()
|
||||||
|
c = iter.nextToken()
|
||||||
|
if c != ':' {
|
||||||
|
iter.ReportError("ReadObject", "expect : after object field, but found "+string([]byte{c}))
|
||||||
|
}
|
||||||
|
return field
|
||||||
|
}
|
||||||
|
if c == '}' {
|
||||||
|
return "" // end of object
|
||||||
|
}
|
||||||
|
iter.ReportError("ReadObject", `expect " after {, but found `+string([]byte{c}))
|
||||||
|
return
|
||||||
|
case ',':
|
||||||
|
field := iter.ReadString()
|
||||||
|
c = iter.nextToken()
|
||||||
|
if c != ':' {
|
||||||
|
iter.ReportError("ReadObject", "expect : after object field, but found "+string([]byte{c}))
|
||||||
|
}
|
||||||
|
return field
|
||||||
|
case '}':
|
||||||
|
return "" // end of object
|
||||||
|
default:
|
||||||
|
iter.ReportError("ReadObject", fmt.Sprintf(`expect { or , or } or n, but found %s`, string([]byte{c})))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CaseInsensitive
|
||||||
|
func (iter *Iterator) readFieldHash() int64 {
|
||||||
|
hash := int64(0x811c9dc5)
|
||||||
|
c := iter.nextToken()
|
||||||
|
if c != '"' {
|
||||||
|
iter.ReportError("readFieldHash", `expect ", but found `+string([]byte{c}))
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
for i := iter.head; i < iter.tail; i++ {
|
||||||
|
// require ascii string and no escape
|
||||||
|
b := iter.buf[i]
|
||||||
|
if b == '\\' {
|
||||||
|
iter.head = i
|
||||||
|
for _, b := range iter.readStringSlowPath() {
|
||||||
|
if 'A' <= b && b <= 'Z' && !iter.cfg.caseSensitive {
|
||||||
|
b += 'a' - 'A'
|
||||||
|
}
|
||||||
|
hash ^= int64(b)
|
||||||
|
hash *= 0x1000193
|
||||||
|
}
|
||||||
|
c = iter.nextToken()
|
||||||
|
if c != ':' {
|
||||||
|
iter.ReportError("readFieldHash", `expect :, but found `+string([]byte{c}))
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return hash
|
||||||
|
}
|
||||||
|
if b == '"' {
|
||||||
|
iter.head = i + 1
|
||||||
|
c = iter.nextToken()
|
||||||
|
if c != ':' {
|
||||||
|
iter.ReportError("readFieldHash", `expect :, but found `+string([]byte{c}))
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return hash
|
||||||
|
}
|
||||||
|
if 'A' <= b && b <= 'Z' && !iter.cfg.caseSensitive {
|
||||||
|
b += 'a' - 'A'
|
||||||
|
}
|
||||||
|
hash ^= int64(b)
|
||||||
|
hash *= 0x1000193
|
||||||
|
}
|
||||||
|
if !iter.loadMore() {
|
||||||
|
iter.ReportError("readFieldHash", `incomplete field name`)
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func calcHash(str string, caseSensitive bool) int64 {
|
||||||
|
if !caseSensitive {
|
||||||
|
str = strings.ToLower(str)
|
||||||
|
}
|
||||||
|
hash := int64(0x811c9dc5)
|
||||||
|
for _, b := range []byte(str) {
|
||||||
|
hash ^= int64(b)
|
||||||
|
hash *= 0x1000193
|
||||||
|
}
|
||||||
|
return int64(hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadObjectCB read object with callback, the key is ascii only and field name not copied
|
||||||
|
func (iter *Iterator) ReadObjectCB(callback func(*Iterator, string) bool) bool {
|
||||||
|
c := iter.nextToken()
|
||||||
|
var field string
|
||||||
|
if c == '{' {
|
||||||
|
if !iter.incrementDepth() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
c = iter.nextToken()
|
||||||
|
if c == '"' {
|
||||||
|
iter.unreadByte()
|
||||||
|
field = iter.ReadString()
|
||||||
|
c = iter.nextToken()
|
||||||
|
if c != ':' {
|
||||||
|
iter.ReportError("ReadObject", "expect : after object field, but found "+string([]byte{c}))
|
||||||
|
}
|
||||||
|
if !callback(iter, field) {
|
||||||
|
iter.decrementDepth()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
c = iter.nextToken()
|
||||||
|
for c == ',' {
|
||||||
|
field = iter.ReadString()
|
||||||
|
c = iter.nextToken()
|
||||||
|
if c != ':' {
|
||||||
|
iter.ReportError("ReadObject", "expect : after object field, but found "+string([]byte{c}))
|
||||||
|
}
|
||||||
|
if !callback(iter, field) {
|
||||||
|
iter.decrementDepth()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
c = iter.nextToken()
|
||||||
|
}
|
||||||
|
if c != '}' {
|
||||||
|
iter.ReportError("ReadObjectCB", `object not ended with }`)
|
||||||
|
iter.decrementDepth()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return iter.decrementDepth()
|
||||||
|
}
|
||||||
|
if c == '}' {
|
||||||
|
return iter.decrementDepth()
|
||||||
|
}
|
||||||
|
iter.ReportError("ReadObjectCB", `expect " after {, but found `+string([]byte{c}))
|
||||||
|
iter.decrementDepth()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if c == 'n' {
|
||||||
|
iter.skipThreeBytes('u', 'l', 'l')
|
||||||
|
return true // null
|
||||||
|
}
|
||||||
|
iter.ReportError("ReadObjectCB", `expect { or n, but found `+string([]byte{c}))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadMapCB read map with callback, the key can be any string
|
||||||
|
func (iter *Iterator) ReadMapCB(callback func(*Iterator, string) bool) bool {
|
||||||
|
c := iter.nextToken()
|
||||||
|
if c == '{' {
|
||||||
|
if !iter.incrementDepth() {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
c = iter.nextToken()
|
||||||
|
if c == '"' {
|
||||||
|
iter.unreadByte()
|
||||||
|
field := iter.ReadString()
|
||||||
|
if iter.nextToken() != ':' {
|
||||||
|
iter.ReportError("ReadMapCB", "expect : after object field, but found "+string([]byte{c}))
|
||||||
|
iter.decrementDepth()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !callback(iter, field) {
|
||||||
|
iter.decrementDepth()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
c = iter.nextToken()
|
||||||
|
for c == ',' {
|
||||||
|
field = iter.ReadString()
|
||||||
|
if iter.nextToken() != ':' {
|
||||||
|
iter.ReportError("ReadMapCB", "expect : after object field, but found "+string([]byte{c}))
|
||||||
|
iter.decrementDepth()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if !callback(iter, field) {
|
||||||
|
iter.decrementDepth()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
c = iter.nextToken()
|
||||||
|
}
|
||||||
|
if c != '}' {
|
||||||
|
iter.ReportError("ReadMapCB", `object not ended with }`)
|
||||||
|
iter.decrementDepth()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return iter.decrementDepth()
|
||||||
|
}
|
||||||
|
if c == '}' {
|
||||||
|
return iter.decrementDepth()
|
||||||
|
}
|
||||||
|
iter.ReportError("ReadMapCB", `expect " after {, but found `+string([]byte{c}))
|
||||||
|
iter.decrementDepth()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if c == 'n' {
|
||||||
|
iter.skipThreeBytes('u', 'l', 'l')
|
||||||
|
return true // null
|
||||||
|
}
|
||||||
|
iter.ReportError("ReadMapCB", `expect { or n, but found `+string([]byte{c}))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *Iterator) readObjectStart() bool {
|
||||||
|
c := iter.nextToken()
|
||||||
|
if c == '{' {
|
||||||
|
c = iter.nextToken()
|
||||||
|
if c == '}' {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
iter.unreadByte()
|
||||||
|
return true
|
||||||
|
} else if c == 'n' {
|
||||||
|
iter.skipThreeBytes('u', 'l', 'l')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
iter.ReportError("readObjectStart", "expect { or n, but found "+string([]byte{c}))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *Iterator) readObjectFieldAsBytes() (ret []byte) {
|
||||||
|
str := iter.ReadStringAsSlice()
|
||||||
|
if iter.skipWhitespacesWithoutLoadMore() {
|
||||||
|
if ret == nil {
|
||||||
|
ret = make([]byte, len(str))
|
||||||
|
copy(ret, str)
|
||||||
|
}
|
||||||
|
if !iter.loadMore() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if iter.buf[iter.head] != ':' {
|
||||||
|
iter.ReportError("readObjectFieldAsBytes", "expect : after object field, but found "+string([]byte{iter.buf[iter.head]}))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
iter.head++
|
||||||
|
if iter.skipWhitespacesWithoutLoadMore() {
|
||||||
|
if ret == nil {
|
||||||
|
ret = make([]byte, len(str))
|
||||||
|
copy(ret, str)
|
||||||
|
}
|
||||||
|
if !iter.loadMore() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ret == nil {
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
130
vendor/github.com/json-iterator/go/iter_skip.go
generated
vendored
Normal file
130
vendor/github.com/json-iterator/go/iter_skip.go
generated
vendored
Normal file
|
|
@ -0,0 +1,130 @@
|
||||||
|
package jsoniter
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
|
// ReadNil reads a json object as nil and
|
||||||
|
// returns whether it's a nil or not
|
||||||
|
func (iter *Iterator) ReadNil() (ret bool) {
|
||||||
|
c := iter.nextToken()
|
||||||
|
if c == 'n' {
|
||||||
|
iter.skipThreeBytes('u', 'l', 'l') // null
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
iter.unreadByte()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadBool reads a json object as BoolValue
|
||||||
|
func (iter *Iterator) ReadBool() (ret bool) {
|
||||||
|
c := iter.nextToken()
|
||||||
|
if c == 't' {
|
||||||
|
iter.skipThreeBytes('r', 'u', 'e')
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if c == 'f' {
|
||||||
|
iter.skipFourBytes('a', 'l', 's', 'e')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
iter.ReportError("ReadBool", "expect t or f, but found "+string([]byte{c}))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// SkipAndReturnBytes skip next JSON element, and return its content as []byte.
|
||||||
|
// The []byte can be kept, it is a copy of data.
|
||||||
|
func (iter *Iterator) SkipAndReturnBytes() []byte {
|
||||||
|
iter.startCapture(iter.head)
|
||||||
|
iter.Skip()
|
||||||
|
return iter.stopCapture()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SkipAndAppendBytes skips next JSON element and appends its content to
|
||||||
|
// buffer, returning the result.
|
||||||
|
func (iter *Iterator) SkipAndAppendBytes(buf []byte) []byte {
|
||||||
|
iter.startCaptureTo(buf, iter.head)
|
||||||
|
iter.Skip()
|
||||||
|
return iter.stopCapture()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *Iterator) startCaptureTo(buf []byte, captureStartedAt int) {
|
||||||
|
if iter.captured != nil {
|
||||||
|
panic("already in capture mode")
|
||||||
|
}
|
||||||
|
iter.captureStartedAt = captureStartedAt
|
||||||
|
iter.captured = buf
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *Iterator) startCapture(captureStartedAt int) {
|
||||||
|
iter.startCaptureTo(make([]byte, 0, 32), captureStartedAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *Iterator) stopCapture() []byte {
|
||||||
|
if iter.captured == nil {
|
||||||
|
panic("not in capture mode")
|
||||||
|
}
|
||||||
|
captured := iter.captured
|
||||||
|
remaining := iter.buf[iter.captureStartedAt:iter.head]
|
||||||
|
iter.captureStartedAt = -1
|
||||||
|
iter.captured = nil
|
||||||
|
return append(captured, remaining...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip skips a json object and positions to relatively the next json object
|
||||||
|
func (iter *Iterator) Skip() {
|
||||||
|
c := iter.nextToken()
|
||||||
|
switch c {
|
||||||
|
case '"':
|
||||||
|
iter.skipString()
|
||||||
|
case 'n':
|
||||||
|
iter.skipThreeBytes('u', 'l', 'l') // null
|
||||||
|
case 't':
|
||||||
|
iter.skipThreeBytes('r', 'u', 'e') // true
|
||||||
|
case 'f':
|
||||||
|
iter.skipFourBytes('a', 'l', 's', 'e') // false
|
||||||
|
case '0':
|
||||||
|
iter.unreadByte()
|
||||||
|
iter.ReadFloat32()
|
||||||
|
case '-', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||||
|
iter.skipNumber()
|
||||||
|
case '[':
|
||||||
|
iter.skipArray()
|
||||||
|
case '{':
|
||||||
|
iter.skipObject()
|
||||||
|
default:
|
||||||
|
iter.ReportError("Skip", fmt.Sprintf("do not know how to skip: %v", c))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *Iterator) skipFourBytes(b1, b2, b3, b4 byte) {
|
||||||
|
if iter.readByte() != b1 {
|
||||||
|
iter.ReportError("skipFourBytes", fmt.Sprintf("expect %s", string([]byte{b1, b2, b3, b4})))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if iter.readByte() != b2 {
|
||||||
|
iter.ReportError("skipFourBytes", fmt.Sprintf("expect %s", string([]byte{b1, b2, b3, b4})))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if iter.readByte() != b3 {
|
||||||
|
iter.ReportError("skipFourBytes", fmt.Sprintf("expect %s", string([]byte{b1, b2, b3, b4})))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if iter.readByte() != b4 {
|
||||||
|
iter.ReportError("skipFourBytes", fmt.Sprintf("expect %s", string([]byte{b1, b2, b3, b4})))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *Iterator) skipThreeBytes(b1, b2, b3 byte) {
|
||||||
|
if iter.readByte() != b1 {
|
||||||
|
iter.ReportError("skipThreeBytes", fmt.Sprintf("expect %s", string([]byte{b1, b2, b3})))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if iter.readByte() != b2 {
|
||||||
|
iter.ReportError("skipThreeBytes", fmt.Sprintf("expect %s", string([]byte{b1, b2, b3})))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if iter.readByte() != b3 {
|
||||||
|
iter.ReportError("skipThreeBytes", fmt.Sprintf("expect %s", string([]byte{b1, b2, b3})))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
163
vendor/github.com/json-iterator/go/iter_skip_sloppy.go
generated
vendored
Normal file
163
vendor/github.com/json-iterator/go/iter_skip_sloppy.go
generated
vendored
Normal file
|
|
@ -0,0 +1,163 @@
|
||||||
|
//+build jsoniter_sloppy
|
||||||
|
|
||||||
|
package jsoniter
|
||||||
|
|
||||||
|
// sloppy but faster implementation, do not validate the input json
|
||||||
|
|
||||||
|
func (iter *Iterator) skipNumber() {
|
||||||
|
for {
|
||||||
|
for i := iter.head; i < iter.tail; i++ {
|
||||||
|
c := iter.buf[i]
|
||||||
|
switch c {
|
||||||
|
case ' ', '\n', '\r', '\t', ',', '}', ']':
|
||||||
|
iter.head = i
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !iter.loadMore() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *Iterator) skipArray() {
|
||||||
|
level := 1
|
||||||
|
if !iter.incrementDepth() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for {
|
||||||
|
for i := iter.head; i < iter.tail; i++ {
|
||||||
|
switch iter.buf[i] {
|
||||||
|
case '"': // If inside string, skip it
|
||||||
|
iter.head = i + 1
|
||||||
|
iter.skipString()
|
||||||
|
i = iter.head - 1 // it will be i++ soon
|
||||||
|
case '[': // If open symbol, increase level
|
||||||
|
level++
|
||||||
|
if !iter.incrementDepth() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case ']': // If close symbol, increase level
|
||||||
|
level--
|
||||||
|
if !iter.decrementDepth() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have returned to the original level, we're done
|
||||||
|
if level == 0 {
|
||||||
|
iter.head = i + 1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !iter.loadMore() {
|
||||||
|
iter.ReportError("skipObject", "incomplete array")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *Iterator) skipObject() {
|
||||||
|
level := 1
|
||||||
|
if !iter.incrementDepth() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
for i := iter.head; i < iter.tail; i++ {
|
||||||
|
switch iter.buf[i] {
|
||||||
|
case '"': // If inside string, skip it
|
||||||
|
iter.head = i + 1
|
||||||
|
iter.skipString()
|
||||||
|
i = iter.head - 1 // it will be i++ soon
|
||||||
|
case '{': // If open symbol, increase level
|
||||||
|
level++
|
||||||
|
if !iter.incrementDepth() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case '}': // If close symbol, increase level
|
||||||
|
level--
|
||||||
|
if !iter.decrementDepth() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we have returned to the original level, we're done
|
||||||
|
if level == 0 {
|
||||||
|
iter.head = i + 1
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !iter.loadMore() {
|
||||||
|
iter.ReportError("skipObject", "incomplete object")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *Iterator) skipString() {
|
||||||
|
for {
|
||||||
|
end, escaped := iter.findStringEnd()
|
||||||
|
if end == -1 {
|
||||||
|
if !iter.loadMore() {
|
||||||
|
iter.ReportError("skipString", "incomplete string")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if escaped {
|
||||||
|
iter.head = 1 // skip the first char as last char read is \
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
iter.head = end
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// adapted from: https://github.com/buger/jsonparser/blob/master/parser.go
|
||||||
|
// Tries to find the end of string
|
||||||
|
// Support if string contains escaped quote symbols.
|
||||||
|
func (iter *Iterator) findStringEnd() (int, bool) {
|
||||||
|
escaped := false
|
||||||
|
for i := iter.head; i < iter.tail; i++ {
|
||||||
|
c := iter.buf[i]
|
||||||
|
if c == '"' {
|
||||||
|
if !escaped {
|
||||||
|
return i + 1, false
|
||||||
|
}
|
||||||
|
j := i - 1
|
||||||
|
for {
|
||||||
|
if j < iter.head || iter.buf[j] != '\\' {
|
||||||
|
// even number of backslashes
|
||||||
|
// either end of buffer, or " found
|
||||||
|
return i + 1, true
|
||||||
|
}
|
||||||
|
j--
|
||||||
|
if j < iter.head || iter.buf[j] != '\\' {
|
||||||
|
// odd number of backslashes
|
||||||
|
// it is \" or \\\"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
j--
|
||||||
|
}
|
||||||
|
} else if c == '\\' {
|
||||||
|
escaped = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
j := iter.tail - 1
|
||||||
|
for {
|
||||||
|
if j < iter.head || iter.buf[j] != '\\' {
|
||||||
|
// even number of backslashes
|
||||||
|
// either end of buffer, or " found
|
||||||
|
return -1, false // do not end with \
|
||||||
|
}
|
||||||
|
j--
|
||||||
|
if j < iter.head || iter.buf[j] != '\\' {
|
||||||
|
// odd number of backslashes
|
||||||
|
// it is \" or \\\"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
j--
|
||||||
|
|
||||||
|
}
|
||||||
|
return -1, true // end with \
|
||||||
|
}
|
||||||
99
vendor/github.com/json-iterator/go/iter_skip_strict.go
generated
vendored
Normal file
99
vendor/github.com/json-iterator/go/iter_skip_strict.go
generated
vendored
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
//+build !jsoniter_sloppy
|
||||||
|
|
||||||
|
package jsoniter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (iter *Iterator) skipNumber() {
|
||||||
|
if !iter.trySkipNumber() {
|
||||||
|
iter.unreadByte()
|
||||||
|
if iter.Error != nil && iter.Error != io.EOF {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
iter.ReadFloat64()
|
||||||
|
if iter.Error != nil && iter.Error != io.EOF {
|
||||||
|
iter.Error = nil
|
||||||
|
iter.ReadBigFloat()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *Iterator) trySkipNumber() bool {
|
||||||
|
dotFound := false
|
||||||
|
for i := iter.head; i < iter.tail; i++ {
|
||||||
|
c := iter.buf[i]
|
||||||
|
switch c {
|
||||||
|
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||||
|
case '.':
|
||||||
|
if dotFound {
|
||||||
|
iter.ReportError("validateNumber", `more than one dot found in number`)
|
||||||
|
return true // already failed
|
||||||
|
}
|
||||||
|
if i+1 == iter.tail {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
c = iter.buf[i+1]
|
||||||
|
switch c {
|
||||||
|
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
|
||||||
|
default:
|
||||||
|
iter.ReportError("validateNumber", `missing digit after dot`)
|
||||||
|
return true // already failed
|
||||||
|
}
|
||||||
|
dotFound = true
|
||||||
|
default:
|
||||||
|
switch c {
|
||||||
|
case ',', ']', '}', ' ', '\t', '\n', '\r':
|
||||||
|
if iter.head == i {
|
||||||
|
return false // if - without following digits
|
||||||
|
}
|
||||||
|
iter.head = i
|
||||||
|
return true // must be valid
|
||||||
|
}
|
||||||
|
return false // may be invalid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *Iterator) skipString() {
|
||||||
|
if !iter.trySkipString() {
|
||||||
|
iter.unreadByte()
|
||||||
|
iter.ReadString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *Iterator) trySkipString() bool {
|
||||||
|
for i := iter.head; i < iter.tail; i++ {
|
||||||
|
c := iter.buf[i]
|
||||||
|
if c == '"' {
|
||||||
|
iter.head = i + 1
|
||||||
|
return true // valid
|
||||||
|
} else if c == '\\' {
|
||||||
|
return false
|
||||||
|
} else if c < ' ' {
|
||||||
|
iter.ReportError("trySkipString",
|
||||||
|
fmt.Sprintf(`invalid control character found: %d`, c))
|
||||||
|
return true // already failed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *Iterator) skipObject() {
|
||||||
|
iter.unreadByte()
|
||||||
|
iter.ReadObjectCB(func(iter *Iterator, field string) bool {
|
||||||
|
iter.Skip()
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *Iterator) skipArray() {
|
||||||
|
iter.unreadByte()
|
||||||
|
iter.ReadArrayCB(func(iter *Iterator) bool {
|
||||||
|
iter.Skip()
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
215
vendor/github.com/json-iterator/go/iter_str.go
generated
vendored
Normal file
215
vendor/github.com/json-iterator/go/iter_str.go
generated
vendored
Normal file
|
|
@ -0,0 +1,215 @@
|
||||||
|
package jsoniter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"unicode/utf16"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReadString read string from iterator
|
||||||
|
func (iter *Iterator) ReadString() (ret string) {
|
||||||
|
c := iter.nextToken()
|
||||||
|
if c == '"' {
|
||||||
|
for i := iter.head; i < iter.tail; i++ {
|
||||||
|
c := iter.buf[i]
|
||||||
|
if c == '"' {
|
||||||
|
ret = string(iter.buf[iter.head:i])
|
||||||
|
iter.head = i + 1
|
||||||
|
return ret
|
||||||
|
} else if c == '\\' {
|
||||||
|
break
|
||||||
|
} else if c < ' ' {
|
||||||
|
iter.ReportError("ReadString",
|
||||||
|
fmt.Sprintf(`invalid control character found: %d`, c))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return iter.readStringSlowPath()
|
||||||
|
} else if c == 'n' {
|
||||||
|
iter.skipThreeBytes('u', 'l', 'l')
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
iter.ReportError("ReadString", `expects " or n, but found `+string([]byte{c}))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *Iterator) readStringSlowPath() (ret string) {
|
||||||
|
var str []byte
|
||||||
|
var c byte
|
||||||
|
for iter.Error == nil {
|
||||||
|
c = iter.readByte()
|
||||||
|
if c == '"' {
|
||||||
|
return string(str)
|
||||||
|
}
|
||||||
|
if c == '\\' {
|
||||||
|
c = iter.readByte()
|
||||||
|
str = iter.readEscapedChar(c, str)
|
||||||
|
} else {
|
||||||
|
str = append(str, c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
iter.ReportError("readStringSlowPath", "unexpected end of input")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *Iterator) readEscapedChar(c byte, str []byte) []byte {
|
||||||
|
switch c {
|
||||||
|
case 'u':
|
||||||
|
r := iter.readU4()
|
||||||
|
if utf16.IsSurrogate(r) {
|
||||||
|
c = iter.readByte()
|
||||||
|
if iter.Error != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if c != '\\' {
|
||||||
|
iter.unreadByte()
|
||||||
|
str = appendRune(str, r)
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
c = iter.readByte()
|
||||||
|
if iter.Error != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if c != 'u' {
|
||||||
|
str = appendRune(str, r)
|
||||||
|
return iter.readEscapedChar(c, str)
|
||||||
|
}
|
||||||
|
r2 := iter.readU4()
|
||||||
|
if iter.Error != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
combined := utf16.DecodeRune(r, r2)
|
||||||
|
if combined == '\uFFFD' {
|
||||||
|
str = appendRune(str, r)
|
||||||
|
str = appendRune(str, r2)
|
||||||
|
} else {
|
||||||
|
str = appendRune(str, combined)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
str = appendRune(str, r)
|
||||||
|
}
|
||||||
|
case '"':
|
||||||
|
str = append(str, '"')
|
||||||
|
case '\\':
|
||||||
|
str = append(str, '\\')
|
||||||
|
case '/':
|
||||||
|
str = append(str, '/')
|
||||||
|
case 'b':
|
||||||
|
str = append(str, '\b')
|
||||||
|
case 'f':
|
||||||
|
str = append(str, '\f')
|
||||||
|
case 'n':
|
||||||
|
str = append(str, '\n')
|
||||||
|
case 'r':
|
||||||
|
str = append(str, '\r')
|
||||||
|
case 't':
|
||||||
|
str = append(str, '\t')
|
||||||
|
default:
|
||||||
|
iter.ReportError("readEscapedChar",
|
||||||
|
`invalid escape char after \`)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return str
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadStringAsSlice read string from iterator without copying into string form.
|
||||||
|
// The []byte can not be kept, as it will change after next iterator call.
|
||||||
|
func (iter *Iterator) ReadStringAsSlice() (ret []byte) {
|
||||||
|
c := iter.nextToken()
|
||||||
|
if c == '"' {
|
||||||
|
for i := iter.head; i < iter.tail; i++ {
|
||||||
|
// require ascii string and no escape
|
||||||
|
// for: field name, base64, number
|
||||||
|
if iter.buf[i] == '"' {
|
||||||
|
// fast path: reuse the underlying buffer
|
||||||
|
ret = iter.buf[iter.head:i]
|
||||||
|
iter.head = i + 1
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
readLen := iter.tail - iter.head
|
||||||
|
copied := make([]byte, readLen, readLen*2)
|
||||||
|
copy(copied, iter.buf[iter.head:iter.tail])
|
||||||
|
iter.head = iter.tail
|
||||||
|
for iter.Error == nil {
|
||||||
|
c := iter.readByte()
|
||||||
|
if c == '"' {
|
||||||
|
return copied
|
||||||
|
}
|
||||||
|
copied = append(copied, c)
|
||||||
|
}
|
||||||
|
return copied
|
||||||
|
}
|
||||||
|
iter.ReportError("ReadStringAsSlice", `expects " or n, but found `+string([]byte{c}))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (iter *Iterator) readU4() (ret rune) {
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
c := iter.readByte()
|
||||||
|
if iter.Error != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if c >= '0' && c <= '9' {
|
||||||
|
ret = ret*16 + rune(c-'0')
|
||||||
|
} else if c >= 'a' && c <= 'f' {
|
||||||
|
ret = ret*16 + rune(c-'a'+10)
|
||||||
|
} else if c >= 'A' && c <= 'F' {
|
||||||
|
ret = ret*16 + rune(c-'A'+10)
|
||||||
|
} else {
|
||||||
|
iter.ReportError("readU4", "expects 0~9 or a~f, but found "+string([]byte{c}))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
t1 = 0x00 // 0000 0000
|
||||||
|
tx = 0x80 // 1000 0000
|
||||||
|
t2 = 0xC0 // 1100 0000
|
||||||
|
t3 = 0xE0 // 1110 0000
|
||||||
|
t4 = 0xF0 // 1111 0000
|
||||||
|
t5 = 0xF8 // 1111 1000
|
||||||
|
|
||||||
|
maskx = 0x3F // 0011 1111
|
||||||
|
mask2 = 0x1F // 0001 1111
|
||||||
|
mask3 = 0x0F // 0000 1111
|
||||||
|
mask4 = 0x07 // 0000 0111
|
||||||
|
|
||||||
|
rune1Max = 1<<7 - 1
|
||||||
|
rune2Max = 1<<11 - 1
|
||||||
|
rune3Max = 1<<16 - 1
|
||||||
|
|
||||||
|
surrogateMin = 0xD800
|
||||||
|
surrogateMax = 0xDFFF
|
||||||
|
|
||||||
|
maxRune = '\U0010FFFF' // Maximum valid Unicode code point.
|
||||||
|
runeError = '\uFFFD' // the "error" Rune or "Unicode replacement character"
|
||||||
|
)
|
||||||
|
|
||||||
|
func appendRune(p []byte, r rune) []byte {
|
||||||
|
// Negative values are erroneous. Making it unsigned addresses the problem.
|
||||||
|
switch i := uint32(r); {
|
||||||
|
case i <= rune1Max:
|
||||||
|
p = append(p, byte(r))
|
||||||
|
return p
|
||||||
|
case i <= rune2Max:
|
||||||
|
p = append(p, t2|byte(r>>6))
|
||||||
|
p = append(p, tx|byte(r)&maskx)
|
||||||
|
return p
|
||||||
|
case i > maxRune, surrogateMin <= i && i <= surrogateMax:
|
||||||
|
r = runeError
|
||||||
|
fallthrough
|
||||||
|
case i <= rune3Max:
|
||||||
|
p = append(p, t3|byte(r>>12))
|
||||||
|
p = append(p, tx|byte(r>>6)&maskx)
|
||||||
|
p = append(p, tx|byte(r)&maskx)
|
||||||
|
return p
|
||||||
|
default:
|
||||||
|
p = append(p, t4|byte(r>>18))
|
||||||
|
p = append(p, tx|byte(r>>12)&maskx)
|
||||||
|
p = append(p, tx|byte(r>>6)&maskx)
|
||||||
|
p = append(p, tx|byte(r)&maskx)
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
}
|
||||||
18
vendor/github.com/json-iterator/go/jsoniter.go
generated
vendored
Normal file
18
vendor/github.com/json-iterator/go/jsoniter.go
generated
vendored
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
// Package jsoniter implements encoding and decoding of JSON as defined in
|
||||||
|
// RFC 4627 and provides interfaces with identical syntax of standard lib encoding/json.
|
||||||
|
// Converting from encoding/json to jsoniter is no more than replacing the package with jsoniter
|
||||||
|
// and variable type declarations (if any).
|
||||||
|
// jsoniter interfaces gives 100% compatibility with code using standard lib.
|
||||||
|
//
|
||||||
|
// "JSON and Go"
|
||||||
|
// (https://golang.org/doc/articles/json_and_go.html)
|
||||||
|
// gives a description of how Marshal/Unmarshal operate
|
||||||
|
// between arbitrary or predefined json objects and bytes,
|
||||||
|
// and it applies to jsoniter.Marshal/Unmarshal as well.
|
||||||
|
//
|
||||||
|
// Besides, jsoniter.Iterator provides a different set of interfaces
|
||||||
|
// iterating given bytes/string/reader
|
||||||
|
// and yielding parsed elements one by one.
|
||||||
|
// This set of interfaces reads input as required and gives
|
||||||
|
// better performance.
|
||||||
|
package jsoniter
|
||||||
42
vendor/github.com/json-iterator/go/pool.go
generated
vendored
Normal file
42
vendor/github.com/json-iterator/go/pool.go
generated
vendored
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
package jsoniter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// IteratorPool a thread safe pool of iterators with same configuration
|
||||||
|
type IteratorPool interface {
|
||||||
|
BorrowIterator(data []byte) *Iterator
|
||||||
|
ReturnIterator(iter *Iterator)
|
||||||
|
}
|
||||||
|
|
||||||
|
// StreamPool a thread safe pool of streams with same configuration
|
||||||
|
type StreamPool interface {
|
||||||
|
BorrowStream(writer io.Writer) *Stream
|
||||||
|
ReturnStream(stream *Stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *frozenConfig) BorrowStream(writer io.Writer) *Stream {
|
||||||
|
stream := cfg.streamPool.Get().(*Stream)
|
||||||
|
stream.Reset(writer)
|
||||||
|
return stream
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *frozenConfig) ReturnStream(stream *Stream) {
|
||||||
|
stream.out = nil
|
||||||
|
stream.Error = nil
|
||||||
|
stream.Attachment = nil
|
||||||
|
cfg.streamPool.Put(stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *frozenConfig) BorrowIterator(data []byte) *Iterator {
|
||||||
|
iter := cfg.iteratorPool.Get().(*Iterator)
|
||||||
|
iter.ResetBytes(data)
|
||||||
|
return iter
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *frozenConfig) ReturnIterator(iter *Iterator) {
|
||||||
|
iter.Error = nil
|
||||||
|
iter.Attachment = nil
|
||||||
|
cfg.iteratorPool.Put(iter)
|
||||||
|
}
|
||||||
337
vendor/github.com/json-iterator/go/reflect.go
generated
vendored
Normal file
337
vendor/github.com/json-iterator/go/reflect.go
generated
vendored
Normal file
|
|
@ -0,0 +1,337 @@
|
||||||
|
package jsoniter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/modern-go/reflect2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ValDecoder is an internal type registered to cache as needed.
|
||||||
|
// Don't confuse jsoniter.ValDecoder with json.Decoder.
|
||||||
|
// For json.Decoder's adapter, refer to jsoniter.AdapterDecoder(todo link).
|
||||||
|
//
|
||||||
|
// Reflection on type to create decoders, which is then cached
|
||||||
|
// Reflection on value is avoided as we can, as the reflect.Value itself will allocate, with following exceptions
|
||||||
|
// 1. create instance of new value, for example *int will need a int to be allocated
|
||||||
|
// 2. append to slice, if the existing cap is not enough, allocate will be done using Reflect.New
|
||||||
|
// 3. assignment to map, both key and value will be reflect.Value
|
||||||
|
// For a simple struct binding, it will be reflect.Value free and allocation free
|
||||||
|
type ValDecoder interface {
|
||||||
|
Decode(ptr unsafe.Pointer, iter *Iterator)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValEncoder is an internal type registered to cache as needed.
|
||||||
|
// Don't confuse jsoniter.ValEncoder with json.Encoder.
|
||||||
|
// For json.Encoder's adapter, refer to jsoniter.AdapterEncoder(todo godoc link).
|
||||||
|
type ValEncoder interface {
|
||||||
|
IsEmpty(ptr unsafe.Pointer) bool
|
||||||
|
Encode(ptr unsafe.Pointer, stream *Stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
type checkIsEmpty interface {
|
||||||
|
IsEmpty(ptr unsafe.Pointer) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type ctx struct {
|
||||||
|
*frozenConfig
|
||||||
|
prefix string
|
||||||
|
encoders map[reflect2.Type]ValEncoder
|
||||||
|
decoders map[reflect2.Type]ValDecoder
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *ctx) caseSensitive() bool {
|
||||||
|
if b.frozenConfig == nil {
|
||||||
|
// default is case-insensitive
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return b.frozenConfig.caseSensitive
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *ctx) append(prefix string) *ctx {
|
||||||
|
return &ctx{
|
||||||
|
frozenConfig: b.frozenConfig,
|
||||||
|
prefix: b.prefix + " " + prefix,
|
||||||
|
encoders: b.encoders,
|
||||||
|
decoders: b.decoders,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadVal copy the underlying JSON into go interface, same as json.Unmarshal
|
||||||
|
func (iter *Iterator) ReadVal(obj interface{}) {
|
||||||
|
depth := iter.depth
|
||||||
|
cacheKey := reflect2.RTypeOf(obj)
|
||||||
|
decoder := iter.cfg.getDecoderFromCache(cacheKey)
|
||||||
|
if decoder == nil {
|
||||||
|
typ := reflect2.TypeOf(obj)
|
||||||
|
if typ == nil || typ.Kind() != reflect.Ptr {
|
||||||
|
iter.ReportError("ReadVal", "can only unmarshal into pointer")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
decoder = iter.cfg.DecoderOf(typ)
|
||||||
|
}
|
||||||
|
ptr := reflect2.PtrOf(obj)
|
||||||
|
if ptr == nil {
|
||||||
|
iter.ReportError("ReadVal", "can not read into nil pointer")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
decoder.Decode(ptr, iter)
|
||||||
|
if iter.depth != depth {
|
||||||
|
iter.ReportError("ReadVal", "unexpected mismatched nesting")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteVal copy the go interface into underlying JSON, same as json.Marshal
|
||||||
|
func (stream *Stream) WriteVal(val interface{}) {
|
||||||
|
if nil == val {
|
||||||
|
stream.WriteNil()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cacheKey := reflect2.RTypeOf(val)
|
||||||
|
encoder := stream.cfg.getEncoderFromCache(cacheKey)
|
||||||
|
if encoder == nil {
|
||||||
|
typ := reflect2.TypeOf(val)
|
||||||
|
encoder = stream.cfg.EncoderOf(typ)
|
||||||
|
}
|
||||||
|
encoder.Encode(reflect2.PtrOf(val), stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *frozenConfig) DecoderOf(typ reflect2.Type) ValDecoder {
|
||||||
|
cacheKey := typ.RType()
|
||||||
|
decoder := cfg.getDecoderFromCache(cacheKey)
|
||||||
|
if decoder != nil {
|
||||||
|
return decoder
|
||||||
|
}
|
||||||
|
ctx := &ctx{
|
||||||
|
frozenConfig: cfg,
|
||||||
|
prefix: "",
|
||||||
|
decoders: map[reflect2.Type]ValDecoder{},
|
||||||
|
encoders: map[reflect2.Type]ValEncoder{},
|
||||||
|
}
|
||||||
|
ptrType := typ.(*reflect2.UnsafePtrType)
|
||||||
|
decoder = decoderOfType(ctx, ptrType.Elem())
|
||||||
|
cfg.addDecoderToCache(cacheKey, decoder)
|
||||||
|
return decoder
|
||||||
|
}
|
||||||
|
|
||||||
|
func decoderOfType(ctx *ctx, typ reflect2.Type) ValDecoder {
|
||||||
|
decoder := getTypeDecoderFromExtension(ctx, typ)
|
||||||
|
if decoder != nil {
|
||||||
|
return decoder
|
||||||
|
}
|
||||||
|
decoder = createDecoderOfType(ctx, typ)
|
||||||
|
for _, extension := range extensions {
|
||||||
|
decoder = extension.DecorateDecoder(typ, decoder)
|
||||||
|
}
|
||||||
|
decoder = ctx.decoderExtension.DecorateDecoder(typ, decoder)
|
||||||
|
for _, extension := range ctx.extraExtensions {
|
||||||
|
decoder = extension.DecorateDecoder(typ, decoder)
|
||||||
|
}
|
||||||
|
return decoder
|
||||||
|
}
|
||||||
|
|
||||||
|
func createDecoderOfType(ctx *ctx, typ reflect2.Type) ValDecoder {
|
||||||
|
decoder := ctx.decoders[typ]
|
||||||
|
if decoder != nil {
|
||||||
|
return decoder
|
||||||
|
}
|
||||||
|
placeholder := &placeholderDecoder{}
|
||||||
|
ctx.decoders[typ] = placeholder
|
||||||
|
decoder = _createDecoderOfType(ctx, typ)
|
||||||
|
placeholder.decoder = decoder
|
||||||
|
return decoder
|
||||||
|
}
|
||||||
|
|
||||||
|
func _createDecoderOfType(ctx *ctx, typ reflect2.Type) ValDecoder {
|
||||||
|
decoder := createDecoderOfJsonRawMessage(ctx, typ)
|
||||||
|
if decoder != nil {
|
||||||
|
return decoder
|
||||||
|
}
|
||||||
|
decoder = createDecoderOfJsonNumber(ctx, typ)
|
||||||
|
if decoder != nil {
|
||||||
|
return decoder
|
||||||
|
}
|
||||||
|
decoder = createDecoderOfMarshaler(ctx, typ)
|
||||||
|
if decoder != nil {
|
||||||
|
return decoder
|
||||||
|
}
|
||||||
|
decoder = createDecoderOfAny(ctx, typ)
|
||||||
|
if decoder != nil {
|
||||||
|
return decoder
|
||||||
|
}
|
||||||
|
decoder = createDecoderOfNative(ctx, typ)
|
||||||
|
if decoder != nil {
|
||||||
|
return decoder
|
||||||
|
}
|
||||||
|
switch typ.Kind() {
|
||||||
|
case reflect.Interface:
|
||||||
|
ifaceType, isIFace := typ.(*reflect2.UnsafeIFaceType)
|
||||||
|
if isIFace {
|
||||||
|
return &ifaceDecoder{valType: ifaceType}
|
||||||
|
}
|
||||||
|
return &efaceDecoder{}
|
||||||
|
case reflect.Struct:
|
||||||
|
return decoderOfStruct(ctx, typ)
|
||||||
|
case reflect.Array:
|
||||||
|
return decoderOfArray(ctx, typ)
|
||||||
|
case reflect.Slice:
|
||||||
|
return decoderOfSlice(ctx, typ)
|
||||||
|
case reflect.Map:
|
||||||
|
return decoderOfMap(ctx, typ)
|
||||||
|
case reflect.Ptr:
|
||||||
|
return decoderOfOptional(ctx, typ)
|
||||||
|
default:
|
||||||
|
return &lazyErrorDecoder{err: fmt.Errorf("%s%s is unsupported type", ctx.prefix, typ.String())}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *frozenConfig) EncoderOf(typ reflect2.Type) ValEncoder {
|
||||||
|
cacheKey := typ.RType()
|
||||||
|
encoder := cfg.getEncoderFromCache(cacheKey)
|
||||||
|
if encoder != nil {
|
||||||
|
return encoder
|
||||||
|
}
|
||||||
|
ctx := &ctx{
|
||||||
|
frozenConfig: cfg,
|
||||||
|
prefix: "",
|
||||||
|
decoders: map[reflect2.Type]ValDecoder{},
|
||||||
|
encoders: map[reflect2.Type]ValEncoder{},
|
||||||
|
}
|
||||||
|
encoder = encoderOfType(ctx, typ)
|
||||||
|
if typ.LikePtr() {
|
||||||
|
encoder = &onePtrEncoder{encoder}
|
||||||
|
}
|
||||||
|
cfg.addEncoderToCache(cacheKey, encoder)
|
||||||
|
return encoder
|
||||||
|
}
|
||||||
|
|
||||||
|
type onePtrEncoder struct {
|
||||||
|
encoder ValEncoder
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *onePtrEncoder) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
return encoder.encoder.IsEmpty(unsafe.Pointer(&ptr))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *onePtrEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
encoder.encoder.Encode(unsafe.Pointer(&ptr), stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
func encoderOfType(ctx *ctx, typ reflect2.Type) ValEncoder {
|
||||||
|
encoder := getTypeEncoderFromExtension(ctx, typ)
|
||||||
|
if encoder != nil {
|
||||||
|
return encoder
|
||||||
|
}
|
||||||
|
encoder = createEncoderOfType(ctx, typ)
|
||||||
|
for _, extension := range extensions {
|
||||||
|
encoder = extension.DecorateEncoder(typ, encoder)
|
||||||
|
}
|
||||||
|
encoder = ctx.encoderExtension.DecorateEncoder(typ, encoder)
|
||||||
|
for _, extension := range ctx.extraExtensions {
|
||||||
|
encoder = extension.DecorateEncoder(typ, encoder)
|
||||||
|
}
|
||||||
|
return encoder
|
||||||
|
}
|
||||||
|
|
||||||
|
func createEncoderOfType(ctx *ctx, typ reflect2.Type) ValEncoder {
|
||||||
|
encoder := ctx.encoders[typ]
|
||||||
|
if encoder != nil {
|
||||||
|
return encoder
|
||||||
|
}
|
||||||
|
placeholder := &placeholderEncoder{}
|
||||||
|
ctx.encoders[typ] = placeholder
|
||||||
|
encoder = _createEncoderOfType(ctx, typ)
|
||||||
|
placeholder.encoder = encoder
|
||||||
|
return encoder
|
||||||
|
}
|
||||||
|
func _createEncoderOfType(ctx *ctx, typ reflect2.Type) ValEncoder {
|
||||||
|
encoder := createEncoderOfJsonRawMessage(ctx, typ)
|
||||||
|
if encoder != nil {
|
||||||
|
return encoder
|
||||||
|
}
|
||||||
|
encoder = createEncoderOfJsonNumber(ctx, typ)
|
||||||
|
if encoder != nil {
|
||||||
|
return encoder
|
||||||
|
}
|
||||||
|
encoder = createEncoderOfMarshaler(ctx, typ)
|
||||||
|
if encoder != nil {
|
||||||
|
return encoder
|
||||||
|
}
|
||||||
|
encoder = createEncoderOfAny(ctx, typ)
|
||||||
|
if encoder != nil {
|
||||||
|
return encoder
|
||||||
|
}
|
||||||
|
encoder = createEncoderOfNative(ctx, typ)
|
||||||
|
if encoder != nil {
|
||||||
|
return encoder
|
||||||
|
}
|
||||||
|
kind := typ.Kind()
|
||||||
|
switch kind {
|
||||||
|
case reflect.Interface:
|
||||||
|
return &dynamicEncoder{typ}
|
||||||
|
case reflect.Struct:
|
||||||
|
return encoderOfStruct(ctx, typ)
|
||||||
|
case reflect.Array:
|
||||||
|
return encoderOfArray(ctx, typ)
|
||||||
|
case reflect.Slice:
|
||||||
|
return encoderOfSlice(ctx, typ)
|
||||||
|
case reflect.Map:
|
||||||
|
return encoderOfMap(ctx, typ)
|
||||||
|
case reflect.Ptr:
|
||||||
|
return encoderOfOptional(ctx, typ)
|
||||||
|
default:
|
||||||
|
return &lazyErrorEncoder{err: fmt.Errorf("%s%s is unsupported type", ctx.prefix, typ.String())}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type lazyErrorDecoder struct {
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (decoder *lazyErrorDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
|
||||||
|
if iter.WhatIsNext() != NilValue {
|
||||||
|
if iter.Error == nil {
|
||||||
|
iter.Error = decoder.err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
iter.Skip()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type lazyErrorEncoder struct {
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *lazyErrorEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
if ptr == nil {
|
||||||
|
stream.WriteNil()
|
||||||
|
} else if stream.Error == nil {
|
||||||
|
stream.Error = encoder.err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *lazyErrorEncoder) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type placeholderDecoder struct {
|
||||||
|
decoder ValDecoder
|
||||||
|
}
|
||||||
|
|
||||||
|
func (decoder *placeholderDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
|
||||||
|
decoder.decoder.Decode(ptr, iter)
|
||||||
|
}
|
||||||
|
|
||||||
|
type placeholderEncoder struct {
|
||||||
|
encoder ValEncoder
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *placeholderEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
encoder.encoder.Encode(ptr, stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *placeholderEncoder) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
return encoder.encoder.IsEmpty(ptr)
|
||||||
|
}
|
||||||
104
vendor/github.com/json-iterator/go/reflect_array.go
generated
vendored
Normal file
104
vendor/github.com/json-iterator/go/reflect_array.go
generated
vendored
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
package jsoniter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/modern-go/reflect2"
|
||||||
|
"io"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func decoderOfArray(ctx *ctx, typ reflect2.Type) ValDecoder {
|
||||||
|
arrayType := typ.(*reflect2.UnsafeArrayType)
|
||||||
|
decoder := decoderOfType(ctx.append("[arrayElem]"), arrayType.Elem())
|
||||||
|
return &arrayDecoder{arrayType, decoder}
|
||||||
|
}
|
||||||
|
|
||||||
|
func encoderOfArray(ctx *ctx, typ reflect2.Type) ValEncoder {
|
||||||
|
arrayType := typ.(*reflect2.UnsafeArrayType)
|
||||||
|
if arrayType.Len() == 0 {
|
||||||
|
return emptyArrayEncoder{}
|
||||||
|
}
|
||||||
|
encoder := encoderOfType(ctx.append("[arrayElem]"), arrayType.Elem())
|
||||||
|
return &arrayEncoder{arrayType, encoder}
|
||||||
|
}
|
||||||
|
|
||||||
|
type emptyArrayEncoder struct{}
|
||||||
|
|
||||||
|
func (encoder emptyArrayEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
stream.WriteEmptyArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder emptyArrayEncoder) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
type arrayEncoder struct {
|
||||||
|
arrayType *reflect2.UnsafeArrayType
|
||||||
|
elemEncoder ValEncoder
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *arrayEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
stream.WriteArrayStart()
|
||||||
|
elemPtr := unsafe.Pointer(ptr)
|
||||||
|
encoder.elemEncoder.Encode(elemPtr, stream)
|
||||||
|
for i := 1; i < encoder.arrayType.Len(); i++ {
|
||||||
|
stream.WriteMore()
|
||||||
|
elemPtr = encoder.arrayType.UnsafeGetIndex(ptr, i)
|
||||||
|
encoder.elemEncoder.Encode(elemPtr, stream)
|
||||||
|
}
|
||||||
|
stream.WriteArrayEnd()
|
||||||
|
if stream.Error != nil && stream.Error != io.EOF {
|
||||||
|
stream.Error = fmt.Errorf("%v: %s", encoder.arrayType, stream.Error.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *arrayEncoder) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type arrayDecoder struct {
|
||||||
|
arrayType *reflect2.UnsafeArrayType
|
||||||
|
elemDecoder ValDecoder
|
||||||
|
}
|
||||||
|
|
||||||
|
func (decoder *arrayDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
|
||||||
|
decoder.doDecode(ptr, iter)
|
||||||
|
if iter.Error != nil && iter.Error != io.EOF {
|
||||||
|
iter.Error = fmt.Errorf("%v: %s", decoder.arrayType, iter.Error.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (decoder *arrayDecoder) doDecode(ptr unsafe.Pointer, iter *Iterator) {
|
||||||
|
c := iter.nextToken()
|
||||||
|
arrayType := decoder.arrayType
|
||||||
|
if c == 'n' {
|
||||||
|
iter.skipThreeBytes('u', 'l', 'l')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if c != '[' {
|
||||||
|
iter.ReportError("decode array", "expect [ or n, but found "+string([]byte{c}))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c = iter.nextToken()
|
||||||
|
if c == ']' {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
iter.unreadByte()
|
||||||
|
elemPtr := arrayType.UnsafeGetIndex(ptr, 0)
|
||||||
|
decoder.elemDecoder.Decode(elemPtr, iter)
|
||||||
|
length := 1
|
||||||
|
for c = iter.nextToken(); c == ','; c = iter.nextToken() {
|
||||||
|
if length >= arrayType.Len() {
|
||||||
|
iter.Skip()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
idx := length
|
||||||
|
length += 1
|
||||||
|
elemPtr = arrayType.UnsafeGetIndex(ptr, idx)
|
||||||
|
decoder.elemDecoder.Decode(elemPtr, iter)
|
||||||
|
}
|
||||||
|
if c != ']' {
|
||||||
|
iter.ReportError("decode array", "expect ], but found "+string([]byte{c}))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
70
vendor/github.com/json-iterator/go/reflect_dynamic.go
generated
vendored
Normal file
70
vendor/github.com/json-iterator/go/reflect_dynamic.go
generated
vendored
Normal file
|
|
@ -0,0 +1,70 @@
|
||||||
|
package jsoniter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/modern-go/reflect2"
|
||||||
|
"reflect"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
type dynamicEncoder struct {
|
||||||
|
valType reflect2.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *dynamicEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
obj := encoder.valType.UnsafeIndirect(ptr)
|
||||||
|
stream.WriteVal(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *dynamicEncoder) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
return encoder.valType.UnsafeIndirect(ptr) == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type efaceDecoder struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (decoder *efaceDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
|
||||||
|
pObj := (*interface{})(ptr)
|
||||||
|
obj := *pObj
|
||||||
|
if obj == nil {
|
||||||
|
*pObj = iter.Read()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
typ := reflect2.TypeOf(obj)
|
||||||
|
if typ.Kind() != reflect.Ptr {
|
||||||
|
*pObj = iter.Read()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ptrType := typ.(*reflect2.UnsafePtrType)
|
||||||
|
ptrElemType := ptrType.Elem()
|
||||||
|
if iter.WhatIsNext() == NilValue {
|
||||||
|
if ptrElemType.Kind() != reflect.Ptr {
|
||||||
|
iter.skipFourBytes('n', 'u', 'l', 'l')
|
||||||
|
*pObj = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if reflect2.IsNil(obj) {
|
||||||
|
obj := ptrElemType.New()
|
||||||
|
iter.ReadVal(obj)
|
||||||
|
*pObj = obj
|
||||||
|
return
|
||||||
|
}
|
||||||
|
iter.ReadVal(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ifaceDecoder struct {
|
||||||
|
valType *reflect2.UnsafeIFaceType
|
||||||
|
}
|
||||||
|
|
||||||
|
func (decoder *ifaceDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
|
||||||
|
if iter.ReadNil() {
|
||||||
|
decoder.valType.UnsafeSet(ptr, decoder.valType.UnsafeNew())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
obj := decoder.valType.UnsafeIndirect(ptr)
|
||||||
|
if reflect2.IsNil(obj) {
|
||||||
|
iter.ReportError("decode non empty interface", "can not unmarshal into nil")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
iter.ReadVal(obj)
|
||||||
|
}
|
||||||
483
vendor/github.com/json-iterator/go/reflect_extension.go
generated
vendored
Normal file
483
vendor/github.com/json-iterator/go/reflect_extension.go
generated
vendored
Normal file
|
|
@ -0,0 +1,483 @@
|
||||||
|
package jsoniter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/modern-go/reflect2"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
var typeDecoders = map[string]ValDecoder{}
|
||||||
|
var fieldDecoders = map[string]ValDecoder{}
|
||||||
|
var typeEncoders = map[string]ValEncoder{}
|
||||||
|
var fieldEncoders = map[string]ValEncoder{}
|
||||||
|
var extensions = []Extension{}
|
||||||
|
|
||||||
|
// StructDescriptor describe how should we encode/decode the struct
|
||||||
|
type StructDescriptor struct {
|
||||||
|
Type reflect2.Type
|
||||||
|
Fields []*Binding
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetField get one field from the descriptor by its name.
|
||||||
|
// Can not use map here to keep field orders.
|
||||||
|
func (structDescriptor *StructDescriptor) GetField(fieldName string) *Binding {
|
||||||
|
for _, binding := range structDescriptor.Fields {
|
||||||
|
if binding.Field.Name() == fieldName {
|
||||||
|
return binding
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Binding describe how should we encode/decode the struct field
|
||||||
|
type Binding struct {
|
||||||
|
levels []int
|
||||||
|
Field reflect2.StructField
|
||||||
|
FromNames []string
|
||||||
|
ToNames []string
|
||||||
|
Encoder ValEncoder
|
||||||
|
Decoder ValDecoder
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extension the one for all SPI. Customize encoding/decoding by specifying alternate encoder/decoder.
|
||||||
|
// Can also rename fields by UpdateStructDescriptor.
|
||||||
|
type Extension interface {
|
||||||
|
UpdateStructDescriptor(structDescriptor *StructDescriptor)
|
||||||
|
CreateMapKeyDecoder(typ reflect2.Type) ValDecoder
|
||||||
|
CreateMapKeyEncoder(typ reflect2.Type) ValEncoder
|
||||||
|
CreateDecoder(typ reflect2.Type) ValDecoder
|
||||||
|
CreateEncoder(typ reflect2.Type) ValEncoder
|
||||||
|
DecorateDecoder(typ reflect2.Type, decoder ValDecoder) ValDecoder
|
||||||
|
DecorateEncoder(typ reflect2.Type, encoder ValEncoder) ValEncoder
|
||||||
|
}
|
||||||
|
|
||||||
|
// DummyExtension embed this type get dummy implementation for all methods of Extension
|
||||||
|
type DummyExtension struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateStructDescriptor No-op
|
||||||
|
func (extension *DummyExtension) UpdateStructDescriptor(structDescriptor *StructDescriptor) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateMapKeyDecoder No-op
|
||||||
|
func (extension *DummyExtension) CreateMapKeyDecoder(typ reflect2.Type) ValDecoder {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateMapKeyEncoder No-op
|
||||||
|
func (extension *DummyExtension) CreateMapKeyEncoder(typ reflect2.Type) ValEncoder {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateDecoder No-op
|
||||||
|
func (extension *DummyExtension) CreateDecoder(typ reflect2.Type) ValDecoder {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateEncoder No-op
|
||||||
|
func (extension *DummyExtension) CreateEncoder(typ reflect2.Type) ValEncoder {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecorateDecoder No-op
|
||||||
|
func (extension *DummyExtension) DecorateDecoder(typ reflect2.Type, decoder ValDecoder) ValDecoder {
|
||||||
|
return decoder
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecorateEncoder No-op
|
||||||
|
func (extension *DummyExtension) DecorateEncoder(typ reflect2.Type, encoder ValEncoder) ValEncoder {
|
||||||
|
return encoder
|
||||||
|
}
|
||||||
|
|
||||||
|
type EncoderExtension map[reflect2.Type]ValEncoder
|
||||||
|
|
||||||
|
// UpdateStructDescriptor No-op
|
||||||
|
func (extension EncoderExtension) UpdateStructDescriptor(structDescriptor *StructDescriptor) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateDecoder No-op
|
||||||
|
func (extension EncoderExtension) CreateDecoder(typ reflect2.Type) ValDecoder {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateEncoder get encoder from map
|
||||||
|
func (extension EncoderExtension) CreateEncoder(typ reflect2.Type) ValEncoder {
|
||||||
|
return extension[typ]
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateMapKeyDecoder No-op
|
||||||
|
func (extension EncoderExtension) CreateMapKeyDecoder(typ reflect2.Type) ValDecoder {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateMapKeyEncoder No-op
|
||||||
|
func (extension EncoderExtension) CreateMapKeyEncoder(typ reflect2.Type) ValEncoder {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecorateDecoder No-op
|
||||||
|
func (extension EncoderExtension) DecorateDecoder(typ reflect2.Type, decoder ValDecoder) ValDecoder {
|
||||||
|
return decoder
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecorateEncoder No-op
|
||||||
|
func (extension EncoderExtension) DecorateEncoder(typ reflect2.Type, encoder ValEncoder) ValEncoder {
|
||||||
|
return encoder
|
||||||
|
}
|
||||||
|
|
||||||
|
type DecoderExtension map[reflect2.Type]ValDecoder
|
||||||
|
|
||||||
|
// UpdateStructDescriptor No-op
|
||||||
|
func (extension DecoderExtension) UpdateStructDescriptor(structDescriptor *StructDescriptor) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateMapKeyDecoder No-op
|
||||||
|
func (extension DecoderExtension) CreateMapKeyDecoder(typ reflect2.Type) ValDecoder {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateMapKeyEncoder No-op
|
||||||
|
func (extension DecoderExtension) CreateMapKeyEncoder(typ reflect2.Type) ValEncoder {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateDecoder get decoder from map
|
||||||
|
func (extension DecoderExtension) CreateDecoder(typ reflect2.Type) ValDecoder {
|
||||||
|
return extension[typ]
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateEncoder No-op
|
||||||
|
func (extension DecoderExtension) CreateEncoder(typ reflect2.Type) ValEncoder {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecorateDecoder No-op
|
||||||
|
func (extension DecoderExtension) DecorateDecoder(typ reflect2.Type, decoder ValDecoder) ValDecoder {
|
||||||
|
return decoder
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecorateEncoder No-op
|
||||||
|
func (extension DecoderExtension) DecorateEncoder(typ reflect2.Type, encoder ValEncoder) ValEncoder {
|
||||||
|
return encoder
|
||||||
|
}
|
||||||
|
|
||||||
|
type funcDecoder struct {
|
||||||
|
fun DecoderFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
func (decoder *funcDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
|
||||||
|
decoder.fun(ptr, iter)
|
||||||
|
}
|
||||||
|
|
||||||
|
type funcEncoder struct {
|
||||||
|
fun EncoderFunc
|
||||||
|
isEmptyFunc func(ptr unsafe.Pointer) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *funcEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
encoder.fun(ptr, stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *funcEncoder) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
if encoder.isEmptyFunc == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return encoder.isEmptyFunc(ptr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DecoderFunc the function form of TypeDecoder
|
||||||
|
type DecoderFunc func(ptr unsafe.Pointer, iter *Iterator)
|
||||||
|
|
||||||
|
// EncoderFunc the function form of TypeEncoder
|
||||||
|
type EncoderFunc func(ptr unsafe.Pointer, stream *Stream)
|
||||||
|
|
||||||
|
// RegisterTypeDecoderFunc register TypeDecoder for a type with function
|
||||||
|
func RegisterTypeDecoderFunc(typ string, fun DecoderFunc) {
|
||||||
|
typeDecoders[typ] = &funcDecoder{fun}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterTypeDecoder register TypeDecoder for a typ
|
||||||
|
func RegisterTypeDecoder(typ string, decoder ValDecoder) {
|
||||||
|
typeDecoders[typ] = decoder
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterFieldDecoderFunc register TypeDecoder for a struct field with function
|
||||||
|
func RegisterFieldDecoderFunc(typ string, field string, fun DecoderFunc) {
|
||||||
|
RegisterFieldDecoder(typ, field, &funcDecoder{fun})
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterFieldDecoder register TypeDecoder for a struct field
|
||||||
|
func RegisterFieldDecoder(typ string, field string, decoder ValDecoder) {
|
||||||
|
fieldDecoders[fmt.Sprintf("%s/%s", typ, field)] = decoder
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterTypeEncoderFunc register TypeEncoder for a type with encode/isEmpty function
|
||||||
|
func RegisterTypeEncoderFunc(typ string, fun EncoderFunc, isEmptyFunc func(unsafe.Pointer) bool) {
|
||||||
|
typeEncoders[typ] = &funcEncoder{fun, isEmptyFunc}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterTypeEncoder register TypeEncoder for a type
|
||||||
|
func RegisterTypeEncoder(typ string, encoder ValEncoder) {
|
||||||
|
typeEncoders[typ] = encoder
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterFieldEncoderFunc register TypeEncoder for a struct field with encode/isEmpty function
|
||||||
|
func RegisterFieldEncoderFunc(typ string, field string, fun EncoderFunc, isEmptyFunc func(unsafe.Pointer) bool) {
|
||||||
|
RegisterFieldEncoder(typ, field, &funcEncoder{fun, isEmptyFunc})
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterFieldEncoder register TypeEncoder for a struct field
|
||||||
|
func RegisterFieldEncoder(typ string, field string, encoder ValEncoder) {
|
||||||
|
fieldEncoders[fmt.Sprintf("%s/%s", typ, field)] = encoder
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterExtension register extension
|
||||||
|
func RegisterExtension(extension Extension) {
|
||||||
|
extensions = append(extensions, extension)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTypeDecoderFromExtension(ctx *ctx, typ reflect2.Type) ValDecoder {
|
||||||
|
decoder := _getTypeDecoderFromExtension(ctx, typ)
|
||||||
|
if decoder != nil {
|
||||||
|
for _, extension := range extensions {
|
||||||
|
decoder = extension.DecorateDecoder(typ, decoder)
|
||||||
|
}
|
||||||
|
decoder = ctx.decoderExtension.DecorateDecoder(typ, decoder)
|
||||||
|
for _, extension := range ctx.extraExtensions {
|
||||||
|
decoder = extension.DecorateDecoder(typ, decoder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return decoder
|
||||||
|
}
|
||||||
|
func _getTypeDecoderFromExtension(ctx *ctx, typ reflect2.Type) ValDecoder {
|
||||||
|
for _, extension := range extensions {
|
||||||
|
decoder := extension.CreateDecoder(typ)
|
||||||
|
if decoder != nil {
|
||||||
|
return decoder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
decoder := ctx.decoderExtension.CreateDecoder(typ)
|
||||||
|
if decoder != nil {
|
||||||
|
return decoder
|
||||||
|
}
|
||||||
|
for _, extension := range ctx.extraExtensions {
|
||||||
|
decoder := extension.CreateDecoder(typ)
|
||||||
|
if decoder != nil {
|
||||||
|
return decoder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
typeName := typ.String()
|
||||||
|
decoder = typeDecoders[typeName]
|
||||||
|
if decoder != nil {
|
||||||
|
return decoder
|
||||||
|
}
|
||||||
|
if typ.Kind() == reflect.Ptr {
|
||||||
|
ptrType := typ.(*reflect2.UnsafePtrType)
|
||||||
|
decoder := typeDecoders[ptrType.Elem().String()]
|
||||||
|
if decoder != nil {
|
||||||
|
return &OptionalDecoder{ptrType.Elem(), decoder}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTypeEncoderFromExtension(ctx *ctx, typ reflect2.Type) ValEncoder {
|
||||||
|
encoder := _getTypeEncoderFromExtension(ctx, typ)
|
||||||
|
if encoder != nil {
|
||||||
|
for _, extension := range extensions {
|
||||||
|
encoder = extension.DecorateEncoder(typ, encoder)
|
||||||
|
}
|
||||||
|
encoder = ctx.encoderExtension.DecorateEncoder(typ, encoder)
|
||||||
|
for _, extension := range ctx.extraExtensions {
|
||||||
|
encoder = extension.DecorateEncoder(typ, encoder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return encoder
|
||||||
|
}
|
||||||
|
|
||||||
|
func _getTypeEncoderFromExtension(ctx *ctx, typ reflect2.Type) ValEncoder {
|
||||||
|
for _, extension := range extensions {
|
||||||
|
encoder := extension.CreateEncoder(typ)
|
||||||
|
if encoder != nil {
|
||||||
|
return encoder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
encoder := ctx.encoderExtension.CreateEncoder(typ)
|
||||||
|
if encoder != nil {
|
||||||
|
return encoder
|
||||||
|
}
|
||||||
|
for _, extension := range ctx.extraExtensions {
|
||||||
|
encoder := extension.CreateEncoder(typ)
|
||||||
|
if encoder != nil {
|
||||||
|
return encoder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
typeName := typ.String()
|
||||||
|
encoder = typeEncoders[typeName]
|
||||||
|
if encoder != nil {
|
||||||
|
return encoder
|
||||||
|
}
|
||||||
|
if typ.Kind() == reflect.Ptr {
|
||||||
|
typePtr := typ.(*reflect2.UnsafePtrType)
|
||||||
|
encoder := typeEncoders[typePtr.Elem().String()]
|
||||||
|
if encoder != nil {
|
||||||
|
return &OptionalEncoder{encoder}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func describeStruct(ctx *ctx, typ reflect2.Type) *StructDescriptor {
|
||||||
|
structType := typ.(*reflect2.UnsafeStructType)
|
||||||
|
embeddedBindings := []*Binding{}
|
||||||
|
bindings := []*Binding{}
|
||||||
|
for i := 0; i < structType.NumField(); i++ {
|
||||||
|
field := structType.Field(i)
|
||||||
|
tag, hastag := field.Tag().Lookup(ctx.getTagKey())
|
||||||
|
if ctx.onlyTaggedField && !hastag && !field.Anonymous() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if tag == "-" || field.Name() == "_" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tagParts := strings.Split(tag, ",")
|
||||||
|
if field.Anonymous() && (tag == "" || tagParts[0] == "") {
|
||||||
|
if field.Type().Kind() == reflect.Struct {
|
||||||
|
structDescriptor := describeStruct(ctx, field.Type())
|
||||||
|
for _, binding := range structDescriptor.Fields {
|
||||||
|
binding.levels = append([]int{i}, binding.levels...)
|
||||||
|
omitempty := binding.Encoder.(*structFieldEncoder).omitempty
|
||||||
|
binding.Encoder = &structFieldEncoder{field, binding.Encoder, omitempty}
|
||||||
|
binding.Decoder = &structFieldDecoder{field, binding.Decoder}
|
||||||
|
embeddedBindings = append(embeddedBindings, binding)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
} else if field.Type().Kind() == reflect.Ptr {
|
||||||
|
ptrType := field.Type().(*reflect2.UnsafePtrType)
|
||||||
|
if ptrType.Elem().Kind() == reflect.Struct {
|
||||||
|
structDescriptor := describeStruct(ctx, ptrType.Elem())
|
||||||
|
for _, binding := range structDescriptor.Fields {
|
||||||
|
binding.levels = append([]int{i}, binding.levels...)
|
||||||
|
omitempty := binding.Encoder.(*structFieldEncoder).omitempty
|
||||||
|
binding.Encoder = &dereferenceEncoder{binding.Encoder}
|
||||||
|
binding.Encoder = &structFieldEncoder{field, binding.Encoder, omitempty}
|
||||||
|
binding.Decoder = &dereferenceDecoder{ptrType.Elem(), binding.Decoder}
|
||||||
|
binding.Decoder = &structFieldDecoder{field, binding.Decoder}
|
||||||
|
embeddedBindings = append(embeddedBindings, binding)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fieldNames := calcFieldNames(field.Name(), tagParts[0], tag)
|
||||||
|
fieldCacheKey := fmt.Sprintf("%s/%s", typ.String(), field.Name())
|
||||||
|
decoder := fieldDecoders[fieldCacheKey]
|
||||||
|
if decoder == nil {
|
||||||
|
decoder = decoderOfType(ctx.append(field.Name()), field.Type())
|
||||||
|
}
|
||||||
|
encoder := fieldEncoders[fieldCacheKey]
|
||||||
|
if encoder == nil {
|
||||||
|
encoder = encoderOfType(ctx.append(field.Name()), field.Type())
|
||||||
|
}
|
||||||
|
binding := &Binding{
|
||||||
|
Field: field,
|
||||||
|
FromNames: fieldNames,
|
||||||
|
ToNames: fieldNames,
|
||||||
|
Decoder: decoder,
|
||||||
|
Encoder: encoder,
|
||||||
|
}
|
||||||
|
binding.levels = []int{i}
|
||||||
|
bindings = append(bindings, binding)
|
||||||
|
}
|
||||||
|
return createStructDescriptor(ctx, typ, bindings, embeddedBindings)
|
||||||
|
}
|
||||||
|
func createStructDescriptor(ctx *ctx, typ reflect2.Type, bindings []*Binding, embeddedBindings []*Binding) *StructDescriptor {
|
||||||
|
structDescriptor := &StructDescriptor{
|
||||||
|
Type: typ,
|
||||||
|
Fields: bindings,
|
||||||
|
}
|
||||||
|
for _, extension := range extensions {
|
||||||
|
extension.UpdateStructDescriptor(structDescriptor)
|
||||||
|
}
|
||||||
|
ctx.encoderExtension.UpdateStructDescriptor(structDescriptor)
|
||||||
|
ctx.decoderExtension.UpdateStructDescriptor(structDescriptor)
|
||||||
|
for _, extension := range ctx.extraExtensions {
|
||||||
|
extension.UpdateStructDescriptor(structDescriptor)
|
||||||
|
}
|
||||||
|
processTags(structDescriptor, ctx.frozenConfig)
|
||||||
|
// merge normal & embedded bindings & sort with original order
|
||||||
|
allBindings := sortableBindings(append(embeddedBindings, structDescriptor.Fields...))
|
||||||
|
sort.Sort(allBindings)
|
||||||
|
structDescriptor.Fields = allBindings
|
||||||
|
return structDescriptor
|
||||||
|
}
|
||||||
|
|
||||||
|
type sortableBindings []*Binding
|
||||||
|
|
||||||
|
func (bindings sortableBindings) Len() int {
|
||||||
|
return len(bindings)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bindings sortableBindings) Less(i, j int) bool {
|
||||||
|
left := bindings[i].levels
|
||||||
|
right := bindings[j].levels
|
||||||
|
k := 0
|
||||||
|
for {
|
||||||
|
if left[k] < right[k] {
|
||||||
|
return true
|
||||||
|
} else if left[k] > right[k] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
k++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bindings sortableBindings) Swap(i, j int) {
|
||||||
|
bindings[i], bindings[j] = bindings[j], bindings[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
func processTags(structDescriptor *StructDescriptor, cfg *frozenConfig) {
|
||||||
|
for _, binding := range structDescriptor.Fields {
|
||||||
|
shouldOmitEmpty := false
|
||||||
|
tagParts := strings.Split(binding.Field.Tag().Get(cfg.getTagKey()), ",")
|
||||||
|
for _, tagPart := range tagParts[1:] {
|
||||||
|
if tagPart == "omitempty" {
|
||||||
|
shouldOmitEmpty = true
|
||||||
|
} else if tagPart == "string" {
|
||||||
|
if binding.Field.Type().Kind() == reflect.String {
|
||||||
|
binding.Decoder = &stringModeStringDecoder{binding.Decoder, cfg}
|
||||||
|
binding.Encoder = &stringModeStringEncoder{binding.Encoder, cfg}
|
||||||
|
} else {
|
||||||
|
binding.Decoder = &stringModeNumberDecoder{binding.Decoder}
|
||||||
|
binding.Encoder = &stringModeNumberEncoder{binding.Encoder}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
binding.Decoder = &structFieldDecoder{binding.Field, binding.Decoder}
|
||||||
|
binding.Encoder = &structFieldEncoder{binding.Field, binding.Encoder, shouldOmitEmpty}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func calcFieldNames(originalFieldName string, tagProvidedFieldName string, wholeTag string) []string {
|
||||||
|
// ignore?
|
||||||
|
if wholeTag == "-" {
|
||||||
|
return []string{}
|
||||||
|
}
|
||||||
|
// rename?
|
||||||
|
var fieldNames []string
|
||||||
|
if tagProvidedFieldName == "" {
|
||||||
|
fieldNames = []string{originalFieldName}
|
||||||
|
} else {
|
||||||
|
fieldNames = []string{tagProvidedFieldName}
|
||||||
|
}
|
||||||
|
// private?
|
||||||
|
isNotExported := unicode.IsLower(rune(originalFieldName[0])) || originalFieldName[0] == '_'
|
||||||
|
if isNotExported {
|
||||||
|
fieldNames = []string{}
|
||||||
|
}
|
||||||
|
return fieldNames
|
||||||
|
}
|
||||||
112
vendor/github.com/json-iterator/go/reflect_json_number.go
generated
vendored
Normal file
112
vendor/github.com/json-iterator/go/reflect_json_number.go
generated
vendored
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
package jsoniter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/modern-go/reflect2"
|
||||||
|
"strconv"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Number string
|
||||||
|
|
||||||
|
// String returns the literal text of the number.
|
||||||
|
func (n Number) String() string { return string(n) }
|
||||||
|
|
||||||
|
// Float64 returns the number as a float64.
|
||||||
|
func (n Number) Float64() (float64, error) {
|
||||||
|
return strconv.ParseFloat(string(n), 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64 returns the number as an int64.
|
||||||
|
func (n Number) Int64() (int64, error) {
|
||||||
|
return strconv.ParseInt(string(n), 10, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
func CastJsonNumber(val interface{}) (string, bool) {
|
||||||
|
switch typedVal := val.(type) {
|
||||||
|
case json.Number:
|
||||||
|
return string(typedVal), true
|
||||||
|
case Number:
|
||||||
|
return string(typedVal), true
|
||||||
|
}
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
|
||||||
|
var jsonNumberType = reflect2.TypeOfPtr((*json.Number)(nil)).Elem()
|
||||||
|
var jsoniterNumberType = reflect2.TypeOfPtr((*Number)(nil)).Elem()
|
||||||
|
|
||||||
|
func createDecoderOfJsonNumber(ctx *ctx, typ reflect2.Type) ValDecoder {
|
||||||
|
if typ.AssignableTo(jsonNumberType) {
|
||||||
|
return &jsonNumberCodec{}
|
||||||
|
}
|
||||||
|
if typ.AssignableTo(jsoniterNumberType) {
|
||||||
|
return &jsoniterNumberCodec{}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createEncoderOfJsonNumber(ctx *ctx, typ reflect2.Type) ValEncoder {
|
||||||
|
if typ.AssignableTo(jsonNumberType) {
|
||||||
|
return &jsonNumberCodec{}
|
||||||
|
}
|
||||||
|
if typ.AssignableTo(jsoniterNumberType) {
|
||||||
|
return &jsoniterNumberCodec{}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type jsonNumberCodec struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *jsonNumberCodec) Decode(ptr unsafe.Pointer, iter *Iterator) {
|
||||||
|
switch iter.WhatIsNext() {
|
||||||
|
case StringValue:
|
||||||
|
*((*json.Number)(ptr)) = json.Number(iter.ReadString())
|
||||||
|
case NilValue:
|
||||||
|
iter.skipFourBytes('n', 'u', 'l', 'l')
|
||||||
|
*((*json.Number)(ptr)) = ""
|
||||||
|
default:
|
||||||
|
*((*json.Number)(ptr)) = json.Number([]byte(iter.readNumberAsString()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *jsonNumberCodec) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
number := *((*json.Number)(ptr))
|
||||||
|
if len(number) == 0 {
|
||||||
|
stream.writeByte('0')
|
||||||
|
} else {
|
||||||
|
stream.WriteRaw(string(number))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *jsonNumberCodec) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
return len(*((*json.Number)(ptr))) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type jsoniterNumberCodec struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *jsoniterNumberCodec) Decode(ptr unsafe.Pointer, iter *Iterator) {
|
||||||
|
switch iter.WhatIsNext() {
|
||||||
|
case StringValue:
|
||||||
|
*((*Number)(ptr)) = Number(iter.ReadString())
|
||||||
|
case NilValue:
|
||||||
|
iter.skipFourBytes('n', 'u', 'l', 'l')
|
||||||
|
*((*Number)(ptr)) = ""
|
||||||
|
default:
|
||||||
|
*((*Number)(ptr)) = Number([]byte(iter.readNumberAsString()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *jsoniterNumberCodec) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
number := *((*Number)(ptr))
|
||||||
|
if len(number) == 0 {
|
||||||
|
stream.writeByte('0')
|
||||||
|
} else {
|
||||||
|
stream.WriteRaw(string(number))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *jsoniterNumberCodec) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
return len(*((*Number)(ptr))) == 0
|
||||||
|
}
|
||||||
76
vendor/github.com/json-iterator/go/reflect_json_raw_message.go
generated
vendored
Normal file
76
vendor/github.com/json-iterator/go/reflect_json_raw_message.go
generated
vendored
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
package jsoniter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"github.com/modern-go/reflect2"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
var jsonRawMessageType = reflect2.TypeOfPtr((*json.RawMessage)(nil)).Elem()
|
||||||
|
var jsoniterRawMessageType = reflect2.TypeOfPtr((*RawMessage)(nil)).Elem()
|
||||||
|
|
||||||
|
func createEncoderOfJsonRawMessage(ctx *ctx, typ reflect2.Type) ValEncoder {
|
||||||
|
if typ == jsonRawMessageType {
|
||||||
|
return &jsonRawMessageCodec{}
|
||||||
|
}
|
||||||
|
if typ == jsoniterRawMessageType {
|
||||||
|
return &jsoniterRawMessageCodec{}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createDecoderOfJsonRawMessage(ctx *ctx, typ reflect2.Type) ValDecoder {
|
||||||
|
if typ == jsonRawMessageType {
|
||||||
|
return &jsonRawMessageCodec{}
|
||||||
|
}
|
||||||
|
if typ == jsoniterRawMessageType {
|
||||||
|
return &jsoniterRawMessageCodec{}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type jsonRawMessageCodec struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *jsonRawMessageCodec) Decode(ptr unsafe.Pointer, iter *Iterator) {
|
||||||
|
if iter.ReadNil() {
|
||||||
|
*((*json.RawMessage)(ptr)) = nil
|
||||||
|
} else {
|
||||||
|
*((*json.RawMessage)(ptr)) = iter.SkipAndReturnBytes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *jsonRawMessageCodec) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
if *((*json.RawMessage)(ptr)) == nil {
|
||||||
|
stream.WriteNil()
|
||||||
|
} else {
|
||||||
|
stream.WriteRaw(string(*((*json.RawMessage)(ptr))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *jsonRawMessageCodec) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
return len(*((*json.RawMessage)(ptr))) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type jsoniterRawMessageCodec struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *jsoniterRawMessageCodec) Decode(ptr unsafe.Pointer, iter *Iterator) {
|
||||||
|
if iter.ReadNil() {
|
||||||
|
*((*RawMessage)(ptr)) = nil
|
||||||
|
} else {
|
||||||
|
*((*RawMessage)(ptr)) = iter.SkipAndReturnBytes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *jsoniterRawMessageCodec) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
if *((*RawMessage)(ptr)) == nil {
|
||||||
|
stream.WriteNil()
|
||||||
|
} else {
|
||||||
|
stream.WriteRaw(string(*((*RawMessage)(ptr))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *jsoniterRawMessageCodec) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
return len(*((*RawMessage)(ptr))) == 0
|
||||||
|
}
|
||||||
346
vendor/github.com/json-iterator/go/reflect_map.go
generated
vendored
Normal file
346
vendor/github.com/json-iterator/go/reflect_map.go
generated
vendored
Normal file
|
|
@ -0,0 +1,346 @@
|
||||||
|
package jsoniter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/modern-go/reflect2"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func decoderOfMap(ctx *ctx, typ reflect2.Type) ValDecoder {
|
||||||
|
mapType := typ.(*reflect2.UnsafeMapType)
|
||||||
|
keyDecoder := decoderOfMapKey(ctx.append("[mapKey]"), mapType.Key())
|
||||||
|
elemDecoder := decoderOfType(ctx.append("[mapElem]"), mapType.Elem())
|
||||||
|
return &mapDecoder{
|
||||||
|
mapType: mapType,
|
||||||
|
keyType: mapType.Key(),
|
||||||
|
elemType: mapType.Elem(),
|
||||||
|
keyDecoder: keyDecoder,
|
||||||
|
elemDecoder: elemDecoder,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func encoderOfMap(ctx *ctx, typ reflect2.Type) ValEncoder {
|
||||||
|
mapType := typ.(*reflect2.UnsafeMapType)
|
||||||
|
if ctx.sortMapKeys {
|
||||||
|
return &sortKeysMapEncoder{
|
||||||
|
mapType: mapType,
|
||||||
|
keyEncoder: encoderOfMapKey(ctx.append("[mapKey]"), mapType.Key()),
|
||||||
|
elemEncoder: encoderOfType(ctx.append("[mapElem]"), mapType.Elem()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &mapEncoder{
|
||||||
|
mapType: mapType,
|
||||||
|
keyEncoder: encoderOfMapKey(ctx.append("[mapKey]"), mapType.Key()),
|
||||||
|
elemEncoder: encoderOfType(ctx.append("[mapElem]"), mapType.Elem()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func decoderOfMapKey(ctx *ctx, typ reflect2.Type) ValDecoder {
|
||||||
|
decoder := ctx.decoderExtension.CreateMapKeyDecoder(typ)
|
||||||
|
if decoder != nil {
|
||||||
|
return decoder
|
||||||
|
}
|
||||||
|
for _, extension := range ctx.extraExtensions {
|
||||||
|
decoder := extension.CreateMapKeyDecoder(typ)
|
||||||
|
if decoder != nil {
|
||||||
|
return decoder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ptrType := reflect2.PtrTo(typ)
|
||||||
|
if ptrType.Implements(unmarshalerType) {
|
||||||
|
return &referenceDecoder{
|
||||||
|
&unmarshalerDecoder{
|
||||||
|
valType: ptrType,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if typ.Implements(unmarshalerType) {
|
||||||
|
return &unmarshalerDecoder{
|
||||||
|
valType: typ,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ptrType.Implements(textUnmarshalerType) {
|
||||||
|
return &referenceDecoder{
|
||||||
|
&textUnmarshalerDecoder{
|
||||||
|
valType: ptrType,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if typ.Implements(textUnmarshalerType) {
|
||||||
|
return &textUnmarshalerDecoder{
|
||||||
|
valType: typ,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch typ.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
return decoderOfType(ctx, reflect2.DefaultTypeOfKind(reflect.String))
|
||||||
|
case reflect.Bool,
|
||||||
|
reflect.Uint8, reflect.Int8,
|
||||||
|
reflect.Uint16, reflect.Int16,
|
||||||
|
reflect.Uint32, reflect.Int32,
|
||||||
|
reflect.Uint64, reflect.Int64,
|
||||||
|
reflect.Uint, reflect.Int,
|
||||||
|
reflect.Float32, reflect.Float64,
|
||||||
|
reflect.Uintptr:
|
||||||
|
typ = reflect2.DefaultTypeOfKind(typ.Kind())
|
||||||
|
return &numericMapKeyDecoder{decoderOfType(ctx, typ)}
|
||||||
|
default:
|
||||||
|
return &lazyErrorDecoder{err: fmt.Errorf("unsupported map key type: %v", typ)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func encoderOfMapKey(ctx *ctx, typ reflect2.Type) ValEncoder {
|
||||||
|
encoder := ctx.encoderExtension.CreateMapKeyEncoder(typ)
|
||||||
|
if encoder != nil {
|
||||||
|
return encoder
|
||||||
|
}
|
||||||
|
for _, extension := range ctx.extraExtensions {
|
||||||
|
encoder := extension.CreateMapKeyEncoder(typ)
|
||||||
|
if encoder != nil {
|
||||||
|
return encoder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if typ == textMarshalerType {
|
||||||
|
return &directTextMarshalerEncoder{
|
||||||
|
stringEncoder: ctx.EncoderOf(reflect2.TypeOf("")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if typ.Implements(textMarshalerType) {
|
||||||
|
return &textMarshalerEncoder{
|
||||||
|
valType: typ,
|
||||||
|
stringEncoder: ctx.EncoderOf(reflect2.TypeOf("")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch typ.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
return encoderOfType(ctx, reflect2.DefaultTypeOfKind(reflect.String))
|
||||||
|
case reflect.Bool,
|
||||||
|
reflect.Uint8, reflect.Int8,
|
||||||
|
reflect.Uint16, reflect.Int16,
|
||||||
|
reflect.Uint32, reflect.Int32,
|
||||||
|
reflect.Uint64, reflect.Int64,
|
||||||
|
reflect.Uint, reflect.Int,
|
||||||
|
reflect.Float32, reflect.Float64,
|
||||||
|
reflect.Uintptr:
|
||||||
|
typ = reflect2.DefaultTypeOfKind(typ.Kind())
|
||||||
|
return &numericMapKeyEncoder{encoderOfType(ctx, typ)}
|
||||||
|
default:
|
||||||
|
if typ.Kind() == reflect.Interface {
|
||||||
|
return &dynamicMapKeyEncoder{ctx, typ}
|
||||||
|
}
|
||||||
|
return &lazyErrorEncoder{err: fmt.Errorf("unsupported map key type: %v", typ)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mapDecoder struct {
|
||||||
|
mapType *reflect2.UnsafeMapType
|
||||||
|
keyType reflect2.Type
|
||||||
|
elemType reflect2.Type
|
||||||
|
keyDecoder ValDecoder
|
||||||
|
elemDecoder ValDecoder
|
||||||
|
}
|
||||||
|
|
||||||
|
func (decoder *mapDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
|
||||||
|
mapType := decoder.mapType
|
||||||
|
c := iter.nextToken()
|
||||||
|
if c == 'n' {
|
||||||
|
iter.skipThreeBytes('u', 'l', 'l')
|
||||||
|
*(*unsafe.Pointer)(ptr) = nil
|
||||||
|
mapType.UnsafeSet(ptr, mapType.UnsafeNew())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if mapType.UnsafeIsNil(ptr) {
|
||||||
|
mapType.UnsafeSet(ptr, mapType.UnsafeMakeMap(0))
|
||||||
|
}
|
||||||
|
if c != '{' {
|
||||||
|
iter.ReportError("ReadMapCB", `expect { or n, but found `+string([]byte{c}))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c = iter.nextToken()
|
||||||
|
if c == '}' {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
iter.unreadByte()
|
||||||
|
key := decoder.keyType.UnsafeNew()
|
||||||
|
decoder.keyDecoder.Decode(key, iter)
|
||||||
|
c = iter.nextToken()
|
||||||
|
if c != ':' {
|
||||||
|
iter.ReportError("ReadMapCB", "expect : after object field, but found "+string([]byte{c}))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
elem := decoder.elemType.UnsafeNew()
|
||||||
|
decoder.elemDecoder.Decode(elem, iter)
|
||||||
|
decoder.mapType.UnsafeSetIndex(ptr, key, elem)
|
||||||
|
for c = iter.nextToken(); c == ','; c = iter.nextToken() {
|
||||||
|
key := decoder.keyType.UnsafeNew()
|
||||||
|
decoder.keyDecoder.Decode(key, iter)
|
||||||
|
c = iter.nextToken()
|
||||||
|
if c != ':' {
|
||||||
|
iter.ReportError("ReadMapCB", "expect : after object field, but found "+string([]byte{c}))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
elem := decoder.elemType.UnsafeNew()
|
||||||
|
decoder.elemDecoder.Decode(elem, iter)
|
||||||
|
decoder.mapType.UnsafeSetIndex(ptr, key, elem)
|
||||||
|
}
|
||||||
|
if c != '}' {
|
||||||
|
iter.ReportError("ReadMapCB", `expect }, but found `+string([]byte{c}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type numericMapKeyDecoder struct {
|
||||||
|
decoder ValDecoder
|
||||||
|
}
|
||||||
|
|
||||||
|
func (decoder *numericMapKeyDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
|
||||||
|
c := iter.nextToken()
|
||||||
|
if c != '"' {
|
||||||
|
iter.ReportError("ReadMapCB", `expect ", but found `+string([]byte{c}))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
decoder.decoder.Decode(ptr, iter)
|
||||||
|
c = iter.nextToken()
|
||||||
|
if c != '"' {
|
||||||
|
iter.ReportError("ReadMapCB", `expect ", but found `+string([]byte{c}))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type numericMapKeyEncoder struct {
|
||||||
|
encoder ValEncoder
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *numericMapKeyEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
stream.writeByte('"')
|
||||||
|
encoder.encoder.Encode(ptr, stream)
|
||||||
|
stream.writeByte('"')
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *numericMapKeyEncoder) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type dynamicMapKeyEncoder struct {
|
||||||
|
ctx *ctx
|
||||||
|
valType reflect2.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *dynamicMapKeyEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
obj := encoder.valType.UnsafeIndirect(ptr)
|
||||||
|
encoderOfMapKey(encoder.ctx, reflect2.TypeOf(obj)).Encode(reflect2.PtrOf(obj), stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *dynamicMapKeyEncoder) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
obj := encoder.valType.UnsafeIndirect(ptr)
|
||||||
|
return encoderOfMapKey(encoder.ctx, reflect2.TypeOf(obj)).IsEmpty(reflect2.PtrOf(obj))
|
||||||
|
}
|
||||||
|
|
||||||
|
type mapEncoder struct {
|
||||||
|
mapType *reflect2.UnsafeMapType
|
||||||
|
keyEncoder ValEncoder
|
||||||
|
elemEncoder ValEncoder
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *mapEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
if *(*unsafe.Pointer)(ptr) == nil {
|
||||||
|
stream.WriteNil()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
stream.WriteObjectStart()
|
||||||
|
iter := encoder.mapType.UnsafeIterate(ptr)
|
||||||
|
for i := 0; iter.HasNext(); i++ {
|
||||||
|
if i != 0 {
|
||||||
|
stream.WriteMore()
|
||||||
|
}
|
||||||
|
key, elem := iter.UnsafeNext()
|
||||||
|
encoder.keyEncoder.Encode(key, stream)
|
||||||
|
if stream.indention > 0 {
|
||||||
|
stream.writeTwoBytes(byte(':'), byte(' '))
|
||||||
|
} else {
|
||||||
|
stream.writeByte(':')
|
||||||
|
}
|
||||||
|
encoder.elemEncoder.Encode(elem, stream)
|
||||||
|
}
|
||||||
|
stream.WriteObjectEnd()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *mapEncoder) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
iter := encoder.mapType.UnsafeIterate(ptr)
|
||||||
|
return !iter.HasNext()
|
||||||
|
}
|
||||||
|
|
||||||
|
type sortKeysMapEncoder struct {
|
||||||
|
mapType *reflect2.UnsafeMapType
|
||||||
|
keyEncoder ValEncoder
|
||||||
|
elemEncoder ValEncoder
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *sortKeysMapEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
if *(*unsafe.Pointer)(ptr) == nil {
|
||||||
|
stream.WriteNil()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
stream.WriteObjectStart()
|
||||||
|
mapIter := encoder.mapType.UnsafeIterate(ptr)
|
||||||
|
subStream := stream.cfg.BorrowStream(nil)
|
||||||
|
subStream.Attachment = stream.Attachment
|
||||||
|
subIter := stream.cfg.BorrowIterator(nil)
|
||||||
|
keyValues := encodedKeyValues{}
|
||||||
|
for mapIter.HasNext() {
|
||||||
|
key, elem := mapIter.UnsafeNext()
|
||||||
|
subStreamIndex := subStream.Buffered()
|
||||||
|
encoder.keyEncoder.Encode(key, subStream)
|
||||||
|
if subStream.Error != nil && subStream.Error != io.EOF && stream.Error == nil {
|
||||||
|
stream.Error = subStream.Error
|
||||||
|
}
|
||||||
|
encodedKey := subStream.Buffer()[subStreamIndex:]
|
||||||
|
subIter.ResetBytes(encodedKey)
|
||||||
|
decodedKey := subIter.ReadString()
|
||||||
|
if stream.indention > 0 {
|
||||||
|
subStream.writeTwoBytes(byte(':'), byte(' '))
|
||||||
|
} else {
|
||||||
|
subStream.writeByte(':')
|
||||||
|
}
|
||||||
|
encoder.elemEncoder.Encode(elem, subStream)
|
||||||
|
keyValues = append(keyValues, encodedKV{
|
||||||
|
key: decodedKey,
|
||||||
|
keyValue: subStream.Buffer()[subStreamIndex:],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
sort.Sort(keyValues)
|
||||||
|
for i, keyValue := range keyValues {
|
||||||
|
if i != 0 {
|
||||||
|
stream.WriteMore()
|
||||||
|
}
|
||||||
|
stream.Write(keyValue.keyValue)
|
||||||
|
}
|
||||||
|
if subStream.Error != nil && stream.Error == nil {
|
||||||
|
stream.Error = subStream.Error
|
||||||
|
}
|
||||||
|
stream.WriteObjectEnd()
|
||||||
|
stream.cfg.ReturnStream(subStream)
|
||||||
|
stream.cfg.ReturnIterator(subIter)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *sortKeysMapEncoder) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
iter := encoder.mapType.UnsafeIterate(ptr)
|
||||||
|
return !iter.HasNext()
|
||||||
|
}
|
||||||
|
|
||||||
|
type encodedKeyValues []encodedKV
|
||||||
|
|
||||||
|
type encodedKV struct {
|
||||||
|
key string
|
||||||
|
keyValue []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (sv encodedKeyValues) Len() int { return len(sv) }
|
||||||
|
func (sv encodedKeyValues) Swap(i, j int) { sv[i], sv[j] = sv[j], sv[i] }
|
||||||
|
func (sv encodedKeyValues) Less(i, j int) bool { return sv[i].key < sv[j].key }
|
||||||
225
vendor/github.com/json-iterator/go/reflect_marshaler.go
generated
vendored
Normal file
225
vendor/github.com/json-iterator/go/reflect_marshaler.go
generated
vendored
Normal file
|
|
@ -0,0 +1,225 @@
|
||||||
|
package jsoniter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding"
|
||||||
|
"encoding/json"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/modern-go/reflect2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var marshalerType = reflect2.TypeOfPtr((*json.Marshaler)(nil)).Elem()
|
||||||
|
var unmarshalerType = reflect2.TypeOfPtr((*json.Unmarshaler)(nil)).Elem()
|
||||||
|
var textMarshalerType = reflect2.TypeOfPtr((*encoding.TextMarshaler)(nil)).Elem()
|
||||||
|
var textUnmarshalerType = reflect2.TypeOfPtr((*encoding.TextUnmarshaler)(nil)).Elem()
|
||||||
|
|
||||||
|
func createDecoderOfMarshaler(ctx *ctx, typ reflect2.Type) ValDecoder {
|
||||||
|
ptrType := reflect2.PtrTo(typ)
|
||||||
|
if ptrType.Implements(unmarshalerType) {
|
||||||
|
return &referenceDecoder{
|
||||||
|
&unmarshalerDecoder{ptrType},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ptrType.Implements(textUnmarshalerType) {
|
||||||
|
return &referenceDecoder{
|
||||||
|
&textUnmarshalerDecoder{ptrType},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createEncoderOfMarshaler(ctx *ctx, typ reflect2.Type) ValEncoder {
|
||||||
|
if typ == marshalerType {
|
||||||
|
checkIsEmpty := createCheckIsEmpty(ctx, typ)
|
||||||
|
var encoder ValEncoder = &directMarshalerEncoder{
|
||||||
|
checkIsEmpty: checkIsEmpty,
|
||||||
|
}
|
||||||
|
return encoder
|
||||||
|
}
|
||||||
|
if typ.Implements(marshalerType) {
|
||||||
|
checkIsEmpty := createCheckIsEmpty(ctx, typ)
|
||||||
|
var encoder ValEncoder = &marshalerEncoder{
|
||||||
|
valType: typ,
|
||||||
|
checkIsEmpty: checkIsEmpty,
|
||||||
|
}
|
||||||
|
return encoder
|
||||||
|
}
|
||||||
|
ptrType := reflect2.PtrTo(typ)
|
||||||
|
if ctx.prefix != "" && ptrType.Implements(marshalerType) {
|
||||||
|
checkIsEmpty := createCheckIsEmpty(ctx, ptrType)
|
||||||
|
var encoder ValEncoder = &marshalerEncoder{
|
||||||
|
valType: ptrType,
|
||||||
|
checkIsEmpty: checkIsEmpty,
|
||||||
|
}
|
||||||
|
return &referenceEncoder{encoder}
|
||||||
|
}
|
||||||
|
if typ == textMarshalerType {
|
||||||
|
checkIsEmpty := createCheckIsEmpty(ctx, typ)
|
||||||
|
var encoder ValEncoder = &directTextMarshalerEncoder{
|
||||||
|
checkIsEmpty: checkIsEmpty,
|
||||||
|
stringEncoder: ctx.EncoderOf(reflect2.TypeOf("")),
|
||||||
|
}
|
||||||
|
return encoder
|
||||||
|
}
|
||||||
|
if typ.Implements(textMarshalerType) {
|
||||||
|
checkIsEmpty := createCheckIsEmpty(ctx, typ)
|
||||||
|
var encoder ValEncoder = &textMarshalerEncoder{
|
||||||
|
valType: typ,
|
||||||
|
stringEncoder: ctx.EncoderOf(reflect2.TypeOf("")),
|
||||||
|
checkIsEmpty: checkIsEmpty,
|
||||||
|
}
|
||||||
|
return encoder
|
||||||
|
}
|
||||||
|
// if prefix is empty, the type is the root type
|
||||||
|
if ctx.prefix != "" && ptrType.Implements(textMarshalerType) {
|
||||||
|
checkIsEmpty := createCheckIsEmpty(ctx, ptrType)
|
||||||
|
var encoder ValEncoder = &textMarshalerEncoder{
|
||||||
|
valType: ptrType,
|
||||||
|
stringEncoder: ctx.EncoderOf(reflect2.TypeOf("")),
|
||||||
|
checkIsEmpty: checkIsEmpty,
|
||||||
|
}
|
||||||
|
return &referenceEncoder{encoder}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type marshalerEncoder struct {
|
||||||
|
checkIsEmpty checkIsEmpty
|
||||||
|
valType reflect2.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *marshalerEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
obj := encoder.valType.UnsafeIndirect(ptr)
|
||||||
|
if encoder.valType.IsNullable() && reflect2.IsNil(obj) {
|
||||||
|
stream.WriteNil()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
marshaler := obj.(json.Marshaler)
|
||||||
|
bytes, err := marshaler.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
stream.Error = err
|
||||||
|
} else {
|
||||||
|
// html escape was already done by jsoniter
|
||||||
|
// but the extra '\n' should be trimed
|
||||||
|
l := len(bytes)
|
||||||
|
if l > 0 && bytes[l-1] == '\n' {
|
||||||
|
bytes = bytes[:l-1]
|
||||||
|
}
|
||||||
|
stream.Write(bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *marshalerEncoder) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
return encoder.checkIsEmpty.IsEmpty(ptr)
|
||||||
|
}
|
||||||
|
|
||||||
|
type directMarshalerEncoder struct {
|
||||||
|
checkIsEmpty checkIsEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *directMarshalerEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
marshaler := *(*json.Marshaler)(ptr)
|
||||||
|
if marshaler == nil {
|
||||||
|
stream.WriteNil()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bytes, err := marshaler.MarshalJSON()
|
||||||
|
if err != nil {
|
||||||
|
stream.Error = err
|
||||||
|
} else {
|
||||||
|
stream.Write(bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *directMarshalerEncoder) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
return encoder.checkIsEmpty.IsEmpty(ptr)
|
||||||
|
}
|
||||||
|
|
||||||
|
type textMarshalerEncoder struct {
|
||||||
|
valType reflect2.Type
|
||||||
|
stringEncoder ValEncoder
|
||||||
|
checkIsEmpty checkIsEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *textMarshalerEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
obj := encoder.valType.UnsafeIndirect(ptr)
|
||||||
|
if encoder.valType.IsNullable() && reflect2.IsNil(obj) {
|
||||||
|
stream.WriteNil()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
marshaler := (obj).(encoding.TextMarshaler)
|
||||||
|
bytes, err := marshaler.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
stream.Error = err
|
||||||
|
} else {
|
||||||
|
str := string(bytes)
|
||||||
|
encoder.stringEncoder.Encode(unsafe.Pointer(&str), stream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *textMarshalerEncoder) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
return encoder.checkIsEmpty.IsEmpty(ptr)
|
||||||
|
}
|
||||||
|
|
||||||
|
type directTextMarshalerEncoder struct {
|
||||||
|
stringEncoder ValEncoder
|
||||||
|
checkIsEmpty checkIsEmpty
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *directTextMarshalerEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
marshaler := *(*encoding.TextMarshaler)(ptr)
|
||||||
|
if marshaler == nil {
|
||||||
|
stream.WriteNil()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
bytes, err := marshaler.MarshalText()
|
||||||
|
if err != nil {
|
||||||
|
stream.Error = err
|
||||||
|
} else {
|
||||||
|
str := string(bytes)
|
||||||
|
encoder.stringEncoder.Encode(unsafe.Pointer(&str), stream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *directTextMarshalerEncoder) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
return encoder.checkIsEmpty.IsEmpty(ptr)
|
||||||
|
}
|
||||||
|
|
||||||
|
type unmarshalerDecoder struct {
|
||||||
|
valType reflect2.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
func (decoder *unmarshalerDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
|
||||||
|
valType := decoder.valType
|
||||||
|
obj := valType.UnsafeIndirect(ptr)
|
||||||
|
unmarshaler := obj.(json.Unmarshaler)
|
||||||
|
iter.nextToken()
|
||||||
|
iter.unreadByte() // skip spaces
|
||||||
|
bytes := iter.SkipAndReturnBytes()
|
||||||
|
err := unmarshaler.UnmarshalJSON(bytes)
|
||||||
|
if err != nil {
|
||||||
|
iter.ReportError("unmarshalerDecoder", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type textUnmarshalerDecoder struct {
|
||||||
|
valType reflect2.Type
|
||||||
|
}
|
||||||
|
|
||||||
|
func (decoder *textUnmarshalerDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
|
||||||
|
valType := decoder.valType
|
||||||
|
obj := valType.UnsafeIndirect(ptr)
|
||||||
|
if reflect2.IsNil(obj) {
|
||||||
|
ptrType := valType.(*reflect2.UnsafePtrType)
|
||||||
|
elemType := ptrType.Elem()
|
||||||
|
elem := elemType.UnsafeNew()
|
||||||
|
ptrType.UnsafeSet(ptr, unsafe.Pointer(&elem))
|
||||||
|
obj = valType.UnsafeIndirect(ptr)
|
||||||
|
}
|
||||||
|
unmarshaler := (obj).(encoding.TextUnmarshaler)
|
||||||
|
str := iter.ReadString()
|
||||||
|
err := unmarshaler.UnmarshalText([]byte(str))
|
||||||
|
if err != nil {
|
||||||
|
iter.ReportError("textUnmarshalerDecoder", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
453
vendor/github.com/json-iterator/go/reflect_native.go
generated
vendored
Normal file
453
vendor/github.com/json-iterator/go/reflect_native.go
generated
vendored
Normal file
|
|
@ -0,0 +1,453 @@
|
||||||
|
package jsoniter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"unsafe"
|
||||||
|
|
||||||
|
"github.com/modern-go/reflect2"
|
||||||
|
)
|
||||||
|
|
||||||
|
const ptrSize = 32 << uintptr(^uintptr(0)>>63)
|
||||||
|
|
||||||
|
func createEncoderOfNative(ctx *ctx, typ reflect2.Type) ValEncoder {
|
||||||
|
if typ.Kind() == reflect.Slice && typ.(reflect2.SliceType).Elem().Kind() == reflect.Uint8 {
|
||||||
|
sliceDecoder := decoderOfSlice(ctx, typ)
|
||||||
|
return &base64Codec{sliceDecoder: sliceDecoder}
|
||||||
|
}
|
||||||
|
typeName := typ.String()
|
||||||
|
kind := typ.Kind()
|
||||||
|
switch kind {
|
||||||
|
case reflect.String:
|
||||||
|
if typeName != "string" {
|
||||||
|
return encoderOfType(ctx, reflect2.TypeOfPtr((*string)(nil)).Elem())
|
||||||
|
}
|
||||||
|
return &stringCodec{}
|
||||||
|
case reflect.Int:
|
||||||
|
if typeName != "int" {
|
||||||
|
return encoderOfType(ctx, reflect2.TypeOfPtr((*int)(nil)).Elem())
|
||||||
|
}
|
||||||
|
if strconv.IntSize == 32 {
|
||||||
|
return &int32Codec{}
|
||||||
|
}
|
||||||
|
return &int64Codec{}
|
||||||
|
case reflect.Int8:
|
||||||
|
if typeName != "int8" {
|
||||||
|
return encoderOfType(ctx, reflect2.TypeOfPtr((*int8)(nil)).Elem())
|
||||||
|
}
|
||||||
|
return &int8Codec{}
|
||||||
|
case reflect.Int16:
|
||||||
|
if typeName != "int16" {
|
||||||
|
return encoderOfType(ctx, reflect2.TypeOfPtr((*int16)(nil)).Elem())
|
||||||
|
}
|
||||||
|
return &int16Codec{}
|
||||||
|
case reflect.Int32:
|
||||||
|
if typeName != "int32" {
|
||||||
|
return encoderOfType(ctx, reflect2.TypeOfPtr((*int32)(nil)).Elem())
|
||||||
|
}
|
||||||
|
return &int32Codec{}
|
||||||
|
case reflect.Int64:
|
||||||
|
if typeName != "int64" {
|
||||||
|
return encoderOfType(ctx, reflect2.TypeOfPtr((*int64)(nil)).Elem())
|
||||||
|
}
|
||||||
|
return &int64Codec{}
|
||||||
|
case reflect.Uint:
|
||||||
|
if typeName != "uint" {
|
||||||
|
return encoderOfType(ctx, reflect2.TypeOfPtr((*uint)(nil)).Elem())
|
||||||
|
}
|
||||||
|
if strconv.IntSize == 32 {
|
||||||
|
return &uint32Codec{}
|
||||||
|
}
|
||||||
|
return &uint64Codec{}
|
||||||
|
case reflect.Uint8:
|
||||||
|
if typeName != "uint8" {
|
||||||
|
return encoderOfType(ctx, reflect2.TypeOfPtr((*uint8)(nil)).Elem())
|
||||||
|
}
|
||||||
|
return &uint8Codec{}
|
||||||
|
case reflect.Uint16:
|
||||||
|
if typeName != "uint16" {
|
||||||
|
return encoderOfType(ctx, reflect2.TypeOfPtr((*uint16)(nil)).Elem())
|
||||||
|
}
|
||||||
|
return &uint16Codec{}
|
||||||
|
case reflect.Uint32:
|
||||||
|
if typeName != "uint32" {
|
||||||
|
return encoderOfType(ctx, reflect2.TypeOfPtr((*uint32)(nil)).Elem())
|
||||||
|
}
|
||||||
|
return &uint32Codec{}
|
||||||
|
case reflect.Uintptr:
|
||||||
|
if typeName != "uintptr" {
|
||||||
|
return encoderOfType(ctx, reflect2.TypeOfPtr((*uintptr)(nil)).Elem())
|
||||||
|
}
|
||||||
|
if ptrSize == 32 {
|
||||||
|
return &uint32Codec{}
|
||||||
|
}
|
||||||
|
return &uint64Codec{}
|
||||||
|
case reflect.Uint64:
|
||||||
|
if typeName != "uint64" {
|
||||||
|
return encoderOfType(ctx, reflect2.TypeOfPtr((*uint64)(nil)).Elem())
|
||||||
|
}
|
||||||
|
return &uint64Codec{}
|
||||||
|
case reflect.Float32:
|
||||||
|
if typeName != "float32" {
|
||||||
|
return encoderOfType(ctx, reflect2.TypeOfPtr((*float32)(nil)).Elem())
|
||||||
|
}
|
||||||
|
return &float32Codec{}
|
||||||
|
case reflect.Float64:
|
||||||
|
if typeName != "float64" {
|
||||||
|
return encoderOfType(ctx, reflect2.TypeOfPtr((*float64)(nil)).Elem())
|
||||||
|
}
|
||||||
|
return &float64Codec{}
|
||||||
|
case reflect.Bool:
|
||||||
|
if typeName != "bool" {
|
||||||
|
return encoderOfType(ctx, reflect2.TypeOfPtr((*bool)(nil)).Elem())
|
||||||
|
}
|
||||||
|
return &boolCodec{}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createDecoderOfNative(ctx *ctx, typ reflect2.Type) ValDecoder {
|
||||||
|
if typ.Kind() == reflect.Slice && typ.(reflect2.SliceType).Elem().Kind() == reflect.Uint8 {
|
||||||
|
sliceDecoder := decoderOfSlice(ctx, typ)
|
||||||
|
return &base64Codec{sliceDecoder: sliceDecoder}
|
||||||
|
}
|
||||||
|
typeName := typ.String()
|
||||||
|
switch typ.Kind() {
|
||||||
|
case reflect.String:
|
||||||
|
if typeName != "string" {
|
||||||
|
return decoderOfType(ctx, reflect2.TypeOfPtr((*string)(nil)).Elem())
|
||||||
|
}
|
||||||
|
return &stringCodec{}
|
||||||
|
case reflect.Int:
|
||||||
|
if typeName != "int" {
|
||||||
|
return decoderOfType(ctx, reflect2.TypeOfPtr((*int)(nil)).Elem())
|
||||||
|
}
|
||||||
|
if strconv.IntSize == 32 {
|
||||||
|
return &int32Codec{}
|
||||||
|
}
|
||||||
|
return &int64Codec{}
|
||||||
|
case reflect.Int8:
|
||||||
|
if typeName != "int8" {
|
||||||
|
return decoderOfType(ctx, reflect2.TypeOfPtr((*int8)(nil)).Elem())
|
||||||
|
}
|
||||||
|
return &int8Codec{}
|
||||||
|
case reflect.Int16:
|
||||||
|
if typeName != "int16" {
|
||||||
|
return decoderOfType(ctx, reflect2.TypeOfPtr((*int16)(nil)).Elem())
|
||||||
|
}
|
||||||
|
return &int16Codec{}
|
||||||
|
case reflect.Int32:
|
||||||
|
if typeName != "int32" {
|
||||||
|
return decoderOfType(ctx, reflect2.TypeOfPtr((*int32)(nil)).Elem())
|
||||||
|
}
|
||||||
|
return &int32Codec{}
|
||||||
|
case reflect.Int64:
|
||||||
|
if typeName != "int64" {
|
||||||
|
return decoderOfType(ctx, reflect2.TypeOfPtr((*int64)(nil)).Elem())
|
||||||
|
}
|
||||||
|
return &int64Codec{}
|
||||||
|
case reflect.Uint:
|
||||||
|
if typeName != "uint" {
|
||||||
|
return decoderOfType(ctx, reflect2.TypeOfPtr((*uint)(nil)).Elem())
|
||||||
|
}
|
||||||
|
if strconv.IntSize == 32 {
|
||||||
|
return &uint32Codec{}
|
||||||
|
}
|
||||||
|
return &uint64Codec{}
|
||||||
|
case reflect.Uint8:
|
||||||
|
if typeName != "uint8" {
|
||||||
|
return decoderOfType(ctx, reflect2.TypeOfPtr((*uint8)(nil)).Elem())
|
||||||
|
}
|
||||||
|
return &uint8Codec{}
|
||||||
|
case reflect.Uint16:
|
||||||
|
if typeName != "uint16" {
|
||||||
|
return decoderOfType(ctx, reflect2.TypeOfPtr((*uint16)(nil)).Elem())
|
||||||
|
}
|
||||||
|
return &uint16Codec{}
|
||||||
|
case reflect.Uint32:
|
||||||
|
if typeName != "uint32" {
|
||||||
|
return decoderOfType(ctx, reflect2.TypeOfPtr((*uint32)(nil)).Elem())
|
||||||
|
}
|
||||||
|
return &uint32Codec{}
|
||||||
|
case reflect.Uintptr:
|
||||||
|
if typeName != "uintptr" {
|
||||||
|
return decoderOfType(ctx, reflect2.TypeOfPtr((*uintptr)(nil)).Elem())
|
||||||
|
}
|
||||||
|
if ptrSize == 32 {
|
||||||
|
return &uint32Codec{}
|
||||||
|
}
|
||||||
|
return &uint64Codec{}
|
||||||
|
case reflect.Uint64:
|
||||||
|
if typeName != "uint64" {
|
||||||
|
return decoderOfType(ctx, reflect2.TypeOfPtr((*uint64)(nil)).Elem())
|
||||||
|
}
|
||||||
|
return &uint64Codec{}
|
||||||
|
case reflect.Float32:
|
||||||
|
if typeName != "float32" {
|
||||||
|
return decoderOfType(ctx, reflect2.TypeOfPtr((*float32)(nil)).Elem())
|
||||||
|
}
|
||||||
|
return &float32Codec{}
|
||||||
|
case reflect.Float64:
|
||||||
|
if typeName != "float64" {
|
||||||
|
return decoderOfType(ctx, reflect2.TypeOfPtr((*float64)(nil)).Elem())
|
||||||
|
}
|
||||||
|
return &float64Codec{}
|
||||||
|
case reflect.Bool:
|
||||||
|
if typeName != "bool" {
|
||||||
|
return decoderOfType(ctx, reflect2.TypeOfPtr((*bool)(nil)).Elem())
|
||||||
|
}
|
||||||
|
return &boolCodec{}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type stringCodec struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *stringCodec) Decode(ptr unsafe.Pointer, iter *Iterator) {
|
||||||
|
*((*string)(ptr)) = iter.ReadString()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *stringCodec) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
str := *((*string)(ptr))
|
||||||
|
stream.WriteString(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *stringCodec) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
return *((*string)(ptr)) == ""
|
||||||
|
}
|
||||||
|
|
||||||
|
type int8Codec struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *int8Codec) Decode(ptr unsafe.Pointer, iter *Iterator) {
|
||||||
|
if !iter.ReadNil() {
|
||||||
|
*((*int8)(ptr)) = iter.ReadInt8()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *int8Codec) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
stream.WriteInt8(*((*int8)(ptr)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *int8Codec) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
return *((*int8)(ptr)) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type int16Codec struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *int16Codec) Decode(ptr unsafe.Pointer, iter *Iterator) {
|
||||||
|
if !iter.ReadNil() {
|
||||||
|
*((*int16)(ptr)) = iter.ReadInt16()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *int16Codec) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
stream.WriteInt16(*((*int16)(ptr)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *int16Codec) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
return *((*int16)(ptr)) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type int32Codec struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *int32Codec) Decode(ptr unsafe.Pointer, iter *Iterator) {
|
||||||
|
if !iter.ReadNil() {
|
||||||
|
*((*int32)(ptr)) = iter.ReadInt32()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *int32Codec) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
stream.WriteInt32(*((*int32)(ptr)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *int32Codec) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
return *((*int32)(ptr)) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type int64Codec struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *int64Codec) Decode(ptr unsafe.Pointer, iter *Iterator) {
|
||||||
|
if !iter.ReadNil() {
|
||||||
|
*((*int64)(ptr)) = iter.ReadInt64()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *int64Codec) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
stream.WriteInt64(*((*int64)(ptr)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *int64Codec) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
return *((*int64)(ptr)) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type uint8Codec struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *uint8Codec) Decode(ptr unsafe.Pointer, iter *Iterator) {
|
||||||
|
if !iter.ReadNil() {
|
||||||
|
*((*uint8)(ptr)) = iter.ReadUint8()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *uint8Codec) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
stream.WriteUint8(*((*uint8)(ptr)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *uint8Codec) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
return *((*uint8)(ptr)) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type uint16Codec struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *uint16Codec) Decode(ptr unsafe.Pointer, iter *Iterator) {
|
||||||
|
if !iter.ReadNil() {
|
||||||
|
*((*uint16)(ptr)) = iter.ReadUint16()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *uint16Codec) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
stream.WriteUint16(*((*uint16)(ptr)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *uint16Codec) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
return *((*uint16)(ptr)) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type uint32Codec struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *uint32Codec) Decode(ptr unsafe.Pointer, iter *Iterator) {
|
||||||
|
if !iter.ReadNil() {
|
||||||
|
*((*uint32)(ptr)) = iter.ReadUint32()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *uint32Codec) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
stream.WriteUint32(*((*uint32)(ptr)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *uint32Codec) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
return *((*uint32)(ptr)) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type uint64Codec struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *uint64Codec) Decode(ptr unsafe.Pointer, iter *Iterator) {
|
||||||
|
if !iter.ReadNil() {
|
||||||
|
*((*uint64)(ptr)) = iter.ReadUint64()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *uint64Codec) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
stream.WriteUint64(*((*uint64)(ptr)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *uint64Codec) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
return *((*uint64)(ptr)) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type float32Codec struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *float32Codec) Decode(ptr unsafe.Pointer, iter *Iterator) {
|
||||||
|
if !iter.ReadNil() {
|
||||||
|
*((*float32)(ptr)) = iter.ReadFloat32()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *float32Codec) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
stream.WriteFloat32(*((*float32)(ptr)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *float32Codec) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
return *((*float32)(ptr)) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type float64Codec struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *float64Codec) Decode(ptr unsafe.Pointer, iter *Iterator) {
|
||||||
|
if !iter.ReadNil() {
|
||||||
|
*((*float64)(ptr)) = iter.ReadFloat64()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *float64Codec) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
stream.WriteFloat64(*((*float64)(ptr)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *float64Codec) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
return *((*float64)(ptr)) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type boolCodec struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *boolCodec) Decode(ptr unsafe.Pointer, iter *Iterator) {
|
||||||
|
if !iter.ReadNil() {
|
||||||
|
*((*bool)(ptr)) = iter.ReadBool()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *boolCodec) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
stream.WriteBool(*((*bool)(ptr)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *boolCodec) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
return !(*((*bool)(ptr)))
|
||||||
|
}
|
||||||
|
|
||||||
|
type base64Codec struct {
|
||||||
|
sliceType *reflect2.UnsafeSliceType
|
||||||
|
sliceDecoder ValDecoder
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *base64Codec) Decode(ptr unsafe.Pointer, iter *Iterator) {
|
||||||
|
if iter.ReadNil() {
|
||||||
|
codec.sliceType.UnsafeSetNil(ptr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch iter.WhatIsNext() {
|
||||||
|
case StringValue:
|
||||||
|
src := iter.ReadString()
|
||||||
|
dst, err := base64.StdEncoding.DecodeString(src)
|
||||||
|
if err != nil {
|
||||||
|
iter.ReportError("decode base64", err.Error())
|
||||||
|
} else {
|
||||||
|
codec.sliceType.UnsafeSet(ptr, unsafe.Pointer(&dst))
|
||||||
|
}
|
||||||
|
case ArrayValue:
|
||||||
|
codec.sliceDecoder.Decode(ptr, iter)
|
||||||
|
default:
|
||||||
|
iter.ReportError("base64Codec", "invalid input")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *base64Codec) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
if codec.sliceType.UnsafeIsNil(ptr) {
|
||||||
|
stream.WriteNil()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
src := *((*[]byte)(ptr))
|
||||||
|
encoding := base64.StdEncoding
|
||||||
|
stream.writeByte('"')
|
||||||
|
if len(src) != 0 {
|
||||||
|
size := encoding.EncodedLen(len(src))
|
||||||
|
buf := make([]byte, size)
|
||||||
|
encoding.Encode(buf, src)
|
||||||
|
stream.buf = append(stream.buf, buf...)
|
||||||
|
}
|
||||||
|
stream.writeByte('"')
|
||||||
|
}
|
||||||
|
|
||||||
|
func (codec *base64Codec) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
return len(*((*[]byte)(ptr))) == 0
|
||||||
|
}
|
||||||
129
vendor/github.com/json-iterator/go/reflect_optional.go
generated
vendored
Normal file
129
vendor/github.com/json-iterator/go/reflect_optional.go
generated
vendored
Normal file
|
|
@ -0,0 +1,129 @@
|
||||||
|
package jsoniter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/modern-go/reflect2"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func decoderOfOptional(ctx *ctx, typ reflect2.Type) ValDecoder {
|
||||||
|
ptrType := typ.(*reflect2.UnsafePtrType)
|
||||||
|
elemType := ptrType.Elem()
|
||||||
|
decoder := decoderOfType(ctx, elemType)
|
||||||
|
return &OptionalDecoder{elemType, decoder}
|
||||||
|
}
|
||||||
|
|
||||||
|
func encoderOfOptional(ctx *ctx, typ reflect2.Type) ValEncoder {
|
||||||
|
ptrType := typ.(*reflect2.UnsafePtrType)
|
||||||
|
elemType := ptrType.Elem()
|
||||||
|
elemEncoder := encoderOfType(ctx, elemType)
|
||||||
|
encoder := &OptionalEncoder{elemEncoder}
|
||||||
|
return encoder
|
||||||
|
}
|
||||||
|
|
||||||
|
type OptionalDecoder struct {
|
||||||
|
ValueType reflect2.Type
|
||||||
|
ValueDecoder ValDecoder
|
||||||
|
}
|
||||||
|
|
||||||
|
func (decoder *OptionalDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
|
||||||
|
if iter.ReadNil() {
|
||||||
|
*((*unsafe.Pointer)(ptr)) = nil
|
||||||
|
} else {
|
||||||
|
if *((*unsafe.Pointer)(ptr)) == nil {
|
||||||
|
//pointer to null, we have to allocate memory to hold the value
|
||||||
|
newPtr := decoder.ValueType.UnsafeNew()
|
||||||
|
decoder.ValueDecoder.Decode(newPtr, iter)
|
||||||
|
*((*unsafe.Pointer)(ptr)) = newPtr
|
||||||
|
} else {
|
||||||
|
//reuse existing instance
|
||||||
|
decoder.ValueDecoder.Decode(*((*unsafe.Pointer)(ptr)), iter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type dereferenceDecoder struct {
|
||||||
|
// only to deference a pointer
|
||||||
|
valueType reflect2.Type
|
||||||
|
valueDecoder ValDecoder
|
||||||
|
}
|
||||||
|
|
||||||
|
func (decoder *dereferenceDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
|
||||||
|
if *((*unsafe.Pointer)(ptr)) == nil {
|
||||||
|
//pointer to null, we have to allocate memory to hold the value
|
||||||
|
newPtr := decoder.valueType.UnsafeNew()
|
||||||
|
decoder.valueDecoder.Decode(newPtr, iter)
|
||||||
|
*((*unsafe.Pointer)(ptr)) = newPtr
|
||||||
|
} else {
|
||||||
|
//reuse existing instance
|
||||||
|
decoder.valueDecoder.Decode(*((*unsafe.Pointer)(ptr)), iter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type OptionalEncoder struct {
|
||||||
|
ValueEncoder ValEncoder
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *OptionalEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
if *((*unsafe.Pointer)(ptr)) == nil {
|
||||||
|
stream.WriteNil()
|
||||||
|
} else {
|
||||||
|
encoder.ValueEncoder.Encode(*((*unsafe.Pointer)(ptr)), stream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *OptionalEncoder) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
return *((*unsafe.Pointer)(ptr)) == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type dereferenceEncoder struct {
|
||||||
|
ValueEncoder ValEncoder
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *dereferenceEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
if *((*unsafe.Pointer)(ptr)) == nil {
|
||||||
|
stream.WriteNil()
|
||||||
|
} else {
|
||||||
|
encoder.ValueEncoder.Encode(*((*unsafe.Pointer)(ptr)), stream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *dereferenceEncoder) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
dePtr := *((*unsafe.Pointer)(ptr))
|
||||||
|
if dePtr == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return encoder.ValueEncoder.IsEmpty(dePtr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *dereferenceEncoder) IsEmbeddedPtrNil(ptr unsafe.Pointer) bool {
|
||||||
|
deReferenced := *((*unsafe.Pointer)(ptr))
|
||||||
|
if deReferenced == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
isEmbeddedPtrNil, converted := encoder.ValueEncoder.(IsEmbeddedPtrNil)
|
||||||
|
if !converted {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
fieldPtr := unsafe.Pointer(deReferenced)
|
||||||
|
return isEmbeddedPtrNil.IsEmbeddedPtrNil(fieldPtr)
|
||||||
|
}
|
||||||
|
|
||||||
|
type referenceEncoder struct {
|
||||||
|
encoder ValEncoder
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *referenceEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
encoder.encoder.Encode(unsafe.Pointer(&ptr), stream)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *referenceEncoder) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
return encoder.encoder.IsEmpty(unsafe.Pointer(&ptr))
|
||||||
|
}
|
||||||
|
|
||||||
|
type referenceDecoder struct {
|
||||||
|
decoder ValDecoder
|
||||||
|
}
|
||||||
|
|
||||||
|
func (decoder *referenceDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
|
||||||
|
decoder.decoder.Decode(unsafe.Pointer(&ptr), iter)
|
||||||
|
}
|
||||||
99
vendor/github.com/json-iterator/go/reflect_slice.go
generated
vendored
Normal file
99
vendor/github.com/json-iterator/go/reflect_slice.go
generated
vendored
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
package jsoniter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/modern-go/reflect2"
|
||||||
|
"io"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func decoderOfSlice(ctx *ctx, typ reflect2.Type) ValDecoder {
|
||||||
|
sliceType := typ.(*reflect2.UnsafeSliceType)
|
||||||
|
decoder := decoderOfType(ctx.append("[sliceElem]"), sliceType.Elem())
|
||||||
|
return &sliceDecoder{sliceType, decoder}
|
||||||
|
}
|
||||||
|
|
||||||
|
func encoderOfSlice(ctx *ctx, typ reflect2.Type) ValEncoder {
|
||||||
|
sliceType := typ.(*reflect2.UnsafeSliceType)
|
||||||
|
encoder := encoderOfType(ctx.append("[sliceElem]"), sliceType.Elem())
|
||||||
|
return &sliceEncoder{sliceType, encoder}
|
||||||
|
}
|
||||||
|
|
||||||
|
type sliceEncoder struct {
|
||||||
|
sliceType *reflect2.UnsafeSliceType
|
||||||
|
elemEncoder ValEncoder
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *sliceEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
if encoder.sliceType.UnsafeIsNil(ptr) {
|
||||||
|
stream.WriteNil()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
length := encoder.sliceType.UnsafeLengthOf(ptr)
|
||||||
|
if length == 0 {
|
||||||
|
stream.WriteEmptyArray()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
stream.WriteArrayStart()
|
||||||
|
encoder.elemEncoder.Encode(encoder.sliceType.UnsafeGetIndex(ptr, 0), stream)
|
||||||
|
for i := 1; i < length; i++ {
|
||||||
|
stream.WriteMore()
|
||||||
|
elemPtr := encoder.sliceType.UnsafeGetIndex(ptr, i)
|
||||||
|
encoder.elemEncoder.Encode(elemPtr, stream)
|
||||||
|
}
|
||||||
|
stream.WriteArrayEnd()
|
||||||
|
if stream.Error != nil && stream.Error != io.EOF {
|
||||||
|
stream.Error = fmt.Errorf("%v: %s", encoder.sliceType, stream.Error.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *sliceEncoder) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
return encoder.sliceType.UnsafeLengthOf(ptr) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
type sliceDecoder struct {
|
||||||
|
sliceType *reflect2.UnsafeSliceType
|
||||||
|
elemDecoder ValDecoder
|
||||||
|
}
|
||||||
|
|
||||||
|
func (decoder *sliceDecoder) Decode(ptr unsafe.Pointer, iter *Iterator) {
|
||||||
|
decoder.doDecode(ptr, iter)
|
||||||
|
if iter.Error != nil && iter.Error != io.EOF {
|
||||||
|
iter.Error = fmt.Errorf("%v: %s", decoder.sliceType, iter.Error.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (decoder *sliceDecoder) doDecode(ptr unsafe.Pointer, iter *Iterator) {
|
||||||
|
c := iter.nextToken()
|
||||||
|
sliceType := decoder.sliceType
|
||||||
|
if c == 'n' {
|
||||||
|
iter.skipThreeBytes('u', 'l', 'l')
|
||||||
|
sliceType.UnsafeSetNil(ptr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if c != '[' {
|
||||||
|
iter.ReportError("decode slice", "expect [ or n, but found "+string([]byte{c}))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
c = iter.nextToken()
|
||||||
|
if c == ']' {
|
||||||
|
sliceType.UnsafeSet(ptr, sliceType.UnsafeMakeSlice(0, 0))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
iter.unreadByte()
|
||||||
|
sliceType.UnsafeGrow(ptr, 1)
|
||||||
|
elemPtr := sliceType.UnsafeGetIndex(ptr, 0)
|
||||||
|
decoder.elemDecoder.Decode(elemPtr, iter)
|
||||||
|
length := 1
|
||||||
|
for c = iter.nextToken(); c == ','; c = iter.nextToken() {
|
||||||
|
idx := length
|
||||||
|
length += 1
|
||||||
|
sliceType.UnsafeGrow(ptr, length)
|
||||||
|
elemPtr = sliceType.UnsafeGetIndex(ptr, idx)
|
||||||
|
decoder.elemDecoder.Decode(elemPtr, iter)
|
||||||
|
}
|
||||||
|
if c != ']' {
|
||||||
|
iter.ReportError("decode slice", "expect ], but found "+string([]byte{c}))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
1097
vendor/github.com/json-iterator/go/reflect_struct_decoder.go
generated
vendored
Normal file
1097
vendor/github.com/json-iterator/go/reflect_struct_decoder.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
211
vendor/github.com/json-iterator/go/reflect_struct_encoder.go
generated
vendored
Normal file
211
vendor/github.com/json-iterator/go/reflect_struct_encoder.go
generated
vendored
Normal file
|
|
@ -0,0 +1,211 @@
|
||||||
|
package jsoniter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/modern-go/reflect2"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func encoderOfStruct(ctx *ctx, typ reflect2.Type) ValEncoder {
|
||||||
|
type bindingTo struct {
|
||||||
|
binding *Binding
|
||||||
|
toName string
|
||||||
|
ignored bool
|
||||||
|
}
|
||||||
|
orderedBindings := []*bindingTo{}
|
||||||
|
structDescriptor := describeStruct(ctx, typ)
|
||||||
|
for _, binding := range structDescriptor.Fields {
|
||||||
|
for _, toName := range binding.ToNames {
|
||||||
|
new := &bindingTo{
|
||||||
|
binding: binding,
|
||||||
|
toName: toName,
|
||||||
|
}
|
||||||
|
for _, old := range orderedBindings {
|
||||||
|
if old.toName != toName {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
old.ignored, new.ignored = resolveConflictBinding(ctx.frozenConfig, old.binding, new.binding)
|
||||||
|
}
|
||||||
|
orderedBindings = append(orderedBindings, new)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(orderedBindings) == 0 {
|
||||||
|
return &emptyStructEncoder{}
|
||||||
|
}
|
||||||
|
finalOrderedFields := []structFieldTo{}
|
||||||
|
for _, bindingTo := range orderedBindings {
|
||||||
|
if !bindingTo.ignored {
|
||||||
|
finalOrderedFields = append(finalOrderedFields, structFieldTo{
|
||||||
|
encoder: bindingTo.binding.Encoder.(*structFieldEncoder),
|
||||||
|
toName: bindingTo.toName,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &structEncoder{typ, finalOrderedFields}
|
||||||
|
}
|
||||||
|
|
||||||
|
func createCheckIsEmpty(ctx *ctx, typ reflect2.Type) checkIsEmpty {
|
||||||
|
encoder := createEncoderOfNative(ctx, typ)
|
||||||
|
if encoder != nil {
|
||||||
|
return encoder
|
||||||
|
}
|
||||||
|
kind := typ.Kind()
|
||||||
|
switch kind {
|
||||||
|
case reflect.Interface:
|
||||||
|
return &dynamicEncoder{typ}
|
||||||
|
case reflect.Struct:
|
||||||
|
return &structEncoder{typ: typ}
|
||||||
|
case reflect.Array:
|
||||||
|
return &arrayEncoder{}
|
||||||
|
case reflect.Slice:
|
||||||
|
return &sliceEncoder{}
|
||||||
|
case reflect.Map:
|
||||||
|
return encoderOfMap(ctx, typ)
|
||||||
|
case reflect.Ptr:
|
||||||
|
return &OptionalEncoder{}
|
||||||
|
default:
|
||||||
|
return &lazyErrorEncoder{err: fmt.Errorf("unsupported type: %v", typ)}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveConflictBinding(cfg *frozenConfig, old, new *Binding) (ignoreOld, ignoreNew bool) {
|
||||||
|
newTagged := new.Field.Tag().Get(cfg.getTagKey()) != ""
|
||||||
|
oldTagged := old.Field.Tag().Get(cfg.getTagKey()) != ""
|
||||||
|
if newTagged {
|
||||||
|
if oldTagged {
|
||||||
|
if len(old.levels) > len(new.levels) {
|
||||||
|
return true, false
|
||||||
|
} else if len(new.levels) > len(old.levels) {
|
||||||
|
return false, true
|
||||||
|
} else {
|
||||||
|
return true, true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return true, false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if oldTagged {
|
||||||
|
return true, false
|
||||||
|
}
|
||||||
|
if len(old.levels) > len(new.levels) {
|
||||||
|
return true, false
|
||||||
|
} else if len(new.levels) > len(old.levels) {
|
||||||
|
return false, true
|
||||||
|
} else {
|
||||||
|
return true, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type structFieldEncoder struct {
|
||||||
|
field reflect2.StructField
|
||||||
|
fieldEncoder ValEncoder
|
||||||
|
omitempty bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *structFieldEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
fieldPtr := encoder.field.UnsafeGet(ptr)
|
||||||
|
encoder.fieldEncoder.Encode(fieldPtr, stream)
|
||||||
|
if stream.Error != nil && stream.Error != io.EOF {
|
||||||
|
stream.Error = fmt.Errorf("%s: %s", encoder.field.Name(), stream.Error.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *structFieldEncoder) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
fieldPtr := encoder.field.UnsafeGet(ptr)
|
||||||
|
return encoder.fieldEncoder.IsEmpty(fieldPtr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *structFieldEncoder) IsEmbeddedPtrNil(ptr unsafe.Pointer) bool {
|
||||||
|
isEmbeddedPtrNil, converted := encoder.fieldEncoder.(IsEmbeddedPtrNil)
|
||||||
|
if !converted {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
fieldPtr := encoder.field.UnsafeGet(ptr)
|
||||||
|
return isEmbeddedPtrNil.IsEmbeddedPtrNil(fieldPtr)
|
||||||
|
}
|
||||||
|
|
||||||
|
type IsEmbeddedPtrNil interface {
|
||||||
|
IsEmbeddedPtrNil(ptr unsafe.Pointer) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type structEncoder struct {
|
||||||
|
typ reflect2.Type
|
||||||
|
fields []structFieldTo
|
||||||
|
}
|
||||||
|
|
||||||
|
type structFieldTo struct {
|
||||||
|
encoder *structFieldEncoder
|
||||||
|
toName string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *structEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
stream.WriteObjectStart()
|
||||||
|
isNotFirst := false
|
||||||
|
for _, field := range encoder.fields {
|
||||||
|
if field.encoder.omitempty && field.encoder.IsEmpty(ptr) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if field.encoder.IsEmbeddedPtrNil(ptr) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if isNotFirst {
|
||||||
|
stream.WriteMore()
|
||||||
|
}
|
||||||
|
stream.WriteObjectField(field.toName)
|
||||||
|
field.encoder.Encode(ptr, stream)
|
||||||
|
isNotFirst = true
|
||||||
|
}
|
||||||
|
stream.WriteObjectEnd()
|
||||||
|
if stream.Error != nil && stream.Error != io.EOF {
|
||||||
|
stream.Error = fmt.Errorf("%v.%s", encoder.typ, stream.Error.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *structEncoder) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type emptyStructEncoder struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *emptyStructEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
stream.WriteEmptyObject()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *emptyStructEncoder) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
type stringModeNumberEncoder struct {
|
||||||
|
elemEncoder ValEncoder
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *stringModeNumberEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
stream.writeByte('"')
|
||||||
|
encoder.elemEncoder.Encode(ptr, stream)
|
||||||
|
stream.writeByte('"')
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *stringModeNumberEncoder) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
return encoder.elemEncoder.IsEmpty(ptr)
|
||||||
|
}
|
||||||
|
|
||||||
|
type stringModeStringEncoder struct {
|
||||||
|
elemEncoder ValEncoder
|
||||||
|
cfg *frozenConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *stringModeStringEncoder) Encode(ptr unsafe.Pointer, stream *Stream) {
|
||||||
|
tempStream := encoder.cfg.BorrowStream(nil)
|
||||||
|
tempStream.Attachment = stream.Attachment
|
||||||
|
defer encoder.cfg.ReturnStream(tempStream)
|
||||||
|
encoder.elemEncoder.Encode(ptr, tempStream)
|
||||||
|
stream.WriteString(string(tempStream.Buffer()))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (encoder *stringModeStringEncoder) IsEmpty(ptr unsafe.Pointer) bool {
|
||||||
|
return encoder.elemEncoder.IsEmpty(ptr)
|
||||||
|
}
|
||||||
210
vendor/github.com/json-iterator/go/stream.go
generated
vendored
Normal file
210
vendor/github.com/json-iterator/go/stream.go
generated
vendored
Normal file
|
|
@ -0,0 +1,210 @@
|
||||||
|
package jsoniter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// stream is a io.Writer like object, with JSON specific write functions.
|
||||||
|
// Error is not returned as return value, but stored as Error member on this stream instance.
|
||||||
|
type Stream struct {
|
||||||
|
cfg *frozenConfig
|
||||||
|
out io.Writer
|
||||||
|
buf []byte
|
||||||
|
Error error
|
||||||
|
indention int
|
||||||
|
Attachment interface{} // open for customized encoder
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewStream create new stream instance.
|
||||||
|
// cfg can be jsoniter.ConfigDefault.
|
||||||
|
// out can be nil if write to internal buffer.
|
||||||
|
// bufSize is the initial size for the internal buffer in bytes.
|
||||||
|
func NewStream(cfg API, out io.Writer, bufSize int) *Stream {
|
||||||
|
return &Stream{
|
||||||
|
cfg: cfg.(*frozenConfig),
|
||||||
|
out: out,
|
||||||
|
buf: make([]byte, 0, bufSize),
|
||||||
|
Error: nil,
|
||||||
|
indention: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pool returns a pool can provide more stream with same configuration
|
||||||
|
func (stream *Stream) Pool() StreamPool {
|
||||||
|
return stream.cfg
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset reuse this stream instance by assign a new writer
|
||||||
|
func (stream *Stream) Reset(out io.Writer) {
|
||||||
|
stream.out = out
|
||||||
|
stream.buf = stream.buf[:0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Available returns how many bytes are unused in the buffer.
|
||||||
|
func (stream *Stream) Available() int {
|
||||||
|
return cap(stream.buf) - len(stream.buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buffered returns the number of bytes that have been written into the current buffer.
|
||||||
|
func (stream *Stream) Buffered() int {
|
||||||
|
return len(stream.buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Buffer if writer is nil, use this method to take the result
|
||||||
|
func (stream *Stream) Buffer() []byte {
|
||||||
|
return stream.buf
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBuffer allows to append to the internal buffer directly
|
||||||
|
func (stream *Stream) SetBuffer(buf []byte) {
|
||||||
|
stream.buf = buf
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write writes the contents of p into the buffer.
|
||||||
|
// It returns the number of bytes written.
|
||||||
|
// If nn < len(p), it also returns an error explaining
|
||||||
|
// why the write is short.
|
||||||
|
func (stream *Stream) Write(p []byte) (nn int, err error) {
|
||||||
|
stream.buf = append(stream.buf, p...)
|
||||||
|
if stream.out != nil {
|
||||||
|
nn, err = stream.out.Write(stream.buf)
|
||||||
|
stream.buf = stream.buf[nn:]
|
||||||
|
return
|
||||||
|
}
|
||||||
|
return len(p), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteByte writes a single byte.
|
||||||
|
func (stream *Stream) writeByte(c byte) {
|
||||||
|
stream.buf = append(stream.buf, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (stream *Stream) writeTwoBytes(c1 byte, c2 byte) {
|
||||||
|
stream.buf = append(stream.buf, c1, c2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (stream *Stream) writeThreeBytes(c1 byte, c2 byte, c3 byte) {
|
||||||
|
stream.buf = append(stream.buf, c1, c2, c3)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (stream *Stream) writeFourBytes(c1 byte, c2 byte, c3 byte, c4 byte) {
|
||||||
|
stream.buf = append(stream.buf, c1, c2, c3, c4)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (stream *Stream) writeFiveBytes(c1 byte, c2 byte, c3 byte, c4 byte, c5 byte) {
|
||||||
|
stream.buf = append(stream.buf, c1, c2, c3, c4, c5)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Flush writes any buffered data to the underlying io.Writer.
|
||||||
|
func (stream *Stream) Flush() error {
|
||||||
|
if stream.out == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if stream.Error != nil {
|
||||||
|
return stream.Error
|
||||||
|
}
|
||||||
|
_, err := stream.out.Write(stream.buf)
|
||||||
|
if err != nil {
|
||||||
|
if stream.Error == nil {
|
||||||
|
stream.Error = err
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
stream.buf = stream.buf[:0]
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteRaw write string out without quotes, just like []byte
|
||||||
|
func (stream *Stream) WriteRaw(s string) {
|
||||||
|
stream.buf = append(stream.buf, s...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteNil write null to stream
|
||||||
|
func (stream *Stream) WriteNil() {
|
||||||
|
stream.writeFourBytes('n', 'u', 'l', 'l')
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteTrue write true to stream
|
||||||
|
func (stream *Stream) WriteTrue() {
|
||||||
|
stream.writeFourBytes('t', 'r', 'u', 'e')
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteFalse write false to stream
|
||||||
|
func (stream *Stream) WriteFalse() {
|
||||||
|
stream.writeFiveBytes('f', 'a', 'l', 's', 'e')
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteBool write true or false into stream
|
||||||
|
func (stream *Stream) WriteBool(val bool) {
|
||||||
|
if val {
|
||||||
|
stream.WriteTrue()
|
||||||
|
} else {
|
||||||
|
stream.WriteFalse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteObjectStart write { with possible indention
|
||||||
|
func (stream *Stream) WriteObjectStart() {
|
||||||
|
stream.indention += stream.cfg.indentionStep
|
||||||
|
stream.writeByte('{')
|
||||||
|
stream.writeIndention(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteObjectField write "field": with possible indention
|
||||||
|
func (stream *Stream) WriteObjectField(field string) {
|
||||||
|
stream.WriteString(field)
|
||||||
|
if stream.indention > 0 {
|
||||||
|
stream.writeTwoBytes(':', ' ')
|
||||||
|
} else {
|
||||||
|
stream.writeByte(':')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteObjectEnd write } with possible indention
|
||||||
|
func (stream *Stream) WriteObjectEnd() {
|
||||||
|
stream.writeIndention(stream.cfg.indentionStep)
|
||||||
|
stream.indention -= stream.cfg.indentionStep
|
||||||
|
stream.writeByte('}')
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteEmptyObject write {}
|
||||||
|
func (stream *Stream) WriteEmptyObject() {
|
||||||
|
stream.writeByte('{')
|
||||||
|
stream.writeByte('}')
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteMore write , with possible indention
|
||||||
|
func (stream *Stream) WriteMore() {
|
||||||
|
stream.writeByte(',')
|
||||||
|
stream.writeIndention(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteArrayStart write [ with possible indention
|
||||||
|
func (stream *Stream) WriteArrayStart() {
|
||||||
|
stream.indention += stream.cfg.indentionStep
|
||||||
|
stream.writeByte('[')
|
||||||
|
stream.writeIndention(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteEmptyArray write []
|
||||||
|
func (stream *Stream) WriteEmptyArray() {
|
||||||
|
stream.writeTwoBytes('[', ']')
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteArrayEnd write ] with possible indention
|
||||||
|
func (stream *Stream) WriteArrayEnd() {
|
||||||
|
stream.writeIndention(stream.cfg.indentionStep)
|
||||||
|
stream.indention -= stream.cfg.indentionStep
|
||||||
|
stream.writeByte(']')
|
||||||
|
}
|
||||||
|
|
||||||
|
func (stream *Stream) writeIndention(delta int) {
|
||||||
|
if stream.indention == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
stream.writeByte('\n')
|
||||||
|
toWrite := stream.indention - delta
|
||||||
|
for i := 0; i < toWrite; i++ {
|
||||||
|
stream.buf = append(stream.buf, ' ')
|
||||||
|
}
|
||||||
|
}
|
||||||
111
vendor/github.com/json-iterator/go/stream_float.go
generated
vendored
Normal file
111
vendor/github.com/json-iterator/go/stream_float.go
generated
vendored
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
package jsoniter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
var pow10 []uint64
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
pow10 = []uint64{1, 10, 100, 1000, 10000, 100000, 1000000}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteFloat32 write float32 to stream
|
||||||
|
func (stream *Stream) WriteFloat32(val float32) {
|
||||||
|
if math.IsInf(float64(val), 0) || math.IsNaN(float64(val)) {
|
||||||
|
stream.Error = fmt.Errorf("unsupported value: %f", val)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
abs := math.Abs(float64(val))
|
||||||
|
fmt := byte('f')
|
||||||
|
// Note: Must use float32 comparisons for underlying float32 value to get precise cutoffs right.
|
||||||
|
if abs != 0 {
|
||||||
|
if float32(abs) < 1e-6 || float32(abs) >= 1e21 {
|
||||||
|
fmt = 'e'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stream.buf = strconv.AppendFloat(stream.buf, float64(val), fmt, -1, 32)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteFloat32Lossy write float32 to stream with ONLY 6 digits precision although much much faster
|
||||||
|
func (stream *Stream) WriteFloat32Lossy(val float32) {
|
||||||
|
if math.IsInf(float64(val), 0) || math.IsNaN(float64(val)) {
|
||||||
|
stream.Error = fmt.Errorf("unsupported value: %f", val)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if val < 0 {
|
||||||
|
stream.writeByte('-')
|
||||||
|
val = -val
|
||||||
|
}
|
||||||
|
if val > 0x4ffffff {
|
||||||
|
stream.WriteFloat32(val)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
precision := 6
|
||||||
|
exp := uint64(1000000) // 6
|
||||||
|
lval := uint64(float64(val)*float64(exp) + 0.5)
|
||||||
|
stream.WriteUint64(lval / exp)
|
||||||
|
fval := lval % exp
|
||||||
|
if fval == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
stream.writeByte('.')
|
||||||
|
for p := precision - 1; p > 0 && fval < pow10[p]; p-- {
|
||||||
|
stream.writeByte('0')
|
||||||
|
}
|
||||||
|
stream.WriteUint64(fval)
|
||||||
|
for stream.buf[len(stream.buf)-1] == '0' {
|
||||||
|
stream.buf = stream.buf[:len(stream.buf)-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteFloat64 write float64 to stream
|
||||||
|
func (stream *Stream) WriteFloat64(val float64) {
|
||||||
|
if math.IsInf(val, 0) || math.IsNaN(val) {
|
||||||
|
stream.Error = fmt.Errorf("unsupported value: %f", val)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
abs := math.Abs(val)
|
||||||
|
fmt := byte('f')
|
||||||
|
// Note: Must use float32 comparisons for underlying float32 value to get precise cutoffs right.
|
||||||
|
if abs != 0 {
|
||||||
|
if abs < 1e-6 || abs >= 1e21 {
|
||||||
|
fmt = 'e'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stream.buf = strconv.AppendFloat(stream.buf, float64(val), fmt, -1, 64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteFloat64Lossy write float64 to stream with ONLY 6 digits precision although much much faster
|
||||||
|
func (stream *Stream) WriteFloat64Lossy(val float64) {
|
||||||
|
if math.IsInf(val, 0) || math.IsNaN(val) {
|
||||||
|
stream.Error = fmt.Errorf("unsupported value: %f", val)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if val < 0 {
|
||||||
|
stream.writeByte('-')
|
||||||
|
val = -val
|
||||||
|
}
|
||||||
|
if val > 0x4ffffff {
|
||||||
|
stream.WriteFloat64(val)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
precision := 6
|
||||||
|
exp := uint64(1000000) // 6
|
||||||
|
lval := uint64(val*float64(exp) + 0.5)
|
||||||
|
stream.WriteUint64(lval / exp)
|
||||||
|
fval := lval % exp
|
||||||
|
if fval == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
stream.writeByte('.')
|
||||||
|
for p := precision - 1; p > 0 && fval < pow10[p]; p-- {
|
||||||
|
stream.writeByte('0')
|
||||||
|
}
|
||||||
|
stream.WriteUint64(fval)
|
||||||
|
for stream.buf[len(stream.buf)-1] == '0' {
|
||||||
|
stream.buf = stream.buf[:len(stream.buf)-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
190
vendor/github.com/json-iterator/go/stream_int.go
generated
vendored
Normal file
190
vendor/github.com/json-iterator/go/stream_int.go
generated
vendored
Normal file
|
|
@ -0,0 +1,190 @@
|
||||||
|
package jsoniter
|
||||||
|
|
||||||
|
var digits []uint32
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
digits = make([]uint32, 1000)
|
||||||
|
for i := uint32(0); i < 1000; i++ {
|
||||||
|
digits[i] = (((i / 100) + '0') << 16) + ((((i / 10) % 10) + '0') << 8) + i%10 + '0'
|
||||||
|
if i < 10 {
|
||||||
|
digits[i] += 2 << 24
|
||||||
|
} else if i < 100 {
|
||||||
|
digits[i] += 1 << 24
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeFirstBuf(space []byte, v uint32) []byte {
|
||||||
|
start := v >> 24
|
||||||
|
if start == 0 {
|
||||||
|
space = append(space, byte(v>>16), byte(v>>8))
|
||||||
|
} else if start == 1 {
|
||||||
|
space = append(space, byte(v>>8))
|
||||||
|
}
|
||||||
|
space = append(space, byte(v))
|
||||||
|
return space
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeBuf(buf []byte, v uint32) []byte {
|
||||||
|
return append(buf, byte(v>>16), byte(v>>8), byte(v))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteUint8 write uint8 to stream
|
||||||
|
func (stream *Stream) WriteUint8(val uint8) {
|
||||||
|
stream.buf = writeFirstBuf(stream.buf, digits[val])
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteInt8 write int8 to stream
|
||||||
|
func (stream *Stream) WriteInt8(nval int8) {
|
||||||
|
var val uint8
|
||||||
|
if nval < 0 {
|
||||||
|
val = uint8(-nval)
|
||||||
|
stream.buf = append(stream.buf, '-')
|
||||||
|
} else {
|
||||||
|
val = uint8(nval)
|
||||||
|
}
|
||||||
|
stream.buf = writeFirstBuf(stream.buf, digits[val])
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteUint16 write uint16 to stream
|
||||||
|
func (stream *Stream) WriteUint16(val uint16) {
|
||||||
|
q1 := val / 1000
|
||||||
|
if q1 == 0 {
|
||||||
|
stream.buf = writeFirstBuf(stream.buf, digits[val])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r1 := val - q1*1000
|
||||||
|
stream.buf = writeFirstBuf(stream.buf, digits[q1])
|
||||||
|
stream.buf = writeBuf(stream.buf, digits[r1])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteInt16 write int16 to stream
|
||||||
|
func (stream *Stream) WriteInt16(nval int16) {
|
||||||
|
var val uint16
|
||||||
|
if nval < 0 {
|
||||||
|
val = uint16(-nval)
|
||||||
|
stream.buf = append(stream.buf, '-')
|
||||||
|
} else {
|
||||||
|
val = uint16(nval)
|
||||||
|
}
|
||||||
|
stream.WriteUint16(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteUint32 write uint32 to stream
|
||||||
|
func (stream *Stream) WriteUint32(val uint32) {
|
||||||
|
q1 := val / 1000
|
||||||
|
if q1 == 0 {
|
||||||
|
stream.buf = writeFirstBuf(stream.buf, digits[val])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r1 := val - q1*1000
|
||||||
|
q2 := q1 / 1000
|
||||||
|
if q2 == 0 {
|
||||||
|
stream.buf = writeFirstBuf(stream.buf, digits[q1])
|
||||||
|
stream.buf = writeBuf(stream.buf, digits[r1])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r2 := q1 - q2*1000
|
||||||
|
q3 := q2 / 1000
|
||||||
|
if q3 == 0 {
|
||||||
|
stream.buf = writeFirstBuf(stream.buf, digits[q2])
|
||||||
|
} else {
|
||||||
|
r3 := q2 - q3*1000
|
||||||
|
stream.buf = append(stream.buf, byte(q3+'0'))
|
||||||
|
stream.buf = writeBuf(stream.buf, digits[r3])
|
||||||
|
}
|
||||||
|
stream.buf = writeBuf(stream.buf, digits[r2])
|
||||||
|
stream.buf = writeBuf(stream.buf, digits[r1])
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteInt32 write int32 to stream
|
||||||
|
func (stream *Stream) WriteInt32(nval int32) {
|
||||||
|
var val uint32
|
||||||
|
if nval < 0 {
|
||||||
|
val = uint32(-nval)
|
||||||
|
stream.buf = append(stream.buf, '-')
|
||||||
|
} else {
|
||||||
|
val = uint32(nval)
|
||||||
|
}
|
||||||
|
stream.WriteUint32(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteUint64 write uint64 to stream
|
||||||
|
func (stream *Stream) WriteUint64(val uint64) {
|
||||||
|
q1 := val / 1000
|
||||||
|
if q1 == 0 {
|
||||||
|
stream.buf = writeFirstBuf(stream.buf, digits[val])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r1 := val - q1*1000
|
||||||
|
q2 := q1 / 1000
|
||||||
|
if q2 == 0 {
|
||||||
|
stream.buf = writeFirstBuf(stream.buf, digits[q1])
|
||||||
|
stream.buf = writeBuf(stream.buf, digits[r1])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r2 := q1 - q2*1000
|
||||||
|
q3 := q2 / 1000
|
||||||
|
if q3 == 0 {
|
||||||
|
stream.buf = writeFirstBuf(stream.buf, digits[q2])
|
||||||
|
stream.buf = writeBuf(stream.buf, digits[r2])
|
||||||
|
stream.buf = writeBuf(stream.buf, digits[r1])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r3 := q2 - q3*1000
|
||||||
|
q4 := q3 / 1000
|
||||||
|
if q4 == 0 {
|
||||||
|
stream.buf = writeFirstBuf(stream.buf, digits[q3])
|
||||||
|
stream.buf = writeBuf(stream.buf, digits[r3])
|
||||||
|
stream.buf = writeBuf(stream.buf, digits[r2])
|
||||||
|
stream.buf = writeBuf(stream.buf, digits[r1])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r4 := q3 - q4*1000
|
||||||
|
q5 := q4 / 1000
|
||||||
|
if q5 == 0 {
|
||||||
|
stream.buf = writeFirstBuf(stream.buf, digits[q4])
|
||||||
|
stream.buf = writeBuf(stream.buf, digits[r4])
|
||||||
|
stream.buf = writeBuf(stream.buf, digits[r3])
|
||||||
|
stream.buf = writeBuf(stream.buf, digits[r2])
|
||||||
|
stream.buf = writeBuf(stream.buf, digits[r1])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r5 := q4 - q5*1000
|
||||||
|
q6 := q5 / 1000
|
||||||
|
if q6 == 0 {
|
||||||
|
stream.buf = writeFirstBuf(stream.buf, digits[q5])
|
||||||
|
} else {
|
||||||
|
stream.buf = writeFirstBuf(stream.buf, digits[q6])
|
||||||
|
r6 := q5 - q6*1000
|
||||||
|
stream.buf = writeBuf(stream.buf, digits[r6])
|
||||||
|
}
|
||||||
|
stream.buf = writeBuf(stream.buf, digits[r5])
|
||||||
|
stream.buf = writeBuf(stream.buf, digits[r4])
|
||||||
|
stream.buf = writeBuf(stream.buf, digits[r3])
|
||||||
|
stream.buf = writeBuf(stream.buf, digits[r2])
|
||||||
|
stream.buf = writeBuf(stream.buf, digits[r1])
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteInt64 write int64 to stream
|
||||||
|
func (stream *Stream) WriteInt64(nval int64) {
|
||||||
|
var val uint64
|
||||||
|
if nval < 0 {
|
||||||
|
val = uint64(-nval)
|
||||||
|
stream.buf = append(stream.buf, '-')
|
||||||
|
} else {
|
||||||
|
val = uint64(nval)
|
||||||
|
}
|
||||||
|
stream.WriteUint64(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteInt write int to stream
|
||||||
|
func (stream *Stream) WriteInt(val int) {
|
||||||
|
stream.WriteInt64(int64(val))
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteUint write uint to stream
|
||||||
|
func (stream *Stream) WriteUint(val uint) {
|
||||||
|
stream.WriteUint64(uint64(val))
|
||||||
|
}
|
||||||
372
vendor/github.com/json-iterator/go/stream_str.go
generated
vendored
Normal file
372
vendor/github.com/json-iterator/go/stream_str.go
generated
vendored
Normal file
|
|
@ -0,0 +1,372 @@
|
||||||
|
package jsoniter
|
||||||
|
|
||||||
|
import (
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// htmlSafeSet holds the value true if the ASCII character with the given
|
||||||
|
// array position can be safely represented inside a JSON string, embedded
|
||||||
|
// inside of HTML <script> tags, without any additional escaping.
|
||||||
|
//
|
||||||
|
// All values are true except for the ASCII control characters (0-31), the
|
||||||
|
// double quote ("), the backslash character ("\"), HTML opening and closing
|
||||||
|
// tags ("<" and ">"), and the ampersand ("&").
|
||||||
|
var htmlSafeSet = [utf8.RuneSelf]bool{
|
||||||
|
' ': true,
|
||||||
|
'!': true,
|
||||||
|
'"': false,
|
||||||
|
'#': true,
|
||||||
|
'$': true,
|
||||||
|
'%': true,
|
||||||
|
'&': false,
|
||||||
|
'\'': true,
|
||||||
|
'(': true,
|
||||||
|
')': true,
|
||||||
|
'*': true,
|
||||||
|
'+': true,
|
||||||
|
',': true,
|
||||||
|
'-': true,
|
||||||
|
'.': true,
|
||||||
|
'/': true,
|
||||||
|
'0': true,
|
||||||
|
'1': true,
|
||||||
|
'2': true,
|
||||||
|
'3': true,
|
||||||
|
'4': true,
|
||||||
|
'5': true,
|
||||||
|
'6': true,
|
||||||
|
'7': true,
|
||||||
|
'8': true,
|
||||||
|
'9': true,
|
||||||
|
':': true,
|
||||||
|
';': true,
|
||||||
|
'<': false,
|
||||||
|
'=': true,
|
||||||
|
'>': false,
|
||||||
|
'?': true,
|
||||||
|
'@': true,
|
||||||
|
'A': true,
|
||||||
|
'B': true,
|
||||||
|
'C': true,
|
||||||
|
'D': true,
|
||||||
|
'E': true,
|
||||||
|
'F': true,
|
||||||
|
'G': true,
|
||||||
|
'H': true,
|
||||||
|
'I': true,
|
||||||
|
'J': true,
|
||||||
|
'K': true,
|
||||||
|
'L': true,
|
||||||
|
'M': true,
|
||||||
|
'N': true,
|
||||||
|
'O': true,
|
||||||
|
'P': true,
|
||||||
|
'Q': true,
|
||||||
|
'R': true,
|
||||||
|
'S': true,
|
||||||
|
'T': true,
|
||||||
|
'U': true,
|
||||||
|
'V': true,
|
||||||
|
'W': true,
|
||||||
|
'X': true,
|
||||||
|
'Y': true,
|
||||||
|
'Z': true,
|
||||||
|
'[': true,
|
||||||
|
'\\': false,
|
||||||
|
']': true,
|
||||||
|
'^': true,
|
||||||
|
'_': true,
|
||||||
|
'`': true,
|
||||||
|
'a': true,
|
||||||
|
'b': true,
|
||||||
|
'c': true,
|
||||||
|
'd': true,
|
||||||
|
'e': true,
|
||||||
|
'f': true,
|
||||||
|
'g': true,
|
||||||
|
'h': true,
|
||||||
|
'i': true,
|
||||||
|
'j': true,
|
||||||
|
'k': true,
|
||||||
|
'l': true,
|
||||||
|
'm': true,
|
||||||
|
'n': true,
|
||||||
|
'o': true,
|
||||||
|
'p': true,
|
||||||
|
'q': true,
|
||||||
|
'r': true,
|
||||||
|
's': true,
|
||||||
|
't': true,
|
||||||
|
'u': true,
|
||||||
|
'v': true,
|
||||||
|
'w': true,
|
||||||
|
'x': true,
|
||||||
|
'y': true,
|
||||||
|
'z': true,
|
||||||
|
'{': true,
|
||||||
|
'|': true,
|
||||||
|
'}': true,
|
||||||
|
'~': true,
|
||||||
|
'\u007f': true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// safeSet holds the value true if the ASCII character with the given array
|
||||||
|
// position can be represented inside a JSON string without any further
|
||||||
|
// escaping.
|
||||||
|
//
|
||||||
|
// All values are true except for the ASCII control characters (0-31), the
|
||||||
|
// double quote ("), and the backslash character ("\").
|
||||||
|
var safeSet = [utf8.RuneSelf]bool{
|
||||||
|
' ': true,
|
||||||
|
'!': true,
|
||||||
|
'"': false,
|
||||||
|
'#': true,
|
||||||
|
'$': true,
|
||||||
|
'%': true,
|
||||||
|
'&': true,
|
||||||
|
'\'': true,
|
||||||
|
'(': true,
|
||||||
|
')': true,
|
||||||
|
'*': true,
|
||||||
|
'+': true,
|
||||||
|
',': true,
|
||||||
|
'-': true,
|
||||||
|
'.': true,
|
||||||
|
'/': true,
|
||||||
|
'0': true,
|
||||||
|
'1': true,
|
||||||
|
'2': true,
|
||||||
|
'3': true,
|
||||||
|
'4': true,
|
||||||
|
'5': true,
|
||||||
|
'6': true,
|
||||||
|
'7': true,
|
||||||
|
'8': true,
|
||||||
|
'9': true,
|
||||||
|
':': true,
|
||||||
|
';': true,
|
||||||
|
'<': true,
|
||||||
|
'=': true,
|
||||||
|
'>': true,
|
||||||
|
'?': true,
|
||||||
|
'@': true,
|
||||||
|
'A': true,
|
||||||
|
'B': true,
|
||||||
|
'C': true,
|
||||||
|
'D': true,
|
||||||
|
'E': true,
|
||||||
|
'F': true,
|
||||||
|
'G': true,
|
||||||
|
'H': true,
|
||||||
|
'I': true,
|
||||||
|
'J': true,
|
||||||
|
'K': true,
|
||||||
|
'L': true,
|
||||||
|
'M': true,
|
||||||
|
'N': true,
|
||||||
|
'O': true,
|
||||||
|
'P': true,
|
||||||
|
'Q': true,
|
||||||
|
'R': true,
|
||||||
|
'S': true,
|
||||||
|
'T': true,
|
||||||
|
'U': true,
|
||||||
|
'V': true,
|
||||||
|
'W': true,
|
||||||
|
'X': true,
|
||||||
|
'Y': true,
|
||||||
|
'Z': true,
|
||||||
|
'[': true,
|
||||||
|
'\\': false,
|
||||||
|
']': true,
|
||||||
|
'^': true,
|
||||||
|
'_': true,
|
||||||
|
'`': true,
|
||||||
|
'a': true,
|
||||||
|
'b': true,
|
||||||
|
'c': true,
|
||||||
|
'd': true,
|
||||||
|
'e': true,
|
||||||
|
'f': true,
|
||||||
|
'g': true,
|
||||||
|
'h': true,
|
||||||
|
'i': true,
|
||||||
|
'j': true,
|
||||||
|
'k': true,
|
||||||
|
'l': true,
|
||||||
|
'm': true,
|
||||||
|
'n': true,
|
||||||
|
'o': true,
|
||||||
|
'p': true,
|
||||||
|
'q': true,
|
||||||
|
'r': true,
|
||||||
|
's': true,
|
||||||
|
't': true,
|
||||||
|
'u': true,
|
||||||
|
'v': true,
|
||||||
|
'w': true,
|
||||||
|
'x': true,
|
||||||
|
'y': true,
|
||||||
|
'z': true,
|
||||||
|
'{': true,
|
||||||
|
'|': true,
|
||||||
|
'}': true,
|
||||||
|
'~': true,
|
||||||
|
'\u007f': true,
|
||||||
|
}
|
||||||
|
|
||||||
|
var hex = "0123456789abcdef"
|
||||||
|
|
||||||
|
// WriteStringWithHTMLEscaped write string to stream with html special characters escaped
|
||||||
|
func (stream *Stream) WriteStringWithHTMLEscaped(s string) {
|
||||||
|
valLen := len(s)
|
||||||
|
stream.buf = append(stream.buf, '"')
|
||||||
|
// write string, the fast path, without utf8 and escape support
|
||||||
|
i := 0
|
||||||
|
for ; i < valLen; i++ {
|
||||||
|
c := s[i]
|
||||||
|
if c < utf8.RuneSelf && htmlSafeSet[c] {
|
||||||
|
stream.buf = append(stream.buf, c)
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if i == valLen {
|
||||||
|
stream.buf = append(stream.buf, '"')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeStringSlowPathWithHTMLEscaped(stream, i, s, valLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeStringSlowPathWithHTMLEscaped(stream *Stream, i int, s string, valLen int) {
|
||||||
|
start := i
|
||||||
|
// for the remaining parts, we process them char by char
|
||||||
|
for i < valLen {
|
||||||
|
if b := s[i]; b < utf8.RuneSelf {
|
||||||
|
if htmlSafeSet[b] {
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if start < i {
|
||||||
|
stream.WriteRaw(s[start:i])
|
||||||
|
}
|
||||||
|
switch b {
|
||||||
|
case '\\', '"':
|
||||||
|
stream.writeTwoBytes('\\', b)
|
||||||
|
case '\n':
|
||||||
|
stream.writeTwoBytes('\\', 'n')
|
||||||
|
case '\r':
|
||||||
|
stream.writeTwoBytes('\\', 'r')
|
||||||
|
case '\t':
|
||||||
|
stream.writeTwoBytes('\\', 't')
|
||||||
|
default:
|
||||||
|
// This encodes bytes < 0x20 except for \t, \n and \r.
|
||||||
|
// If escapeHTML is set, it also escapes <, >, and &
|
||||||
|
// because they can lead to security holes when
|
||||||
|
// user-controlled strings are rendered into JSON
|
||||||
|
// and served to some browsers.
|
||||||
|
stream.WriteRaw(`\u00`)
|
||||||
|
stream.writeTwoBytes(hex[b>>4], hex[b&0xF])
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
start = i
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
c, size := utf8.DecodeRuneInString(s[i:])
|
||||||
|
if c == utf8.RuneError && size == 1 {
|
||||||
|
if start < i {
|
||||||
|
stream.WriteRaw(s[start:i])
|
||||||
|
}
|
||||||
|
stream.WriteRaw(`\ufffd`)
|
||||||
|
i++
|
||||||
|
start = i
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// U+2028 is LINE SEPARATOR.
|
||||||
|
// U+2029 is PARAGRAPH SEPARATOR.
|
||||||
|
// They are both technically valid characters in JSON strings,
|
||||||
|
// but don't work in JSONP, which has to be evaluated as JavaScript,
|
||||||
|
// and can lead to security holes there. It is valid JSON to
|
||||||
|
// escape them, so we do so unconditionally.
|
||||||
|
// See http://timelessrepo.com/json-isnt-a-javascript-subset for discussion.
|
||||||
|
if c == '\u2028' || c == '\u2029' {
|
||||||
|
if start < i {
|
||||||
|
stream.WriteRaw(s[start:i])
|
||||||
|
}
|
||||||
|
stream.WriteRaw(`\u202`)
|
||||||
|
stream.writeByte(hex[c&0xF])
|
||||||
|
i += size
|
||||||
|
start = i
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
i += size
|
||||||
|
}
|
||||||
|
if start < len(s) {
|
||||||
|
stream.WriteRaw(s[start:])
|
||||||
|
}
|
||||||
|
stream.writeByte('"')
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteString write string to stream without html escape
|
||||||
|
func (stream *Stream) WriteString(s string) {
|
||||||
|
valLen := len(s)
|
||||||
|
stream.buf = append(stream.buf, '"')
|
||||||
|
// write string, the fast path, without utf8 and escape support
|
||||||
|
i := 0
|
||||||
|
for ; i < valLen; i++ {
|
||||||
|
c := s[i]
|
||||||
|
if c > 31 && c != '"' && c != '\\' {
|
||||||
|
stream.buf = append(stream.buf, c)
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if i == valLen {
|
||||||
|
stream.buf = append(stream.buf, '"')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
writeStringSlowPath(stream, i, s, valLen)
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeStringSlowPath(stream *Stream, i int, s string, valLen int) {
|
||||||
|
start := i
|
||||||
|
// for the remaining parts, we process them char by char
|
||||||
|
for i < valLen {
|
||||||
|
if b := s[i]; b < utf8.RuneSelf {
|
||||||
|
if safeSet[b] {
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if start < i {
|
||||||
|
stream.WriteRaw(s[start:i])
|
||||||
|
}
|
||||||
|
switch b {
|
||||||
|
case '\\', '"':
|
||||||
|
stream.writeTwoBytes('\\', b)
|
||||||
|
case '\n':
|
||||||
|
stream.writeTwoBytes('\\', 'n')
|
||||||
|
case '\r':
|
||||||
|
stream.writeTwoBytes('\\', 'r')
|
||||||
|
case '\t':
|
||||||
|
stream.writeTwoBytes('\\', 't')
|
||||||
|
default:
|
||||||
|
// This encodes bytes < 0x20 except for \t, \n and \r.
|
||||||
|
// If escapeHTML is set, it also escapes <, >, and &
|
||||||
|
// because they can lead to security holes when
|
||||||
|
// user-controlled strings are rendered into JSON
|
||||||
|
// and served to some browsers.
|
||||||
|
stream.WriteRaw(`\u00`)
|
||||||
|
stream.writeTwoBytes(hex[b>>4], hex[b&0xF])
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
start = i
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if start < len(s) {
|
||||||
|
stream.WriteRaw(s[start:])
|
||||||
|
}
|
||||||
|
stream.writeByte('"')
|
||||||
|
}
|
||||||
12
vendor/github.com/json-iterator/go/test.sh
generated
vendored
Normal file
12
vendor/github.com/json-iterator/go/test.sh
generated
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
echo "" > coverage.txt
|
||||||
|
|
||||||
|
for d in $(go list ./... | grep -v vendor); do
|
||||||
|
go test -coverprofile=profile.out -coverpkg=github.com/json-iterator/go $d
|
||||||
|
if [ -f profile.out ]; then
|
||||||
|
cat profile.out >> coverage.txt
|
||||||
|
rm profile.out
|
||||||
|
fi
|
||||||
|
done
|
||||||
304
vendor/github.com/klauspost/compress/LICENSE
generated
vendored
Normal file
304
vendor/github.com/klauspost/compress/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,304 @@
|
||||||
|
Copyright (c) 2012 The Go Authors. All rights reserved.
|
||||||
|
Copyright (c) 2019 Klaus Post. 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.
|
||||||
|
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Files: gzhttp/*
|
||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
copyright license to reproduce, prepare Derivative Works of,
|
||||||
|
publicly display, publicly perform, sublicense, and distribute the
|
||||||
|
Work and such Derivative Works in Source or Object form.
|
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of
|
||||||
|
this License, each Contributor hereby grants to You a perpetual,
|
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||||
|
(except as stated in this section) patent license to make, have made,
|
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||||
|
where such license applies only to those patent claims licensable
|
||||||
|
by such Contributor that are necessarily infringed by their
|
||||||
|
Contribution(s) alone or by combination of their Contribution(s)
|
||||||
|
with the Work to which such Contribution(s) was submitted. If You
|
||||||
|
institute patent litigation against any entity (including a
|
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||||
|
or a Contribution incorporated within the Work constitutes direct
|
||||||
|
or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate
|
||||||
|
as of the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the
|
||||||
|
Work or Derivative Works thereof in any medium, with or without
|
||||||
|
modifications, and in Source or Object form, provided that You
|
||||||
|
meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or
|
||||||
|
Derivative Works a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works
|
||||||
|
that You distribute, all copyright, patent, trademark, and
|
||||||
|
attribution notices from the Source form of the Work,
|
||||||
|
excluding those notices that do not pertain to any part of
|
||||||
|
the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one
|
||||||
|
of the following places: within a NOTICE text file distributed
|
||||||
|
as part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided along with the Derivative Works; or,
|
||||||
|
within a display generated by the Derivative Works, if and
|
||||||
|
wherever such third-party notices normally appear. The contents
|
||||||
|
of the NOTICE file are for informational purposes only and
|
||||||
|
do not modify the License. You may add Your own attribution
|
||||||
|
notices within Derivative Works that You distribute, alongside
|
||||||
|
or as an addendum to the NOTICE text from the Work, provided
|
||||||
|
that such additional attribution notices cannot be construed
|
||||||
|
as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||||
|
any Contribution intentionally submitted for inclusion in the Work
|
||||||
|
by You to the Licensor shall be under the terms and conditions of
|
||||||
|
this License, without any additional terms or conditions.
|
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify
|
||||||
|
the terms of any separate license agreement you may have executed
|
||||||
|
with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade
|
||||||
|
names, trademarks, service marks, or product names of the Licensor,
|
||||||
|
except as required for reasonable and customary use in describing the
|
||||||
|
origin of the Work and reproducing the content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||||
|
agreed to in writing, Licensor provides the Work (and each
|
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||||
|
implied, including, without limitation, any warranties or conditions
|
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||||
|
appropriateness of using or redistributing the Work and assume any
|
||||||
|
risks associated with Your exercise of permissions under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory,
|
||||||
|
whether in tort (including negligence), contract, or otherwise,
|
||||||
|
unless required by applicable law (such as deliberate and grossly
|
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be
|
||||||
|
liable to You for damages, including any direct, indirect, special,
|
||||||
|
incidental, or consequential damages of any character arising as a
|
||||||
|
result of this License or out of the use or inability to use the
|
||||||
|
Work (including but not limited to damages for loss of goodwill,
|
||||||
|
work stoppage, computer failure or malfunction, or any and all
|
||||||
|
other commercial damages or losses), even if such Contributor
|
||||||
|
has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing
|
||||||
|
the Work or Derivative Works thereof, You may choose to offer,
|
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||||
|
or other liability obligations and/or rights consistent with this
|
||||||
|
License. However, in accepting such obligations, You may act only
|
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf
|
||||||
|
of any other Contributor, and only if You agree to indemnify,
|
||||||
|
defend, and hold each Contributor harmless for any liability
|
||||||
|
incurred by, or claims asserted against, such Contributor by reason
|
||||||
|
of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
APPENDIX: How to apply the Apache License to your work.
|
||||||
|
|
||||||
|
To apply the Apache License to your work, attach the following
|
||||||
|
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||||
|
replaced with your own identifying information. (Don't include
|
||||||
|
the brackets!) The text should be enclosed in the appropriate
|
||||||
|
comment syntax for the file format. We also recommend that a
|
||||||
|
file or class name and description of purpose be included on the
|
||||||
|
same "printed page" as the copyright notice for easier
|
||||||
|
identification within third-party archives.
|
||||||
|
|
||||||
|
Copyright 2016-2017 The New York Times Company
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Files: s2/cmd/internal/readahead/*
|
||||||
|
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2015 Klaus Post
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
---------------------
|
||||||
|
Files: snappy/*
|
||||||
|
Files: internal/snapref/*
|
||||||
|
|
||||||
|
Copyright (c) 2011 The Snappy-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.
|
||||||
|
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
Files: s2/cmd/internal/filepathx/*
|
||||||
|
|
||||||
|
Copyright 2016 The filepathx Authors
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
15
vendor/github.com/klauspost/compress/s2/.gitignore
generated
vendored
Normal file
15
vendor/github.com/klauspost/compress/s2/.gitignore
generated
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
testdata/bench
|
||||||
|
|
||||||
|
# These explicitly listed benchmark data files are for an obsolete version of
|
||||||
|
# snappy_test.go.
|
||||||
|
testdata/alice29.txt
|
||||||
|
testdata/asyoulik.txt
|
||||||
|
testdata/fireworks.jpeg
|
||||||
|
testdata/geo.protodata
|
||||||
|
testdata/html
|
||||||
|
testdata/html_x_4
|
||||||
|
testdata/kppkn.gtb
|
||||||
|
testdata/lcet10.txt
|
||||||
|
testdata/paper-100k.pdf
|
||||||
|
testdata/plrabn12.txt
|
||||||
|
testdata/urls.10K
|
||||||
28
vendor/github.com/klauspost/compress/s2/LICENSE
generated
vendored
Normal file
28
vendor/github.com/klauspost/compress/s2/LICENSE
generated
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
Copyright (c) 2011 The Snappy-Go Authors. All rights reserved.
|
||||||
|
Copyright (c) 2019 Klaus Post. 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.
|
||||||
965
vendor/github.com/klauspost/compress/s2/README.md
generated
vendored
Normal file
965
vendor/github.com/klauspost/compress/s2/README.md
generated
vendored
Normal file
|
|
@ -0,0 +1,965 @@
|
||||||
|
# S2 Compression
|
||||||
|
|
||||||
|
S2 is an extension of [Snappy](https://github.com/google/snappy).
|
||||||
|
|
||||||
|
S2 is aimed for high throughput, which is why it features concurrent compression for bigger payloads.
|
||||||
|
|
||||||
|
Decoding is compatible with Snappy compressed content, but content compressed with S2 cannot be decompressed by Snappy.
|
||||||
|
This means that S2 can seamlessly replace Snappy without converting compressed content.
|
||||||
|
|
||||||
|
S2 can produce Snappy compatible output, faster and better than Snappy.
|
||||||
|
If you want full benefit of the changes you should use s2 without Snappy compatibility.
|
||||||
|
|
||||||
|
S2 is designed to have high throughput on content that cannot be compressed.
|
||||||
|
This is important, so you don't have to worry about spending CPU cycles on already compressed data.
|
||||||
|
|
||||||
|
## Benefits over Snappy
|
||||||
|
|
||||||
|
* Better compression
|
||||||
|
* Adjustable compression (3 levels)
|
||||||
|
* Concurrent stream compression
|
||||||
|
* Faster decompression, even for Snappy compatible content
|
||||||
|
* Concurrent Snappy/S2 stream decompression
|
||||||
|
* Ability to quickly skip forward in compressed stream
|
||||||
|
* Random seeking with indexes
|
||||||
|
* Compatible with reading Snappy compressed content
|
||||||
|
* Smaller block size overhead on incompressible blocks
|
||||||
|
* Block concatenation
|
||||||
|
* Uncompressed stream mode
|
||||||
|
* Automatic stream size padding
|
||||||
|
* Snappy compatible block compression
|
||||||
|
|
||||||
|
## Drawbacks over Snappy
|
||||||
|
|
||||||
|
* Not optimized for 32 bit systems
|
||||||
|
* Streams use slightly more memory due to larger blocks and concurrency (configurable)
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
|
||||||
|
Installation: `go get -u github.com/klauspost/compress/s2`
|
||||||
|
|
||||||
|
Full package documentation:
|
||||||
|
|
||||||
|
[![godoc][1]][2]
|
||||||
|
|
||||||
|
[1]: https://godoc.org/github.com/klauspost/compress?status.svg
|
||||||
|
[2]: https://godoc.org/github.com/klauspost/compress/s2
|
||||||
|
|
||||||
|
## Compression
|
||||||
|
|
||||||
|
```Go
|
||||||
|
func EncodeStream(src io.Reader, dst io.Writer) error {
|
||||||
|
enc := s2.NewWriter(dst)
|
||||||
|
_, err := io.Copy(enc, src)
|
||||||
|
if err != nil {
|
||||||
|
enc.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Blocks until compression is done.
|
||||||
|
return enc.Close()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You should always call `enc.Close()`, otherwise you will leak resources and your encode will be incomplete.
|
||||||
|
|
||||||
|
For the best throughput, you should attempt to reuse the `Writer` using the `Reset()` method.
|
||||||
|
|
||||||
|
The Writer in S2 is always buffered, therefore `NewBufferedWriter` in Snappy can be replaced with `NewWriter` in S2.
|
||||||
|
It is possible to flush any buffered data using the `Flush()` method.
|
||||||
|
This will block until all data sent to the encoder has been written to the output.
|
||||||
|
|
||||||
|
S2 also supports the `io.ReaderFrom` interface, which will consume all input from a reader.
|
||||||
|
|
||||||
|
As a final method to compress data, if you have a single block of data you would like to have encoded as a stream,
|
||||||
|
a slightly more efficient method is to use the `EncodeBuffer` method.
|
||||||
|
This will take ownership of the buffer until the stream is closed.
|
||||||
|
|
||||||
|
```Go
|
||||||
|
func EncodeStream(src []byte, dst io.Writer) error {
|
||||||
|
enc := s2.NewWriter(dst)
|
||||||
|
// The encoder owns the buffer until Flush or Close is called.
|
||||||
|
err := enc.EncodeBuffer(buf)
|
||||||
|
if err != nil {
|
||||||
|
enc.Close()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Blocks until compression is done.
|
||||||
|
return enc.Close()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Each call to `EncodeBuffer` will result in discrete blocks being created without buffering,
|
||||||
|
so it should only be used a single time per stream.
|
||||||
|
If you need to write several blocks, you should use the regular io.Writer interface.
|
||||||
|
|
||||||
|
|
||||||
|
## Decompression
|
||||||
|
|
||||||
|
```Go
|
||||||
|
func DecodeStream(src io.Reader, dst io.Writer) error {
|
||||||
|
dec := s2.NewReader(src)
|
||||||
|
_, err := io.Copy(dst, dec)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Similar to the Writer, a Reader can be reused using the `Reset` method.
|
||||||
|
|
||||||
|
For the best possible throughput, there is a `EncodeBuffer(buf []byte)` function available.
|
||||||
|
However, it requires that the provided buffer isn't used after it is handed over to S2 and until the stream is flushed or closed.
|
||||||
|
|
||||||
|
For smaller data blocks, there is also a non-streaming interface: `Encode()`, `EncodeBetter()` and `Decode()`.
|
||||||
|
Do however note that these functions (similar to Snappy) does not provide validation of data,
|
||||||
|
so data corruption may be undetected. Stream encoding provides CRC checks of data.
|
||||||
|
|
||||||
|
It is possible to efficiently skip forward in a compressed stream using the `Skip()` method.
|
||||||
|
For big skips the decompressor is able to skip blocks without decompressing them.
|
||||||
|
|
||||||
|
## Single Blocks
|
||||||
|
|
||||||
|
Similar to Snappy S2 offers single block compression.
|
||||||
|
Blocks do not offer the same flexibility and safety as streams,
|
||||||
|
but may be preferable for very small payloads, less than 100K.
|
||||||
|
|
||||||
|
Using a simple `dst := s2.Encode(nil, src)` will compress `src` and return the compressed result.
|
||||||
|
It is possible to provide a destination buffer.
|
||||||
|
If the buffer has a capacity of `s2.MaxEncodedLen(len(src))` it will be used.
|
||||||
|
If not a new will be allocated.
|
||||||
|
|
||||||
|
Alternatively `EncodeBetter`/`EncodeBest` can also be used for better, but slightly slower compression.
|
||||||
|
|
||||||
|
Similarly to decompress a block you can use `dst, err := s2.Decode(nil, src)`.
|
||||||
|
Again an optional destination buffer can be supplied.
|
||||||
|
The `s2.DecodedLen(src)` can be used to get the minimum capacity needed.
|
||||||
|
If that is not satisfied a new buffer will be allocated.
|
||||||
|
|
||||||
|
Block function always operate on a single goroutine since it should only be used for small payloads.
|
||||||
|
|
||||||
|
# Commandline tools
|
||||||
|
|
||||||
|
Some very simply commandline tools are provided; `s2c` for compression and `s2d` for decompression.
|
||||||
|
|
||||||
|
Binaries can be downloaded on the [Releases Page](https://github.com/klauspost/compress/releases).
|
||||||
|
|
||||||
|
Installing then requires Go to be installed. To install them, use:
|
||||||
|
|
||||||
|
`go install github.com/klauspost/compress/s2/cmd/s2c@latest && go install github.com/klauspost/compress/s2/cmd/s2d@latest`
|
||||||
|
|
||||||
|
To build binaries to the current folder use:
|
||||||
|
|
||||||
|
`go build github.com/klauspost/compress/s2/cmd/s2c && go build github.com/klauspost/compress/s2/cmd/s2d`
|
||||||
|
|
||||||
|
|
||||||
|
## s2c
|
||||||
|
|
||||||
|
```
|
||||||
|
Usage: s2c [options] file1 file2
|
||||||
|
|
||||||
|
Compresses all files supplied as input separately.
|
||||||
|
Output files are written as 'filename.ext.s2' or 'filename.ext.snappy'.
|
||||||
|
By default output files will be overwritten.
|
||||||
|
Use - as the only file name to read from stdin and write to stdout.
|
||||||
|
|
||||||
|
Wildcards are accepted: testdir/*.txt will compress all files in testdir ending with .txt
|
||||||
|
Directories can be wildcards as well. testdir/*/*.txt will match testdir/subdir/b.txt
|
||||||
|
|
||||||
|
File names beginning with 'http://' and 'https://' will be downloaded and compressed.
|
||||||
|
Only http response code 200 is accepted.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-bench int
|
||||||
|
Run benchmark n times. No output will be written
|
||||||
|
-blocksize string
|
||||||
|
Max block size. Examples: 64K, 256K, 1M, 4M. Must be power of two and <= 4MB (default "4M")
|
||||||
|
-c Write all output to stdout. Multiple input files will be concatenated
|
||||||
|
-cpu int
|
||||||
|
Compress using this amount of threads (default 32)
|
||||||
|
-faster
|
||||||
|
Compress faster, but with a minor compression loss
|
||||||
|
-help
|
||||||
|
Display help
|
||||||
|
-index
|
||||||
|
Add seek index (default true)
|
||||||
|
-o string
|
||||||
|
Write output to another file. Single input file only
|
||||||
|
-pad string
|
||||||
|
Pad size to a multiple of this value, Examples: 500, 64K, 256K, 1M, 4M, etc (default "1")
|
||||||
|
-q Don't write any output to terminal, except errors
|
||||||
|
-rm
|
||||||
|
Delete source file(s) after successful compression
|
||||||
|
-safe
|
||||||
|
Do not overwrite output files
|
||||||
|
-slower
|
||||||
|
Compress more, but a lot slower
|
||||||
|
-snappy
|
||||||
|
Generate Snappy compatible output stream
|
||||||
|
-verify
|
||||||
|
Verify written files
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## s2d
|
||||||
|
|
||||||
|
```
|
||||||
|
Usage: s2d [options] file1 file2
|
||||||
|
|
||||||
|
Decompresses all files supplied as input. Input files must end with '.s2' or '.snappy'.
|
||||||
|
Output file names have the extension removed. By default output files will be overwritten.
|
||||||
|
Use - as the only file name to read from stdin and write to stdout.
|
||||||
|
|
||||||
|
Wildcards are accepted: testdir/*.txt will compress all files in testdir ending with .txt
|
||||||
|
Directories can be wildcards as well. testdir/*/*.txt will match testdir/subdir/b.txt
|
||||||
|
|
||||||
|
File names beginning with 'http://' and 'https://' will be downloaded and decompressed.
|
||||||
|
Extensions on downloaded files are ignored. Only http response code 200 is accepted.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-bench int
|
||||||
|
Run benchmark n times. No output will be written
|
||||||
|
-c Write all output to stdout. Multiple input files will be concatenated
|
||||||
|
-help
|
||||||
|
Display help
|
||||||
|
-o string
|
||||||
|
Write output to another file. Single input file only
|
||||||
|
-offset string
|
||||||
|
Start at offset. Examples: 92, 64K, 256K, 1M, 4M. Requires Index
|
||||||
|
-q Don't write any output to terminal, except errors
|
||||||
|
-rm
|
||||||
|
Delete source file(s) after successful decompression
|
||||||
|
-safe
|
||||||
|
Do not overwrite output files
|
||||||
|
-tail string
|
||||||
|
Return last of compressed file. Examples: 92, 64K, 256K, 1M, 4M. Requires Index
|
||||||
|
-verify
|
||||||
|
Verify files, but do not write output
|
||||||
|
```
|
||||||
|
|
||||||
|
## s2sx: self-extracting archives
|
||||||
|
|
||||||
|
s2sx allows creating self-extracting archives with no dependencies.
|
||||||
|
|
||||||
|
By default, executables are created for the same platforms as the host os,
|
||||||
|
but this can be overridden with `-os` and `-arch` parameters.
|
||||||
|
|
||||||
|
Extracted files have 0666 permissions, except when untar option used.
|
||||||
|
|
||||||
|
```
|
||||||
|
Usage: s2sx [options] file1 file2
|
||||||
|
|
||||||
|
Compresses all files supplied as input separately.
|
||||||
|
If files have '.s2' extension they are assumed to be compressed already.
|
||||||
|
Output files are written as 'filename.s2sx' and with '.exe' for windows targets.
|
||||||
|
If output is big, an additional file with ".more" is written. This must be included as well.
|
||||||
|
By default output files will be overwritten.
|
||||||
|
|
||||||
|
Wildcards are accepted: testdir/*.txt will compress all files in testdir ending with .txt
|
||||||
|
Directories can be wildcards as well. testdir/*/*.txt will match testdir/subdir/b.txt
|
||||||
|
|
||||||
|
Options:
|
||||||
|
-arch string
|
||||||
|
Destination architecture (default "amd64")
|
||||||
|
-c Write all output to stdout. Multiple input files will be concatenated
|
||||||
|
-cpu int
|
||||||
|
Compress using this amount of threads (default 32)
|
||||||
|
-help
|
||||||
|
Display help
|
||||||
|
-max string
|
||||||
|
Maximum executable size. Rest will be written to another file. (default "1G")
|
||||||
|
-os string
|
||||||
|
Destination operating system (default "windows")
|
||||||
|
-q Don't write any output to terminal, except errors
|
||||||
|
-rm
|
||||||
|
Delete source file(s) after successful compression
|
||||||
|
-safe
|
||||||
|
Do not overwrite output files
|
||||||
|
-untar
|
||||||
|
Untar on destination
|
||||||
|
```
|
||||||
|
|
||||||
|
Available platforms are:
|
||||||
|
|
||||||
|
* darwin-amd64
|
||||||
|
* darwin-arm64
|
||||||
|
* linux-amd64
|
||||||
|
* linux-arm
|
||||||
|
* linux-arm64
|
||||||
|
* linux-mips64
|
||||||
|
* linux-ppc64le
|
||||||
|
* windows-386
|
||||||
|
* windows-amd64
|
||||||
|
|
||||||
|
By default, there is a size limit of 1GB for the output executable.
|
||||||
|
|
||||||
|
When this is exceeded the remaining file content is written to a file called
|
||||||
|
output+`.more`. This file must be included for a successful extraction and
|
||||||
|
placed alongside the executable for a successful extraction.
|
||||||
|
|
||||||
|
This file *must* have the same name as the executable, so if the executable is renamed,
|
||||||
|
so must the `.more` file.
|
||||||
|
|
||||||
|
This functionality is disabled with stdin/stdout.
|
||||||
|
|
||||||
|
### Self-extracting TAR files
|
||||||
|
|
||||||
|
If you wrap a TAR file you can specify `-untar` to make it untar on the destination host.
|
||||||
|
|
||||||
|
Files are extracted to the current folder with the path specified in the tar file.
|
||||||
|
|
||||||
|
Note that tar files are not validated before they are wrapped.
|
||||||
|
|
||||||
|
For security reasons files that move below the root folder are not allowed.
|
||||||
|
|
||||||
|
# Performance
|
||||||
|
|
||||||
|
This section will focus on comparisons to Snappy.
|
||||||
|
This package is solely aimed at replacing Snappy as a high speed compression package.
|
||||||
|
If you are mainly looking for better compression [zstandard](https://github.com/klauspost/compress/tree/master/zstd#zstd)
|
||||||
|
gives better compression, but typically at speeds slightly below "better" mode in this package.
|
||||||
|
|
||||||
|
Compression is increased compared to Snappy, mostly around 5-20% and the throughput is typically 25-40% increased (single threaded) compared to the Snappy Go implementation.
|
||||||
|
|
||||||
|
Streams are concurrently compressed. The stream will be distributed among all available CPU cores for the best possible throughput.
|
||||||
|
|
||||||
|
A "better" compression mode is also available. This allows to trade a bit of speed for a minor compression gain.
|
||||||
|
The content compressed in this mode is fully compatible with the standard decoder.
|
||||||
|
|
||||||
|
Snappy vs S2 **compression** speed on 16 core (32 thread) computer, using all threads and a single thread (1 CPU):
|
||||||
|
|
||||||
|
| File | S2 speed | S2 Throughput | S2 % smaller | S2 "better" | "better" throughput | "better" % smaller |
|
||||||
|
|-----------------------------------------------------------------------------------------------------|----------|---------------|--------------|-------------|---------------------|--------------------|
|
||||||
|
| [rawstudio-mint14.tar](https://files.klauspost.com/compress/rawstudio-mint14.7z) | 12.70x | 10556 MB/s | 7.35% | 4.15x | 3455 MB/s | 12.79% |
|
||||||
|
| (1 CPU) | 1.14x | 948 MB/s | - | 0.42x | 349 MB/s | - |
|
||||||
|
| [github-june-2days-2019.json](https://files.klauspost.com/compress/github-june-2days-2019.json.zst) | 17.13x | 14484 MB/s | 31.60% | 10.09x | 8533 MB/s | 37.71% |
|
||||||
|
| (1 CPU) | 1.33x | 1127 MB/s | - | 0.70x | 589 MB/s | - |
|
||||||
|
| [github-ranks-backup.bin](https://files.klauspost.com/compress/github-ranks-backup.bin.zst) | 15.14x | 12000 MB/s | -5.79% | 6.59x | 5223 MB/s | 5.80% |
|
||||||
|
| (1 CPU) | 1.11x | 877 MB/s | - | 0.47x | 370 MB/s | - |
|
||||||
|
| [consensus.db.10gb](https://files.klauspost.com/compress/consensus.db.10gb.zst) | 14.62x | 12116 MB/s | 15.90% | 5.35x | 4430 MB/s | 16.08% |
|
||||||
|
| (1 CPU) | 1.38x | 1146 MB/s | - | 0.38x | 312 MB/s | - |
|
||||||
|
| [adresser.json](https://files.klauspost.com/compress/adresser.json.zst) | 8.83x | 17579 MB/s | 43.86% | 6.54x | 13011 MB/s | 47.23% |
|
||||||
|
| (1 CPU) | 1.14x | 2259 MB/s | - | 0.74x | 1475 MB/s | - |
|
||||||
|
| [gob-stream](https://files.klauspost.com/compress/gob-stream.7z) | 16.72x | 14019 MB/s | 24.02% | 10.11x | 8477 MB/s | 30.48% |
|
||||||
|
| (1 CPU) | 1.24x | 1043 MB/s | - | 0.70x | 586 MB/s | - |
|
||||||
|
| [10gb.tar](http://mattmahoney.net/dc/10gb.html) | 13.33x | 9254 MB/s | 1.84% | 6.75x | 4686 MB/s | 6.72% |
|
||||||
|
| (1 CPU) | 0.97x | 672 MB/s | - | 0.53x | 366 MB/s | - |
|
||||||
|
| sharnd.out.2gb | 2.11x | 12639 MB/s | 0.01% | 1.98x | 11833 MB/s | 0.01% |
|
||||||
|
| (1 CPU) | 0.93x | 5594 MB/s | - | 1.34x | 8030 MB/s | - |
|
||||||
|
| [enwik9](http://mattmahoney.net/dc/textdata.html) | 19.34x | 8220 MB/s | 3.98% | 7.87x | 3345 MB/s | 15.82% |
|
||||||
|
| (1 CPU) | 1.06x | 452 MB/s | - | 0.50x | 213 MB/s | - |
|
||||||
|
| [silesia.tar](http://sun.aei.polsl.pl/~sdeor/corpus/silesia.zip) | 10.48x | 6124 MB/s | 5.67% | 3.76x | 2197 MB/s | 12.60% |
|
||||||
|
| (1 CPU) | 0.97x | 568 MB/s | - | 0.46x | 271 MB/s | - |
|
||||||
|
| [enwik10](https://encode.su/threads/3315-enwik10-benchmark-results) | 21.07x | 9020 MB/s | 6.36% | 6.91x | 2959 MB/s | 16.95% |
|
||||||
|
| (1 CPU) | 1.07x | 460 MB/s | - | 0.51x | 220 MB/s | - |
|
||||||
|
|
||||||
|
### Legend
|
||||||
|
|
||||||
|
* `S2 speed`: Speed of S2 compared to Snappy, using 16 cores and 1 core.
|
||||||
|
* `S2 throughput`: Throughput of S2 in MB/s.
|
||||||
|
* `S2 % smaller`: How many percent of the Snappy output size is S2 better.
|
||||||
|
* `S2 "better"`: Speed when enabling "better" compression mode in S2 compared to Snappy.
|
||||||
|
* `"better" throughput`: Speed when enabling "better" compression mode in S2 compared to Snappy.
|
||||||
|
* `"better" % smaller`: How many percent of the Snappy output size is S2 better when using "better" compression.
|
||||||
|
|
||||||
|
There is a good speedup across the board when using a single thread and a significant speedup when using multiple threads.
|
||||||
|
|
||||||
|
Machine generated data gets by far the biggest compression boost, with size being being reduced by up to 45% of Snappy size.
|
||||||
|
|
||||||
|
The "better" compression mode sees a good improvement in all cases, but usually at a performance cost.
|
||||||
|
|
||||||
|
Incompressible content (`sharnd.out.2gb`, 2GB random data) sees the smallest speedup.
|
||||||
|
This is likely dominated by synchronization overhead, which is confirmed by the fact that single threaded performance is higher (see above).
|
||||||
|
|
||||||
|
## Decompression
|
||||||
|
|
||||||
|
S2 attempts to create content that is also fast to decompress, except in "better" mode where the smallest representation is used.
|
||||||
|
|
||||||
|
S2 vs Snappy **decompression** speed. Both operating on single core:
|
||||||
|
|
||||||
|
| File | S2 Throughput | vs. Snappy | Better Throughput | vs. Snappy |
|
||||||
|
|-----------------------------------------------------------------------------------------------------|---------------|------------|-------------------|------------|
|
||||||
|
| [rawstudio-mint14.tar](https://files.klauspost.com/compress/rawstudio-mint14.7z) | 2117 MB/s | 1.14x | 1738 MB/s | 0.94x |
|
||||||
|
| [github-june-2days-2019.json](https://files.klauspost.com/compress/github-june-2days-2019.json.zst) | 2401 MB/s | 1.25x | 2307 MB/s | 1.20x |
|
||||||
|
| [github-ranks-backup.bin](https://files.klauspost.com/compress/github-ranks-backup.bin.zst) | 2075 MB/s | 0.98x | 1764 MB/s | 0.83x |
|
||||||
|
| [consensus.db.10gb](https://files.klauspost.com/compress/consensus.db.10gb.zst) | 2967 MB/s | 1.05x | 2885 MB/s | 1.02x |
|
||||||
|
| [adresser.json](https://files.klauspost.com/compress/adresser.json.zst) | 4141 MB/s | 1.07x | 4184 MB/s | 1.08x |
|
||||||
|
| [gob-stream](https://files.klauspost.com/compress/gob-stream.7z) | 2264 MB/s | 1.12x | 2185 MB/s | 1.08x |
|
||||||
|
| [10gb.tar](http://mattmahoney.net/dc/10gb.html) | 1525 MB/s | 1.03x | 1347 MB/s | 0.91x |
|
||||||
|
| sharnd.out.2gb | 3813 MB/s | 0.79x | 3900 MB/s | 0.81x |
|
||||||
|
| [enwik9](http://mattmahoney.net/dc/textdata.html) | 1246 MB/s | 1.29x | 967 MB/s | 1.00x |
|
||||||
|
| [silesia.tar](http://sun.aei.polsl.pl/~sdeor/corpus/silesia.zip) | 1433 MB/s | 1.12x | 1203 MB/s | 0.94x |
|
||||||
|
| [enwik10](https://encode.su/threads/3315-enwik10-benchmark-results) | 1284 MB/s | 1.32x | 1010 MB/s | 1.04x |
|
||||||
|
|
||||||
|
### Legend
|
||||||
|
|
||||||
|
* `S2 Throughput`: Decompression speed of S2 encoded content.
|
||||||
|
* `Better Throughput`: Decompression speed of S2 "better" encoded content.
|
||||||
|
* `vs Snappy`: Decompression speed of S2 "better" mode compared to Snappy and absolute speed.
|
||||||
|
|
||||||
|
|
||||||
|
While the decompression code hasn't changed, there is a significant speedup in decompression speed.
|
||||||
|
S2 prefers longer matches and will typically only find matches that are 6 bytes or longer.
|
||||||
|
While this reduces compression a bit, it improves decompression speed.
|
||||||
|
|
||||||
|
The "better" compression mode will actively look for shorter matches, which is why it has a decompression speed quite similar to Snappy.
|
||||||
|
|
||||||
|
Without assembly decompression is also very fast; single goroutine decompression speed. No assembly:
|
||||||
|
|
||||||
|
| File | S2 Throughput | S2 throughput |
|
||||||
|
|--------------------------------|--------------|---------------|
|
||||||
|
| consensus.db.10gb.s2 | 1.84x | 2289.8 MB/s |
|
||||||
|
| 10gb.tar.s2 | 1.30x | 867.07 MB/s |
|
||||||
|
| rawstudio-mint14.tar.s2 | 1.66x | 1329.65 MB/s |
|
||||||
|
| github-june-2days-2019.json.s2 | 2.36x | 1831.59 MB/s |
|
||||||
|
| github-ranks-backup.bin.s2 | 1.73x | 1390.7 MB/s |
|
||||||
|
| enwik9.s2 | 1.67x | 681.53 MB/s |
|
||||||
|
| adresser.json.s2 | 3.41x | 4230.53 MB/s |
|
||||||
|
| silesia.tar.s2 | 1.52x | 811.58 |
|
||||||
|
|
||||||
|
Even though S2 typically compresses better than Snappy, decompression speed is always better.
|
||||||
|
|
||||||
|
### Concurrent Stream Decompression
|
||||||
|
|
||||||
|
For full stream decompression S2 offers a [DecodeConcurrent](https://pkg.go.dev/github.com/klauspost/compress/s2#Reader.DecodeConcurrent)
|
||||||
|
that will decode a full stream using multiple goroutines.
|
||||||
|
|
||||||
|
Example scaling, AMD Ryzen 3950X, 16 cores, decompression using `s2d -bench=3 <input>`, best of 3:
|
||||||
|
|
||||||
|
| Input | `-cpu=1` | `-cpu=2` | `-cpu=4` | `-cpu=8` | `-cpu=16` |
|
||||||
|
|-------------------------------------------|------------|------------|------------|------------|-------------|
|
||||||
|
| enwik10.snappy | 1098.6MB/s | 1819.8MB/s | 3625.6MB/s | 6910.6MB/s | 10818.2MB/s |
|
||||||
|
| enwik10.s2 | 1303.5MB/s | 2606.1MB/s | 4847.9MB/s | 8878.4MB/s | 9592.1MB/s |
|
||||||
|
| sofia-air-quality-dataset.tar.snappy | 1302.0MB/s | 2165.0MB/s | 4244.5MB/s | 8241.0MB/s | 12920.5MB/s |
|
||||||
|
| sofia-air-quality-dataset.tar.s2 | 1399.2MB/s | 2463.2MB/s | 5196.5MB/s | 9639.8MB/s | 11439.5MB/s |
|
||||||
|
| sofia-air-quality-dataset.tar.s2 (no asm) | 837.5MB/s | 1652.6MB/s | 3183.6MB/s | 5945.0MB/s | 9620.7MB/s |
|
||||||
|
|
||||||
|
Scaling can be expected to be pretty linear until memory bandwidth is saturated.
|
||||||
|
|
||||||
|
For now the DecodeConcurrent can only be used for full streams without seeking or combining with regular reads.
|
||||||
|
|
||||||
|
## Block compression
|
||||||
|
|
||||||
|
|
||||||
|
When compressing blocks no concurrent compression is performed just as Snappy.
|
||||||
|
This is because blocks are for smaller payloads and generally will not benefit from concurrent compression.
|
||||||
|
|
||||||
|
An important change is that incompressible blocks will not be more than at most 10 bytes bigger than the input.
|
||||||
|
In rare, worst case scenario Snappy blocks could be significantly bigger than the input.
|
||||||
|
|
||||||
|
### Mixed content blocks
|
||||||
|
|
||||||
|
The most reliable is a wide dataset.
|
||||||
|
For this we use [`webdevdata.org-2015-01-07-subset`](https://files.klauspost.com/compress/webdevdata.org-2015-01-07-4GB-subset.7z),
|
||||||
|
53927 files, total input size: 4,014,735,833 bytes. Single goroutine used.
|
||||||
|
|
||||||
|
| * | Input | Output | Reduction | MB/s |
|
||||||
|
|-------------------|------------|------------|-----------|--------|
|
||||||
|
| S2 | 4014735833 | 1059723369 | 73.60% | **934.34** |
|
||||||
|
| S2 Better | 4014735833 | 969670507 | 75.85% | 532.70 |
|
||||||
|
| S2 Best | 4014735833 | 906625668 | **77.85%** | 46.84 |
|
||||||
|
| Snappy | 4014735833 | 1128706759 | 71.89% | 762.59 |
|
||||||
|
| S2, Snappy Output | 4014735833 | 1093821420 | 72.75% | 908.60 |
|
||||||
|
| LZ4 | 4014735833 | 1079259294 | 73.12% | 526.94 |
|
||||||
|
|
||||||
|
S2 delivers both the best single threaded throughput with regular mode and the best compression rate with "best".
|
||||||
|
"Better" mode provides the same compression speed as LZ4 with better compression ratio.
|
||||||
|
|
||||||
|
When outputting Snappy compatible output it still delivers better throughput (150MB/s more) and better compression.
|
||||||
|
|
||||||
|
As can be seen from the other benchmarks decompression should also be easier on the S2 generated output.
|
||||||
|
|
||||||
|
Though they cannot be compared due to different decompression speeds here are the speed/size comparisons for
|
||||||
|
other Go compressors:
|
||||||
|
|
||||||
|
| * | Input | Output | Reduction | MB/s |
|
||||||
|
|-------------------|------------|------------|-----------|--------|
|
||||||
|
| Zstd Fastest (Go) | 4014735833 | 794608518 | 80.21% | 236.04 |
|
||||||
|
| Zstd Best (Go) | 4014735833 | 704603356 | 82.45% | 35.63 |
|
||||||
|
| Deflate (Go) l1 | 4014735833 | 871294239 | 78.30% | 214.04 |
|
||||||
|
| Deflate (Go) l9 | 4014735833 | 730389060 | 81.81% | 41.17 |
|
||||||
|
|
||||||
|
### Standard block compression
|
||||||
|
|
||||||
|
Benchmarking single block performance is subject to a lot more variation since it only tests a limited number of file patterns.
|
||||||
|
So individual benchmarks should only be seen as a guideline and the overall picture is more important.
|
||||||
|
|
||||||
|
These micro-benchmarks are with data in cache and trained branch predictors. For a more realistic benchmark see the mixed content above.
|
||||||
|
|
||||||
|
Block compression. Parallel benchmark running on 16 cores, 16 goroutines.
|
||||||
|
|
||||||
|
AMD64 assembly is use for both S2 and Snappy.
|
||||||
|
|
||||||
|
| Absolute Perf | Snappy size | S2 Size | Snappy Speed | S2 Speed | Snappy dec | S2 dec |
|
||||||
|
|-----------------------|-------------|---------|--------------|-------------|-------------|-------------|
|
||||||
|
| html | 22843 | 21111 | 16246 MB/s | 17438 MB/s | 40972 MB/s | 49263 MB/s |
|
||||||
|
| urls.10K | 335492 | 287326 | 7943 MB/s | 9693 MB/s | 22523 MB/s | 26484 MB/s |
|
||||||
|
| fireworks.jpeg | 123034 | 123100 | 349544 MB/s | 273889 MB/s | 718321 MB/s | 827552 MB/s |
|
||||||
|
| fireworks.jpeg (200B) | 146 | 155 | 8869 MB/s | 17773 MB/s | 33691 MB/s | 52421 MB/s |
|
||||||
|
| paper-100k.pdf | 85304 | 84459 | 167546 MB/s | 101263 MB/s | 326905 MB/s | 291944 MB/s |
|
||||||
|
| html_x_4 | 92234 | 21113 | 15194 MB/s | 50670 MB/s | 30843 MB/s | 32217 MB/s |
|
||||||
|
| alice29.txt | 88034 | 85975 | 5936 MB/s | 6139 MB/s | 12882 MB/s | 20044 MB/s |
|
||||||
|
| asyoulik.txt | 77503 | 79650 | 5517 MB/s | 6366 MB/s | 12735 MB/s | 22806 MB/s |
|
||||||
|
| lcet10.txt | 234661 | 220670 | 6235 MB/s | 6067 MB/s | 14519 MB/s | 18697 MB/s |
|
||||||
|
| plrabn12.txt | 319267 | 317985 | 5159 MB/s | 5726 MB/s | 11923 MB/s | 19901 MB/s |
|
||||||
|
| geo.protodata | 23335 | 18690 | 21220 MB/s | 26529 MB/s | 56271 MB/s | 62540 MB/s |
|
||||||
|
| kppkn.gtb | 69526 | 65312 | 9732 MB/s | 8559 MB/s | 18491 MB/s | 18969 MB/s |
|
||||||
|
| alice29.txt (128B) | 80 | 82 | 6691 MB/s | 15489 MB/s | 31883 MB/s | 38874 MB/s |
|
||||||
|
| alice29.txt (1000B) | 774 | 774 | 12204 MB/s | 13000 MB/s | 48056 MB/s | 52341 MB/s |
|
||||||
|
| alice29.txt (10000B) | 6648 | 6933 | 10044 MB/s | 12806 MB/s | 32378 MB/s | 46322 MB/s |
|
||||||
|
| alice29.txt (20000B) | 12686 | 13574 | 7733 MB/s | 11210 MB/s | 30566 MB/s | 58969 MB/s |
|
||||||
|
|
||||||
|
|
||||||
|
| Relative Perf | Snappy size | S2 size improved | S2 Speed | S2 Dec Speed |
|
||||||
|
|-----------------------|-------------|------------------|----------|--------------|
|
||||||
|
| html | 22.31% | 7.58% | 1.07x | 1.20x |
|
||||||
|
| urls.10K | 47.78% | 14.36% | 1.22x | 1.18x |
|
||||||
|
| fireworks.jpeg | 99.95% | -0.05% | 0.78x | 1.15x |
|
||||||
|
| fireworks.jpeg (200B) | 73.00% | -6.16% | 2.00x | 1.56x |
|
||||||
|
| paper-100k.pdf | 83.30% | 0.99% | 0.60x | 0.89x |
|
||||||
|
| html_x_4 | 22.52% | 77.11% | 3.33x | 1.04x |
|
||||||
|
| alice29.txt | 57.88% | 2.34% | 1.03x | 1.56x |
|
||||||
|
| asyoulik.txt | 61.91% | -2.77% | 1.15x | 1.79x |
|
||||||
|
| lcet10.txt | 54.99% | 5.96% | 0.97x | 1.29x |
|
||||||
|
| plrabn12.txt | 66.26% | 0.40% | 1.11x | 1.67x |
|
||||||
|
| geo.protodata | 19.68% | 19.91% | 1.25x | 1.11x |
|
||||||
|
| kppkn.gtb | 37.72% | 6.06% | 0.88x | 1.03x |
|
||||||
|
| alice29.txt (128B) | 62.50% | -2.50% | 2.31x | 1.22x |
|
||||||
|
| alice29.txt (1000B) | 77.40% | 0.00% | 1.07x | 1.09x |
|
||||||
|
| alice29.txt (10000B) | 66.48% | -4.29% | 1.27x | 1.43x |
|
||||||
|
| alice29.txt (20000B) | 63.43% | -7.00% | 1.45x | 1.93x |
|
||||||
|
|
||||||
|
Speed is generally at or above Snappy. Small blocks gets a significant speedup, although at the expense of size.
|
||||||
|
|
||||||
|
Decompression speed is better than Snappy, except in one case.
|
||||||
|
|
||||||
|
Since payloads are very small the variance in terms of size is rather big, so they should only be seen as a general guideline.
|
||||||
|
|
||||||
|
Size is on average around Snappy, but varies on content type.
|
||||||
|
In cases where compression is worse, it usually is compensated by a speed boost.
|
||||||
|
|
||||||
|
|
||||||
|
### Better compression
|
||||||
|
|
||||||
|
Benchmarking single block performance is subject to a lot more variation since it only tests a limited number of file patterns.
|
||||||
|
So individual benchmarks should only be seen as a guideline and the overall picture is more important.
|
||||||
|
|
||||||
|
| Absolute Perf | Snappy size | Better Size | Snappy Speed | Better Speed | Snappy dec | Better dec |
|
||||||
|
|-----------------------|-------------|-------------|--------------|--------------|-------------|-------------|
|
||||||
|
| html | 22843 | 19833 | 16246 MB/s | 7731 MB/s | 40972 MB/s | 40292 MB/s |
|
||||||
|
| urls.10K | 335492 | 253529 | 7943 MB/s | 3980 MB/s | 22523 MB/s | 20981 MB/s |
|
||||||
|
| fireworks.jpeg | 123034 | 123100 | 349544 MB/s | 9760 MB/s | 718321 MB/s | 823698 MB/s |
|
||||||
|
| fireworks.jpeg (200B) | 146 | 142 | 8869 MB/s | 594 MB/s | 33691 MB/s | 30101 MB/s |
|
||||||
|
| paper-100k.pdf | 85304 | 82915 | 167546 MB/s | 7470 MB/s | 326905 MB/s | 198869 MB/s |
|
||||||
|
| html_x_4 | 92234 | 19841 | 15194 MB/s | 23403 MB/s | 30843 MB/s | 30937 MB/s |
|
||||||
|
| alice29.txt | 88034 | 73218 | 5936 MB/s | 2945 MB/s | 12882 MB/s | 16611 MB/s |
|
||||||
|
| asyoulik.txt | 77503 | 66844 | 5517 MB/s | 2739 MB/s | 12735 MB/s | 14975 MB/s |
|
||||||
|
| lcet10.txt | 234661 | 190589 | 6235 MB/s | 3099 MB/s | 14519 MB/s | 16634 MB/s |
|
||||||
|
| plrabn12.txt | 319267 | 270828 | 5159 MB/s | 2600 MB/s | 11923 MB/s | 13382 MB/s |
|
||||||
|
| geo.protodata | 23335 | 18278 | 21220 MB/s | 11208 MB/s | 56271 MB/s | 57961 MB/s |
|
||||||
|
| kppkn.gtb | 69526 | 61851 | 9732 MB/s | 4556 MB/s | 18491 MB/s | 16524 MB/s |
|
||||||
|
| alice29.txt (128B) | 80 | 81 | 6691 MB/s | 529 MB/s | 31883 MB/s | 34225 MB/s |
|
||||||
|
| alice29.txt (1000B) | 774 | 748 | 12204 MB/s | 1943 MB/s | 48056 MB/s | 42068 MB/s |
|
||||||
|
| alice29.txt (10000B) | 6648 | 6234 | 10044 MB/s | 2949 MB/s | 32378 MB/s | 28813 MB/s |
|
||||||
|
| alice29.txt (20000B) | 12686 | 11584 | 7733 MB/s | 2822 MB/s | 30566 MB/s | 27315 MB/s |
|
||||||
|
|
||||||
|
|
||||||
|
| Relative Perf | Snappy size | Better size | Better Speed | Better dec |
|
||||||
|
|-----------------------|-------------|-------------|--------------|------------|
|
||||||
|
| html | 22.31% | 13.18% | 0.48x | 0.98x |
|
||||||
|
| urls.10K | 47.78% | 24.43% | 0.50x | 0.93x |
|
||||||
|
| fireworks.jpeg | 99.95% | -0.05% | 0.03x | 1.15x |
|
||||||
|
| fireworks.jpeg (200B) | 73.00% | 2.74% | 0.07x | 0.89x |
|
||||||
|
| paper-100k.pdf | 83.30% | 2.80% | 0.07x | 0.61x |
|
||||||
|
| html_x_4 | 22.52% | 78.49% | 0.04x | 1.00x |
|
||||||
|
| alice29.txt | 57.88% | 16.83% | 1.54x | 1.29x |
|
||||||
|
| asyoulik.txt | 61.91% | 13.75% | 0.50x | 1.18x |
|
||||||
|
| lcet10.txt | 54.99% | 18.78% | 0.50x | 1.15x |
|
||||||
|
| plrabn12.txt | 66.26% | 15.17% | 0.50x | 1.12x |
|
||||||
|
| geo.protodata | 19.68% | 21.67% | 0.50x | 1.03x |
|
||||||
|
| kppkn.gtb | 37.72% | 11.04% | 0.53x | 0.89x |
|
||||||
|
| alice29.txt (128B) | 62.50% | -1.25% | 0.47x | 1.07x |
|
||||||
|
| alice29.txt (1000B) | 77.40% | 3.36% | 0.08x | 0.88x |
|
||||||
|
| alice29.txt (10000B) | 66.48% | 6.23% | 0.16x | 0.89x |
|
||||||
|
| alice29.txt (20000B) | 63.43% | 8.69% | 0.29x | 0.89x |
|
||||||
|
|
||||||
|
Except for the mostly incompressible JPEG image compression is better and usually in the
|
||||||
|
double digits in terms of percentage reduction over Snappy.
|
||||||
|
|
||||||
|
The PDF sample shows a significant slowdown compared to Snappy, as this mode tries harder
|
||||||
|
to compress the data. Very small blocks are also not favorable for better compression, so throughput is way down.
|
||||||
|
|
||||||
|
This mode aims to provide better compression at the expense of performance and achieves that
|
||||||
|
without a huge performance penalty, except on very small blocks.
|
||||||
|
|
||||||
|
Decompression speed suffers a little compared to the regular S2 mode,
|
||||||
|
but still manages to be close to Snappy in spite of increased compression.
|
||||||
|
|
||||||
|
# Best compression mode
|
||||||
|
|
||||||
|
S2 offers a "best" compression mode.
|
||||||
|
|
||||||
|
This will compress as much as possible with little regard to CPU usage.
|
||||||
|
|
||||||
|
Mainly for offline compression, but where decompression speed should still
|
||||||
|
be high and compatible with other S2 compressed data.
|
||||||
|
|
||||||
|
Some examples compared on 16 core CPU, amd64 assembly used:
|
||||||
|
|
||||||
|
```
|
||||||
|
* enwik10
|
||||||
|
Default... 10000000000 -> 4761467548 [47.61%]; 1.098s, 8685.6MB/s
|
||||||
|
Better... 10000000000 -> 4219438251 [42.19%]; 1.925s, 4954.2MB/s
|
||||||
|
Best... 10000000000 -> 3627364337 [36.27%]; 43.051s, 221.5MB/s
|
||||||
|
|
||||||
|
* github-june-2days-2019.json
|
||||||
|
Default... 6273951764 -> 1043196283 [16.63%]; 431ms, 13882.3MB/s
|
||||||
|
Better... 6273951764 -> 949146808 [15.13%]; 547ms, 10938.4MB/s
|
||||||
|
Best... 6273951764 -> 832855506 [13.27%]; 9.455s, 632.8MB/s
|
||||||
|
|
||||||
|
* nyc-taxi-data-10M.csv
|
||||||
|
Default... 3325605752 -> 1095998837 [32.96%]; 324ms, 9788.7MB/s
|
||||||
|
Better... 3325605752 -> 954776589 [28.71%]; 491ms, 6459.4MB/s
|
||||||
|
Best... 3325605752 -> 779098746 [23.43%]; 8.29s, 382.6MB/s
|
||||||
|
|
||||||
|
* 10gb.tar
|
||||||
|
Default... 10065157632 -> 5916578242 [58.78%]; 1.028s, 9337.4MB/s
|
||||||
|
Better... 10065157632 -> 5649207485 [56.13%]; 1.597s, 6010.6MB/s
|
||||||
|
Best... 10065157632 -> 5208719802 [51.75%]; 32.78s, 292.8MB/
|
||||||
|
|
||||||
|
* consensus.db.10gb
|
||||||
|
Default... 10737418240 -> 4562648848 [42.49%]; 882ms, 11610.0MB/s
|
||||||
|
Better... 10737418240 -> 4542428129 [42.30%]; 1.533s, 6679.7MB/s
|
||||||
|
Best... 10737418240 -> 4244773384 [39.53%]; 42.96s, 238.4MB/s
|
||||||
|
```
|
||||||
|
|
||||||
|
Decompression speed should be around the same as using the 'better' compression mode.
|
||||||
|
|
||||||
|
# Snappy Compatibility
|
||||||
|
|
||||||
|
S2 now offers full compatibility with Snappy.
|
||||||
|
|
||||||
|
This means that the efficient encoders of S2 can be used to generate fully Snappy compatible output.
|
||||||
|
|
||||||
|
There is a [snappy](https://github.com/klauspost/compress/tree/master/snappy) package that can be used by
|
||||||
|
simply changing imports from `github.com/golang/snappy` to `github.com/klauspost/compress/snappy`.
|
||||||
|
This uses "better" mode for all operations.
|
||||||
|
If you would like more control, you can use the s2 package as described below:
|
||||||
|
|
||||||
|
## Blocks
|
||||||
|
|
||||||
|
Snappy compatible blocks can be generated with the S2 encoder.
|
||||||
|
Compression and speed is typically a bit better `MaxEncodedLen` is also smaller for smaller memory usage. Replace
|
||||||
|
|
||||||
|
| Snappy | S2 replacement |
|
||||||
|
|----------------------------|-------------------------|
|
||||||
|
| snappy.Encode(...) | s2.EncodeSnappy(...) |
|
||||||
|
| snappy.MaxEncodedLen(...) | s2.MaxEncodedLen(...) |
|
||||||
|
|
||||||
|
`s2.EncodeSnappy` can be replaced with `s2.EncodeSnappyBetter` or `s2.EncodeSnappyBest` to get more efficiently compressed snappy compatible output.
|
||||||
|
|
||||||
|
`s2.ConcatBlocks` is compatible with snappy blocks.
|
||||||
|
|
||||||
|
Comparison of [`webdevdata.org-2015-01-07-subset`](https://files.klauspost.com/compress/webdevdata.org-2015-01-07-4GB-subset.7z),
|
||||||
|
53927 files, total input size: 4,014,735,833 bytes. amd64, single goroutine used:
|
||||||
|
|
||||||
|
| Encoder | Size | MB/s | Reduction |
|
||||||
|
|-----------------------|------------|------------|------------
|
||||||
|
| snappy.Encode | 1128706759 | 725.59 | 71.89% |
|
||||||
|
| s2.EncodeSnappy | 1093823291 | **899.16** | 72.75% |
|
||||||
|
| s2.EncodeSnappyBetter | 1001158548 | 578.49 | 75.06% |
|
||||||
|
| s2.EncodeSnappyBest | 944507998 | 66.00 | **76.47%**|
|
||||||
|
|
||||||
|
## Streams
|
||||||
|
|
||||||
|
For streams, replace `enc = snappy.NewBufferedWriter(w)` with `enc = s2.NewWriter(w, s2.WriterSnappyCompat())`.
|
||||||
|
All other options are available, but note that block size limit is different for snappy.
|
||||||
|
|
||||||
|
Comparison of different streams, AMD Ryzen 3950x, 16 cores. Size and throughput:
|
||||||
|
|
||||||
|
| File | snappy.NewWriter | S2 Snappy | S2 Snappy, Better | S2 Snappy, Best |
|
||||||
|
|-----------------------------|--------------------------|---------------------------|--------------------------|-------------------------|
|
||||||
|
| nyc-taxi-data-10M.csv | 1316042016 - 539.47MB/s | 1307003093 - 10132.73MB/s | 1174534014 - 5002.44MB/s | 1115904679 - 177.97MB/s |
|
||||||
|
| enwik10 (xml) | 5088294643 - 451.13MB/s | 5175840939 - 9440.69MB/s | 4560784526 - 4487.21MB/s | 4340299103 - 158.92MB/s |
|
||||||
|
| 10gb.tar (mixed) | 6056946612 - 729.73MB/s | 6208571995 - 9978.05MB/s | 5741646126 - 4919.98MB/s | 5548973895 - 180.44MB/s |
|
||||||
|
| github-june-2days-2019.json | 1525176492 - 933.00MB/s | 1476519054 - 13150.12MB/s | 1400547532 - 5803.40MB/s | 1321887137 - 204.29MB/s |
|
||||||
|
| consensus.db.10gb (db) | 5412897703 - 1102.14MB/s | 5354073487 - 13562.91MB/s | 5335069899 - 5294.73MB/s | 5201000954 - 175.72MB/s |
|
||||||
|
|
||||||
|
# Decompression
|
||||||
|
|
||||||
|
All decompression functions map directly to equivalent s2 functions.
|
||||||
|
|
||||||
|
| Snappy | S2 replacement |
|
||||||
|
|------------------------|--------------------|
|
||||||
|
| snappy.Decode(...) | s2.Decode(...) |
|
||||||
|
| snappy.DecodedLen(...) | s2.DecodedLen(...) |
|
||||||
|
| snappy.NewReader(...) | s2.NewReader(...) |
|
||||||
|
|
||||||
|
Features like [quick forward skipping without decompression](https://pkg.go.dev/github.com/klauspost/compress/s2#Reader.Skip)
|
||||||
|
are also available for Snappy streams.
|
||||||
|
|
||||||
|
If you know you are only decompressing snappy streams, setting [`ReaderMaxBlockSize(64<<10)`](https://pkg.go.dev/github.com/klauspost/compress/s2#ReaderMaxBlockSize)
|
||||||
|
on your Reader will reduce memory consumption.
|
||||||
|
|
||||||
|
# Concatenating blocks and streams.
|
||||||
|
|
||||||
|
Concatenating streams will concatenate the output of both without recompressing them.
|
||||||
|
While this is inefficient in terms of compression it might be usable in certain scenarios.
|
||||||
|
The 10 byte 'stream identifier' of the second stream can optionally be stripped, but it is not a requirement.
|
||||||
|
|
||||||
|
Blocks can be concatenated using the `ConcatBlocks` function.
|
||||||
|
|
||||||
|
Snappy blocks/streams can safely be concatenated with S2 blocks and streams.
|
||||||
|
Streams with indexes (see below) will currently not work on concatenated streams.
|
||||||
|
|
||||||
|
# Stream Seek Index
|
||||||
|
|
||||||
|
S2 and Snappy streams can have indexes. These indexes will allow random seeking within the compressed data.
|
||||||
|
|
||||||
|
The index can either be appended to the stream as a skippable block or returned for separate storage.
|
||||||
|
|
||||||
|
When the index is appended to a stream it will be skipped by regular decoders,
|
||||||
|
so the output remains compatible with other decoders.
|
||||||
|
|
||||||
|
## Creating an Index
|
||||||
|
|
||||||
|
To automatically add an index to a stream, add `WriterAddIndex()` option to your writer.
|
||||||
|
Then the index will be added to the stream when `Close()` is called.
|
||||||
|
|
||||||
|
```
|
||||||
|
// Add Index to stream...
|
||||||
|
enc := s2.NewWriter(w, s2.WriterAddIndex())
|
||||||
|
io.Copy(enc, r)
|
||||||
|
enc.Close()
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want to store the index separately, you can use `CloseIndex()` instead of the regular `Close()`.
|
||||||
|
This will return the index. Note that `CloseIndex()` should only be called once, and you shouldn't call `Close()`.
|
||||||
|
|
||||||
|
```
|
||||||
|
// Get index for separate storage...
|
||||||
|
enc := s2.NewWriter(w)
|
||||||
|
io.Copy(enc, r)
|
||||||
|
index, err := enc.CloseIndex()
|
||||||
|
```
|
||||||
|
|
||||||
|
The `index` can then be used needing to read from the stream.
|
||||||
|
This means the index can be used without needing to seek to the end of the stream
|
||||||
|
or for manually forwarding streams. See below.
|
||||||
|
|
||||||
|
Finally, an existing S2/Snappy stream can be indexed using the `s2.IndexStream(r io.Reader)` function.
|
||||||
|
|
||||||
|
## Using Indexes
|
||||||
|
|
||||||
|
To use indexes there is a `ReadSeeker(random bool, index []byte) (*ReadSeeker, error)` function available.
|
||||||
|
|
||||||
|
Calling ReadSeeker will return an [io.ReadSeeker](https://pkg.go.dev/io#ReadSeeker) compatible version of the reader.
|
||||||
|
|
||||||
|
If 'random' is specified the returned io.Seeker can be used for random seeking, otherwise only forward seeking is supported.
|
||||||
|
Enabling random seeking requires the original input to support the [io.Seeker](https://pkg.go.dev/io#Seeker) interface.
|
||||||
|
|
||||||
|
```
|
||||||
|
dec := s2.NewReader(r)
|
||||||
|
rs, err := dec.ReadSeeker(false, nil)
|
||||||
|
rs.Seek(wantOffset, io.SeekStart)
|
||||||
|
```
|
||||||
|
|
||||||
|
Get a seeker to seek forward. Since no index is provided, the index is read from the stream.
|
||||||
|
This requires that an index was added and that `r` supports the [io.Seeker](https://pkg.go.dev/io#Seeker) interface.
|
||||||
|
|
||||||
|
A custom index can be specified which will be used if supplied.
|
||||||
|
When using a custom index, it will not be read from the input stream.
|
||||||
|
|
||||||
|
```
|
||||||
|
dec := s2.NewReader(r)
|
||||||
|
rs, err := dec.ReadSeeker(false, index)
|
||||||
|
rs.Seek(wantOffset, io.SeekStart)
|
||||||
|
```
|
||||||
|
|
||||||
|
This will read the index from `index`. Since we specify non-random (forward only) seeking `r` does not have to be an io.Seeker
|
||||||
|
|
||||||
|
```
|
||||||
|
dec := s2.NewReader(r)
|
||||||
|
rs, err := dec.ReadSeeker(true, index)
|
||||||
|
rs.Seek(wantOffset, io.SeekStart)
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, since we specify that we want to do random seeking `r` must be an io.Seeker.
|
||||||
|
|
||||||
|
The returned [ReadSeeker](https://pkg.go.dev/github.com/klauspost/compress/s2#ReadSeeker) contains a shallow reference to the existing Reader,
|
||||||
|
meaning changes performed to one is reflected in the other.
|
||||||
|
|
||||||
|
To check if a stream contains an index at the end, the `(*Index).LoadStream(rs io.ReadSeeker) error` can be used.
|
||||||
|
|
||||||
|
## Manually Forwarding Streams
|
||||||
|
|
||||||
|
Indexes can also be read outside the decoder using the [Index](https://pkg.go.dev/github.com/klauspost/compress/s2#Index) type.
|
||||||
|
This can be used for parsing indexes, either separate or in streams.
|
||||||
|
|
||||||
|
In some cases it may not be possible to serve a seekable stream.
|
||||||
|
This can for instance be an HTTP stream, where the Range request
|
||||||
|
is sent at the start of the stream.
|
||||||
|
|
||||||
|
With a little bit of extra code it is still possible to use indexes
|
||||||
|
to forward to specific offset with a single forward skip.
|
||||||
|
|
||||||
|
It is possible to load the index manually like this:
|
||||||
|
```
|
||||||
|
var index s2.Index
|
||||||
|
_, err = index.Load(idxBytes)
|
||||||
|
```
|
||||||
|
|
||||||
|
This can be used to figure out how much to offset the compressed stream:
|
||||||
|
|
||||||
|
```
|
||||||
|
compressedOffset, uncompressedOffset, err := index.Find(wantOffset)
|
||||||
|
```
|
||||||
|
|
||||||
|
The `compressedOffset` is the number of bytes that should be skipped
|
||||||
|
from the beginning of the compressed file.
|
||||||
|
|
||||||
|
The `uncompressedOffset` will then be offset of the uncompressed bytes returned
|
||||||
|
when decoding from that position. This will always be <= wantOffset.
|
||||||
|
|
||||||
|
When creating a decoder it must be specified that it should *not* expect a stream identifier
|
||||||
|
at the beginning of the stream. Assuming the io.Reader `r` has been forwarded to `compressedOffset`
|
||||||
|
we create the decoder like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
dec := s2.NewReader(r, s2.ReaderIgnoreStreamIdentifier())
|
||||||
|
```
|
||||||
|
|
||||||
|
We are not completely done. We still need to forward the stream the uncompressed bytes we didn't want.
|
||||||
|
This is done using the regular "Skip" function:
|
||||||
|
|
||||||
|
```
|
||||||
|
err = dec.Skip(wantOffset - uncompressedOffset)
|
||||||
|
```
|
||||||
|
|
||||||
|
This will ensure that we are at exactly the offset we want, and reading from `dec` will start at the requested offset.
|
||||||
|
|
||||||
|
## Index Format:
|
||||||
|
|
||||||
|
Each block is structured as a snappy skippable block, with the chunk ID 0x99.
|
||||||
|
|
||||||
|
The block can be read from the front, but contains information so it can be read from the back as well.
|
||||||
|
|
||||||
|
Numbers are stored as fixed size little endian values or [zigzag encoded](https://developers.google.com/protocol-buffers/docs/encoding#signed_integers) [base 128 varints](https://developers.google.com/protocol-buffers/docs/encoding),
|
||||||
|
with un-encoded value length of 64 bits, unless other limits are specified.
|
||||||
|
|
||||||
|
| Content | Format |
|
||||||
|
|---------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| ID, `[1]byte` | Always 0x99. |
|
||||||
|
| Data Length, `[3]byte` | 3 byte little-endian length of the chunk in bytes, following this. |
|
||||||
|
| Header `[6]byte` | Header, must be `[115, 50, 105, 100, 120, 0]` or in text: "s2idx\x00". |
|
||||||
|
| UncompressedSize, Varint | Total Uncompressed size. |
|
||||||
|
| CompressedSize, Varint | Total Compressed size if known. Should be -1 if unknown. |
|
||||||
|
| EstBlockSize, Varint | Block Size, used for guessing uncompressed offsets. Must be >= 0. |
|
||||||
|
| Entries, Varint | Number of Entries in index, must be < 65536 and >=0. |
|
||||||
|
| HasUncompressedOffsets `byte` | 0 if no uncompressed offsets are present, 1 if present. Other values are invalid. |
|
||||||
|
| UncompressedOffsets, [Entries]VarInt | Uncompressed offsets. See below how to decode. |
|
||||||
|
| CompressedOffsets, [Entries]VarInt | Compressed offsets. See below how to decode. |
|
||||||
|
| Block Size, `[4]byte` | Little Endian total encoded size (including header and trailer). Can be used for searching backwards to start of block. |
|
||||||
|
| Trailer `[6]byte` | Trailer, must be `[0, 120, 100, 105, 50, 115]` or in text: "\x00xdi2s". Can be used for identifying block from end of stream. |
|
||||||
|
|
||||||
|
For regular streams the uncompressed offsets are fully predictable,
|
||||||
|
so `HasUncompressedOffsets` allows to specify that compressed blocks all have
|
||||||
|
exactly `EstBlockSize` bytes of uncompressed content.
|
||||||
|
|
||||||
|
Entries *must* be in order, starting with the lowest offset,
|
||||||
|
and there *must* be no uncompressed offset duplicates.
|
||||||
|
Entries *may* point to the start of a skippable block,
|
||||||
|
but it is then not allowed to also have an entry for the next block since
|
||||||
|
that would give an uncompressed offset duplicate.
|
||||||
|
|
||||||
|
There is no requirement for all blocks to be represented in the index.
|
||||||
|
In fact there is a maximum of 65536 block entries in an index.
|
||||||
|
|
||||||
|
The writer can use any method to reduce the number of entries.
|
||||||
|
An implicit block start at 0,0 can be assumed.
|
||||||
|
|
||||||
|
### Decoding entries:
|
||||||
|
|
||||||
|
```
|
||||||
|
// Read Uncompressed entries.
|
||||||
|
// Each assumes EstBlockSize delta from previous.
|
||||||
|
for each entry {
|
||||||
|
uOff = 0
|
||||||
|
if HasUncompressedOffsets == 1 {
|
||||||
|
uOff = ReadVarInt // Read value from stream
|
||||||
|
}
|
||||||
|
|
||||||
|
// Except for the first entry, use previous values.
|
||||||
|
if entryNum == 0 {
|
||||||
|
entry[entryNum].UncompressedOffset = uOff
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uncompressed uses previous offset and adds EstBlockSize
|
||||||
|
entry[entryNum].UncompressedOffset = entry[entryNum-1].UncompressedOffset + EstBlockSize + uOff
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Guess that the first block will be 50% of uncompressed size.
|
||||||
|
// Integer truncating division must be used.
|
||||||
|
CompressGuess := EstBlockSize / 2
|
||||||
|
|
||||||
|
// Read Compressed entries.
|
||||||
|
// Each assumes CompressGuess delta from previous.
|
||||||
|
// CompressGuess is adjusted for each value.
|
||||||
|
for each entry {
|
||||||
|
cOff = ReadVarInt // Read value from stream
|
||||||
|
|
||||||
|
// Except for the first entry, use previous values.
|
||||||
|
if entryNum == 0 {
|
||||||
|
entry[entryNum].CompressedOffset = cOff
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compressed uses previous and our estimate.
|
||||||
|
entry[entryNum].CompressedOffset = entry[entryNum-1].CompressedOffset + CompressGuess + cOff
|
||||||
|
|
||||||
|
// Adjust compressed offset for next loop, integer truncating division must be used.
|
||||||
|
CompressGuess += cOff/2
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
To decode from any given uncompressed offset `(wantOffset)`:
|
||||||
|
|
||||||
|
* Iterate entries until `entry[n].UncompressedOffset > wantOffset`.
|
||||||
|
* Start decoding from `entry[n-1].CompressedOffset`.
|
||||||
|
* Discard `entry[n-1].UncompressedOffset - wantOffset` bytes from the decoded stream.
|
||||||
|
|
||||||
|
See [using indexes](https://github.com/klauspost/compress/tree/master/s2#using-indexes) for functions that perform the operations with a simpler interface.
|
||||||
|
|
||||||
|
# Format Extensions
|
||||||
|
|
||||||
|
* Frame [Stream identifier](https://github.com/google/snappy/blob/master/framing_format.txt#L68) changed from `sNaPpY` to `S2sTwO`.
|
||||||
|
* [Framed compressed blocks](https://github.com/google/snappy/blob/master/format_description.txt) can be up to 4MB (up from 64KB).
|
||||||
|
* Compressed blocks can have an offset of `0`, which indicates to repeat the last seen offset.
|
||||||
|
|
||||||
|
Repeat offsets must be encoded as a [2.2.1. Copy with 1-byte offset (01)](https://github.com/google/snappy/blob/master/format_description.txt#L89), where the offset is 0.
|
||||||
|
|
||||||
|
The length is specified by reading the 3-bit length specified in the tag and decode using this table:
|
||||||
|
|
||||||
|
| Length | Actual Length |
|
||||||
|
|--------|----------------------|
|
||||||
|
| 0 | 4 |
|
||||||
|
| 1 | 5 |
|
||||||
|
| 2 | 6 |
|
||||||
|
| 3 | 7 |
|
||||||
|
| 4 | 8 |
|
||||||
|
| 5 | 8 + read 1 byte |
|
||||||
|
| 6 | 260 + read 2 bytes |
|
||||||
|
| 7 | 65540 + read 3 bytes |
|
||||||
|
|
||||||
|
This allows any repeat offset + length to be represented by 2 to 5 bytes.
|
||||||
|
|
||||||
|
Lengths are stored as little endian values.
|
||||||
|
|
||||||
|
The first copy of a block cannot be a repeat offset and the offset is not carried across blocks in streams.
|
||||||
|
|
||||||
|
Default streaming block size is 1MB.
|
||||||
|
|
||||||
|
# LICENSE
|
||||||
|
|
||||||
|
This code is based on the [Snappy-Go](https://github.com/golang/snappy) implementation.
|
||||||
|
|
||||||
|
Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
|
||||||
1046
vendor/github.com/klauspost/compress/s2/decode.go
generated
vendored
Normal file
1046
vendor/github.com/klauspost/compress/s2/decode.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
568
vendor/github.com/klauspost/compress/s2/decode_amd64.s
generated
vendored
Normal file
568
vendor/github.com/klauspost/compress/s2/decode_amd64.s
generated
vendored
Normal file
|
|
@ -0,0 +1,568 @@
|
||||||
|
// Copyright 2016 The Go Authors. All rights reserved.
|
||||||
|
// Copyright (c) 2019 Klaus Post. 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
|
||||||
|
// +build gc
|
||||||
|
// +build !noasm
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
#define R_TMP0 AX
|
||||||
|
#define R_TMP1 BX
|
||||||
|
#define R_LEN CX
|
||||||
|
#define R_OFF DX
|
||||||
|
#define R_SRC SI
|
||||||
|
#define R_DST DI
|
||||||
|
#define R_DBASE R8
|
||||||
|
#define R_DLEN R9
|
||||||
|
#define R_DEND R10
|
||||||
|
#define R_SBASE R11
|
||||||
|
#define R_SLEN R12
|
||||||
|
#define R_SEND R13
|
||||||
|
#define R_TMP2 R14
|
||||||
|
#define R_TMP3 R15
|
||||||
|
|
||||||
|
// The asm code generally follows the pure Go code in decode_other.go, except
|
||||||
|
// where marked with a "!!!".
|
||||||
|
|
||||||
|
// func decode(dst, src []byte) int
|
||||||
|
//
|
||||||
|
// All local variables fit into registers. The non-zero stack size is only to
|
||||||
|
// spill registers and push args when issuing a CALL. The register allocation:
|
||||||
|
// - R_TMP0 scratch
|
||||||
|
// - R_TMP1 scratch
|
||||||
|
// - R_LEN length or x (shared)
|
||||||
|
// - R_OFF offset
|
||||||
|
// - R_SRC &src[s]
|
||||||
|
// - R_DST &dst[d]
|
||||||
|
// + R_DBASE dst_base
|
||||||
|
// + R_DLEN dst_len
|
||||||
|
// + R_DEND dst_base + dst_len
|
||||||
|
// + R_SBASE src_base
|
||||||
|
// + R_SLEN src_len
|
||||||
|
// + R_SEND src_base + src_len
|
||||||
|
// - R_TMP2 used by doCopy
|
||||||
|
// - R_TMP3 used by doCopy
|
||||||
|
//
|
||||||
|
// The registers R_DBASE-R_SEND (marked with a "+") are set at the start of the
|
||||||
|
// function, and after a CALL returns, and are not otherwise modified.
|
||||||
|
//
|
||||||
|
// The d variable is implicitly R_DST - R_DBASE, and len(dst)-d is R_DEND - R_DST.
|
||||||
|
// The s variable is implicitly R_SRC - R_SBASE, and len(src)-s is R_SEND - R_SRC.
|
||||||
|
TEXT ·s2Decode(SB), NOSPLIT, $48-56
|
||||||
|
// Initialize R_SRC, R_DST and R_DBASE-R_SEND.
|
||||||
|
MOVQ dst_base+0(FP), R_DBASE
|
||||||
|
MOVQ dst_len+8(FP), R_DLEN
|
||||||
|
MOVQ R_DBASE, R_DST
|
||||||
|
MOVQ R_DBASE, R_DEND
|
||||||
|
ADDQ R_DLEN, R_DEND
|
||||||
|
MOVQ src_base+24(FP), R_SBASE
|
||||||
|
MOVQ src_len+32(FP), R_SLEN
|
||||||
|
MOVQ R_SBASE, R_SRC
|
||||||
|
MOVQ R_SBASE, R_SEND
|
||||||
|
ADDQ R_SLEN, R_SEND
|
||||||
|
XORQ R_OFF, R_OFF
|
||||||
|
|
||||||
|
loop:
|
||||||
|
// for s < len(src)
|
||||||
|
CMPQ R_SRC, R_SEND
|
||||||
|
JEQ end
|
||||||
|
|
||||||
|
// R_LEN = uint32(src[s])
|
||||||
|
//
|
||||||
|
// switch src[s] & 0x03
|
||||||
|
MOVBLZX (R_SRC), R_LEN
|
||||||
|
MOVL R_LEN, R_TMP1
|
||||||
|
ANDL $3, R_TMP1
|
||||||
|
CMPL R_TMP1, $1
|
||||||
|
JAE tagCopy
|
||||||
|
|
||||||
|
// ----------------------------------------
|
||||||
|
// The code below handles literal tags.
|
||||||
|
|
||||||
|
// case tagLiteral:
|
||||||
|
// x := uint32(src[s] >> 2)
|
||||||
|
// switch
|
||||||
|
SHRL $2, R_LEN
|
||||||
|
CMPL R_LEN, $60
|
||||||
|
JAE tagLit60Plus
|
||||||
|
|
||||||
|
// case x < 60:
|
||||||
|
// s++
|
||||||
|
INCQ R_SRC
|
||||||
|
|
||||||
|
doLit:
|
||||||
|
// This is the end of the inner "switch", when we have a literal tag.
|
||||||
|
//
|
||||||
|
// We assume that R_LEN == x and x fits in a uint32, where x is the variable
|
||||||
|
// used in the pure Go decode_other.go code.
|
||||||
|
|
||||||
|
// length = int(x) + 1
|
||||||
|
//
|
||||||
|
// Unlike the pure Go code, we don't need to check if length <= 0 because
|
||||||
|
// R_LEN can hold 64 bits, so the increment cannot overflow.
|
||||||
|
INCQ R_LEN
|
||||||
|
|
||||||
|
// Prepare to check if copying length bytes will run past the end of dst or
|
||||||
|
// src.
|
||||||
|
//
|
||||||
|
// R_TMP0 = len(dst) - d
|
||||||
|
// R_TMP1 = len(src) - s
|
||||||
|
MOVQ R_DEND, R_TMP0
|
||||||
|
SUBQ R_DST, R_TMP0
|
||||||
|
MOVQ R_SEND, R_TMP1
|
||||||
|
SUBQ R_SRC, R_TMP1
|
||||||
|
|
||||||
|
// !!! Try a faster technique for short (16 or fewer bytes) copies.
|
||||||
|
//
|
||||||
|
// if length > 16 || len(dst)-d < 16 || len(src)-s < 16 {
|
||||||
|
// goto callMemmove // Fall back on calling runtime·memmove.
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// The C++ snappy code calls this TryFastAppend. It also checks len(src)-s
|
||||||
|
// against 21 instead of 16, because it cannot assume that all of its input
|
||||||
|
// is contiguous in memory and so it needs to leave enough source bytes to
|
||||||
|
// read the next tag without refilling buffers, but Go's Decode assumes
|
||||||
|
// contiguousness (the src argument is a []byte).
|
||||||
|
CMPQ R_LEN, $16
|
||||||
|
JGT callMemmove
|
||||||
|
CMPQ R_TMP0, $16
|
||||||
|
JLT callMemmove
|
||||||
|
CMPQ R_TMP1, $16
|
||||||
|
JLT callMemmove
|
||||||
|
|
||||||
|
// !!! Implement the copy from src to dst as a 16-byte load and store.
|
||||||
|
// (Decode's documentation says that dst and src must not overlap.)
|
||||||
|
//
|
||||||
|
// This always copies 16 bytes, instead of only length bytes, but that's
|
||||||
|
// OK. If the input is a valid Snappy encoding then subsequent iterations
|
||||||
|
// will fix up the overrun. Otherwise, Decode returns a nil []byte (and a
|
||||||
|
// non-nil error), so the overrun will be ignored.
|
||||||
|
//
|
||||||
|
// Note that on amd64, it is legal and cheap to issue unaligned 8-byte or
|
||||||
|
// 16-byte loads and stores. This technique probably wouldn't be as
|
||||||
|
// effective on architectures that are fussier about alignment.
|
||||||
|
MOVOU 0(R_SRC), X0
|
||||||
|
MOVOU X0, 0(R_DST)
|
||||||
|
|
||||||
|
// d += length
|
||||||
|
// s += length
|
||||||
|
ADDQ R_LEN, R_DST
|
||||||
|
ADDQ R_LEN, R_SRC
|
||||||
|
JMP loop
|
||||||
|
|
||||||
|
callMemmove:
|
||||||
|
// if length > len(dst)-d || length > len(src)-s { etc }
|
||||||
|
CMPQ R_LEN, R_TMP0
|
||||||
|
JGT errCorrupt
|
||||||
|
CMPQ R_LEN, R_TMP1
|
||||||
|
JGT errCorrupt
|
||||||
|
|
||||||
|
// copy(dst[d:], src[s:s+length])
|
||||||
|
//
|
||||||
|
// This means calling runtime·memmove(&dst[d], &src[s], length), so we push
|
||||||
|
// R_DST, R_SRC and R_LEN as arguments. Coincidentally, we also need to spill those
|
||||||
|
// three registers to the stack, to save local variables across the CALL.
|
||||||
|
MOVQ R_DST, 0(SP)
|
||||||
|
MOVQ R_SRC, 8(SP)
|
||||||
|
MOVQ R_LEN, 16(SP)
|
||||||
|
MOVQ R_DST, 24(SP)
|
||||||
|
MOVQ R_SRC, 32(SP)
|
||||||
|
MOVQ R_LEN, 40(SP)
|
||||||
|
MOVQ R_OFF, 48(SP)
|
||||||
|
CALL runtime·memmove(SB)
|
||||||
|
|
||||||
|
// Restore local variables: unspill registers from the stack and
|
||||||
|
// re-calculate R_DBASE-R_SEND.
|
||||||
|
MOVQ 24(SP), R_DST
|
||||||
|
MOVQ 32(SP), R_SRC
|
||||||
|
MOVQ 40(SP), R_LEN
|
||||||
|
MOVQ 48(SP), R_OFF
|
||||||
|
MOVQ dst_base+0(FP), R_DBASE
|
||||||
|
MOVQ dst_len+8(FP), R_DLEN
|
||||||
|
MOVQ R_DBASE, R_DEND
|
||||||
|
ADDQ R_DLEN, R_DEND
|
||||||
|
MOVQ src_base+24(FP), R_SBASE
|
||||||
|
MOVQ src_len+32(FP), R_SLEN
|
||||||
|
MOVQ R_SBASE, R_SEND
|
||||||
|
ADDQ R_SLEN, R_SEND
|
||||||
|
|
||||||
|
// d += length
|
||||||
|
// s += length
|
||||||
|
ADDQ R_LEN, R_DST
|
||||||
|
ADDQ R_LEN, R_SRC
|
||||||
|
JMP loop
|
||||||
|
|
||||||
|
tagLit60Plus:
|
||||||
|
// !!! This fragment does the
|
||||||
|
//
|
||||||
|
// s += x - 58; if uint(s) > uint(len(src)) { etc }
|
||||||
|
//
|
||||||
|
// checks. In the asm version, we code it once instead of once per switch case.
|
||||||
|
ADDQ R_LEN, R_SRC
|
||||||
|
SUBQ $58, R_SRC
|
||||||
|
CMPQ R_SRC, R_SEND
|
||||||
|
JA errCorrupt
|
||||||
|
|
||||||
|
// case x == 60:
|
||||||
|
CMPL R_LEN, $61
|
||||||
|
JEQ tagLit61
|
||||||
|
JA tagLit62Plus
|
||||||
|
|
||||||
|
// x = uint32(src[s-1])
|
||||||
|
MOVBLZX -1(R_SRC), R_LEN
|
||||||
|
JMP doLit
|
||||||
|
|
||||||
|
tagLit61:
|
||||||
|
// case x == 61:
|
||||||
|
// x = uint32(src[s-2]) | uint32(src[s-1])<<8
|
||||||
|
MOVWLZX -2(R_SRC), R_LEN
|
||||||
|
JMP doLit
|
||||||
|
|
||||||
|
tagLit62Plus:
|
||||||
|
CMPL R_LEN, $62
|
||||||
|
JA tagLit63
|
||||||
|
|
||||||
|
// case x == 62:
|
||||||
|
// x = uint32(src[s-3]) | uint32(src[s-2])<<8 | uint32(src[s-1])<<16
|
||||||
|
// We read one byte, safe to read one back, since we are just reading tag.
|
||||||
|
// x = binary.LittleEndian.Uint32(src[s-1:]) >> 8
|
||||||
|
MOVL -4(R_SRC), R_LEN
|
||||||
|
SHRL $8, R_LEN
|
||||||
|
JMP doLit
|
||||||
|
|
||||||
|
tagLit63:
|
||||||
|
// case x == 63:
|
||||||
|
// x = uint32(src[s-4]) | uint32(src[s-3])<<8 | uint32(src[s-2])<<16 | uint32(src[s-1])<<24
|
||||||
|
MOVL -4(R_SRC), R_LEN
|
||||||
|
JMP doLit
|
||||||
|
|
||||||
|
// The code above handles literal tags.
|
||||||
|
// ----------------------------------------
|
||||||
|
// The code below handles copy tags.
|
||||||
|
|
||||||
|
tagCopy4:
|
||||||
|
// case tagCopy4:
|
||||||
|
// s += 5
|
||||||
|
ADDQ $5, R_SRC
|
||||||
|
|
||||||
|
// if uint(s) > uint(len(src)) { etc }
|
||||||
|
CMPQ R_SRC, R_SEND
|
||||||
|
JA errCorrupt
|
||||||
|
|
||||||
|
// length = 1 + int(src[s-5])>>2
|
||||||
|
SHRQ $2, R_LEN
|
||||||
|
INCQ R_LEN
|
||||||
|
|
||||||
|
// offset = int(uint32(src[s-4]) | uint32(src[s-3])<<8 | uint32(src[s-2])<<16 | uint32(src[s-1])<<24)
|
||||||
|
MOVLQZX -4(R_SRC), R_OFF
|
||||||
|
JMP doCopy
|
||||||
|
|
||||||
|
tagCopy2:
|
||||||
|
// case tagCopy2:
|
||||||
|
// s += 3
|
||||||
|
ADDQ $3, R_SRC
|
||||||
|
|
||||||
|
// if uint(s) > uint(len(src)) { etc }
|
||||||
|
CMPQ R_SRC, R_SEND
|
||||||
|
JA errCorrupt
|
||||||
|
|
||||||
|
// length = 1 + int(src[s-3])>>2
|
||||||
|
SHRQ $2, R_LEN
|
||||||
|
INCQ R_LEN
|
||||||
|
|
||||||
|
// offset = int(uint32(src[s-2]) | uint32(src[s-1])<<8)
|
||||||
|
MOVWQZX -2(R_SRC), R_OFF
|
||||||
|
JMP doCopy
|
||||||
|
|
||||||
|
tagCopy:
|
||||||
|
// We have a copy tag. We assume that:
|
||||||
|
// - R_TMP1 == src[s] & 0x03
|
||||||
|
// - R_LEN == src[s]
|
||||||
|
CMPQ R_TMP1, $2
|
||||||
|
JEQ tagCopy2
|
||||||
|
JA tagCopy4
|
||||||
|
|
||||||
|
// case tagCopy1:
|
||||||
|
// s += 2
|
||||||
|
ADDQ $2, R_SRC
|
||||||
|
|
||||||
|
// if uint(s) > uint(len(src)) { etc }
|
||||||
|
CMPQ R_SRC, R_SEND
|
||||||
|
JA errCorrupt
|
||||||
|
|
||||||
|
// offset = int(uint32(src[s-2])&0xe0<<3 | uint32(src[s-1]))
|
||||||
|
// length = 4 + int(src[s-2])>>2&0x7
|
||||||
|
MOVBQZX -1(R_SRC), R_TMP1
|
||||||
|
MOVQ R_LEN, R_TMP0
|
||||||
|
SHRQ $2, R_LEN
|
||||||
|
ANDQ $0xe0, R_TMP0
|
||||||
|
ANDQ $7, R_LEN
|
||||||
|
SHLQ $3, R_TMP0
|
||||||
|
ADDQ $4, R_LEN
|
||||||
|
ORQ R_TMP1, R_TMP0
|
||||||
|
|
||||||
|
// check if repeat code, ZF set by ORQ.
|
||||||
|
JZ repeatCode
|
||||||
|
|
||||||
|
// This is a regular copy, transfer our temporary value to R_OFF (length)
|
||||||
|
MOVQ R_TMP0, R_OFF
|
||||||
|
JMP doCopy
|
||||||
|
|
||||||
|
// This is a repeat code.
|
||||||
|
repeatCode:
|
||||||
|
// If length < 9, reuse last offset, with the length already calculated.
|
||||||
|
CMPQ R_LEN, $9
|
||||||
|
JL doCopyRepeat
|
||||||
|
|
||||||
|
// Read additional bytes for length.
|
||||||
|
JE repeatLen1
|
||||||
|
|
||||||
|
// Rare, so the extra branch shouldn't hurt too much.
|
||||||
|
CMPQ R_LEN, $10
|
||||||
|
JE repeatLen2
|
||||||
|
JMP repeatLen3
|
||||||
|
|
||||||
|
// Read repeat lengths.
|
||||||
|
repeatLen1:
|
||||||
|
// s ++
|
||||||
|
ADDQ $1, R_SRC
|
||||||
|
|
||||||
|
// if uint(s) > uint(len(src)) { etc }
|
||||||
|
CMPQ R_SRC, R_SEND
|
||||||
|
JA errCorrupt
|
||||||
|
|
||||||
|
// length = src[s-1] + 8
|
||||||
|
MOVBQZX -1(R_SRC), R_LEN
|
||||||
|
ADDL $8, R_LEN
|
||||||
|
JMP doCopyRepeat
|
||||||
|
|
||||||
|
repeatLen2:
|
||||||
|
// s +=2
|
||||||
|
ADDQ $2, R_SRC
|
||||||
|
|
||||||
|
// if uint(s) > uint(len(src)) { etc }
|
||||||
|
CMPQ R_SRC, R_SEND
|
||||||
|
JA errCorrupt
|
||||||
|
|
||||||
|
// length = uint32(src[s-2]) | (uint32(src[s-1])<<8) + (1 << 8)
|
||||||
|
MOVWQZX -2(R_SRC), R_LEN
|
||||||
|
ADDL $260, R_LEN
|
||||||
|
JMP doCopyRepeat
|
||||||
|
|
||||||
|
repeatLen3:
|
||||||
|
// s +=3
|
||||||
|
ADDQ $3, R_SRC
|
||||||
|
|
||||||
|
// if uint(s) > uint(len(src)) { etc }
|
||||||
|
CMPQ R_SRC, R_SEND
|
||||||
|
JA errCorrupt
|
||||||
|
|
||||||
|
// length = uint32(src[s-3]) | (uint32(src[s-2])<<8) | (uint32(src[s-1])<<16) + (1 << 16)
|
||||||
|
// Read one byte further back (just part of the tag, shifted out)
|
||||||
|
MOVL -4(R_SRC), R_LEN
|
||||||
|
SHRL $8, R_LEN
|
||||||
|
ADDL $65540, R_LEN
|
||||||
|
JMP doCopyRepeat
|
||||||
|
|
||||||
|
doCopy:
|
||||||
|
// This is the end of the outer "switch", when we have a copy tag.
|
||||||
|
//
|
||||||
|
// We assume that:
|
||||||
|
// - R_LEN == length && R_LEN > 0
|
||||||
|
// - R_OFF == offset
|
||||||
|
|
||||||
|
// if d < offset { etc }
|
||||||
|
MOVQ R_DST, R_TMP1
|
||||||
|
SUBQ R_DBASE, R_TMP1
|
||||||
|
CMPQ R_TMP1, R_OFF
|
||||||
|
JLT errCorrupt
|
||||||
|
|
||||||
|
// Repeat values can skip the test above, since any offset > 0 will be in dst.
|
||||||
|
doCopyRepeat:
|
||||||
|
// if offset <= 0 { etc }
|
||||||
|
CMPQ R_OFF, $0
|
||||||
|
JLE errCorrupt
|
||||||
|
|
||||||
|
// if length > len(dst)-d { etc }
|
||||||
|
MOVQ R_DEND, R_TMP1
|
||||||
|
SUBQ R_DST, R_TMP1
|
||||||
|
CMPQ R_LEN, R_TMP1
|
||||||
|
JGT errCorrupt
|
||||||
|
|
||||||
|
// forwardCopy(dst[d:d+length], dst[d-offset:]); d += length
|
||||||
|
//
|
||||||
|
// Set:
|
||||||
|
// - R_TMP2 = len(dst)-d
|
||||||
|
// - R_TMP3 = &dst[d-offset]
|
||||||
|
MOVQ R_DEND, R_TMP2
|
||||||
|
SUBQ R_DST, R_TMP2
|
||||||
|
MOVQ R_DST, R_TMP3
|
||||||
|
SUBQ R_OFF, R_TMP3
|
||||||
|
|
||||||
|
// !!! Try a faster technique for short (16 or fewer bytes) forward copies.
|
||||||
|
//
|
||||||
|
// First, try using two 8-byte load/stores, similar to the doLit technique
|
||||||
|
// above. Even if dst[d:d+length] and dst[d-offset:] can overlap, this is
|
||||||
|
// still OK if offset >= 8. Note that this has to be two 8-byte load/stores
|
||||||
|
// and not one 16-byte load/store, and the first store has to be before the
|
||||||
|
// second load, due to the overlap if offset is in the range [8, 16).
|
||||||
|
//
|
||||||
|
// if length > 16 || offset < 8 || len(dst)-d < 16 {
|
||||||
|
// goto slowForwardCopy
|
||||||
|
// }
|
||||||
|
// copy 16 bytes
|
||||||
|
// d += length
|
||||||
|
CMPQ R_LEN, $16
|
||||||
|
JGT slowForwardCopy
|
||||||
|
CMPQ R_OFF, $8
|
||||||
|
JLT slowForwardCopy
|
||||||
|
CMPQ R_TMP2, $16
|
||||||
|
JLT slowForwardCopy
|
||||||
|
MOVQ 0(R_TMP3), R_TMP0
|
||||||
|
MOVQ R_TMP0, 0(R_DST)
|
||||||
|
MOVQ 8(R_TMP3), R_TMP1
|
||||||
|
MOVQ R_TMP1, 8(R_DST)
|
||||||
|
ADDQ R_LEN, R_DST
|
||||||
|
JMP loop
|
||||||
|
|
||||||
|
slowForwardCopy:
|
||||||
|
// !!! If the forward copy is longer than 16 bytes, or if offset < 8, we
|
||||||
|
// can still try 8-byte load stores, provided we can overrun up to 10 extra
|
||||||
|
// bytes. As above, the overrun will be fixed up by subsequent iterations
|
||||||
|
// of the outermost loop.
|
||||||
|
//
|
||||||
|
// The C++ snappy code calls this technique IncrementalCopyFastPath. Its
|
||||||
|
// commentary says:
|
||||||
|
//
|
||||||
|
// ----
|
||||||
|
//
|
||||||
|
// The main part of this loop is a simple copy of eight bytes at a time
|
||||||
|
// until we've copied (at least) the requested amount of bytes. However,
|
||||||
|
// if d and d-offset are less than eight bytes apart (indicating a
|
||||||
|
// repeating pattern of length < 8), we first need to expand the pattern in
|
||||||
|
// order to get the correct results. For instance, if the buffer looks like
|
||||||
|
// this, with the eight-byte <d-offset> and <d> patterns marked as
|
||||||
|
// intervals:
|
||||||
|
//
|
||||||
|
// abxxxxxxxxxxxx
|
||||||
|
// [------] d-offset
|
||||||
|
// [------] d
|
||||||
|
//
|
||||||
|
// a single eight-byte copy from <d-offset> to <d> will repeat the pattern
|
||||||
|
// once, after which we can move <d> two bytes without moving <d-offset>:
|
||||||
|
//
|
||||||
|
// ababxxxxxxxxxx
|
||||||
|
// [------] d-offset
|
||||||
|
// [------] d
|
||||||
|
//
|
||||||
|
// and repeat the exercise until the two no longer overlap.
|
||||||
|
//
|
||||||
|
// This allows us to do very well in the special case of one single byte
|
||||||
|
// repeated many times, without taking a big hit for more general cases.
|
||||||
|
//
|
||||||
|
// The worst case of extra writing past the end of the match occurs when
|
||||||
|
// offset == 1 and length == 1; the last copy will read from byte positions
|
||||||
|
// [0..7] and write to [4..11], whereas it was only supposed to write to
|
||||||
|
// position 1. Thus, ten excess bytes.
|
||||||
|
//
|
||||||
|
// ----
|
||||||
|
//
|
||||||
|
// That "10 byte overrun" worst case is confirmed by Go's
|
||||||
|
// TestSlowForwardCopyOverrun, which also tests the fixUpSlowForwardCopy
|
||||||
|
// and finishSlowForwardCopy algorithm.
|
||||||
|
//
|
||||||
|
// if length > len(dst)-d-10 {
|
||||||
|
// goto verySlowForwardCopy
|
||||||
|
// }
|
||||||
|
SUBQ $10, R_TMP2
|
||||||
|
CMPQ R_LEN, R_TMP2
|
||||||
|
JGT verySlowForwardCopy
|
||||||
|
|
||||||
|
// We want to keep the offset, so we use R_TMP2 from here.
|
||||||
|
MOVQ R_OFF, R_TMP2
|
||||||
|
|
||||||
|
makeOffsetAtLeast8:
|
||||||
|
// !!! As above, expand the pattern so that offset >= 8 and we can use
|
||||||
|
// 8-byte load/stores.
|
||||||
|
//
|
||||||
|
// for offset < 8 {
|
||||||
|
// copy 8 bytes from dst[d-offset:] to dst[d:]
|
||||||
|
// length -= offset
|
||||||
|
// d += offset
|
||||||
|
// offset += offset
|
||||||
|
// // The two previous lines together means that d-offset, and therefore
|
||||||
|
// // R_TMP3, is unchanged.
|
||||||
|
// }
|
||||||
|
CMPQ R_TMP2, $8
|
||||||
|
JGE fixUpSlowForwardCopy
|
||||||
|
MOVQ (R_TMP3), R_TMP1
|
||||||
|
MOVQ R_TMP1, (R_DST)
|
||||||
|
SUBQ R_TMP2, R_LEN
|
||||||
|
ADDQ R_TMP2, R_DST
|
||||||
|
ADDQ R_TMP2, R_TMP2
|
||||||
|
JMP makeOffsetAtLeast8
|
||||||
|
|
||||||
|
fixUpSlowForwardCopy:
|
||||||
|
// !!! Add length (which might be negative now) to d (implied by R_DST being
|
||||||
|
// &dst[d]) so that d ends up at the right place when we jump back to the
|
||||||
|
// top of the loop. Before we do that, though, we save R_DST to R_TMP0 so that, if
|
||||||
|
// length is positive, copying the remaining length bytes will write to the
|
||||||
|
// right place.
|
||||||
|
MOVQ R_DST, R_TMP0
|
||||||
|
ADDQ R_LEN, R_DST
|
||||||
|
|
||||||
|
finishSlowForwardCopy:
|
||||||
|
// !!! Repeat 8-byte load/stores until length <= 0. Ending with a negative
|
||||||
|
// length means that we overrun, but as above, that will be fixed up by
|
||||||
|
// subsequent iterations of the outermost loop.
|
||||||
|
CMPQ R_LEN, $0
|
||||||
|
JLE loop
|
||||||
|
MOVQ (R_TMP3), R_TMP1
|
||||||
|
MOVQ R_TMP1, (R_TMP0)
|
||||||
|
ADDQ $8, R_TMP3
|
||||||
|
ADDQ $8, R_TMP0
|
||||||
|
SUBQ $8, R_LEN
|
||||||
|
JMP finishSlowForwardCopy
|
||||||
|
|
||||||
|
verySlowForwardCopy:
|
||||||
|
// verySlowForwardCopy is a simple implementation of forward copy. In C
|
||||||
|
// parlance, this is a do/while loop instead of a while loop, since we know
|
||||||
|
// that length > 0. In Go syntax:
|
||||||
|
//
|
||||||
|
// for {
|
||||||
|
// dst[d] = dst[d - offset]
|
||||||
|
// d++
|
||||||
|
// length--
|
||||||
|
// if length == 0 {
|
||||||
|
// break
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
MOVB (R_TMP3), R_TMP1
|
||||||
|
MOVB R_TMP1, (R_DST)
|
||||||
|
INCQ R_TMP3
|
||||||
|
INCQ R_DST
|
||||||
|
DECQ R_LEN
|
||||||
|
JNZ verySlowForwardCopy
|
||||||
|
JMP loop
|
||||||
|
|
||||||
|
// The code above handles copy tags.
|
||||||
|
// ----------------------------------------
|
||||||
|
|
||||||
|
end:
|
||||||
|
// This is the end of the "for s < len(src)".
|
||||||
|
//
|
||||||
|
// if d != len(dst) { etc }
|
||||||
|
CMPQ R_DST, R_DEND
|
||||||
|
JNE errCorrupt
|
||||||
|
|
||||||
|
// return 0
|
||||||
|
MOVQ $0, ret+48(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
errCorrupt:
|
||||||
|
// return decodeErrCodeCorrupt
|
||||||
|
MOVQ $1, ret+48(FP)
|
||||||
|
RET
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue