From 91ea4a214bfce103a619eb8525898e135e2a6126 Mon Sep 17 00:00:00 2001 From: fulldump Date: Sat, 2 Apr 2022 19:12:23 +0200 Subject: [PATCH 001/163] OpenCollection now return error + fix tests --- collection/collection.go | 12 +++--- collection/collection_test.go | 69 +++++++++++++++-------------------- 2 files changed, 36 insertions(+), 45 deletions(-) diff --git a/collection/collection.go b/collection/collection.go index 20abeca..572017b 100644 --- a/collection/collection.go +++ b/collection/collection.go @@ -17,12 +17,12 @@ type Collection struct { type Index map[string]json.RawMessage -func OpenCollection(filename string) *Collection { +func OpenCollection(filename string) (*Collection, error) { // TODO: initialize, read all file and apply its changes into memory f, err := os.OpenFile(filename, os.O_RDONLY|os.O_CREATE, 0666) if err != nil { - panic(err) + return nil, fmt.Errorf("open file for read: %w", err) } rows := []json.RawMessage{} @@ -34,7 +34,8 @@ func OpenCollection(filename string) *Collection { break } if err != nil { - panic(err) + // todo: try a best effort? + return nil, fmt.Errorf("decode json: %w", err) } rows = append(rows, row) } @@ -43,8 +44,7 @@ func OpenCollection(filename string) *Collection { // todo: investigate O_SYNC f, err = os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666) if err != nil { - // TODO: handle or return error - panic(err) + return nil, fmt.Errorf("open file for write: %w", err) } return &Collection{ @@ -52,7 +52,7 @@ func OpenCollection(filename string) *Collection { rows: rows, filename: filename, indexes: map[string]Index{}, - } + }, nil } // TODO: test concurrency diff --git a/collection/collection_test.go b/collection/collection_test.go index bf0b1eb..1bbe70b 100644 --- a/collection/collection_test.go +++ b/collection/collection_test.go @@ -2,76 +2,72 @@ package collection import ( "io/ioutil" - "reflect" "testing" . "github.com/fulldump/biff" ) func TestInsert(t *testing.T) { - Environment(func(filename string) { - c := OpenCollection(filename) + // Setup + c, _ := OpenCollection(filename) + defer c.Close() + + // Run c.Insert(map[string]interface{}{ "hello": "world", }) - c.Close() + // Check fileContent, readFileErr := ioutil.ReadFile(filename) AssertNil(readFileErr) AssertEqual(fileContent, []byte(`{"hello":"world"}`+"\n")) - }) - } func TestFindOne(t *testing.T) { - Environment(func(filename string) { + // Setup ioutil.WriteFile(filename, []byte("{\"name\":\"Fulanez\"}\n"), 0666) - c := OpenCollection(filename) + // Run + c, _ := OpenCollection(filename) + defer c.Close() + // Check r := map[string]interface{}{} c.FindOne(&r) - - c.Close() - - if !reflect.DeepEqual(r, map[string]interface{}{"name": "Fulanez"}) { - t.Error("Unexpected retrieved information") - } - + AssertEqualJson(r, map[string]interface{}{"name": "Fulanez"}) }) } func TestInsert100K(t *testing.T) { - Environment(func(filename string) { + // Setup + c, _ := OpenCollection(filename) + defer c.Close() - c := OpenCollection(filename) + // Run n := 100 * 1000 for i := 0; i < n; i++ { c.Insert(map[string]interface{}{"hello": "world", "n": i}) } - c.Close() + // Check + AssertEqual(len(c.rows), n) }) - } func TestIndex(t *testing.T) { - type User struct { Id string `json:"id"` Name string `json:"name"` } - Environment(func(filename string) { - // Setup - c := OpenCollection(filename) + c, _ := OpenCollection(filename) c.Insert(&User{"1", "Pablo"}) c.Insert(&User{"2", "Sara"}) @@ -87,16 +83,14 @@ func TestIndex(t *testing.T) { } func TestInsertAfterIndex(t *testing.T) { - type User struct { Id string `json:"id"` Name string `json:"name"` } - Environment(func(filename string) { // Setup - c := OpenCollection(filename) + c, _ := OpenCollection(filename) // Run c.Index("id") @@ -111,26 +105,25 @@ func TestInsertAfterIndex(t *testing.T) { } func TestIndexMultiValue(t *testing.T) { - type User struct { Id string `json:"id"` Email []string `json:"email"` } - Environment(func(filename string) { // Setup - c := OpenCollection(filename) - c.Insert(&User{"1", []string{"pablo@hotmail.com", "p18@yahoo.com"}}) + newUser := &User{"1", []string{"pablo@hotmail.com", "p18@yahoo.com"}} + c, _ := OpenCollection(filename) + c.Insert(newUser) // Run - c.Index("email") + indexErr := c.Index("email") // Check + AssertNil(indexErr) u := &User{} - errFindBy := c.FindBy("email", "p18@yahoo.com", u) - AssertNil(errFindBy) - AssertEqual(u.Id, "1") + c.FindBy("email", "p18@yahoo.com", u) + AssertEqual(u.Id, newUser.Id) }) } @@ -139,7 +132,7 @@ func TestIndexSparse(t *testing.T) { Environment(func(filename string) { // Setup - c := OpenCollection(filename) + c, _ := OpenCollection(filename) c.Insert(map[string]interface{}{"id": "1"}) // Run @@ -152,16 +145,14 @@ func TestIndexSparse(t *testing.T) { } func TestCollection_Index_Collision(t *testing.T) { - type User struct { Id string `json:"id"` Name string `json:"name"` } - Environment(func(filename string) { // Setup - c := OpenCollection(filename) + c, _ := OpenCollection(filename) c.Insert(&User{"1", "Pablo"}) c.Insert(&User{"1", "Sara"}) @@ -175,7 +166,7 @@ func TestCollection_Index_Collision(t *testing.T) { func TestDoThings(t *testing.T) { - //c := OpenCollection("users") + //c, _ := OpenCollection("users") //c.Drop() //c.Insert(map[string]interface{}{"id": "1", "name": "Gerardo", "email": []string{"gerardo@email.com", "gerardo@hotmail.com"}}) From 9ee2bd47397d003f6d4c490edf60c8a8a4f70d15 Mon Sep 17 00:00:00 2001 From: fulldump Date: Sun, 3 Apr 2022 23:03:33 +0200 Subject: [PATCH 002/163] change persistence "layer" (now it is a monolith), introduce command to support diverse operations, (insert, index, delete...) --- .gitignore | 1 + Makefile | 7 + collection/collection.go | 101 ++++-- collection/collection_test.go | 83 +++-- collection/command.go | 11 + collection/environment_test.go | 3 - collection/index.go | 12 + go.mod | 1 + go.sum | 2 + vendor/github.com/google/uuid/.travis.yml | 9 + vendor/github.com/google/uuid/CONTRIBUTING.md | 10 + vendor/github.com/google/uuid/CONTRIBUTORS | 9 + vendor/github.com/google/uuid/LICENSE | 27 ++ vendor/github.com/google/uuid/README.md | 19 ++ vendor/github.com/google/uuid/dce.go | 80 +++++ vendor/github.com/google/uuid/doc.go | 12 + vendor/github.com/google/uuid/hash.go | 53 ++++ vendor/github.com/google/uuid/marshal.go | 38 +++ vendor/github.com/google/uuid/node.go | 90 ++++++ vendor/github.com/google/uuid/node_js.go | 12 + vendor/github.com/google/uuid/node_net.go | 33 ++ vendor/github.com/google/uuid/null.go | 118 +++++++ vendor/github.com/google/uuid/sql.go | 59 ++++ vendor/github.com/google/uuid/time.go | 123 ++++++++ vendor/github.com/google/uuid/util.go | 43 +++ vendor/github.com/google/uuid/uuid.go | 294 ++++++++++++++++++ vendor/github.com/google/uuid/version1.go | 44 +++ vendor/github.com/google/uuid/version4.go | 76 +++++ vendor/modules.txt | 3 + 29 files changed, 1300 insertions(+), 73 deletions(-) create mode 100644 collection/command.go create mode 100644 collection/index.go create mode 100644 vendor/github.com/google/uuid/.travis.yml create mode 100644 vendor/github.com/google/uuid/CONTRIBUTING.md create mode 100644 vendor/github.com/google/uuid/CONTRIBUTORS create mode 100644 vendor/github.com/google/uuid/LICENSE create mode 100644 vendor/github.com/google/uuid/README.md create mode 100644 vendor/github.com/google/uuid/dce.go create mode 100644 vendor/github.com/google/uuid/doc.go create mode 100644 vendor/github.com/google/uuid/hash.go create mode 100644 vendor/github.com/google/uuid/marshal.go create mode 100644 vendor/github.com/google/uuid/node.go create mode 100644 vendor/github.com/google/uuid/node_js.go create mode 100644 vendor/github.com/google/uuid/node_net.go create mode 100644 vendor/github.com/google/uuid/null.go create mode 100644 vendor/github.com/google/uuid/sql.go create mode 100644 vendor/github.com/google/uuid/time.go create mode 100644 vendor/github.com/google/uuid/util.go create mode 100644 vendor/github.com/google/uuid/uuid.go create mode 100644 vendor/github.com/google/uuid/version1.go create mode 100644 vendor/github.com/google/uuid/version4.go diff --git a/.gitignore b/.gitignore index 63cfe8c..1afd5da 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /.idea +/bin /collection/temp-* \ No newline at end of file diff --git a/Makefile b/Makefile index df7182a..a689528 100644 --- a/Makefile +++ b/Makefile @@ -1,3 +1,10 @@ +test: + go test ./... + run: go run . + +build: + go build -o bin/ . + diff --git a/collection/collection.go b/collection/collection.go index 572017b..5771892 100644 --- a/collection/collection.go +++ b/collection/collection.go @@ -5,6 +5,9 @@ import ( "fmt" "io" "os" + "time" + + "github.com/google/uuid" ) type Collection struct { @@ -15,8 +18,6 @@ type Collection struct { indexes map[string]Index } -type Index map[string]json.RawMessage - func OpenCollection(filename string) (*Collection, error) { // TODO: initialize, read all file and apply its changes into memory @@ -25,11 +26,16 @@ func OpenCollection(filename string) (*Collection, error) { return nil, fmt.Errorf("open file for read: %w", err) } - rows := []json.RawMessage{} + collection := &Collection{ + rows: []json.RawMessage{}, + filename: filename, + indexes: map[string]Index{}, + } + j := json.NewDecoder(f) for { - row := json.RawMessage{} - err := j.Decode(&row) + command := &Command{} + err := j.Decode(&command) if err == io.EOF { break } @@ -37,22 +43,30 @@ func OpenCollection(filename string) (*Collection, error) { // todo: try a best effort? return nil, fmt.Errorf("decode json: %w", err) } - rows = append(rows, row) + + switch command.Name { + case "insert": + collection.addRow(command.Payload) + case "index": + options := &IndexOptions{} + json.Unmarshal(command.Payload, options) + collection.indexRows(options) + } } // Open file for append only // todo: investigate O_SYNC - f, err = os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666) + collection.file, err = os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0666) if err != nil { return nil, fmt.Errorf("open file for write: %w", err) } - return &Collection{ - file: f, - rows: rows, - filename: filename, - indexes: map[string]Index{}, - }, nil + return collection, nil +} + +func (c *Collection) addRow(payload json.RawMessage) { + indexInsert(c.indexes, payload) + c.rows = append(c.rows, payload) } // TODO: test concurrency @@ -61,17 +75,27 @@ func (c *Collection) Insert(item interface{}) error { return fmt.Errorf("collection is closed") } - data, err := json.Marshal(item) + payload, err := json.Marshal(item) if err != nil { - return fmt.Errorf("json encode: %w", err) + return fmt.Errorf("json encode payload: %w", err) } - // update indexes - indexInsert(c.indexes, data) + // Add row + c.addRow(payload) + + // Persist + command := &Command{ + Name: "insert", + Uuid: uuid.New().String(), + Timestamp: time.Now().UnixNano(), + StartByte: 0, + Payload: payload, + } - c.rows = append(c.rows, data) - c.file.Write(data) - c.file.WriteString("\n") + err = json.NewEncoder(c.file).Encode(command) + if err != nil { + return fmt.Errorf("json encode command: %w", err) + } return nil } @@ -90,19 +114,46 @@ func (c *Collection) Traverse(f func(data []byte)) { } } -// Index create a unique index with a name -// Constraints: values can be only scalar strings or array of strings -func (c *Collection) Index(field string) error { +func (c *Collection) indexRows(options *IndexOptions) error { index := Index{} for _, rowData := range c.rows { - err := indexRow(index, field, rowData) + err := indexRow(index, options.Field, rowData) if err != nil { return fmt.Errorf("index row: %w, data: %s", err, string(rowData)) } } + c.indexes[options.Field] = index - c.indexes[field] = index + return nil +} + +// Index create a unique index with a name +// Constraints: values can be only scalar strings or array of strings +func (c *Collection) Index(options *IndexOptions) error { + + err := c.indexRows(options) + if err != nil { + return err + } + + payload, err := json.Marshal(options) + if err != nil { + return fmt.Errorf("json encode payload: %w", err) + } + + command := &Command{ + Name: "index", + Uuid: uuid.New().String(), + Timestamp: time.Now().UnixNano(), + StartByte: 0, + Payload: payload, + } + + err = json.NewEncoder(c.file).Encode(command) + if err != nil { + return fmt.Errorf("json encode command: %w", err) + } return nil } diff --git a/collection/collection_test.go b/collection/collection_test.go index 1bbe70b..86ccd08 100644 --- a/collection/collection_test.go +++ b/collection/collection_test.go @@ -1,6 +1,7 @@ package collection import ( + "encoding/json" "io/ioutil" "testing" @@ -20,9 +21,10 @@ func TestInsert(t *testing.T) { }) // Check - fileContent, readFileErr := ioutil.ReadFile(filename) - AssertNil(readFileErr) - AssertEqual(fileContent, []byte(`{"hello":"world"}`+"\n")) + fileContent, _ := ioutil.ReadFile(filename) + command := &Command{} + json.Unmarshal(fileContent, command) + AssertEqual(string(command.Payload), `{"hello":"world"}`) }) } @@ -30,16 +32,16 @@ func TestFindOne(t *testing.T) { Environment(func(filename string) { // Setup - ioutil.WriteFile(filename, []byte("{\"name\":\"Fulanez\"}\n"), 0666) + ioutil.WriteFile(filename, []byte(`{"name":"insert","uuid":"ec59a0e6-8fcb-4c1c-91e5-3dd7df6a0b80","timestamp":1648937091073939741,"start_byte":0,"payload":{"name": "Fulanez"}}`), 0666) // Run c, _ := OpenCollection(filename) defer c.Close() // Check - r := map[string]interface{}{} - c.FindOne(&r) - AssertEqualJson(r, map[string]interface{}{"name": "Fulanez"}) + row := map[string]interface{}{} + c.FindOne(&row) + AssertEqualJson(row, map[string]interface{}{"name": "Fulanez"}) }) } @@ -72,7 +74,7 @@ func TestIndex(t *testing.T) { c.Insert(&User{"2", "Sara"}) // Run - c.Index("id") + c.Index(&IndexOptions{Field: "id"}) // Check user := &User{} @@ -93,7 +95,7 @@ func TestInsertAfterIndex(t *testing.T) { c, _ := OpenCollection(filename) // Run - c.Index("id") + c.Index(&IndexOptions{Field: "id"}) c.Insert(&User{"1", "Pablo"}) // Check @@ -117,7 +119,7 @@ func TestIndexMultiValue(t *testing.T) { c.Insert(newUser) // Run - indexErr := c.Index("email") + indexErr := c.Index(&IndexOptions{Field: "email"}) // Check AssertNil(indexErr) @@ -136,7 +138,7 @@ func TestIndexSparse(t *testing.T) { c.Insert(map[string]interface{}{"id": "1"}) // Run - errIndex := c.Index("email") + errIndex := c.Index(&IndexOptions{Field: "email"}) // Check AssertNil(errIndex) @@ -157,44 +159,35 @@ func TestCollection_Index_Collision(t *testing.T) { c.Insert(&User{"1", "Sara"}) // Run - err := c.Index("id") + err := c.Index(&IndexOptions{Field: "id"}) // Check AssertNotNil(err) }) } -func TestDoThings(t *testing.T) { - - //c, _ := OpenCollection("users") - //c.Drop() - - //c.Insert(map[string]interface{}{"id": "1", "name": "Gerardo", "email": []string{"gerardo@email.com", "gerardo@hotmail.com"}}) - //c.Insert(map[string]interface{}{"id": "2", "name": "Pablo", "email": []string{"pablo@email.com", "pablo2018@yahoo.com"}}) - - //c.Traverse(func(data []byte) { - // u := struct { - // Id string - // Email string - // }{} - // - // json.Unmarshal(data, &u) - // - // if u.Id != "2" { - // return - // } - // - // fmt.Println(u) - //}) - - //err := c.Index("email") - //AssertNil(err) - // - //u := struct { - // Id string - // Name string - // Email []string - //}{} - // - //fmt.Println(c.FindBy("email", "gerardo@email.com", &u), u) +func TestPersistence(t *testing.T) { + Environment(func(filename string) { + + // Setup + c, _ := OpenCollection(filename) + c.Insert(map[string]interface{}{"id": "1", "name": "Pablo", "email": []string{"pablo@email.com", "pablo2018@yahoo.com"}}) + c.Index(&IndexOptions{Field: "email"}) + c.Insert(map[string]interface{}{"id": "2", "name": "Sara", "email": []string{"sara@email.com", "sara.jimenez8@yahoo.com"}}) + c.Close() + + // Run + c, _ = OpenCollection(filename) + user := struct { + Id string + Name string + Email []string + }{} + findByErr := c.FindBy("email", "sara@email.com", &user) + + // Check + AssertNil(findByErr) + AssertEqual(user.Id, "2") + + }) } diff --git a/collection/command.go b/collection/command.go new file mode 100644 index 0000000..e4fa7d2 --- /dev/null +++ b/collection/command.go @@ -0,0 +1,11 @@ +package collection + +import "encoding/json" + +type Command struct { + Name string `json:"name"` + Uuid string `json:"uuid"` + Timestamp int64 `json:"timestamp"` + StartByte int64 `json:"start_byte"` + Payload json.RawMessage `json:"payload"` +} diff --git a/collection/environment_test.go b/collection/environment_test.go index 1f2f3be..b46ad30 100644 --- a/collection/environment_test.go +++ b/collection/environment_test.go @@ -7,11 +7,8 @@ import ( ) func Environment(f func(filename string)) { - filename := fmt.Sprintf("temp-%v", time.Now().UnixNano()) - defer os.Remove(filename) f(filename) - } diff --git a/collection/index.go b/collection/index.go new file mode 100644 index 0000000..5f99deb --- /dev/null +++ b/collection/index.go @@ -0,0 +1,12 @@ +package collection + +import "encoding/json" + +// Index should be an interface to allow multiple kinds and implementations +type Index map[string]json.RawMessage + +// IndexOptions should have attributes like unique, sparse, multikey, sorted, background, etc... +// Index should be an interface to have multiple indexes implementations, key value, B-Tree, bitmap, geo, cache... +type IndexOptions struct { + Field string `json:"field"` +} diff --git a/go.mod b/go.mod index 2efaede..d114e6e 100644 --- a/go.mod +++ b/go.mod @@ -5,4 +5,5 @@ go 1.17 require ( github.com/fulldump/biff v1.3.0 github.com/fulldump/box v0.1.3 + github.com/google/uuid v1.3.0 ) diff --git a/go.sum b/go.sum index 6dc2499..8241c30 100644 --- a/go.sum +++ b/go.sum @@ -2,3 +2,5 @@ github.com/fulldump/biff v1.3.0 h1:FZDqvP8lkrCMDv/oNEH+j2unpuAY+8aXZ44GIvXYOx4= github.com/fulldump/biff v1.3.0/go.mod h1:TnBce9eRITmnv3otdmITKeU/zmC08DxotA9s0VcJELg= github.com/fulldump/box v0.1.3 h1:Qz1tDf0giqC+EVj51CdFLHgaLAayboTT8/FnP6Zd1EA= github.com/fulldump/box v0.1.3/go.mod h1:UO8uDIKLNvnsp/ru2za7gev9VBlRIeAVH3bLl7xdfNg= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= diff --git a/vendor/github.com/google/uuid/.travis.yml b/vendor/github.com/google/uuid/.travis.yml new file mode 100644 index 0000000..d8156a6 --- /dev/null +++ b/vendor/github.com/google/uuid/.travis.yml @@ -0,0 +1,9 @@ +language: go + +go: + - 1.4.3 + - 1.5.3 + - tip + +script: + - go test -v ./... diff --git a/vendor/github.com/google/uuid/CONTRIBUTING.md b/vendor/github.com/google/uuid/CONTRIBUTING.md new file mode 100644 index 0000000..04fdf09 --- /dev/null +++ b/vendor/github.com/google/uuid/CONTRIBUTING.md @@ -0,0 +1,10 @@ +# How to contribute + +We definitely welcome patches and contribution to this project! + +### Legal requirements + +In order to protect both you and ourselves, you will need to sign the +[Contributor License Agreement](https://cla.developers.google.com/clas). + +You may have already signed it for other Google projects. diff --git a/vendor/github.com/google/uuid/CONTRIBUTORS b/vendor/github.com/google/uuid/CONTRIBUTORS new file mode 100644 index 0000000..b4bb97f --- /dev/null +++ b/vendor/github.com/google/uuid/CONTRIBUTORS @@ -0,0 +1,9 @@ +Paul Borman +bmatsuo +shawnps +theory +jboverfelt +dsymonds +cd1 +wallclockbuilder +dansouza diff --git a/vendor/github.com/google/uuid/LICENSE b/vendor/github.com/google/uuid/LICENSE new file mode 100644 index 0000000..5dc6826 --- /dev/null +++ b/vendor/github.com/google/uuid/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009,2014 Google Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/google/uuid/README.md b/vendor/github.com/google/uuid/README.md new file mode 100644 index 0000000..f765a46 --- /dev/null +++ b/vendor/github.com/google/uuid/README.md @@ -0,0 +1,19 @@ +# uuid ![build status](https://travis-ci.org/google/uuid.svg?branch=master) +The uuid package generates and inspects UUIDs based on +[RFC 4122](http://tools.ietf.org/html/rfc4122) +and DCE 1.1: Authentication and Security Services. + +This package is based on the github.com/pborman/uuid package (previously named +code.google.com/p/go-uuid). It differs from these earlier packages in that +a UUID is a 16 byte array rather than a byte slice. One loss due to this +change is the ability to represent an invalid UUID (vs a NIL UUID). + +###### Install +`go get github.com/google/uuid` + +###### Documentation +[![GoDoc](https://godoc.org/github.com/google/uuid?status.svg)](http://godoc.org/github.com/google/uuid) + +Full `go doc` style documentation for the package can be viewed online without +installing this package by using the GoDoc site here: +http://pkg.go.dev/github.com/google/uuid diff --git a/vendor/github.com/google/uuid/dce.go b/vendor/github.com/google/uuid/dce.go new file mode 100644 index 0000000..fa820b9 --- /dev/null +++ b/vendor/github.com/google/uuid/dce.go @@ -0,0 +1,80 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "encoding/binary" + "fmt" + "os" +) + +// A Domain represents a Version 2 domain +type Domain byte + +// Domain constants for DCE Security (Version 2) UUIDs. +const ( + Person = Domain(0) + Group = Domain(1) + Org = Domain(2) +) + +// NewDCESecurity returns a DCE Security (Version 2) UUID. +// +// The domain should be one of Person, Group or Org. +// On a POSIX system the id should be the users UID for the Person +// domain and the users GID for the Group. The meaning of id for +// the domain Org or on non-POSIX systems is site defined. +// +// For a given domain/id pair the same token may be returned for up to +// 7 minutes and 10 seconds. +func NewDCESecurity(domain Domain, id uint32) (UUID, error) { + uuid, err := NewUUID() + if err == nil { + uuid[6] = (uuid[6] & 0x0f) | 0x20 // Version 2 + uuid[9] = byte(domain) + binary.BigEndian.PutUint32(uuid[0:], id) + } + return uuid, err +} + +// NewDCEPerson returns a DCE Security (Version 2) UUID in the person +// domain with the id returned by os.Getuid. +// +// NewDCESecurity(Person, uint32(os.Getuid())) +func NewDCEPerson() (UUID, error) { + return NewDCESecurity(Person, uint32(os.Getuid())) +} + +// NewDCEGroup returns a DCE Security (Version 2) UUID in the group +// domain with the id returned by os.Getgid. +// +// NewDCESecurity(Group, uint32(os.Getgid())) +func NewDCEGroup() (UUID, error) { + return NewDCESecurity(Group, uint32(os.Getgid())) +} + +// Domain returns the domain for a Version 2 UUID. Domains are only defined +// for Version 2 UUIDs. +func (uuid UUID) Domain() Domain { + return Domain(uuid[9]) +} + +// ID returns the id for a Version 2 UUID. IDs are only defined for Version 2 +// UUIDs. +func (uuid UUID) ID() uint32 { + return binary.BigEndian.Uint32(uuid[0:4]) +} + +func (d Domain) String() string { + switch d { + case Person: + return "Person" + case Group: + return "Group" + case Org: + return "Org" + } + return fmt.Sprintf("Domain%d", int(d)) +} diff --git a/vendor/github.com/google/uuid/doc.go b/vendor/github.com/google/uuid/doc.go new file mode 100644 index 0000000..5b8a4b9 --- /dev/null +++ b/vendor/github.com/google/uuid/doc.go @@ -0,0 +1,12 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package uuid generates and inspects UUIDs. +// +// UUIDs are based on RFC 4122 and DCE 1.1: Authentication and Security +// Services. +// +// A UUID is a 16 byte (128 bit) array. UUIDs may be used as keys to +// maps or compared directly. +package uuid diff --git a/vendor/github.com/google/uuid/hash.go b/vendor/github.com/google/uuid/hash.go new file mode 100644 index 0000000..b404f4b --- /dev/null +++ b/vendor/github.com/google/uuid/hash.go @@ -0,0 +1,53 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "crypto/md5" + "crypto/sha1" + "hash" +) + +// Well known namespace IDs and UUIDs +var ( + NameSpaceDNS = Must(Parse("6ba7b810-9dad-11d1-80b4-00c04fd430c8")) + NameSpaceURL = Must(Parse("6ba7b811-9dad-11d1-80b4-00c04fd430c8")) + NameSpaceOID = Must(Parse("6ba7b812-9dad-11d1-80b4-00c04fd430c8")) + NameSpaceX500 = Must(Parse("6ba7b814-9dad-11d1-80b4-00c04fd430c8")) + Nil UUID // empty UUID, all zeros +) + +// NewHash returns a new UUID derived from the hash of space concatenated with +// data generated by h. The hash should be at least 16 byte in length. The +// first 16 bytes of the hash are used to form the UUID. The version of the +// UUID will be the lower 4 bits of version. NewHash is used to implement +// NewMD5 and NewSHA1. +func NewHash(h hash.Hash, space UUID, data []byte, version int) UUID { + h.Reset() + h.Write(space[:]) //nolint:errcheck + h.Write(data) //nolint:errcheck + s := h.Sum(nil) + var uuid UUID + copy(uuid[:], s) + uuid[6] = (uuid[6] & 0x0f) | uint8((version&0xf)<<4) + uuid[8] = (uuid[8] & 0x3f) | 0x80 // RFC 4122 variant + return uuid +} + +// NewMD5 returns a new MD5 (Version 3) UUID based on the +// supplied name space and data. It is the same as calling: +// +// NewHash(md5.New(), space, data, 3) +func NewMD5(space UUID, data []byte) UUID { + return NewHash(md5.New(), space, data, 3) +} + +// NewSHA1 returns a new SHA1 (Version 5) UUID based on the +// supplied name space and data. It is the same as calling: +// +// NewHash(sha1.New(), space, data, 5) +func NewSHA1(space UUID, data []byte) UUID { + return NewHash(sha1.New(), space, data, 5) +} diff --git a/vendor/github.com/google/uuid/marshal.go b/vendor/github.com/google/uuid/marshal.go new file mode 100644 index 0000000..14bd340 --- /dev/null +++ b/vendor/github.com/google/uuid/marshal.go @@ -0,0 +1,38 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import "fmt" + +// MarshalText implements encoding.TextMarshaler. +func (uuid UUID) MarshalText() ([]byte, error) { + var js [36]byte + encodeHex(js[:], uuid) + return js[:], nil +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (uuid *UUID) UnmarshalText(data []byte) error { + id, err := ParseBytes(data) + if err != nil { + return err + } + *uuid = id + return nil +} + +// MarshalBinary implements encoding.BinaryMarshaler. +func (uuid UUID) MarshalBinary() ([]byte, error) { + return uuid[:], nil +} + +// UnmarshalBinary implements encoding.BinaryUnmarshaler. +func (uuid *UUID) UnmarshalBinary(data []byte) error { + if len(data) != 16 { + return fmt.Errorf("invalid UUID (got %d bytes)", len(data)) + } + copy(uuid[:], data) + return nil +} diff --git a/vendor/github.com/google/uuid/node.go b/vendor/github.com/google/uuid/node.go new file mode 100644 index 0000000..d651a2b --- /dev/null +++ b/vendor/github.com/google/uuid/node.go @@ -0,0 +1,90 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "sync" +) + +var ( + nodeMu sync.Mutex + ifname string // name of interface being used + nodeID [6]byte // hardware for version 1 UUIDs + zeroID [6]byte // nodeID with only 0's +) + +// NodeInterface returns the name of the interface from which the NodeID was +// derived. The interface "user" is returned if the NodeID was set by +// SetNodeID. +func NodeInterface() string { + defer nodeMu.Unlock() + nodeMu.Lock() + return ifname +} + +// SetNodeInterface selects the hardware address to be used for Version 1 UUIDs. +// If name is "" then the first usable interface found will be used or a random +// Node ID will be generated. If a named interface cannot be found then false +// is returned. +// +// SetNodeInterface never fails when name is "". +func SetNodeInterface(name string) bool { + defer nodeMu.Unlock() + nodeMu.Lock() + return setNodeInterface(name) +} + +func setNodeInterface(name string) bool { + iname, addr := getHardwareInterface(name) // null implementation for js + if iname != "" && addr != nil { + ifname = iname + copy(nodeID[:], addr) + return true + } + + // We found no interfaces with a valid hardware address. If name + // does not specify a specific interface generate a random Node ID + // (section 4.1.6) + if name == "" { + ifname = "random" + randomBits(nodeID[:]) + return true + } + return false +} + +// NodeID returns a slice of a copy of the current Node ID, setting the Node ID +// if not already set. +func NodeID() []byte { + defer nodeMu.Unlock() + nodeMu.Lock() + if nodeID == zeroID { + setNodeInterface("") + } + nid := nodeID + return nid[:] +} + +// SetNodeID sets the Node ID to be used for Version 1 UUIDs. The first 6 bytes +// of id are used. If id is less than 6 bytes then false is returned and the +// Node ID is not set. +func SetNodeID(id []byte) bool { + if len(id) < 6 { + return false + } + defer nodeMu.Unlock() + nodeMu.Lock() + copy(nodeID[:], id) + ifname = "user" + return true +} + +// NodeID returns the 6 byte node id encoded in uuid. It returns nil if uuid is +// not valid. The NodeID is only well defined for version 1 and 2 UUIDs. +func (uuid UUID) NodeID() []byte { + var node [6]byte + copy(node[:], uuid[10:]) + return node[:] +} diff --git a/vendor/github.com/google/uuid/node_js.go b/vendor/github.com/google/uuid/node_js.go new file mode 100644 index 0000000..24b78ed --- /dev/null +++ b/vendor/github.com/google/uuid/node_js.go @@ -0,0 +1,12 @@ +// Copyright 2017 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build js + +package uuid + +// getHardwareInterface returns nil values for the JS version of the code. +// This remvoves the "net" dependency, because it is not used in the browser. +// Using the "net" library inflates the size of the transpiled JS code by 673k bytes. +func getHardwareInterface(name string) (string, []byte) { return "", nil } diff --git a/vendor/github.com/google/uuid/node_net.go b/vendor/github.com/google/uuid/node_net.go new file mode 100644 index 0000000..0cbbcdd --- /dev/null +++ b/vendor/github.com/google/uuid/node_net.go @@ -0,0 +1,33 @@ +// Copyright 2017 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build !js + +package uuid + +import "net" + +var interfaces []net.Interface // cached list of interfaces + +// getHardwareInterface returns the name and hardware address of interface name. +// If name is "" then the name and hardware address of one of the system's +// interfaces is returned. If no interfaces are found (name does not exist or +// there are no interfaces) then "", nil is returned. +// +// Only addresses of at least 6 bytes are returned. +func getHardwareInterface(name string) (string, []byte) { + if interfaces == nil { + var err error + interfaces, err = net.Interfaces() + if err != nil { + return "", nil + } + } + for _, ifs := range interfaces { + if len(ifs.HardwareAddr) >= 6 && (name == "" || name == ifs.Name) { + return ifs.Name, ifs.HardwareAddr + } + } + return "", nil +} diff --git a/vendor/github.com/google/uuid/null.go b/vendor/github.com/google/uuid/null.go new file mode 100644 index 0000000..d7fcbf2 --- /dev/null +++ b/vendor/github.com/google/uuid/null.go @@ -0,0 +1,118 @@ +// Copyright 2021 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "bytes" + "database/sql/driver" + "encoding/json" + "fmt" +) + +var jsonNull = []byte("null") + +// NullUUID represents a UUID that may be null. +// NullUUID implements the SQL driver.Scanner interface so +// it can be used as a scan destination: +// +// var u uuid.NullUUID +// err := db.QueryRow("SELECT name FROM foo WHERE id=?", id).Scan(&u) +// ... +// if u.Valid { +// // use u.UUID +// } else { +// // NULL value +// } +// +type NullUUID struct { + UUID UUID + Valid bool // Valid is true if UUID is not NULL +} + +// Scan implements the SQL driver.Scanner interface. +func (nu *NullUUID) Scan(value interface{}) error { + if value == nil { + nu.UUID, nu.Valid = Nil, false + return nil + } + + err := nu.UUID.Scan(value) + if err != nil { + nu.Valid = false + return err + } + + nu.Valid = true + return nil +} + +// Value implements the driver Valuer interface. +func (nu NullUUID) Value() (driver.Value, error) { + if !nu.Valid { + return nil, nil + } + // Delegate to UUID Value function + return nu.UUID.Value() +} + +// MarshalBinary implements encoding.BinaryMarshaler. +func (nu NullUUID) MarshalBinary() ([]byte, error) { + if nu.Valid { + return nu.UUID[:], nil + } + + return []byte(nil), nil +} + +// UnmarshalBinary implements encoding.BinaryUnmarshaler. +func (nu *NullUUID) UnmarshalBinary(data []byte) error { + if len(data) != 16 { + return fmt.Errorf("invalid UUID (got %d bytes)", len(data)) + } + copy(nu.UUID[:], data) + nu.Valid = true + return nil +} + +// MarshalText implements encoding.TextMarshaler. +func (nu NullUUID) MarshalText() ([]byte, error) { + if nu.Valid { + return nu.UUID.MarshalText() + } + + return jsonNull, nil +} + +// UnmarshalText implements encoding.TextUnmarshaler. +func (nu *NullUUID) UnmarshalText(data []byte) error { + id, err := ParseBytes(data) + if err != nil { + nu.Valid = false + return err + } + nu.UUID = id + nu.Valid = true + return nil +} + +// MarshalJSON implements json.Marshaler. +func (nu NullUUID) MarshalJSON() ([]byte, error) { + if nu.Valid { + return json.Marshal(nu.UUID) + } + + return jsonNull, nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (nu *NullUUID) UnmarshalJSON(data []byte) error { + if bytes.Equal(data, jsonNull) { + *nu = NullUUID{} + return nil // valid null UUID + } + err := json.Unmarshal(data, &nu.UUID) + nu.Valid = err == nil + return err +} diff --git a/vendor/github.com/google/uuid/sql.go b/vendor/github.com/google/uuid/sql.go new file mode 100644 index 0000000..2e02ec0 --- /dev/null +++ b/vendor/github.com/google/uuid/sql.go @@ -0,0 +1,59 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "database/sql/driver" + "fmt" +) + +// Scan implements sql.Scanner so UUIDs can be read from databases transparently. +// Currently, database types that map to string and []byte are supported. Please +// consult database-specific driver documentation for matching types. +func (uuid *UUID) Scan(src interface{}) error { + switch src := src.(type) { + case nil: + return nil + + case string: + // if an empty UUID comes from a table, we return a null UUID + if src == "" { + return nil + } + + // see Parse for required string format + u, err := Parse(src) + if err != nil { + return fmt.Errorf("Scan: %v", err) + } + + *uuid = u + + case []byte: + // if an empty UUID comes from a table, we return a null UUID + if len(src) == 0 { + return nil + } + + // assumes a simple slice of bytes if 16 bytes + // otherwise attempts to parse + if len(src) != 16 { + return uuid.Scan(string(src)) + } + copy((*uuid)[:], src) + + default: + return fmt.Errorf("Scan: unable to scan type %T into UUID", src) + } + + return nil +} + +// Value implements sql.Valuer so that UUIDs can be written to databases +// transparently. Currently, UUIDs map to strings. Please consult +// database-specific driver documentation for matching types. +func (uuid UUID) Value() (driver.Value, error) { + return uuid.String(), nil +} diff --git a/vendor/github.com/google/uuid/time.go b/vendor/github.com/google/uuid/time.go new file mode 100644 index 0000000..e6ef06c --- /dev/null +++ b/vendor/github.com/google/uuid/time.go @@ -0,0 +1,123 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "encoding/binary" + "sync" + "time" +) + +// A Time represents a time as the number of 100's of nanoseconds since 15 Oct +// 1582. +type Time int64 + +const ( + lillian = 2299160 // Julian day of 15 Oct 1582 + unix = 2440587 // Julian day of 1 Jan 1970 + epoch = unix - lillian // Days between epochs + g1582 = epoch * 86400 // seconds between epochs + g1582ns100 = g1582 * 10000000 // 100s of a nanoseconds between epochs +) + +var ( + timeMu sync.Mutex + lasttime uint64 // last time we returned + clockSeq uint16 // clock sequence for this run + + timeNow = time.Now // for testing +) + +// UnixTime converts t the number of seconds and nanoseconds using the Unix +// epoch of 1 Jan 1970. +func (t Time) UnixTime() (sec, nsec int64) { + sec = int64(t - g1582ns100) + nsec = (sec % 10000000) * 100 + sec /= 10000000 + return sec, nsec +} + +// GetTime returns the current Time (100s of nanoseconds since 15 Oct 1582) and +// clock sequence as well as adjusting the clock sequence as needed. An error +// is returned if the current time cannot be determined. +func GetTime() (Time, uint16, error) { + defer timeMu.Unlock() + timeMu.Lock() + return getTime() +} + +func getTime() (Time, uint16, error) { + t := timeNow() + + // If we don't have a clock sequence already, set one. + if clockSeq == 0 { + setClockSequence(-1) + } + now := uint64(t.UnixNano()/100) + g1582ns100 + + // If time has gone backwards with this clock sequence then we + // increment the clock sequence + if now <= lasttime { + clockSeq = ((clockSeq + 1) & 0x3fff) | 0x8000 + } + lasttime = now + return Time(now), clockSeq, nil +} + +// ClockSequence returns the current clock sequence, generating one if not +// already set. The clock sequence is only used for Version 1 UUIDs. +// +// The uuid package does not use global static storage for the clock sequence or +// the last time a UUID was generated. Unless SetClockSequence is used, a new +// random clock sequence is generated the first time a clock sequence is +// requested by ClockSequence, GetTime, or NewUUID. (section 4.2.1.1) +func ClockSequence() int { + defer timeMu.Unlock() + timeMu.Lock() + return clockSequence() +} + +func clockSequence() int { + if clockSeq == 0 { + setClockSequence(-1) + } + return int(clockSeq & 0x3fff) +} + +// SetClockSequence sets the clock sequence to the lower 14 bits of seq. Setting to +// -1 causes a new sequence to be generated. +func SetClockSequence(seq int) { + defer timeMu.Unlock() + timeMu.Lock() + setClockSequence(seq) +} + +func setClockSequence(seq int) { + if seq == -1 { + var b [2]byte + randomBits(b[:]) // clock sequence + seq = int(b[0])<<8 | int(b[1]) + } + oldSeq := clockSeq + clockSeq = uint16(seq&0x3fff) | 0x8000 // Set our variant + if oldSeq != clockSeq { + lasttime = 0 + } +} + +// Time returns the time in 100s of nanoseconds since 15 Oct 1582 encoded in +// uuid. The time is only defined for version 1 and 2 UUIDs. +func (uuid UUID) Time() Time { + time := int64(binary.BigEndian.Uint32(uuid[0:4])) + time |= int64(binary.BigEndian.Uint16(uuid[4:6])) << 32 + time |= int64(binary.BigEndian.Uint16(uuid[6:8])&0xfff) << 48 + return Time(time) +} + +// ClockSequence returns the clock sequence encoded in uuid. +// The clock sequence is only well defined for version 1 and 2 UUIDs. +func (uuid UUID) ClockSequence() int { + return int(binary.BigEndian.Uint16(uuid[8:10])) & 0x3fff +} diff --git a/vendor/github.com/google/uuid/util.go b/vendor/github.com/google/uuid/util.go new file mode 100644 index 0000000..5ea6c73 --- /dev/null +++ b/vendor/github.com/google/uuid/util.go @@ -0,0 +1,43 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "io" +) + +// randomBits completely fills slice b with random data. +func randomBits(b []byte) { + if _, err := io.ReadFull(rander, b); err != nil { + panic(err.Error()) // rand should never fail + } +} + +// xvalues returns the value of a byte as a hexadecimal digit or 255. +var xvalues = [256]byte{ + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 255, 255, 255, 255, 255, 255, + 255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 10, 11, 12, 13, 14, 15, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, +} + +// xtob converts hex characters x1 and x2 into a byte. +func xtob(x1, x2 byte) (byte, bool) { + b1 := xvalues[x1] + b2 := xvalues[x2] + return (b1 << 4) | b2, b1 != 255 && b2 != 255 +} diff --git a/vendor/github.com/google/uuid/uuid.go b/vendor/github.com/google/uuid/uuid.go new file mode 100644 index 0000000..a57207a --- /dev/null +++ b/vendor/github.com/google/uuid/uuid.go @@ -0,0 +1,294 @@ +// Copyright 2018 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "bytes" + "crypto/rand" + "encoding/hex" + "errors" + "fmt" + "io" + "strings" + "sync" +) + +// A UUID is a 128 bit (16 byte) Universal Unique IDentifier as defined in RFC +// 4122. +type UUID [16]byte + +// A Version represents a UUID's version. +type Version byte + +// A Variant represents a UUID's variant. +type Variant byte + +// Constants returned by Variant. +const ( + Invalid = Variant(iota) // Invalid UUID + RFC4122 // The variant specified in RFC4122 + Reserved // Reserved, NCS backward compatibility. + Microsoft // Reserved, Microsoft Corporation backward compatibility. + Future // Reserved for future definition. +) + +const randPoolSize = 16 * 16 + +var ( + rander = rand.Reader // random function + poolEnabled = false + poolMu sync.Mutex + poolPos = randPoolSize // protected with poolMu + pool [randPoolSize]byte // protected with poolMu +) + +type invalidLengthError struct{ len int } + +func (err invalidLengthError) Error() string { + return fmt.Sprintf("invalid UUID length: %d", err.len) +} + +// IsInvalidLengthError is matcher function for custom error invalidLengthError +func IsInvalidLengthError(err error) bool { + _, ok := err.(invalidLengthError) + return ok +} + +// Parse decodes s into a UUID or returns an error. Both the standard UUID +// forms of xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx and +// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx are decoded as well as the +// Microsoft encoding {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} and the raw hex +// encoding: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx. +func Parse(s string) (UUID, error) { + var uuid UUID + switch len(s) { + // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + case 36: + + // urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + case 36 + 9: + if strings.ToLower(s[:9]) != "urn:uuid:" { + return uuid, fmt.Errorf("invalid urn prefix: %q", s[:9]) + } + s = s[9:] + + // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} + case 36 + 2: + s = s[1:] + + // xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + case 32: + var ok bool + for i := range uuid { + uuid[i], ok = xtob(s[i*2], s[i*2+1]) + if !ok { + return uuid, errors.New("invalid UUID format") + } + } + return uuid, nil + default: + return uuid, invalidLengthError{len(s)} + } + // s is now at least 36 bytes long + // it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + if s[8] != '-' || s[13] != '-' || s[18] != '-' || s[23] != '-' { + return uuid, errors.New("invalid UUID format") + } + for i, x := range [16]int{ + 0, 2, 4, 6, + 9, 11, + 14, 16, + 19, 21, + 24, 26, 28, 30, 32, 34} { + v, ok := xtob(s[x], s[x+1]) + if !ok { + return uuid, errors.New("invalid UUID format") + } + uuid[i] = v + } + return uuid, nil +} + +// ParseBytes is like Parse, except it parses a byte slice instead of a string. +func ParseBytes(b []byte) (UUID, error) { + var uuid UUID + switch len(b) { + case 36: // xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + case 36 + 9: // urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + if !bytes.Equal(bytes.ToLower(b[:9]), []byte("urn:uuid:")) { + return uuid, fmt.Errorf("invalid urn prefix: %q", b[:9]) + } + b = b[9:] + case 36 + 2: // {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} + b = b[1:] + case 32: // xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + var ok bool + for i := 0; i < 32; i += 2 { + uuid[i/2], ok = xtob(b[i], b[i+1]) + if !ok { + return uuid, errors.New("invalid UUID format") + } + } + return uuid, nil + default: + return uuid, invalidLengthError{len(b)} + } + // s is now at least 36 bytes long + // it must be of the form xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx + if b[8] != '-' || b[13] != '-' || b[18] != '-' || b[23] != '-' { + return uuid, errors.New("invalid UUID format") + } + for i, x := range [16]int{ + 0, 2, 4, 6, + 9, 11, + 14, 16, + 19, 21, + 24, 26, 28, 30, 32, 34} { + v, ok := xtob(b[x], b[x+1]) + if !ok { + return uuid, errors.New("invalid UUID format") + } + uuid[i] = v + } + return uuid, nil +} + +// MustParse is like Parse but panics if the string cannot be parsed. +// It simplifies safe initialization of global variables holding compiled UUIDs. +func MustParse(s string) UUID { + uuid, err := Parse(s) + if err != nil { + panic(`uuid: Parse(` + s + `): ` + err.Error()) + } + return uuid +} + +// FromBytes creates a new UUID from a byte slice. Returns an error if the slice +// does not have a length of 16. The bytes are copied from the slice. +func FromBytes(b []byte) (uuid UUID, err error) { + err = uuid.UnmarshalBinary(b) + return uuid, err +} + +// Must returns uuid if err is nil and panics otherwise. +func Must(uuid UUID, err error) UUID { + if err != nil { + panic(err) + } + return uuid +} + +// String returns the string form of uuid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx +// , or "" if uuid is invalid. +func (uuid UUID) String() string { + var buf [36]byte + encodeHex(buf[:], uuid) + return string(buf[:]) +} + +// URN returns the RFC 2141 URN form of uuid, +// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, or "" if uuid is invalid. +func (uuid UUID) URN() string { + var buf [36 + 9]byte + copy(buf[:], "urn:uuid:") + encodeHex(buf[9:], uuid) + return string(buf[:]) +} + +func encodeHex(dst []byte, uuid UUID) { + hex.Encode(dst, uuid[:4]) + dst[8] = '-' + hex.Encode(dst[9:13], uuid[4:6]) + dst[13] = '-' + hex.Encode(dst[14:18], uuid[6:8]) + dst[18] = '-' + hex.Encode(dst[19:23], uuid[8:10]) + dst[23] = '-' + hex.Encode(dst[24:], uuid[10:]) +} + +// Variant returns the variant encoded in uuid. +func (uuid UUID) Variant() Variant { + switch { + case (uuid[8] & 0xc0) == 0x80: + return RFC4122 + case (uuid[8] & 0xe0) == 0xc0: + return Microsoft + case (uuid[8] & 0xe0) == 0xe0: + return Future + default: + return Reserved + } +} + +// Version returns the version of uuid. +func (uuid UUID) Version() Version { + return Version(uuid[6] >> 4) +} + +func (v Version) String() string { + if v > 15 { + return fmt.Sprintf("BAD_VERSION_%d", v) + } + return fmt.Sprintf("VERSION_%d", v) +} + +func (v Variant) String() string { + switch v { + case RFC4122: + return "RFC4122" + case Reserved: + return "Reserved" + case Microsoft: + return "Microsoft" + case Future: + return "Future" + case Invalid: + return "Invalid" + } + return fmt.Sprintf("BadVariant%d", int(v)) +} + +// SetRand sets the random number generator to r, which implements io.Reader. +// If r.Read returns an error when the package requests random data then +// a panic will be issued. +// +// Calling SetRand with nil sets the random number generator to the default +// generator. +func SetRand(r io.Reader) { + if r == nil { + rander = rand.Reader + return + } + rander = r +} + +// EnableRandPool enables internal randomness pool used for Random +// (Version 4) UUID generation. The pool contains random bytes read from +// the random number generator on demand in batches. Enabling the pool +// may improve the UUID generation throughput significantly. +// +// Since the pool is stored on the Go heap, this feature may be a bad fit +// for security sensitive applications. +// +// Both EnableRandPool and DisableRandPool are not thread-safe and should +// only be called when there is no possibility that New or any other +// UUID Version 4 generation function will be called concurrently. +func EnableRandPool() { + poolEnabled = true +} + +// DisableRandPool disables the randomness pool if it was previously +// enabled with EnableRandPool. +// +// Both EnableRandPool and DisableRandPool are not thread-safe and should +// only be called when there is no possibility that New or any other +// UUID Version 4 generation function will be called concurrently. +func DisableRandPool() { + poolEnabled = false + defer poolMu.Unlock() + poolMu.Lock() + poolPos = randPoolSize +} diff --git a/vendor/github.com/google/uuid/version1.go b/vendor/github.com/google/uuid/version1.go new file mode 100644 index 0000000..4631096 --- /dev/null +++ b/vendor/github.com/google/uuid/version1.go @@ -0,0 +1,44 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import ( + "encoding/binary" +) + +// NewUUID returns a Version 1 UUID based on the current NodeID and clock +// sequence, and the current time. If the NodeID has not been set by SetNodeID +// or SetNodeInterface then it will be set automatically. If the NodeID cannot +// be set NewUUID returns nil. If clock sequence has not been set by +// SetClockSequence then it will be set automatically. If GetTime fails to +// return the current NewUUID returns nil and an error. +// +// In most cases, New should be used. +func NewUUID() (UUID, error) { + var uuid UUID + now, seq, err := GetTime() + if err != nil { + return uuid, err + } + + timeLow := uint32(now & 0xffffffff) + timeMid := uint16((now >> 32) & 0xffff) + timeHi := uint16((now >> 48) & 0x0fff) + timeHi |= 0x1000 // Version 1 + + binary.BigEndian.PutUint32(uuid[0:], timeLow) + binary.BigEndian.PutUint16(uuid[4:], timeMid) + binary.BigEndian.PutUint16(uuid[6:], timeHi) + binary.BigEndian.PutUint16(uuid[8:], seq) + + nodeMu.Lock() + if nodeID == zeroID { + setNodeInterface("") + } + copy(uuid[10:], nodeID[:]) + nodeMu.Unlock() + + return uuid, nil +} diff --git a/vendor/github.com/google/uuid/version4.go b/vendor/github.com/google/uuid/version4.go new file mode 100644 index 0000000..7697802 --- /dev/null +++ b/vendor/github.com/google/uuid/version4.go @@ -0,0 +1,76 @@ +// Copyright 2016 Google Inc. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package uuid + +import "io" + +// New creates a new random UUID or panics. New is equivalent to +// the expression +// +// uuid.Must(uuid.NewRandom()) +func New() UUID { + return Must(NewRandom()) +} + +// NewString creates a new random UUID and returns it as a string or panics. +// NewString is equivalent to the expression +// +// uuid.New().String() +func NewString() string { + return Must(NewRandom()).String() +} + +// NewRandom returns a Random (Version 4) UUID. +// +// The strength of the UUIDs is based on the strength of the crypto/rand +// package. +// +// Uses the randomness pool if it was enabled with EnableRandPool. +// +// A note about uniqueness derived from the UUID Wikipedia entry: +// +// Randomly generated UUIDs have 122 random bits. One's annual risk of being +// hit by a meteorite is estimated to be one chance in 17 billion, that +// means the probability is about 0.00000000006 (6 × 10−11), +// equivalent to the odds of creating a few tens of trillions of UUIDs in a +// year and having one duplicate. +func NewRandom() (UUID, error) { + if !poolEnabled { + return NewRandomFromReader(rander) + } + return newRandomFromPool() +} + +// NewRandomFromReader returns a UUID based on bytes read from a given io.Reader. +func NewRandomFromReader(r io.Reader) (UUID, error) { + var uuid UUID + _, err := io.ReadFull(r, uuid[:]) + if err != nil { + return Nil, err + } + uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4 + uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10 + return uuid, nil +} + +func newRandomFromPool() (UUID, error) { + var uuid UUID + poolMu.Lock() + if poolPos == randPoolSize { + _, err := io.ReadFull(rander, pool[:]) + if err != nil { + poolMu.Unlock() + return Nil, err + } + poolPos = 0 + } + copy(uuid[:], pool[poolPos:(poolPos+16)]) + poolPos += 16 + poolMu.Unlock() + + uuid[6] = (uuid[6] & 0x0f) | 0x40 // Version 4 + uuid[8] = (uuid[8] & 0x3f) | 0x80 // Variant is 10 + return uuid, nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 133d94f..db0b893 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -4,3 +4,6 @@ github.com/fulldump/biff # github.com/fulldump/box v0.1.3 ## explicit github.com/fulldump/box +# github.com/google/uuid v1.3.0 +## explicit +github.com/google/uuid From 5f71a7d56b3ca4aafe2c70255a0624e0cecd055c Mon Sep 17 00:00:00 2001 From: fulldump Date: Mon, 4 Apr 2022 00:39:47 +0200 Subject: [PATCH 003/163] start API rest --- main.go | 108 +++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 104 insertions(+), 4 deletions(-) diff --git a/main.go b/main.go index 2af9e0f..10eae35 100644 --- a/main.go +++ b/main.go @@ -1,9 +1,14 @@ package main import ( + "context" "fmt" + "inceptiondb/collection" "io/fs" + "net/http" + "path" "path/filepath" + "strings" "github.com/fulldump/box" ) @@ -18,17 +23,112 @@ func main() { Dir: "data", } - collections := map[string][]interface{}{} + collections := map[string]*collection.Collection{} + + filepath.WalkDir(c.Dir, func(filename string, d fs.DirEntry, err error) error { + if d.IsDir() { + return nil + } + + name := filename + name = strings.TrimPrefix(name, c.Dir) + name = strings.TrimPrefix(name, "/") + + col, err := collection.OpenCollection(filename) + if err != nil { + fmt.Printf("open collection '%s': %s\n", filename, err.Error()) + } + + collections[name] = col - filepath.WalkDir(c.Dir, func(path string, d fs.DirEntry, err error) error { - fmt.Println(d) return nil }) fmt.Println(collections) b := box.NewBox() - b.Resource("collections") + b.Resource("collections"). + WithActions( + box.Get(listCollections(collections)), + box.Post(createCollection(collections, c.Dir)), + ) + + b.Resource("collections/{collection_name}"). + WithActions( + box.Get(listItems(collections)), + box.Post(insertItem(collections)), + ) b.Serve() } + +func getBoxContext(ctx context.Context) *box.C { + + v := ctx.Value("box_context") + if c, ok := v.(*box.C); ok { + return c + } + + return nil +} + +func listCollections(collections map[string]*collection.Collection) interface{} { + return func() []string { + result := []string{} + + for k, _ := range collections { + result = append(result, k) + } + + return result + } +} + +type createCollectionRequest struct { + Name string `json:"name"` +} + +func createCollection(collections map[string]*collection.Collection, dir string) interface{} { + return func(input *createCollectionRequest) (*createCollectionRequest, error) { + + filename := path.Join(dir, input.Name) + + col, err := collection.OpenCollection(filename) + if err != nil { + return nil, err + } + + collections[input.Name] = col + + return input, nil + } +} + +func listItems(collections map[string]*collection.Collection) interface{} { + return func(ctx context.Context, w http.ResponseWriter) { + + name := getBoxContext(ctx).Parameters["collection_name"] + collections[name].Traverse(func(data []byte) { + w.Write(data) + w.Write([]byte("\n")) + }) + + } +} + +func insertItem(collections map[string]*collection.Collection) interface{} { + + type Item map[string]interface{} + + return func(ctx context.Context, item Item) (Item, error) { + + name := getBoxContext(ctx).Parameters["collection_name"] + + err := collections[name].Insert(item) + if err != nil { + return nil, err + } + + return item, nil + } +} From 866056f97f56552b60e4bd5981a7b99ec7b6bf9a Mon Sep 17 00:00:00 2001 From: fulldump Date: Tue, 5 Apr 2022 00:08:31 +0200 Subject: [PATCH 004/163] export Collection.Rows and Collection.Indexes + fix addRow error + some API methods --- .gitignore | 1 + collection/collection.go | 55 +++++++++----- collection/collection_test.go | 32 +++++++- main.go | 136 ++++++++++++++++++++++++++++++++-- 4 files changed, 196 insertions(+), 28 deletions(-) diff --git a/.gitignore b/.gitignore index 1afd5da..be45f02 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /.idea /bin +/data /collection/temp-* \ No newline at end of file diff --git a/collection/collection.go b/collection/collection.go index 5771892..3b95dcb 100644 --- a/collection/collection.go +++ b/collection/collection.go @@ -11,11 +11,11 @@ import ( ) type Collection struct { - file *os.File - //buffer *bufio.Writer // TODO: use write buffer to improve performance (x3 in tests) - rows []json.RawMessage filename string // Just informative... - indexes map[string]Index + file *os.File + //buffer *bufio.Writer // TODO: use write buffer to improve performance (x3 in tests) + Rows []json.RawMessage + Indexes map[string]Index } func OpenCollection(filename string) (*Collection, error) { @@ -27,9 +27,9 @@ func OpenCollection(filename string) (*Collection, error) { } collection := &Collection{ - rows: []json.RawMessage{}, + Rows: []json.RawMessage{}, filename: filename, - indexes: map[string]Index{}, + Indexes: map[string]Index{}, } j := json.NewDecoder(f) @@ -46,11 +46,17 @@ func OpenCollection(filename string) (*Collection, error) { switch command.Name { case "insert": - collection.addRow(command.Payload) + err := collection.addRow(command.Payload) + if err != nil { + return nil, err + } case "index": options := &IndexOptions{} json.Unmarshal(command.Payload, options) - collection.indexRows(options) + err := collection.indexRows(options) + if err != nil { + fmt.Printf("WARNING: create index '%s': %s", options.Field, err.Error()) + } } } @@ -64,9 +70,16 @@ func OpenCollection(filename string) (*Collection, error) { return collection, nil } -func (c *Collection) addRow(payload json.RawMessage) { - indexInsert(c.indexes, payload) - c.rows = append(c.rows, payload) +func (c *Collection) addRow(payload json.RawMessage) error { + + err := indexInsert(c.Indexes, payload) + if err != nil { + return err + } + + c.Rows = append(c.Rows, payload) + + return nil } // TODO: test concurrency @@ -81,7 +94,10 @@ func (c *Collection) Insert(item interface{}) error { } // Add row - c.addRow(payload) + err = c.addRow(payload) + if err != nil { + return err + } // Persist command := &Command{ @@ -101,7 +117,7 @@ func (c *Collection) Insert(item interface{}) error { } func (c *Collection) FindOne(data interface{}) { - for _, row := range c.rows { + for _, row := range c.Rows { json.Unmarshal(row, data) return } @@ -109,7 +125,7 @@ func (c *Collection) FindOne(data interface{}) { } func (c *Collection) Traverse(f func(data []byte)) { - for _, row := range c.rows { + for _, row := range c.Rows { f(row) } } @@ -117,13 +133,13 @@ func (c *Collection) Traverse(f func(data []byte)) { func (c *Collection) indexRows(options *IndexOptions) error { index := Index{} - for _, rowData := range c.rows { + for _, rowData := range c.Rows { err := indexRow(index, options.Field, rowData) if err != nil { return fmt.Errorf("index row: %w, data: %s", err, string(rowData)) } } - c.indexes[options.Field] = index + c.Indexes[options.Field] = index return nil } @@ -165,7 +181,6 @@ func indexInsert(indexes map[string]Index, rowData []byte) (err error) { // TODO: undo previous work? two phases (check+commit) ? break } - } return @@ -189,14 +204,14 @@ func indexRow(index Index, field string, rowData []byte) error { switch value := itemValue.(type) { case string: if _, exists := index[value]; exists { - return fmt.Errorf("conflict: field '%s' with value '%s'", field, value) + return fmt.Errorf("index conflict: field '%s' with value '%s'", field, value) } index[value] = rowData case []interface{}: for _, v := range value { s := v.(string) // TODO: handle this casting error if _, exists := index[s]; exists { - return fmt.Errorf("conflict: field '%s' with value '%s'", field, value) + return fmt.Errorf("index conflict: field '%s' with value '%s'", field, value) } } for _, v := range value { @@ -212,7 +227,7 @@ func indexRow(index Index, field string, rowData []byte) error { func (c *Collection) FindBy(field string, value string, data interface{}) error { - index, ok := c.indexes[field] + index, ok := c.Indexes[field] if !ok { return fmt.Errorf("field '%s' is not indexed", field) } diff --git a/collection/collection_test.go b/collection/collection_test.go index 86ccd08..d228321 100644 --- a/collection/collection_test.go +++ b/collection/collection_test.go @@ -3,9 +3,11 @@ package collection import ( "encoding/json" "io/ioutil" + "strconv" "testing" . "github.com/fulldump/biff" + "github.com/google/uuid" ) func TestInsert(t *testing.T) { @@ -58,7 +60,7 @@ func TestInsert100K(t *testing.T) { } // Check - AssertEqual(len(c.rows), n) + AssertEqual(len(c.Rows), n) }) } @@ -142,7 +144,7 @@ func TestIndexSparse(t *testing.T) { // Check AssertNil(errIndex) - AssertEqual(len(c.indexes["email"]), 0) + AssertEqual(len(c.Indexes["email"]), 0) }) } @@ -191,3 +193,29 @@ func TestPersistence(t *testing.T) { }) } + +func TestInsert100Kssss(t *testing.T) { + + t.Skip() + + // Setup + c, _ := OpenCollection("../data/mongodb") + defer c.Close() + + c.Index(&IndexOptions{ + Field: "uuid", + }) + c.Index(&IndexOptions{ + Field: "i", + }) + + // Run + n := 1000 * 1000 + for i := 0; i < n; i++ { + c.Insert(map[string]interface{}{"uuid": uuid.New().String(), "hello": "world", "i": strconv.Itoa(i)}) + } + + // Check + AssertEqual(len(c.Rows), n) + +} diff --git a/main.go b/main.go index 10eae35..23fbfef 100644 --- a/main.go +++ b/main.go @@ -2,29 +2,43 @@ package main import ( "context" + "encoding/json" "fmt" + "github.com/fulldump/box" "inceptiondb/collection" "io/fs" "net/http" "path" "path/filepath" "strings" - - "github.com/fulldump/box" + "time" ) type Configuration struct { Dir string `usage:"data directory"` } +var banner = ` + _____ _ _ ____________ +|_ _| | | (_) | _ \ ___ \ + | | _ __ ___ ___ _ __ | |_ _ ___ _ __ | | | | |_/ / + | || '_ \ / __/ _ \ '_ \| __| |/ _ \| '_ \| | | | ___ \ + _| || | | | (_| __/ |_) | |_| | (_) | | | | |/ /| |_/ / + \___/_| |_|\___\___| .__/ \__|_|\___/|_| |_|___/ \____/ + | | + |_| +` + func main() { c := &Configuration{ Dir: "data", } - collections := map[string]*collection.Collection{} + fmt.Println(banner) + collections := map[string]*collection.Collection{} + fmt.Printf("Loading data...\n") filepath.WalkDir(c.Dir, func(filename string, d fs.DirEntry, err error) error { if d.IsDir() { return nil @@ -34,19 +48,23 @@ func main() { name = strings.TrimPrefix(name, c.Dir) name = strings.TrimPrefix(name, "/") + t0 := time.Now() col, err := collection.OpenCollection(filename) if err != nil { - fmt.Printf("open collection '%s': %s\n", filename, err.Error()) + fmt.Printf("ERROR: open collection '%s': %s\n", filename, err.Error()) + return nil } + fmt.Println(name, len(col.Rows), time.Since(t0)) collections[name] = col return nil }) - fmt.Println(collections) - b := box.NewBox() + + b.WithInterceptors(InterceptorPrintError) + b.Resource("collections"). WithActions( box.Get(listCollections(collections)), @@ -59,9 +77,42 @@ func main() { box.Post(insertItem(collections)), ) + b.Resource("collections/{collection_name}/count"). + WithActions( + box.Get(countItems(collections)), + ) + + b.Resource("collections/{collection_name}/index"). + WithActions( + box.Get(listIndexes(collections)), + box.Post(createIndex(collections)), + ) + + b.Resource("collections/{collection_name}/index/{index_name}"). + WithActions( + box.Get(getIndex(collections)), + ) + + b.Resource("collections/{collection_name}/index/{index_name}/findBy/{value}"). + WithActions( + box.Get(indexFindBy(collections)), + ) + b.Serve() } +func InterceptorPrintError(next box.H) box.H { + return func(ctx context.Context) { + next(ctx) + err := box.GetError(ctx) + if nil != err { + json.NewEncoder(box.GetResponse(ctx)).Encode(map[string]interface{}{ + "error": err.Error(), + }) + } + } +} + func getBoxContext(ctx context.Context) *box.C { v := ctx.Value("box_context") @@ -116,6 +167,17 @@ func listItems(collections map[string]*collection.Collection) interface{} { } } +func countItems(collections map[string]*collection.Collection) interface{} { + return func(ctx context.Context) interface{} { + + name := getBoxContext(ctx).Parameters["collection_name"] + + return map[string]interface{}{ + "count": len(collections[name].Rows), + } + } +} + func insertItem(collections map[string]*collection.Collection) interface{} { type Item map[string]interface{} @@ -132,3 +194,65 @@ func insertItem(collections map[string]*collection.Collection) interface{} { return item, nil } } + +func listIndexes(collections map[string]*collection.Collection) interface{} { + return func(ctx context.Context) []string { + + result := []string{} + + name := getBoxContext(ctx).Parameters["collection_name"] + for name, _ := range collections[name].Indexes { + result = append(result, name) + } + + return result + } +} + +func createIndex(collections map[string]*collection.Collection) interface{} { + + return func(ctx context.Context, indexOptions *collection.IndexOptions) (*collection.IndexOptions, error) { + + name := getBoxContext(ctx).Parameters["collection_name"] + err := collections[name].Index(indexOptions) + if err != nil { + return nil, err + } + + return indexOptions, nil + } +} + +func getIndex(collections map[string]*collection.Collection) interface{} { + + return func(ctx context.Context) (*collection.IndexOptions, error) { + + collectionName := getBoxContext(ctx).Parameters["collection_name"] + indexName := getBoxContext(ctx).Parameters["index_name"] + _, exists := collections[collectionName].Indexes[indexName] + if !exists { + return nil, fmt.Errorf("index '%s' does not exist", indexName) + } + + return &collection.IndexOptions{ + Field: indexName, + }, nil + } +} + +func indexFindBy(collections map[string]*collection.Collection) interface{} { + + return func(ctx context.Context) (interface{}, error) { + + collectionName := getBoxContext(ctx).Parameters["collection_name"] + indexName := getBoxContext(ctx).Parameters["index_name"] + value := getBoxContext(ctx).Parameters["value"] + result := map[string]interface{}{} + err := collections[collectionName].FindBy(indexName, value, &result) + if err != nil { + return nil, fmt.Errorf("item %s='%s' does not exist", indexName, value) + } + + return result, nil + } +} From 8e1733b0f9c5a6f0173ddaa46168fecb55532d7f Mon Sep 17 00:00:00 2001 From: fulldump Date: Tue, 5 Apr 2022 23:06:56 +0200 Subject: [PATCH 005/163] fix concurrency issue on insert --- collection/collection.go | 19 ++++++++++++------- collection/collection_test.go | 23 +++++++++++++++++++++++ 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/collection/collection.go b/collection/collection.go index 3b95dcb..e364e51 100644 --- a/collection/collection.go +++ b/collection/collection.go @@ -5,17 +5,19 @@ import ( "fmt" "io" "os" + "sync" "time" "github.com/google/uuid" ) type Collection struct { - filename string // Just informative... - file *os.File + filename string // Just informative... + file *os.File + Rows []json.RawMessage + rowsMutex *sync.Mutex + Indexes map[string]Index //buffer *bufio.Writer // TODO: use write buffer to improve performance (x3 in tests) - Rows []json.RawMessage - Indexes map[string]Index } func OpenCollection(filename string) (*Collection, error) { @@ -27,9 +29,10 @@ func OpenCollection(filename string) (*Collection, error) { } collection := &Collection{ - Rows: []json.RawMessage{}, - filename: filename, - Indexes: map[string]Index{}, + Rows: []json.RawMessage{}, + rowsMutex: &sync.Mutex{}, + filename: filename, + Indexes: map[string]Index{}, } j := json.NewDecoder(f) @@ -77,7 +80,9 @@ func (c *Collection) addRow(payload json.RawMessage) error { return err } + c.rowsMutex.Lock() c.Rows = append(c.Rows, payload) + c.rowsMutex.Unlock() return nil } diff --git a/collection/collection_test.go b/collection/collection_test.go index d228321..00ead8c 100644 --- a/collection/collection_test.go +++ b/collection/collection_test.go @@ -4,6 +4,7 @@ import ( "encoding/json" "io/ioutil" "strconv" + "sync" "testing" . "github.com/fulldump/biff" @@ -30,6 +31,28 @@ func TestInsert(t *testing.T) { }) } +func TestCollection_Insert_Concurrency(t *testing.T) { + Environment(func(filename string) { + + c, _ := OpenCollection(filename) + + n := 100 + + wg := &sync.WaitGroup{} + for i := 0; i < n; i++ { + wg.Add(1) + go func() { + defer wg.Done() + c.Insert(map[string]interface{}{"hello": "world"}) + }() + } + + wg.Wait() + + AssertEqual(len(c.Rows), n) + }) +} + func TestFindOne(t *testing.T) { Environment(func(filename string) { From e6e9e1ac8fcd037c71242e2958d410125a197e66 Mon Sep 17 00:00:00 2001 From: fulldump Date: Tue, 5 Apr 2022 23:31:30 +0200 Subject: [PATCH 006/163] split main into packages "configuration" and "api" --- api/0_build.go | 49 ++++++++ api/0_helpers.go | 34 ++++++ api/countItems.go | 18 +++ api/createCollection.go | 27 +++++ api/createIndex.go | 21 ++++ api/getIndex.go | 25 ++++ api/indexFindBy.go | 25 ++++ api/insertItem.go | 24 ++++ api/listCollections.go | 15 +++ api/listIndexes.go | 21 ++++ api/listItems.go | 20 ++++ configuration/configuration.go | 5 + configuration/default.go | 7 ++ main.go | 213 +-------------------------------- 14 files changed, 297 insertions(+), 207 deletions(-) create mode 100644 api/0_build.go create mode 100644 api/0_helpers.go create mode 100644 api/countItems.go create mode 100644 api/createCollection.go create mode 100644 api/createIndex.go create mode 100644 api/getIndex.go create mode 100644 api/indexFindBy.go create mode 100644 api/insertItem.go create mode 100644 api/listCollections.go create mode 100644 api/listIndexes.go create mode 100644 api/listItems.go create mode 100644 configuration/configuration.go create mode 100644 configuration/default.go diff --git a/api/0_build.go b/api/0_build.go new file mode 100644 index 0000000..0a5a876 --- /dev/null +++ b/api/0_build.go @@ -0,0 +1,49 @@ +package api + +import ( + "github.com/fulldump/box" + + "inceptiondb/collection" +) + +func Build(collections map[string]*collection.Collection, dataDir string) *box.B { + + b := box.NewBox() + + b.WithInterceptors(interceptorPrintError) + + b.Resource("collections"). + WithActions( + box.Get(listCollections(collections)), + box.Post(createCollection(collections, dataDir)), + ) + + b.Resource("collections/{collection_name}"). + WithActions( + box.Get(listItems(collections)), + box.Post(insertItem(collections)), + ) + + b.Resource("collections/{collection_name}/count"). + WithActions( + box.Get(countItems(collections)), + ) + + b.Resource("collections/{collection_name}/index"). + WithActions( + box.Get(listIndexes(collections)), + box.Post(createIndex(collections)), + ) + + b.Resource("collections/{collection_name}/index/{index_name}"). + WithActions( + box.Get(getIndex(collections)), + ) + + b.Resource("collections/{collection_name}/index/{index_name}/findBy/{value}"). + WithActions( + box.Get(indexFindBy(collections)), + ) + + return b +} diff --git a/api/0_helpers.go b/api/0_helpers.go new file mode 100644 index 0000000..fbc0378 --- /dev/null +++ b/api/0_helpers.go @@ -0,0 +1,34 @@ +package api + +import ( + "context" + "encoding/json" + + "github.com/fulldump/box" +) + +func getBoxContext(ctx context.Context) *box.C { + + v := ctx.Value("box_context") + if c, ok := v.(*box.C); ok { + return c + } + + return nil +} + +func getParam(ctx context.Context, name string) (value string) { + return getBoxContext(ctx).Parameters[name] +} + +func interceptorPrintError(next box.H) box.H { + return func(ctx context.Context) { + next(ctx) + err := box.GetError(ctx) + if nil != err { + json.NewEncoder(box.GetResponse(ctx)).Encode(map[string]interface{}{ + "error": err.Error(), + }) + } + } +} diff --git a/api/countItems.go b/api/countItems.go new file mode 100644 index 0000000..f2c80a3 --- /dev/null +++ b/api/countItems.go @@ -0,0 +1,18 @@ +package api + +import ( + "context" + + "inceptiondb/collection" +) + +func countItems(collections map[string]*collection.Collection) interface{} { + return func(ctx context.Context) interface{} { + + collectionName := getParam(ctx, "collection_name") + + return map[string]interface{}{ + "count": len(collections[collectionName].Rows), + } + } +} diff --git a/api/createCollection.go b/api/createCollection.go new file mode 100644 index 0000000..ecf2928 --- /dev/null +++ b/api/createCollection.go @@ -0,0 +1,27 @@ +package api + +import ( + "path" + + "inceptiondb/collection" +) + +type createCollectionRequest struct { + Name string `json:"name"` +} + +func createCollection(collections map[string]*collection.Collection, dir string) interface{} { + return func(input *createCollectionRequest) (*createCollectionRequest, error) { + + filename := path.Join(dir, input.Name) + + col, err := collection.OpenCollection(filename) + if err != nil { + return nil, err + } + + collections[input.Name] = col + + return input, nil + } +} diff --git a/api/createIndex.go b/api/createIndex.go new file mode 100644 index 0000000..75a79ca --- /dev/null +++ b/api/createIndex.go @@ -0,0 +1,21 @@ +package api + +import ( + "context" + + "inceptiondb/collection" +) + +func createIndex(collections map[string]*collection.Collection) interface{} { + + return func(ctx context.Context, indexOptions *collection.IndexOptions) (*collection.IndexOptions, error) { + + collectionName := getParam(ctx, "collection_name") + err := collections[collectionName].Index(indexOptions) + if err != nil { + return nil, err + } + + return indexOptions, nil + } +} diff --git a/api/getIndex.go b/api/getIndex.go new file mode 100644 index 0000000..94f5afa --- /dev/null +++ b/api/getIndex.go @@ -0,0 +1,25 @@ +package api + +import ( + "context" + "fmt" + + "inceptiondb/collection" +) + +func getIndex(collections map[string]*collection.Collection) interface{} { + + return func(ctx context.Context) (*collection.IndexOptions, error) { + + collectionName := getParam(ctx, "collection_name") + indexName := getParam(ctx, "index_name") + _, exists := collections[collectionName].Indexes[indexName] + if !exists { + return nil, fmt.Errorf("index '%s' does not exist", indexName) + } + + return &collection.IndexOptions{ + Field: indexName, + }, nil + } +} diff --git a/api/indexFindBy.go b/api/indexFindBy.go new file mode 100644 index 0000000..9c9be45 --- /dev/null +++ b/api/indexFindBy.go @@ -0,0 +1,25 @@ +package api + +import ( + "context" + "fmt" + + "inceptiondb/collection" +) + +func indexFindBy(collections map[string]*collection.Collection) interface{} { + + return func(ctx context.Context) (interface{}, error) { + + collectionName := getParam(ctx, "collection_name") + indexName := getParam(ctx, "index_name") + value := getParam(ctx, "value") + result := map[string]interface{}{} + err := collections[collectionName].FindBy(indexName, value, &result) + if err != nil { + return nil, fmt.Errorf("item %s='%s' does not exist", indexName, value) + } + + return result, nil + } +} diff --git a/api/insertItem.go b/api/insertItem.go new file mode 100644 index 0000000..58a8510 --- /dev/null +++ b/api/insertItem.go @@ -0,0 +1,24 @@ +package api + +import ( + "context" + + "inceptiondb/collection" +) + +func insertItem(collections map[string]*collection.Collection) interface{} { + + type Item map[string]interface{} + + return func(ctx context.Context, item Item) (Item, error) { + + collectionName := getParam(ctx, "collection_name") + + err := collections[collectionName].Insert(item) + if err != nil { + return nil, err + } + + return item, nil + } +} diff --git a/api/listCollections.go b/api/listCollections.go new file mode 100644 index 0000000..3669ab7 --- /dev/null +++ b/api/listCollections.go @@ -0,0 +1,15 @@ +package api + +import "inceptiondb/collection" + +func listCollections(collections map[string]*collection.Collection) interface{} { + return func() []string { + result := []string{} + + for k, _ := range collections { + result = append(result, k) + } + + return result + } +} diff --git a/api/listIndexes.go b/api/listIndexes.go new file mode 100644 index 0000000..2a980f5 --- /dev/null +++ b/api/listIndexes.go @@ -0,0 +1,21 @@ +package api + +import ( + "context" + + "inceptiondb/collection" +) + +func listIndexes(collections map[string]*collection.Collection) interface{} { + return func(ctx context.Context) []string { + + result := []string{} + + collectionName := getParam(ctx, "collection_name") + for name, _ := range collections[collectionName].Indexes { + result = append(result, name) + } + + return result + } +} diff --git a/api/listItems.go b/api/listItems.go new file mode 100644 index 0000000..6eda350 --- /dev/null +++ b/api/listItems.go @@ -0,0 +1,20 @@ +package api + +import ( + "context" + "net/http" + + "inceptiondb/collection" +) + +func listItems(collections map[string]*collection.Collection) interface{} { + return func(ctx context.Context, w http.ResponseWriter) { + + collectionName := getParam(ctx, "collection_name") + collections[collectionName].Traverse(func(data []byte) { + w.Write(data) + w.Write([]byte("\n")) + }) + + } +} diff --git a/configuration/configuration.go b/configuration/configuration.go new file mode 100644 index 0000000..a5bc9f2 --- /dev/null +++ b/configuration/configuration.go @@ -0,0 +1,5 @@ +package configuration + +type Configuration struct { + Dir string `usage:"data directory"` +} diff --git a/configuration/default.go b/configuration/default.go new file mode 100644 index 0000000..f93d86b --- /dev/null +++ b/configuration/default.go @@ -0,0 +1,7 @@ +package configuration + +func Default() *Configuration { + return &Configuration{ + Dir: "data", + } +} diff --git a/main.go b/main.go index 23fbfef..22475ce 100644 --- a/main.go +++ b/main.go @@ -1,22 +1,16 @@ package main import ( - "context" - "encoding/json" "fmt" - "github.com/fulldump/box" - "inceptiondb/collection" "io/fs" - "net/http" - "path" "path/filepath" "strings" "time" -) -type Configuration struct { - Dir string `usage:"data directory"` -} + "inceptiondb/api" + "inceptiondb/collection" + "inceptiondb/configuration" +) var banner = ` _____ _ _ ____________ @@ -31,9 +25,7 @@ var banner = ` func main() { - c := &Configuration{ - Dir: "data", - } + c := configuration.Default() fmt.Println(banner) @@ -61,198 +53,5 @@ func main() { return nil }) - b := box.NewBox() - - b.WithInterceptors(InterceptorPrintError) - - b.Resource("collections"). - WithActions( - box.Get(listCollections(collections)), - box.Post(createCollection(collections, c.Dir)), - ) - - b.Resource("collections/{collection_name}"). - WithActions( - box.Get(listItems(collections)), - box.Post(insertItem(collections)), - ) - - b.Resource("collections/{collection_name}/count"). - WithActions( - box.Get(countItems(collections)), - ) - - b.Resource("collections/{collection_name}/index"). - WithActions( - box.Get(listIndexes(collections)), - box.Post(createIndex(collections)), - ) - - b.Resource("collections/{collection_name}/index/{index_name}"). - WithActions( - box.Get(getIndex(collections)), - ) - - b.Resource("collections/{collection_name}/index/{index_name}/findBy/{value}"). - WithActions( - box.Get(indexFindBy(collections)), - ) - - b.Serve() -} - -func InterceptorPrintError(next box.H) box.H { - return func(ctx context.Context) { - next(ctx) - err := box.GetError(ctx) - if nil != err { - json.NewEncoder(box.GetResponse(ctx)).Encode(map[string]interface{}{ - "error": err.Error(), - }) - } - } -} - -func getBoxContext(ctx context.Context) *box.C { - - v := ctx.Value("box_context") - if c, ok := v.(*box.C); ok { - return c - } - - return nil -} - -func listCollections(collections map[string]*collection.Collection) interface{} { - return func() []string { - result := []string{} - - for k, _ := range collections { - result = append(result, k) - } - - return result - } -} - -type createCollectionRequest struct { - Name string `json:"name"` -} - -func createCollection(collections map[string]*collection.Collection, dir string) interface{} { - return func(input *createCollectionRequest) (*createCollectionRequest, error) { - - filename := path.Join(dir, input.Name) - - col, err := collection.OpenCollection(filename) - if err != nil { - return nil, err - } - - collections[input.Name] = col - - return input, nil - } -} - -func listItems(collections map[string]*collection.Collection) interface{} { - return func(ctx context.Context, w http.ResponseWriter) { - - name := getBoxContext(ctx).Parameters["collection_name"] - collections[name].Traverse(func(data []byte) { - w.Write(data) - w.Write([]byte("\n")) - }) - - } -} - -func countItems(collections map[string]*collection.Collection) interface{} { - return func(ctx context.Context) interface{} { - - name := getBoxContext(ctx).Parameters["collection_name"] - - return map[string]interface{}{ - "count": len(collections[name].Rows), - } - } -} - -func insertItem(collections map[string]*collection.Collection) interface{} { - - type Item map[string]interface{} - - return func(ctx context.Context, item Item) (Item, error) { - - name := getBoxContext(ctx).Parameters["collection_name"] - - err := collections[name].Insert(item) - if err != nil { - return nil, err - } - - return item, nil - } -} - -func listIndexes(collections map[string]*collection.Collection) interface{} { - return func(ctx context.Context) []string { - - result := []string{} - - name := getBoxContext(ctx).Parameters["collection_name"] - for name, _ := range collections[name].Indexes { - result = append(result, name) - } - - return result - } -} - -func createIndex(collections map[string]*collection.Collection) interface{} { - - return func(ctx context.Context, indexOptions *collection.IndexOptions) (*collection.IndexOptions, error) { - - name := getBoxContext(ctx).Parameters["collection_name"] - err := collections[name].Index(indexOptions) - if err != nil { - return nil, err - } - - return indexOptions, nil - } -} - -func getIndex(collections map[string]*collection.Collection) interface{} { - - return func(ctx context.Context) (*collection.IndexOptions, error) { - - collectionName := getBoxContext(ctx).Parameters["collection_name"] - indexName := getBoxContext(ctx).Parameters["index_name"] - _, exists := collections[collectionName].Indexes[indexName] - if !exists { - return nil, fmt.Errorf("index '%s' does not exist", indexName) - } - - return &collection.IndexOptions{ - Field: indexName, - }, nil - } -} - -func indexFindBy(collections map[string]*collection.Collection) interface{} { - - return func(ctx context.Context) (interface{}, error) { - - collectionName := getBoxContext(ctx).Parameters["collection_name"] - indexName := getBoxContext(ctx).Parameters["index_name"] - value := getBoxContext(ctx).Parameters["value"] - result := map[string]interface{}{} - err := collections[collectionName].FindBy(indexName, value, &result) - if err != nil { - return nil, fmt.Errorf("item %s='%s' does not exist", indexName, value) - } - - return result, nil - } + api.Build(collections, c.Dir).Serve() } From 78ea681f13c8d8cbd20160443e606e98b98be099 Mon Sep 17 00:00:00 2001 From: fulldump Date: Wed, 6 Apr 2022 01:00:53 +0200 Subject: [PATCH 007/163] handle SIGTERM and SIGINT signals to close http server and files properly --- api/0_build.go | 8 ++- api/0_helpers.go | 20 +++++++ api/createCollection.go | 2 +- configuration/configuration.go | 3 +- configuration/default.go | 3 +- database/database.go | 104 +++++++++++++++++++++++++++++++++ main.go | 68 ++++++++++++--------- 7 files changed, 176 insertions(+), 32 deletions(-) create mode 100644 database/database.go diff --git a/api/0_build.go b/api/0_build.go index 0a5a876..156a5db 100644 --- a/api/0_build.go +++ b/api/0_build.go @@ -2,15 +2,17 @@ package api import ( "github.com/fulldump/box" - - "inceptiondb/collection" + "inceptiondb/database" ) -func Build(collections map[string]*collection.Collection, dataDir string) *box.B { +func Build(db *database.Database, dataDir string) *box.B { // TODO: remove datadir + + collections := db.Collections b := box.NewBox() b.WithInterceptors(interceptorPrintError) + b.WithInterceptors(interceptorUnavailable(db)) b.Resource("collections"). WithActions( diff --git a/api/0_helpers.go b/api/0_helpers.go index fbc0378..25d3986 100644 --- a/api/0_helpers.go +++ b/api/0_helpers.go @@ -3,6 +3,8 @@ package api import ( "context" "encoding/json" + "fmt" + "inceptiondb/database" "github.com/fulldump/box" ) @@ -32,3 +34,21 @@ func interceptorPrintError(next box.H) box.H { } } } + +func interceptorUnavailable(db *database.Database) box.I { + return func(next box.H) box.H { + return func(ctx context.Context) { + + status := db.GetStatus() + if status == database.StatusOpening { + box.SetError(ctx, fmt.Errorf("temporary unavailable: opening")) + return + } + if status == database.StatusClosing { + box.SetError(ctx, fmt.Errorf("temporary unavailable: closing")) + return + } + next(ctx) + } + } +} diff --git a/api/createCollection.go b/api/createCollection.go index ecf2928..e47b4ef 100644 --- a/api/createCollection.go +++ b/api/createCollection.go @@ -2,7 +2,7 @@ package api import ( "path" - + "inceptiondb/collection" ) diff --git a/configuration/configuration.go b/configuration/configuration.go index a5bc9f2..507fbed 100644 --- a/configuration/configuration.go +++ b/configuration/configuration.go @@ -1,5 +1,6 @@ package configuration type Configuration struct { - Dir string `usage:"data directory"` + HttpAddr string `usage:"HTTP address"` + Dir string `usage:"data directory"` } diff --git a/configuration/default.go b/configuration/default.go index f93d86b..8fd5ed2 100644 --- a/configuration/default.go +++ b/configuration/default.go @@ -2,6 +2,7 @@ package configuration func Default() *Configuration { return &Configuration{ - Dir: "data", + Dir: "data", + HttpAddr: "127.0.0.1:8080", } } diff --git a/database/database.go b/database/database.go new file mode 100644 index 0000000..8e177f7 --- /dev/null +++ b/database/database.go @@ -0,0 +1,104 @@ +package database + +import ( + "fmt" + "inceptiondb/collection" + "inceptiondb/configuration" + "io/fs" + "path/filepath" + "strings" + "time" +) + +const ( + StatusOpening = "opening" + StatusOperating = "operating" + StatusClosing = "closing" +) + +type Database struct { + config *configuration.Configuration + status string + Collections map[string]*collection.Collection + exit chan struct{} +} + +func NewDatabase(config *configuration.Configuration) *Database { // todo: return error? + s := &Database{ + config: config, + status: StatusOpening, + Collections: map[string]*collection.Collection{}, + exit: make(chan struct{}), + } + + return s +} + +func (db *Database) GetStatus() string { + return db.status +} + +func (db *Database) load() error { + + fmt.Printf("Loading data...\n") // todo: move to logger + dir := db.config.Dir + err := filepath.WalkDir(dir, func(filename string, d fs.DirEntry, err error) error { + if d.IsDir() { + return nil + } + + name := filename + name = strings.TrimPrefix(name, dir) + name = strings.TrimPrefix(name, "/") + + t0 := time.Now() + col, err := collection.OpenCollection(filename) + if err != nil { + fmt.Printf("ERROR: open collection '%s': %s\n", filename, err.Error()) // todo: move to logger + return err + } + fmt.Println(name, len(col.Rows), time.Since(t0)) // todo: move to logger + + db.Collections[name] = col + + return nil + }) + + if err != nil { + db.status = StatusClosing + return err + } + + db.status = StatusOperating + + return nil + +} + +func (db *Database) Start() error { + + go db.load() + + <-db.exit + + return nil +} + +func (db *Database) Stop() error { + + defer close(db.exit) + + db.status = StatusClosing + + var lastErr error + for name, col := range db.Collections { + fmt.Printf("Closing '%s'...\n", name) + err := col.Close() + if err != nil { + fmt.Printf("ERROR: close(%s): %s", name, err.Error()) + lastErr = err + } + } + + return lastErr +} diff --git a/main.go b/main.go index 22475ce..9357571 100644 --- a/main.go +++ b/main.go @@ -1,15 +1,17 @@ package main import ( + "context" "fmt" - "io/fs" - "path/filepath" - "strings" - "time" - + "github.com/fulldump/box" "inceptiondb/api" - "inceptiondb/collection" "inceptiondb/configuration" + "inceptiondb/database" + "net/http" + "os" + "os/signal" + "sync" + "syscall" ) var banner = ` @@ -25,33 +27,47 @@ var banner = ` func main() { - c := configuration.Default() - fmt.Println(banner) - collections := map[string]*collection.Collection{} - fmt.Printf("Loading data...\n") - filepath.WalkDir(c.Dir, func(filename string, d fs.DirEntry, err error) error { - if d.IsDir() { - return nil + c := configuration.Default() + d := database.NewDatabase(c) + b := api.Build(d, c.Dir) + s := &http.Server{ + Addr: c.HttpAddr, + Handler: box.Box2Http(b), + } + + signalChan := make(chan os.Signal, 1) + signal.Notify(signalChan, syscall.SIGTERM, syscall.SIGINT) + go func() { + for { + sig := <-signalChan + fmt.Println("Signal received", sig.String()) + d.Stop() + s.Shutdown(context.Background()) } + }() - name := filename - name = strings.TrimPrefix(name, c.Dir) - name = strings.TrimPrefix(name, "/") + wg := &sync.WaitGroup{} - t0 := time.Now() - col, err := collection.OpenCollection(filename) + wg.Add(1) + go func() { + defer wg.Done() + err := d.Start() if err != nil { - fmt.Printf("ERROR: open collection '%s': %s\n", filename, err.Error()) - return nil + fmt.Println(err.Error()) } - fmt.Println(name, len(col.Rows), time.Since(t0)) - - collections[name] = col + }() - return nil - }) + wg.Add(1) + go func() { + defer wg.Done() + fmt.Println("listening on", s.Addr) + err := s.ListenAndServe() + if err != nil { + fmt.Println(err.Error()) + } + }() - api.Build(collections, c.Dir).Serve() + wg.Wait() } From e461cf6afc18d5f9328e4d693c741494d8efc3b0 Mon Sep 17 00:00:00 2001 From: Gerardo JT Date: Sat, 9 Apr 2022 03:24:34 +0200 Subject: [PATCH 008/163] Add license AGPL3 The decision has been made prioritizing the freedom of people. License can be modified in the future (if possible) in order to preserve freedom of people. --- LICENSE | 661 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 661 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0ad25db --- /dev/null +++ b/LICENSE @@ -0,0 +1,661 @@ + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. From 5946ebd882e1b4f1b4e60725e6ed5cbadfb25334 Mon Sep 17 00:00:00 2001 From: fulldump Date: Thu, 21 Apr 2022 01:34:39 +0200 Subject: [PATCH 009/163] Implement Delete and: use Row struct to wrap the payload wrap index entries index is now map of *Row --- api/0_build.go | 1 + api/indexDeleteBy.go | 31 +++++++ collection/collection.go | 163 +++++++++++++++++++++++++++++----- collection/collection_test.go | 109 +++++++++++++++++++---- collection/index.go | 7 +- 5 files changed, 269 insertions(+), 42 deletions(-) create mode 100644 api/indexDeleteBy.go diff --git a/api/0_build.go b/api/0_build.go index 156a5db..520130d 100644 --- a/api/0_build.go +++ b/api/0_build.go @@ -45,6 +45,7 @@ func Build(db *database.Database, dataDir string) *box.B { // TODO: remove datad b.Resource("collections/{collection_name}/index/{index_name}/findBy/{value}"). WithActions( box.Get(indexFindBy(collections)), + box.Delete(indexDeleteBy(collections)), ) return b diff --git a/api/indexDeleteBy.go b/api/indexDeleteBy.go new file mode 100644 index 0000000..0b93a11 --- /dev/null +++ b/api/indexDeleteBy.go @@ -0,0 +1,31 @@ +package api + +import ( + "context" + "fmt" + + "inceptiondb/collection" +) + +func indexDeleteBy(collections map[string]*collection.Collection) interface{} { + + return func(ctx context.Context) (interface{}, error) { + + collectionName := getParam(ctx, "collection_name") + indexName := getParam(ctx, "index_name") + value := getParam(ctx, "value") + result := map[string]interface{}{} + + err := collections[collectionName].FindBy(indexName, value, &result) + if err != nil { + return nil, fmt.Errorf("item %s='%s' does not exist", indexName, value) + } + + err = collections[collectionName].DeleteBy(indexName, value) + if err != nil { + return nil, fmt.Errorf("item %s='%s' does not exist", indexName, value) + } + + return result, nil + } +} diff --git a/collection/collection.go b/collection/collection.go index e364e51..400ddde 100644 --- a/collection/collection.go +++ b/collection/collection.go @@ -14,12 +14,17 @@ import ( type Collection struct { filename string // Just informative... file *os.File - Rows []json.RawMessage + Rows []*Row rowsMutex *sync.Mutex Indexes map[string]Index //buffer *bufio.Writer // TODO: use write buffer to improve performance (x3 in tests) } +type Row struct { + I int // position in Rows + Payload json.RawMessage +} + func OpenCollection(filename string) (*Collection, error) { // TODO: initialize, read all file and apply its changes into memory @@ -29,7 +34,7 @@ func OpenCollection(filename string) (*Collection, error) { } collection := &Collection{ - Rows: []json.RawMessage{}, + Rows: []*Row{}, rowsMutex: &sync.Mutex{}, filename: filename, Indexes: map[string]Index{}, @@ -55,11 +60,21 @@ func OpenCollection(filename string) (*Collection, error) { } case "index": options := &IndexOptions{} - json.Unmarshal(command.Payload, options) + json.Unmarshal(command.Payload, options) // Todo: handle error properly err := collection.indexRows(options) if err != nil { fmt.Printf("WARNING: create index '%s': %s", options.Field, err.Error()) } + case "delete": + filter := struct { + Field string + Value string + }{} + json.Unmarshal(command.Payload, &filter) // Todo: handle error properly + err := collection.DeleteBy(filter.Field, filter.Value) + if err != nil { + fmt.Printf("WARNING: delete item '%s'='%s': %s", filter.Field, filter.Value, err.Error()) + } } } @@ -75,13 +90,18 @@ func OpenCollection(filename string) (*Collection, error) { func (c *Collection) addRow(payload json.RawMessage) error { - err := indexInsert(c.Indexes, payload) + row := &Row{ + Payload: payload, + } + + err := indexInsert(c.Indexes, row) if err != nil { return err } c.rowsMutex.Lock() - c.Rows = append(c.Rows, payload) + row.I = len(c.Rows) + c.Rows = append(c.Rows, row) c.rowsMutex.Unlock() return nil @@ -123,25 +143,28 @@ func (c *Collection) Insert(item interface{}) error { func (c *Collection) FindOne(data interface{}) { for _, row := range c.Rows { - json.Unmarshal(row, data) + json.Unmarshal(row.Payload, data) return } // TODO return with error not found? or nil? } -func (c *Collection) Traverse(f func(data []byte)) { +func (c *Collection) Traverse(f func(data []byte)) { // todo: return *Row instead of data? for _, row := range c.Rows { - f(row) + f(row.Payload) } } func (c *Collection) indexRows(options *IndexOptions) error { - index := Index{} - for _, rowData := range c.Rows { - err := indexRow(index, options.Field, rowData) + index := Index{ + Entries: map[string]*Row{}, + RWmutex: &sync.RWMutex{}, + } + for _, row := range c.Rows { + err := indexRow(index, options.Field, row) if err != nil { - return fmt.Errorf("index row: %w, data: %s", err, string(rowData)) + return fmt.Errorf("index row: %w, data: %s", err, string(row.Payload)) } } c.Indexes[options.Field] = index @@ -179,9 +202,9 @@ func (c *Collection) Index(options *IndexOptions) error { return nil } -func indexInsert(indexes map[string]Index, rowData []byte) (err error) { +func indexInsert(indexes map[string]Index, row *Row) (err error) { for key, index := range indexes { - err = indexRow(index, key, rowData) + err = indexRow(index, key, row) if err != nil { // TODO: undo previous work? two phases (check+commit) ? break @@ -191,11 +214,11 @@ func indexInsert(indexes map[string]Index, rowData []byte) (err error) { return } -func indexRow(index Index, field string, rowData []byte) error { +func indexRow(index Index, field string, row *Row) error { item := map[string]interface{}{} - err := json.Unmarshal(rowData, &item) + err := json.Unmarshal(row.Payload, &item) if err != nil { return fmt.Errorf("unmarshal: %w", err) } @@ -208,20 +231,27 @@ func indexRow(index Index, field string, rowData []byte) error { switch value := itemValue.(type) { case string: - if _, exists := index[value]; exists { + + index.RWmutex.RLock() + _, exists := index.Entries[value] + index.RWmutex.RUnlock() + if exists { return fmt.Errorf("index conflict: field '%s' with value '%s'", field, value) } - index[value] = rowData + + index.RWmutex.Lock() + index.Entries[value] = row + index.RWmutex.Unlock() case []interface{}: for _, v := range value { s := v.(string) // TODO: handle this casting error - if _, exists := index[s]; exists { + if _, exists := index.Entries[s]; exists { return fmt.Errorf("index conflict: field '%s' with value '%s'", field, value) } } for _, v := range value { s := v.(string) // TODO: handle this casting error - index[s] = rowData + index.Entries[s] = row } default: return fmt.Errorf("type not supported") @@ -230,6 +260,49 @@ func indexRow(index Index, field string, rowData []byte) error { return nil } +func indexRemove(indexes map[string]Index, row *Row) (err error) { + for key, index := range indexes { + err = unindexRow(index, key, row) + if err != nil { + // TODO: does this make any sense? + break + } + } + + return +} + +func unindexRow(index Index, field string, row *Row) error { + + item := map[string]interface{}{} + + err := json.Unmarshal(row.Payload, &item) + if err != nil { + return fmt.Errorf("unmarshal: %w", err) + } + + itemValue, itemExists := item[field] + if !itemExists { + // Do not index + return nil + } + + switch value := itemValue.(type) { + case string: + delete(index.Entries, value) + case []interface{}: + for _, v := range value { + s := v.(string) // TODO: handle this casting error + delete(index.Entries, s) + } + default: + // Should this error? + return fmt.Errorf("type not supported") + } + + return nil +} + func (c *Collection) FindBy(field string, value string, data interface{}) error { index, ok := c.Indexes[field] @@ -237,12 +310,58 @@ func (c *Collection) FindBy(field string, value string, data interface{}) error return fmt.Errorf("field '%s' is not indexed", field) } - row, ok := index[value] + row, ok := index.Entries[value] + if !ok { + return fmt.Errorf("%s '%s' not found", field, value) + } + + return json.Unmarshal(row.Payload, &data) +} + +func (c *Collection) DeleteBy(field string, value string) error { + + index, ok := c.Indexes[field] + if !ok { + return fmt.Errorf("field '%s' is not indexed", field) + } + + row, ok := index.Entries[value] if !ok { return fmt.Errorf("%s '%s' not found", field, value) } - return json.Unmarshal(row, &data) + err := indexRemove(c.Indexes, row) + if err != nil { + return fmt.Errorf("could not unindex %s='%v'", field, value) + } + + c.rowsMutex.Lock() + c.Rows[row.I] = c.Rows[len(c.Rows)-1] + c.Rows = c.Rows[:len(c.Rows)-1] + c.rowsMutex.Unlock() + + // Persist + payload, err := json.Marshal(map[string]interface{}{ + "field": field, + "value": value, + }) + if err != nil { + return err // todo: wrap error + } + command := &Command{ + Name: "delete", + Uuid: uuid.New().String(), + Timestamp: time.Now().UnixNano(), + StartByte: 0, + Payload: payload, + } + + err = json.NewEncoder(c.file).Encode(command) + if err != nil { + return fmt.Errorf("json encode command: %w", err) + } + + return nil } func (c *Collection) Close() error { diff --git a/collection/collection_test.go b/collection/collection_test.go index 00ead8c..79752af 100644 --- a/collection/collection_test.go +++ b/collection/collection_test.go @@ -167,7 +167,7 @@ func TestIndexSparse(t *testing.T) { // Check AssertNil(errIndex) - AssertEqual(len(c.Indexes["email"]), 0) + AssertEqual(len(c.Indexes["email"].Entries), 0) }) } @@ -191,7 +191,7 @@ func TestCollection_Index_Collision(t *testing.T) { }) } -func TestPersistence(t *testing.T) { +func TestPersistenceInsertAndIndex(t *testing.T) { Environment(func(filename string) { // Setup @@ -217,28 +217,101 @@ func TestPersistence(t *testing.T) { }) } -func TestInsert100Kssss(t *testing.T) { +func TestPersistenceDelete(t *testing.T) { + Environment(func(filename string) { - t.Skip() + // Setup + c, _ := OpenCollection(filename) + c.Index(&IndexOptions{Field: "email"}) + c.Insert(map[string]interface{}{"id": "1", "name": "Pablo", "email": []string{"pablo@email.com", "pablo2018@yahoo.com"}}) + c.Insert(map[string]interface{}{"id": "2", "name": "Sara", "email": []string{"sara@email.com", "sara.jimenez8@yahoo.com"}}) + c.Insert(map[string]interface{}{"id": "3", "name": "Ana", "email": []string{"ana@email.com", "ana@yahoo.com"}}) + c.DeleteBy("email", "sara@email.com") + c.Close() + + // Run + c, _ = OpenCollection(filename) + user := struct { + Id string + Name string + Email []string + }{} + findByErr := c.FindBy("email", "sara@email.com", &user) + + // Check + AssertNotNil(findByErr) + AssertEqual(findByErr.Error(), "email 'sara@email.com' not found") - // Setup - c, _ := OpenCollection("../data/mongodb") - defer c.Close() + AssertEqual(len(c.Rows), 2) - c.Index(&IndexOptions{ - Field: "uuid", }) - c.Index(&IndexOptions{ - Field: "i", +} + +func TestInsert1M_serial(t *testing.T) { + + t.Skip() + + Environment(func(filename string) { + // Setup + c, _ := OpenCollection(filename) + defer c.Close() + + c.Index(&IndexOptions{ + Field: "uuid", + }) + c.Index(&IndexOptions{ + Field: "i", + }) + + // Run + n := 1000 * 1000 + for i := 0; i < n; i++ { + c.Insert(map[string]interface{}{"uuid": uuid.New().String(), "hello": "world", "i": strconv.Itoa(i)}) + } + + // Check + AssertEqual(len(c.Rows), n) }) - // Run - n := 1000 * 1000 - for i := 0; i < n; i++ { - c.Insert(map[string]interface{}{"uuid": uuid.New().String(), "hello": "world", "i": strconv.Itoa(i)}) - } +} - // Check - AssertEqual(len(c.Rows), n) +func TestInsert1M_concurrent(t *testing.T) { + + t.Skip() + + Environment(func(filename string) { + + // Setup + c, _ := OpenCollection(filename) + defer c.Close() + + c.Index(&IndexOptions{ + Field: "uuid", + }) + c.Index(&IndexOptions{ + Field: "i", + }) + + // Run + wg := &sync.WaitGroup{} + workers := 16 + n := 1000 * 1000 / workers + for w := 0; w < workers; w++ { + wg.Add(1) + go func(w int) { + defer wg.Done() + for i := 0; i < n; i++ { + c.Insert(map[string]interface{}{"uuid": uuid.New().String(), "hello": "world", "i": strconv.Itoa(i + n*w)}) + } + }(w) + } + + wg.Wait() + + // Check + AssertEqual(len(c.Rows), n*workers) + }) } + +// TODO: test concurrent delete diff --git a/collection/index.go b/collection/index.go index 5f99deb..febe84b 100644 --- a/collection/index.go +++ b/collection/index.go @@ -1,9 +1,12 @@ package collection -import "encoding/json" +import "sync" // Index should be an interface to allow multiple kinds and implementations -type Index map[string]json.RawMessage +type Index struct { + Entries map[string]*Row + RWmutex *sync.RWMutex +} // IndexOptions should have attributes like unique, sparse, multikey, sorted, background, etc... // Index should be an interface to have multiple indexes implementations, key value, B-Tree, bitmap, geo, cache... From 44d0834a64fcde7285c517335a7fab41467c22e3 Mon Sep 17 00:00:00 2001 From: fulldump Date: Thu, 21 Apr 2022 03:07:53 +0200 Subject: [PATCH 010/163] add data model example diagram --- doc/data-model-example.drawio.svg | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 doc/data-model-example.drawio.svg diff --git a/doc/data-model-example.drawio.svg b/doc/data-model-example.drawio.svg new file mode 100644 index 0000000..1634a3a --- /dev/null +++ b/doc/data-model-example.drawio.svg @@ -0,0 +1,4 @@ + + + +
Row {
  I: 0
  Payload: {
    dni: "123123W",
    colors: ["red", "blue"]
  }
}
Row {...
Row {
  I: 1
  Payload: {
    dni: "456456X",
    colors: ["green", "white"]
  }
}
Row {...
0
0
1
1
[]Rows
[]Rows
"red"
"red"
"blue"
"blue"
"green"
"green"
"white"
"white"
Index: colors
Index: colors
"123123W"
"123123W"
"456456X"
"456456X"
Index: dni
Index: dni
Row {
  I: 2
  Payload: {
    dni: ["11111A", "22222B"],
    colors: "black"
  }
}
Row {...
"11111A"
"11111A"
"22222B"
"22222B"
"black"
"black"
2
2
Viewer does not support full SVG 1.1
\ No newline at end of file From ecebbaa12593970d6992a16a789306ad2ce25c1e Mon Sep 17 00:00:00 2001 From: fulldump Date: Thu, 12 May 2022 01:01:28 +0200 Subject: [PATCH 011/163] feat: Implement update with json patch (RFC7386) + test Co-authored-by: Alfonso Cantos --- collection/collection.go | 77 ++ collection/collection_test.go | 28 + go.mod | 5 + go.sum | 5 + vendor/github.com/evanphx/json-patch/LICENSE | 25 + .../github.com/evanphx/json-patch/README.md | 317 +++++++ .../github.com/evanphx/json-patch/errors.go | 38 + vendor/github.com/evanphx/json-patch/merge.go | 386 +++++++++ vendor/github.com/evanphx/json-patch/patch.go | 784 ++++++++++++++++++ vendor/github.com/pkg/errors/.gitignore | 24 + vendor/github.com/pkg/errors/.travis.yml | 10 + vendor/github.com/pkg/errors/LICENSE | 23 + vendor/github.com/pkg/errors/Makefile | 44 + vendor/github.com/pkg/errors/README.md | 59 ++ vendor/github.com/pkg/errors/appveyor.yml | 32 + vendor/github.com/pkg/errors/errors.go | 288 +++++++ vendor/github.com/pkg/errors/go113.go | 38 + vendor/github.com/pkg/errors/stack.go | 177 ++++ vendor/modules.txt | 6 + 19 files changed, 2366 insertions(+) create mode 100644 vendor/github.com/evanphx/json-patch/LICENSE create mode 100644 vendor/github.com/evanphx/json-patch/README.md create mode 100644 vendor/github.com/evanphx/json-patch/errors.go create mode 100644 vendor/github.com/evanphx/json-patch/merge.go create mode 100644 vendor/github.com/evanphx/json-patch/patch.go create mode 100644 vendor/github.com/pkg/errors/.gitignore create mode 100644 vendor/github.com/pkg/errors/.travis.yml create mode 100644 vendor/github.com/pkg/errors/LICENSE create mode 100644 vendor/github.com/pkg/errors/Makefile create mode 100644 vendor/github.com/pkg/errors/README.md create mode 100644 vendor/github.com/pkg/errors/appveyor.yml create mode 100644 vendor/github.com/pkg/errors/errors.go create mode 100644 vendor/github.com/pkg/errors/go113.go create mode 100644 vendor/github.com/pkg/errors/stack.go diff --git a/collection/collection.go b/collection/collection.go index 400ddde..9ae1f00 100644 --- a/collection/collection.go +++ b/collection/collection.go @@ -8,6 +8,7 @@ import ( "sync" "time" + jsonpatch "github.com/evanphx/json-patch" "github.com/google/uuid" ) @@ -75,6 +76,17 @@ func OpenCollection(filename string) (*Collection, error) { if err != nil { fmt.Printf("WARNING: delete item '%s'='%s': %s", filter.Field, filter.Value, err.Error()) } + case "patch": + params := struct { + Field string + Value string + Diff map[string]interface{} + }{} + json.Unmarshal(command.Payload, ¶ms) + err := collection.PatchBy(params.Field, params.Value, params.Diff) + if err != nil { + fmt.Printf("WARNING: patch item '%s'='%s': %s", params.Field, params.Value, err.Error()) + } } } @@ -364,6 +376,71 @@ func (c *Collection) DeleteBy(field string, value string) error { return nil } +func (c *Collection) PatchBy(field string, value string, patch map[string]interface{}) error { + + index, ok := c.Indexes[field] + if !ok { + return fmt.Errorf("field '%s' is not indexed", field) + } + + row, ok := index.Entries[value] + if !ok { + return fmt.Errorf("%s '%s' not found", field, value) + } + + patchBytes, err := json.Marshal(patch) + if err != nil { + return fmt.Errorf("marshal patch: %w", err) + } + + newPayload, err := jsonpatch.MergePatch(row.Payload, patchBytes) + if err != nil { + return fmt.Errorf("cannot apply patch: %w", err) + } + + diff, err := jsonpatch.CreateMergePatch(row.Payload, newPayload) // todo: optimization: discard operation if empty + if err != nil { + return fmt.Errorf("cannot diff: %w", err) + } + + // index update + err = indexRemove(c.Indexes, row) + if err != nil { + return fmt.Errorf("indexRemove: %w", err) + } + + row.Payload = newPayload + + err = indexInsert(c.Indexes, row) + if err != nil { + return fmt.Errorf("indexInsert: %w", err) + } + + // Persist + payload, err := json.Marshal(map[string]interface{}{ + "field": field, + "value": value, + "diff": json.RawMessage(diff), + }) + if err != nil { + return err // todo: wrap error + } + command := &Command{ + Name: "patch", + Uuid: uuid.New().String(), + Timestamp: time.Now().UnixNano(), + StartByte: 0, + Payload: payload, + } + + err = json.NewEncoder(c.file).Encode(command) + if err != nil { + return fmt.Errorf("json encode command: %w", err) + } + + return nil +} + func (c *Collection) Close() error { err := c.file.Close() c.file = nil diff --git a/collection/collection_test.go b/collection/collection_test.go index 79752af..cbb466b 100644 --- a/collection/collection_test.go +++ b/collection/collection_test.go @@ -247,6 +247,34 @@ func TestPersistenceDelete(t *testing.T) { }) } +func TestPersistenceUpdate(t *testing.T) { + Environment(func(filename string) { + + // Setup + c, _ := OpenCollection(filename) + c.Index(&IndexOptions{Field: "id"}) + c.Insert(map[string]interface{}{"id": "1", "name": "Pablo", "email": []string{"pablo@email.com", "pablo2018@yahoo.com"}}) + c.PatchBy("id", "1", map[string]interface{}{"name": "Jaime"}) + c.Close() + + // Run + c, _ = OpenCollection(filename) + user := struct { + Id string + Name string + Email []string + }{} + findByErr := c.FindBy("id", "1", &user) + + // Check + AssertNil(findByErr) + AssertEqual(user.Name, "Jaime") + + AssertEqual(len(c.Rows), 1) + + }) +} + func TestInsert1M_serial(t *testing.T) { t.Skip() diff --git a/go.mod b/go.mod index d114e6e..42e2840 100644 --- a/go.mod +++ b/go.mod @@ -7,3 +7,8 @@ require ( github.com/fulldump/box v0.1.3 github.com/google/uuid v1.3.0 ) + +require ( + github.com/evanphx/json-patch v0.5.2 // indirect + github.com/pkg/errors v0.9.1 // indirect +) diff --git a/go.sum b/go.sum index 8241c30..27b740d 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,11 @@ +github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k= +github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= github.com/fulldump/biff v1.3.0 h1:FZDqvP8lkrCMDv/oNEH+j2unpuAY+8aXZ44GIvXYOx4= github.com/fulldump/biff v1.3.0/go.mod h1:TnBce9eRITmnv3otdmITKeU/zmC08DxotA9s0VcJELg= github.com/fulldump/box v0.1.3 h1:Qz1tDf0giqC+EVj51CdFLHgaLAayboTT8/FnP6Zd1EA= github.com/fulldump/box v0.1.3/go.mod h1:UO8uDIKLNvnsp/ru2za7gev9VBlRIeAVH3bLl7xdfNg= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/vendor/github.com/evanphx/json-patch/LICENSE b/vendor/github.com/evanphx/json-patch/LICENSE new file mode 100644 index 0000000..df76d7d --- /dev/null +++ b/vendor/github.com/evanphx/json-patch/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) 2014, Evan Phoenix +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +* Neither the name of the Evan Phoenix nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/evanphx/json-patch/README.md b/vendor/github.com/evanphx/json-patch/README.md new file mode 100644 index 0000000..28e3516 --- /dev/null +++ b/vendor/github.com/evanphx/json-patch/README.md @@ -0,0 +1,317 @@ +# JSON-Patch +`jsonpatch` is a library which provides functionality for both applying +[RFC6902 JSON patches](http://tools.ietf.org/html/rfc6902) against documents, as +well as for calculating & applying [RFC7396 JSON merge patches](https://tools.ietf.org/html/rfc7396). + +[![GoDoc](https://godoc.org/github.com/evanphx/json-patch?status.svg)](http://godoc.org/github.com/evanphx/json-patch) +[![Build Status](https://travis-ci.org/evanphx/json-patch.svg?branch=master)](https://travis-ci.org/evanphx/json-patch) +[![Report Card](https://goreportcard.com/badge/github.com/evanphx/json-patch)](https://goreportcard.com/report/github.com/evanphx/json-patch) + +# Get It! + +**Latest and greatest**: +```bash +go get -u github.com/evanphx/json-patch/v5 +``` + +**Stable Versions**: +* Version 5: `go get -u gopkg.in/evanphx/json-patch.v5` +* Version 4: `go get -u gopkg.in/evanphx/json-patch.v4` + +(previous versions below `v3` are unavailable) + +# Use It! +* [Create and apply a merge patch](#create-and-apply-a-merge-patch) +* [Create and apply a JSON Patch](#create-and-apply-a-json-patch) +* [Comparing JSON documents](#comparing-json-documents) +* [Combine merge patches](#combine-merge-patches) + + +# Configuration + +* There is a global configuration variable `jsonpatch.SupportNegativeIndices`. + This defaults to `true` and enables the non-standard practice of allowing + negative indices to mean indices starting at the end of an array. This + functionality can be disabled by setting `jsonpatch.SupportNegativeIndices = + false`. + +* There is a global configuration variable `jsonpatch.AccumulatedCopySizeLimit`, + which limits the total size increase in bytes caused by "copy" operations in a + patch. It defaults to 0, which means there is no limit. + +These global variables control the behavior of `jsonpatch.Apply`. + +An alternative to `jsonpatch.Apply` is `jsonpatch.ApplyWithOptions` whose behavior +is controlled by an `options` parameter of type `*jsonpatch.ApplyOptions`. + +Structure `jsonpatch.ApplyOptions` includes the configuration options above +and adds two new options: `AllowMissingPathOnRemove` and `EnsurePathExistsOnAdd`. + +When `AllowMissingPathOnRemove` is set to `true`, `jsonpatch.ApplyWithOptions` will ignore +`remove` operations whose `path` points to a non-existent location in the JSON document. +`AllowMissingPathOnRemove` defaults to `false` which will lead to `jsonpatch.ApplyWithOptions` +returning an error when hitting a missing `path` on `remove`. + +When `EnsurePathExistsOnAdd` is set to `true`, `jsonpatch.ApplyWithOptions` will make sure +that `add` operations produce all the `path` elements that are missing from the target object. + +Use `jsonpatch.NewApplyOptions` to create an instance of `jsonpatch.ApplyOptions` +whose values are populated from the global configuration variables. + +## Create and apply a merge patch +Given both an original JSON document and a modified JSON document, you can create +a [Merge Patch](https://tools.ietf.org/html/rfc7396) document. + +It can describe the changes needed to convert from the original to the +modified JSON document. + +Once you have a merge patch, you can apply it to other JSON documents using the +`jsonpatch.MergePatch(document, patch)` function. + +```go +package main + +import ( + "fmt" + + jsonpatch "github.com/evanphx/json-patch" +) + +func main() { + // Let's create a merge patch from these two documents... + original := []byte(`{"name": "John", "age": 24, "height": 3.21}`) + target := []byte(`{"name": "Jane", "age": 24}`) + + patch, err := jsonpatch.CreateMergePatch(original, target) + if err != nil { + panic(err) + } + + // Now lets apply the patch against a different JSON document... + + alternative := []byte(`{"name": "Tina", "age": 28, "height": 3.75}`) + modifiedAlternative, err := jsonpatch.MergePatch(alternative, patch) + + fmt.Printf("patch document: %s\n", patch) + fmt.Printf("updated alternative doc: %s\n", modifiedAlternative) +} +``` + +When ran, you get the following output: + +```bash +$ go run main.go +patch document: {"height":null,"name":"Jane"} +updated alternative doc: {"age":28,"name":"Jane"} +``` + +## Create and apply a JSON Patch +You can create patch objects using `DecodePatch([]byte)`, which can then +be applied against JSON documents. + +The following is an example of creating a patch from two operations, and +applying it against a JSON document. + +```go +package main + +import ( + "fmt" + + jsonpatch "github.com/evanphx/json-patch" +) + +func main() { + original := []byte(`{"name": "John", "age": 24, "height": 3.21}`) + patchJSON := []byte(`[ + {"op": "replace", "path": "/name", "value": "Jane"}, + {"op": "remove", "path": "/height"} + ]`) + + patch, err := jsonpatch.DecodePatch(patchJSON) + if err != nil { + panic(err) + } + + modified, err := patch.Apply(original) + if err != nil { + panic(err) + } + + fmt.Printf("Original document: %s\n", original) + fmt.Printf("Modified document: %s\n", modified) +} +``` + +When ran, you get the following output: + +```bash +$ go run main.go +Original document: {"name": "John", "age": 24, "height": 3.21} +Modified document: {"age":24,"name":"Jane"} +``` + +## Comparing JSON documents +Due to potential whitespace and ordering differences, one cannot simply compare +JSON strings or byte-arrays directly. + +As such, you can instead use `jsonpatch.Equal(document1, document2)` to +determine if two JSON documents are _structurally_ equal. This ignores +whitespace differences, and key-value ordering. + +```go +package main + +import ( + "fmt" + + jsonpatch "github.com/evanphx/json-patch" +) + +func main() { + original := []byte(`{"name": "John", "age": 24, "height": 3.21}`) + similar := []byte(` + { + "age": 24, + "height": 3.21, + "name": "John" + } + `) + different := []byte(`{"name": "Jane", "age": 20, "height": 3.37}`) + + if jsonpatch.Equal(original, similar) { + fmt.Println(`"original" is structurally equal to "similar"`) + } + + if !jsonpatch.Equal(original, different) { + fmt.Println(`"original" is _not_ structurally equal to "different"`) + } +} +``` + +When ran, you get the following output: +```bash +$ go run main.go +"original" is structurally equal to "similar" +"original" is _not_ structurally equal to "different" +``` + +## Combine merge patches +Given two JSON merge patch documents, it is possible to combine them into a +single merge patch which can describe both set of changes. + +The resulting merge patch can be used such that applying it results in a +document structurally similar as merging each merge patch to the document +in succession. + +```go +package main + +import ( + "fmt" + + jsonpatch "github.com/evanphx/json-patch" +) + +func main() { + original := []byte(`{"name": "John", "age": 24, "height": 3.21}`) + + nameAndHeight := []byte(`{"height":null,"name":"Jane"}`) + ageAndEyes := []byte(`{"age":4.23,"eyes":"blue"}`) + + // Let's combine these merge patch documents... + combinedPatch, err := jsonpatch.MergeMergePatches(nameAndHeight, ageAndEyes) + if err != nil { + panic(err) + } + + // Apply each patch individual against the original document + withoutCombinedPatch, err := jsonpatch.MergePatch(original, nameAndHeight) + if err != nil { + panic(err) + } + + withoutCombinedPatch, err = jsonpatch.MergePatch(withoutCombinedPatch, ageAndEyes) + if err != nil { + panic(err) + } + + // Apply the combined patch against the original document + + withCombinedPatch, err := jsonpatch.MergePatch(original, combinedPatch) + if err != nil { + panic(err) + } + + // Do both result in the same thing? They should! + if jsonpatch.Equal(withCombinedPatch, withoutCombinedPatch) { + fmt.Println("Both JSON documents are structurally the same!") + } + + fmt.Printf("combined merge patch: %s", combinedPatch) +} +``` + +When ran, you get the following output: +```bash +$ go run main.go +Both JSON documents are structurally the same! +combined merge patch: {"age":4.23,"eyes":"blue","height":null,"name":"Jane"} +``` + +# CLI for comparing JSON documents +You can install the commandline program `json-patch`. + +This program can take multiple JSON patch documents as arguments, +and fed a JSON document from `stdin`. It will apply the patch(es) against +the document and output the modified doc. + +**patch.1.json** +```json +[ + {"op": "replace", "path": "/name", "value": "Jane"}, + {"op": "remove", "path": "/height"} +] +``` + +**patch.2.json** +```json +[ + {"op": "add", "path": "/address", "value": "123 Main St"}, + {"op": "replace", "path": "/age", "value": "21"} +] +``` + +**document.json** +```json +{ + "name": "John", + "age": 24, + "height": 3.21 +} +``` + +You can then run: + +```bash +$ go install github.com/evanphx/json-patch/cmd/json-patch +$ cat document.json | json-patch -p patch.1.json -p patch.2.json +{"address":"123 Main St","age":"21","name":"Jane"} +``` + +# Help It! +Contributions are welcomed! Leave [an issue](https://github.com/evanphx/json-patch/issues) +or [create a PR](https://github.com/evanphx/json-patch/compare). + + +Before creating a pull request, we'd ask that you make sure tests are passing +and that you have added new tests when applicable. + +Contributors can run tests using: + +```bash +go test -cover ./... +``` + +Builds for pull requests are tested automatically +using [TravisCI](https://travis-ci.org/evanphx/json-patch). diff --git a/vendor/github.com/evanphx/json-patch/errors.go b/vendor/github.com/evanphx/json-patch/errors.go new file mode 100644 index 0000000..75304b4 --- /dev/null +++ b/vendor/github.com/evanphx/json-patch/errors.go @@ -0,0 +1,38 @@ +package jsonpatch + +import "fmt" + +// AccumulatedCopySizeError is an error type returned when the accumulated size +// increase caused by copy operations in a patch operation has exceeded the +// limit. +type AccumulatedCopySizeError struct { + limit int64 + accumulated int64 +} + +// NewAccumulatedCopySizeError returns an AccumulatedCopySizeError. +func NewAccumulatedCopySizeError(l, a int64) *AccumulatedCopySizeError { + return &AccumulatedCopySizeError{limit: l, accumulated: a} +} + +// Error implements the error interface. +func (a *AccumulatedCopySizeError) Error() string { + return fmt.Sprintf("Unable to complete the copy, the accumulated size increase of copy is %d, exceeding the limit %d", a.accumulated, a.limit) +} + +// ArraySizeError is an error type returned when the array size has exceeded +// the limit. +type ArraySizeError struct { + limit int + size int +} + +// NewArraySizeError returns an ArraySizeError. +func NewArraySizeError(l, s int) *ArraySizeError { + return &ArraySizeError{limit: l, size: s} +} + +// Error implements the error interface. +func (a *ArraySizeError) Error() string { + return fmt.Sprintf("Unable to create array of size %d, limit is %d", a.size, a.limit) +} diff --git a/vendor/github.com/evanphx/json-patch/merge.go b/vendor/github.com/evanphx/json-patch/merge.go new file mode 100644 index 0000000..7c5fadc --- /dev/null +++ b/vendor/github.com/evanphx/json-patch/merge.go @@ -0,0 +1,386 @@ +package jsonpatch + +import ( + "bytes" + "encoding/json" + "fmt" + "reflect" +) + +func merge(cur, patch *lazyNode, mergeMerge bool) *lazyNode { + curDoc, err := cur.intoDoc() + + if err != nil { + pruneNulls(patch) + return patch + } + + patchDoc, err := patch.intoDoc() + + if err != nil { + return patch + } + + mergeDocs(curDoc, patchDoc, mergeMerge) + + return cur +} + +func mergeDocs(doc, patch *partialDoc, mergeMerge bool) { + for k, v := range *patch { + if v == nil { + if mergeMerge { + (*doc)[k] = nil + } else { + delete(*doc, k) + } + } else { + cur, ok := (*doc)[k] + + if !ok || cur == nil { + pruneNulls(v) + (*doc)[k] = v + } else { + (*doc)[k] = merge(cur, v, mergeMerge) + } + } + } +} + +func pruneNulls(n *lazyNode) { + sub, err := n.intoDoc() + + if err == nil { + pruneDocNulls(sub) + } else { + ary, err := n.intoAry() + + if err == nil { + pruneAryNulls(ary) + } + } +} + +func pruneDocNulls(doc *partialDoc) *partialDoc { + for k, v := range *doc { + if v == nil { + delete(*doc, k) + } else { + pruneNulls(v) + } + } + + return doc +} + +func pruneAryNulls(ary *partialArray) *partialArray { + newAry := []*lazyNode{} + + for _, v := range *ary { + if v != nil { + pruneNulls(v) + newAry = append(newAry, v) + } + } + + *ary = newAry + + return ary +} + +var ErrBadJSONDoc = fmt.Errorf("Invalid JSON Document") +var ErrBadJSONPatch = fmt.Errorf("Invalid JSON Patch") +var errBadMergeTypes = fmt.Errorf("Mismatched JSON Documents") + +// MergeMergePatches merges two merge patches together, such that +// applying this resulting merged merge patch to a document yields the same +// as merging each merge patch to the document in succession. +func MergeMergePatches(patch1Data, patch2Data []byte) ([]byte, error) { + return doMergePatch(patch1Data, patch2Data, true) +} + +// MergePatch merges the patchData into the docData. +func MergePatch(docData, patchData []byte) ([]byte, error) { + return doMergePatch(docData, patchData, false) +} + +func doMergePatch(docData, patchData []byte, mergeMerge bool) ([]byte, error) { + doc := &partialDoc{} + + docErr := json.Unmarshal(docData, doc) + + patch := &partialDoc{} + + patchErr := json.Unmarshal(patchData, patch) + + if _, ok := docErr.(*json.SyntaxError); ok { + return nil, ErrBadJSONDoc + } + + if _, ok := patchErr.(*json.SyntaxError); ok { + return nil, ErrBadJSONPatch + } + + if docErr == nil && *doc == nil { + return nil, ErrBadJSONDoc + } + + if patchErr == nil && *patch == nil { + return nil, ErrBadJSONPatch + } + + if docErr != nil || patchErr != nil { + // Not an error, just not a doc, so we turn straight into the patch + if patchErr == nil { + if mergeMerge { + doc = patch + } else { + doc = pruneDocNulls(patch) + } + } else { + patchAry := &partialArray{} + patchErr = json.Unmarshal(patchData, patchAry) + + if patchErr != nil { + return nil, ErrBadJSONPatch + } + + pruneAryNulls(patchAry) + + out, patchErr := json.Marshal(patchAry) + + if patchErr != nil { + return nil, ErrBadJSONPatch + } + + return out, nil + } + } else { + mergeDocs(doc, patch, mergeMerge) + } + + return json.Marshal(doc) +} + +// resemblesJSONArray indicates whether the byte-slice "appears" to be +// a JSON array or not. +// False-positives are possible, as this function does not check the internal +// structure of the array. It only checks that the outer syntax is present and +// correct. +func resemblesJSONArray(input []byte) bool { + input = bytes.TrimSpace(input) + + hasPrefix := bytes.HasPrefix(input, []byte("[")) + hasSuffix := bytes.HasSuffix(input, []byte("]")) + + return hasPrefix && hasSuffix +} + +// CreateMergePatch will return a merge patch document capable of converting +// the original document(s) to the modified document(s). +// The parameters can be bytes of either two JSON Documents, or two arrays of +// JSON documents. +// The merge patch returned follows the specification defined at http://tools.ietf.org/html/draft-ietf-appsawg-json-merge-patch-07 +func CreateMergePatch(originalJSON, modifiedJSON []byte) ([]byte, error) { + originalResemblesArray := resemblesJSONArray(originalJSON) + modifiedResemblesArray := resemblesJSONArray(modifiedJSON) + + // Do both byte-slices seem like JSON arrays? + if originalResemblesArray && modifiedResemblesArray { + return createArrayMergePatch(originalJSON, modifiedJSON) + } + + // Are both byte-slices are not arrays? Then they are likely JSON objects... + if !originalResemblesArray && !modifiedResemblesArray { + return createObjectMergePatch(originalJSON, modifiedJSON) + } + + // None of the above? Then return an error because of mismatched types. + return nil, errBadMergeTypes +} + +// createObjectMergePatch will return a merge-patch document capable of +// converting the original document to the modified document. +func createObjectMergePatch(originalJSON, modifiedJSON []byte) ([]byte, error) { + originalDoc := map[string]interface{}{} + modifiedDoc := map[string]interface{}{} + + err := json.Unmarshal(originalJSON, &originalDoc) + if err != nil { + return nil, ErrBadJSONDoc + } + + err = json.Unmarshal(modifiedJSON, &modifiedDoc) + if err != nil { + return nil, ErrBadJSONDoc + } + + dest, err := getDiff(originalDoc, modifiedDoc) + if err != nil { + return nil, err + } + + return json.Marshal(dest) +} + +// createArrayMergePatch will return an array of merge-patch documents capable +// of converting the original document to the modified document for each +// pair of JSON documents provided in the arrays. +// Arrays of mismatched sizes will result in an error. +func createArrayMergePatch(originalJSON, modifiedJSON []byte) ([]byte, error) { + originalDocs := []json.RawMessage{} + modifiedDocs := []json.RawMessage{} + + err := json.Unmarshal(originalJSON, &originalDocs) + if err != nil { + return nil, ErrBadJSONDoc + } + + err = json.Unmarshal(modifiedJSON, &modifiedDocs) + if err != nil { + return nil, ErrBadJSONDoc + } + + total := len(originalDocs) + if len(modifiedDocs) != total { + return nil, ErrBadJSONDoc + } + + result := []json.RawMessage{} + for i := 0; i < len(originalDocs); i++ { + original := originalDocs[i] + modified := modifiedDocs[i] + + patch, err := createObjectMergePatch(original, modified) + if err != nil { + return nil, err + } + + result = append(result, json.RawMessage(patch)) + } + + return json.Marshal(result) +} + +// Returns true if the array matches (must be json types). +// As is idiomatic for go, an empty array is not the same as a nil array. +func matchesArray(a, b []interface{}) bool { + if len(a) != len(b) { + return false + } + if (a == nil && b != nil) || (a != nil && b == nil) { + return false + } + for i := range a { + if !matchesValue(a[i], b[i]) { + return false + } + } + return true +} + +// Returns true if the values matches (must be json types) +// The types of the values must match, otherwise it will always return false +// If two map[string]interface{} are given, all elements must match. +func matchesValue(av, bv interface{}) bool { + if reflect.TypeOf(av) != reflect.TypeOf(bv) { + return false + } + switch at := av.(type) { + case string: + bt := bv.(string) + if bt == at { + return true + } + case float64: + bt := bv.(float64) + if bt == at { + return true + } + case bool: + bt := bv.(bool) + if bt == at { + return true + } + case nil: + // Both nil, fine. + return true + case map[string]interface{}: + bt := bv.(map[string]interface{}) + if len(bt) != len(at) { + return false + } + for key := range bt { + av, aOK := at[key] + bv, bOK := bt[key] + if aOK != bOK { + return false + } + if !matchesValue(av, bv) { + return false + } + } + return true + case []interface{}: + bt := bv.([]interface{}) + return matchesArray(at, bt) + } + return false +} + +// getDiff returns the (recursive) difference between a and b as a map[string]interface{}. +func getDiff(a, b map[string]interface{}) (map[string]interface{}, error) { + into := map[string]interface{}{} + for key, bv := range b { + av, ok := a[key] + // value was added + if !ok { + into[key] = bv + continue + } + // If types have changed, replace completely + if reflect.TypeOf(av) != reflect.TypeOf(bv) { + into[key] = bv + continue + } + // Types are the same, compare values + switch at := av.(type) { + case map[string]interface{}: + bt := bv.(map[string]interface{}) + dst := make(map[string]interface{}, len(bt)) + dst, err := getDiff(at, bt) + if err != nil { + return nil, err + } + if len(dst) > 0 { + into[key] = dst + } + case string, float64, bool: + if !matchesValue(av, bv) { + into[key] = bv + } + case []interface{}: + bt := bv.([]interface{}) + if !matchesArray(at, bt) { + into[key] = bv + } + case nil: + switch bv.(type) { + case nil: + // Both nil, fine. + default: + into[key] = bv + } + default: + panic(fmt.Sprintf("Unknown type:%T in key %s", av, key)) + } + } + // Now add all deleted values as nil + for key := range a { + _, found := b[key] + if !found { + into[key] = nil + } + } + return into, nil +} diff --git a/vendor/github.com/evanphx/json-patch/patch.go b/vendor/github.com/evanphx/json-patch/patch.go new file mode 100644 index 0000000..f185a45 --- /dev/null +++ b/vendor/github.com/evanphx/json-patch/patch.go @@ -0,0 +1,784 @@ +package jsonpatch + +import ( + "bytes" + "encoding/json" + "fmt" + "strconv" + "strings" + + "github.com/pkg/errors" +) + +const ( + eRaw = iota + eDoc + eAry +) + +var ( + // SupportNegativeIndices decides whether to support non-standard practice of + // allowing negative indices to mean indices starting at the end of an array. + // Default to true. + SupportNegativeIndices bool = true + // AccumulatedCopySizeLimit limits the total size increase in bytes caused by + // "copy" operations in a patch. + AccumulatedCopySizeLimit int64 = 0 +) + +var ( + ErrTestFailed = errors.New("test failed") + ErrMissing = errors.New("missing value") + ErrUnknownType = errors.New("unknown object type") + ErrInvalid = errors.New("invalid state detected") + ErrInvalidIndex = errors.New("invalid index referenced") +) + +type lazyNode struct { + raw *json.RawMessage + doc partialDoc + ary partialArray + which int +} + +// Operation is a single JSON-Patch step, such as a single 'add' operation. +type Operation map[string]*json.RawMessage + +// Patch is an ordered collection of Operations. +type Patch []Operation + +type partialDoc map[string]*lazyNode +type partialArray []*lazyNode + +type container interface { + get(key string) (*lazyNode, error) + set(key string, val *lazyNode) error + add(key string, val *lazyNode) error + remove(key string) error +} + +func newLazyNode(raw *json.RawMessage) *lazyNode { + return &lazyNode{raw: raw, doc: nil, ary: nil, which: eRaw} +} + +func (n *lazyNode) MarshalJSON() ([]byte, error) { + switch n.which { + case eRaw: + return json.Marshal(n.raw) + case eDoc: + return json.Marshal(n.doc) + case eAry: + return json.Marshal(n.ary) + default: + return nil, ErrUnknownType + } +} + +func (n *lazyNode) UnmarshalJSON(data []byte) error { + dest := make(json.RawMessage, len(data)) + copy(dest, data) + n.raw = &dest + n.which = eRaw + return nil +} + +func deepCopy(src *lazyNode) (*lazyNode, int, error) { + if src == nil { + return nil, 0, nil + } + a, err := src.MarshalJSON() + if err != nil { + return nil, 0, err + } + sz := len(a) + ra := make(json.RawMessage, sz) + copy(ra, a) + return newLazyNode(&ra), sz, nil +} + +func (n *lazyNode) intoDoc() (*partialDoc, error) { + if n.which == eDoc { + return &n.doc, nil + } + + if n.raw == nil { + return nil, ErrInvalid + } + + err := json.Unmarshal(*n.raw, &n.doc) + + if err != nil { + return nil, err + } + + n.which = eDoc + return &n.doc, nil +} + +func (n *lazyNode) intoAry() (*partialArray, error) { + if n.which == eAry { + return &n.ary, nil + } + + if n.raw == nil { + return nil, ErrInvalid + } + + err := json.Unmarshal(*n.raw, &n.ary) + + if err != nil { + return nil, err + } + + n.which = eAry + return &n.ary, nil +} + +func (n *lazyNode) compact() []byte { + buf := &bytes.Buffer{} + + if n.raw == nil { + return nil + } + + err := json.Compact(buf, *n.raw) + + if err != nil { + return *n.raw + } + + return buf.Bytes() +} + +func (n *lazyNode) tryDoc() bool { + if n.raw == nil { + return false + } + + err := json.Unmarshal(*n.raw, &n.doc) + + if err != nil { + return false + } + + n.which = eDoc + return true +} + +func (n *lazyNode) tryAry() bool { + if n.raw == nil { + return false + } + + err := json.Unmarshal(*n.raw, &n.ary) + + if err != nil { + return false + } + + n.which = eAry + return true +} + +func (n *lazyNode) equal(o *lazyNode) bool { + if n.which == eRaw { + if !n.tryDoc() && !n.tryAry() { + if o.which != eRaw { + return false + } + + return bytes.Equal(n.compact(), o.compact()) + } + } + + if n.which == eDoc { + if o.which == eRaw { + if !o.tryDoc() { + return false + } + } + + if o.which != eDoc { + return false + } + + if len(n.doc) != len(o.doc) { + return false + } + + for k, v := range n.doc { + ov, ok := o.doc[k] + + if !ok { + return false + } + + if (v == nil) != (ov == nil) { + return false + } + + if v == nil && ov == nil { + continue + } + + if !v.equal(ov) { + return false + } + } + + return true + } + + if o.which != eAry && !o.tryAry() { + return false + } + + if len(n.ary) != len(o.ary) { + return false + } + + for idx, val := range n.ary { + if !val.equal(o.ary[idx]) { + return false + } + } + + return true +} + +// Kind reads the "op" field of the Operation. +func (o Operation) Kind() string { + if obj, ok := o["op"]; ok && obj != nil { + var op string + + err := json.Unmarshal(*obj, &op) + + if err != nil { + return "unknown" + } + + return op + } + + return "unknown" +} + +// Path reads the "path" field of the Operation. +func (o Operation) Path() (string, error) { + if obj, ok := o["path"]; ok && obj != nil { + var op string + + err := json.Unmarshal(*obj, &op) + + if err != nil { + return "unknown", err + } + + return op, nil + } + + return "unknown", errors.Wrapf(ErrMissing, "operation missing path field") +} + +// From reads the "from" field of the Operation. +func (o Operation) From() (string, error) { + if obj, ok := o["from"]; ok && obj != nil { + var op string + + err := json.Unmarshal(*obj, &op) + + if err != nil { + return "unknown", err + } + + return op, nil + } + + return "unknown", errors.Wrapf(ErrMissing, "operation, missing from field") +} + +func (o Operation) value() *lazyNode { + if obj, ok := o["value"]; ok { + return newLazyNode(obj) + } + + return nil +} + +// ValueInterface decodes the operation value into an interface. +func (o Operation) ValueInterface() (interface{}, error) { + if obj, ok := o["value"]; ok && obj != nil { + var v interface{} + + err := json.Unmarshal(*obj, &v) + + if err != nil { + return nil, err + } + + return v, nil + } + + return nil, errors.Wrapf(ErrMissing, "operation, missing value field") +} + +func isArray(buf []byte) bool { +Loop: + for _, c := range buf { + switch c { + case ' ': + case '\n': + case '\t': + continue + case '[': + return true + default: + break Loop + } + } + + return false +} + +func findObject(pd *container, path string) (container, string) { + doc := *pd + + split := strings.Split(path, "/") + + if len(split) < 2 { + return nil, "" + } + + parts := split[1 : len(split)-1] + + key := split[len(split)-1] + + var err error + + for _, part := range parts { + + next, ok := doc.get(decodePatchKey(part)) + + if next == nil || ok != nil { + return nil, "" + } + + if isArray(*next.raw) { + doc, err = next.intoAry() + + if err != nil { + return nil, "" + } + } else { + doc, err = next.intoDoc() + + if err != nil { + return nil, "" + } + } + } + + return doc, decodePatchKey(key) +} + +func (d *partialDoc) set(key string, val *lazyNode) error { + (*d)[key] = val + return nil +} + +func (d *partialDoc) add(key string, val *lazyNode) error { + (*d)[key] = val + return nil +} + +func (d *partialDoc) get(key string) (*lazyNode, error) { + return (*d)[key], nil +} + +func (d *partialDoc) remove(key string) error { + _, ok := (*d)[key] + if !ok { + return errors.Wrapf(ErrMissing, "Unable to remove nonexistent key: %s", key) + } + + delete(*d, key) + return nil +} + +// set should only be used to implement the "replace" operation, so "key" must +// be an already existing index in "d". +func (d *partialArray) set(key string, val *lazyNode) error { + idx, err := strconv.Atoi(key) + if err != nil { + return err + } + (*d)[idx] = val + return nil +} + +func (d *partialArray) add(key string, val *lazyNode) error { + if key == "-" { + *d = append(*d, val) + return nil + } + + idx, err := strconv.Atoi(key) + if err != nil { + return errors.Wrapf(err, "value was not a proper array index: '%s'", key) + } + + sz := len(*d) + 1 + + ary := make([]*lazyNode, sz) + + cur := *d + + if idx >= len(ary) { + return errors.Wrapf(ErrInvalidIndex, "Unable to access invalid index: %d", idx) + } + + if idx < 0 { + if !SupportNegativeIndices { + return errors.Wrapf(ErrInvalidIndex, "Unable to access invalid index: %d", idx) + } + if idx < -len(ary) { + return errors.Wrapf(ErrInvalidIndex, "Unable to access invalid index: %d", idx) + } + idx += len(ary) + } + + copy(ary[0:idx], cur[0:idx]) + ary[idx] = val + copy(ary[idx+1:], cur[idx:]) + + *d = ary + return nil +} + +func (d *partialArray) get(key string) (*lazyNode, error) { + idx, err := strconv.Atoi(key) + + if err != nil { + return nil, err + } + + if idx >= len(*d) { + return nil, errors.Wrapf(ErrInvalidIndex, "Unable to access invalid index: %d", idx) + } + + return (*d)[idx], nil +} + +func (d *partialArray) remove(key string) error { + idx, err := strconv.Atoi(key) + if err != nil { + return err + } + + cur := *d + + if idx >= len(cur) { + return errors.Wrapf(ErrInvalidIndex, "Unable to access invalid index: %d", idx) + } + + if idx < 0 { + if !SupportNegativeIndices { + return errors.Wrapf(ErrInvalidIndex, "Unable to access invalid index: %d", idx) + } + if idx < -len(cur) { + return errors.Wrapf(ErrInvalidIndex, "Unable to access invalid index: %d", idx) + } + idx += len(cur) + } + + ary := make([]*lazyNode, len(cur)-1) + + copy(ary[0:idx], cur[0:idx]) + copy(ary[idx:], cur[idx+1:]) + + *d = ary + return nil + +} + +func (p Patch) add(doc *container, op Operation) error { + path, err := op.Path() + if err != nil { + return errors.Wrapf(ErrMissing, "add operation failed to decode path") + } + + con, key := findObject(doc, path) + + if con == nil { + return errors.Wrapf(ErrMissing, "add operation does not apply: doc is missing path: \"%s\"", path) + } + + err = con.add(key, op.value()) + if err != nil { + return errors.Wrapf(err, "error in add for path: '%s'", path) + } + + return nil +} + +func (p Patch) remove(doc *container, op Operation) error { + path, err := op.Path() + if err != nil { + return errors.Wrapf(ErrMissing, "remove operation failed to decode path") + } + + con, key := findObject(doc, path) + + if con == nil { + return errors.Wrapf(ErrMissing, "remove operation does not apply: doc is missing path: \"%s\"", path) + } + + err = con.remove(key) + if err != nil { + return errors.Wrapf(err, "error in remove for path: '%s'", path) + } + + return nil +} + +func (p Patch) replace(doc *container, op Operation) error { + path, err := op.Path() + if err != nil { + return errors.Wrapf(err, "replace operation failed to decode path") + } + + con, key := findObject(doc, path) + + if con == nil { + return errors.Wrapf(ErrMissing, "replace operation does not apply: doc is missing path: %s", path) + } + + _, ok := con.get(key) + if ok != nil { + return errors.Wrapf(ErrMissing, "replace operation does not apply: doc is missing key: %s", path) + } + + err = con.set(key, op.value()) + if err != nil { + return errors.Wrapf(err, "error in remove for path: '%s'", path) + } + + return nil +} + +func (p Patch) move(doc *container, op Operation) error { + from, err := op.From() + if err != nil { + return errors.Wrapf(err, "move operation failed to decode from") + } + + con, key := findObject(doc, from) + + if con == nil { + return errors.Wrapf(ErrMissing, "move operation does not apply: doc is missing from path: %s", from) + } + + val, err := con.get(key) + if err != nil { + return errors.Wrapf(err, "error in move for path: '%s'", key) + } + + err = con.remove(key) + if err != nil { + return errors.Wrapf(err, "error in move for path: '%s'", key) + } + + path, err := op.Path() + if err != nil { + return errors.Wrapf(err, "move operation failed to decode path") + } + + con, key = findObject(doc, path) + + if con == nil { + return errors.Wrapf(ErrMissing, "move operation does not apply: doc is missing destination path: %s", path) + } + + err = con.add(key, val) + if err != nil { + return errors.Wrapf(err, "error in move for path: '%s'", path) + } + + return nil +} + +func (p Patch) test(doc *container, op Operation) error { + path, err := op.Path() + if err != nil { + return errors.Wrapf(err, "test operation failed to decode path") + } + + con, key := findObject(doc, path) + + if con == nil { + return errors.Wrapf(ErrMissing, "test operation does not apply: is missing path: %s", path) + } + + val, err := con.get(key) + if err != nil { + return errors.Wrapf(err, "error in test for path: '%s'", path) + } + + if val == nil { + if op.value().raw == nil { + return nil + } + return errors.Wrapf(ErrTestFailed, "testing value %s failed", path) + } else if op.value() == nil { + return errors.Wrapf(ErrTestFailed, "testing value %s failed", path) + } + + if val.equal(op.value()) { + return nil + } + + return errors.Wrapf(ErrTestFailed, "testing value %s failed", path) +} + +func (p Patch) copy(doc *container, op Operation, accumulatedCopySize *int64) error { + from, err := op.From() + if err != nil { + return errors.Wrapf(err, "copy operation failed to decode from") + } + + con, key := findObject(doc, from) + + if con == nil { + return errors.Wrapf(ErrMissing, "copy operation does not apply: doc is missing from path: %s", from) + } + + val, err := con.get(key) + if err != nil { + return errors.Wrapf(err, "error in copy for from: '%s'", from) + } + + path, err := op.Path() + if err != nil { + return errors.Wrapf(ErrMissing, "copy operation failed to decode path") + } + + con, key = findObject(doc, path) + + if con == nil { + return errors.Wrapf(ErrMissing, "copy operation does not apply: doc is missing destination path: %s", path) + } + + valCopy, sz, err := deepCopy(val) + if err != nil { + return errors.Wrapf(err, "error while performing deep copy") + } + + (*accumulatedCopySize) += int64(sz) + if AccumulatedCopySizeLimit > 0 && *accumulatedCopySize > AccumulatedCopySizeLimit { + return NewAccumulatedCopySizeError(AccumulatedCopySizeLimit, *accumulatedCopySize) + } + + err = con.add(key, valCopy) + if err != nil { + return errors.Wrapf(err, "error while adding value during copy") + } + + return nil +} + +// Equal indicates if 2 JSON documents have the same structural equality. +func Equal(a, b []byte) bool { + ra := make(json.RawMessage, len(a)) + copy(ra, a) + la := newLazyNode(&ra) + + rb := make(json.RawMessage, len(b)) + copy(rb, b) + lb := newLazyNode(&rb) + + return la.equal(lb) +} + +// DecodePatch decodes the passed JSON document as an RFC 6902 patch. +func DecodePatch(buf []byte) (Patch, error) { + var p Patch + + err := json.Unmarshal(buf, &p) + + if err != nil { + return nil, err + } + + return p, nil +} + +// Apply mutates a JSON document according to the patch, and returns the new +// document. +func (p Patch) Apply(doc []byte) ([]byte, error) { + return p.ApplyIndent(doc, "") +} + +// ApplyIndent mutates a JSON document according to the patch, and returns the new +// document indented. +func (p Patch) ApplyIndent(doc []byte, indent string) ([]byte, error) { + var pd container + if doc[0] == '[' { + pd = &partialArray{} + } else { + pd = &partialDoc{} + } + + err := json.Unmarshal(doc, pd) + + if err != nil { + return nil, err + } + + err = nil + + var accumulatedCopySize int64 + + for _, op := range p { + switch op.Kind() { + case "add": + err = p.add(&pd, op) + case "remove": + err = p.remove(&pd, op) + case "replace": + err = p.replace(&pd, op) + case "move": + err = p.move(&pd, op) + case "test": + err = p.test(&pd, op) + case "copy": + err = p.copy(&pd, op, &accumulatedCopySize) + default: + err = fmt.Errorf("Unexpected kind: %s", op.Kind()) + } + + if err != nil { + return nil, err + } + } + + if indent != "" { + return json.MarshalIndent(pd, "", indent) + } + + return json.Marshal(pd) +} + +// From http://tools.ietf.org/html/rfc6901#section-4 : +// +// Evaluation of each reference token begins by decoding any escaped +// character sequence. This is performed by first transforming any +// occurrence of the sequence '~1' to '/', and then transforming any +// occurrence of the sequence '~0' to '~'. + +var ( + rfc6901Decoder = strings.NewReplacer("~1", "/", "~0", "~") +) + +func decodePatchKey(k string) string { + return rfc6901Decoder.Replace(k) +} diff --git a/vendor/github.com/pkg/errors/.gitignore b/vendor/github.com/pkg/errors/.gitignore new file mode 100644 index 0000000..daf913b --- /dev/null +++ b/vendor/github.com/pkg/errors/.gitignore @@ -0,0 +1,24 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof diff --git a/vendor/github.com/pkg/errors/.travis.yml b/vendor/github.com/pkg/errors/.travis.yml new file mode 100644 index 0000000..9159de0 --- /dev/null +++ b/vendor/github.com/pkg/errors/.travis.yml @@ -0,0 +1,10 @@ +language: go +go_import_path: github.com/pkg/errors +go: + - 1.11.x + - 1.12.x + - 1.13.x + - tip + +script: + - make check diff --git a/vendor/github.com/pkg/errors/LICENSE b/vendor/github.com/pkg/errors/LICENSE new file mode 100644 index 0000000..835ba3e --- /dev/null +++ b/vendor/github.com/pkg/errors/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2015, Dave Cheney +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/vendor/github.com/pkg/errors/Makefile b/vendor/github.com/pkg/errors/Makefile new file mode 100644 index 0000000..ce9d7cd --- /dev/null +++ b/vendor/github.com/pkg/errors/Makefile @@ -0,0 +1,44 @@ +PKGS := github.com/pkg/errors +SRCDIRS := $(shell go list -f '{{.Dir}}' $(PKGS)) +GO := go + +check: test vet gofmt misspell unconvert staticcheck ineffassign unparam + +test: + $(GO) test $(PKGS) + +vet: | test + $(GO) vet $(PKGS) + +staticcheck: + $(GO) get honnef.co/go/tools/cmd/staticcheck + staticcheck -checks all $(PKGS) + +misspell: + $(GO) get github.com/client9/misspell/cmd/misspell + misspell \ + -locale GB \ + -error \ + *.md *.go + +unconvert: + $(GO) get github.com/mdempsky/unconvert + unconvert -v $(PKGS) + +ineffassign: + $(GO) get github.com/gordonklaus/ineffassign + find $(SRCDIRS) -name '*.go' | xargs ineffassign + +pedantic: check errcheck + +unparam: + $(GO) get mvdan.cc/unparam + unparam ./... + +errcheck: + $(GO) get github.com/kisielk/errcheck + errcheck $(PKGS) + +gofmt: + @echo Checking code is gofmted + @test -z "$(shell gofmt -s -l -d -e $(SRCDIRS) | tee /dev/stderr)" diff --git a/vendor/github.com/pkg/errors/README.md b/vendor/github.com/pkg/errors/README.md new file mode 100644 index 0000000..54dfdcb --- /dev/null +++ b/vendor/github.com/pkg/errors/README.md @@ -0,0 +1,59 @@ +# errors [![Travis-CI](https://travis-ci.org/pkg/errors.svg)](https://travis-ci.org/pkg/errors) [![AppVeyor](https://ci.appveyor.com/api/projects/status/b98mptawhudj53ep/branch/master?svg=true)](https://ci.appveyor.com/project/davecheney/errors/branch/master) [![GoDoc](https://godoc.org/github.com/pkg/errors?status.svg)](http://godoc.org/github.com/pkg/errors) [![Report card](https://goreportcard.com/badge/github.com/pkg/errors)](https://goreportcard.com/report/github.com/pkg/errors) [![Sourcegraph](https://sourcegraph.com/github.com/pkg/errors/-/badge.svg)](https://sourcegraph.com/github.com/pkg/errors?badge) + +Package errors provides simple error handling primitives. + +`go get github.com/pkg/errors` + +The traditional error handling idiom in Go is roughly akin to +```go +if err != nil { + return err +} +``` +which applied recursively up the call stack results in error reports without context or debugging information. The errors package allows programmers to add context to the failure path in their code in a way that does not destroy the original value of the error. + +## Adding context to an error + +The errors.Wrap function returns a new error that adds context to the original error. For example +```go +_, err := ioutil.ReadAll(r) +if err != nil { + return errors.Wrap(err, "read failed") +} +``` +## Retrieving the cause of an error + +Using `errors.Wrap` constructs a stack of errors, adding context to the preceding error. Depending on the nature of the error it may be necessary to reverse the operation of errors.Wrap to retrieve the original error for inspection. Any error value which implements this interface can be inspected by `errors.Cause`. +```go +type causer interface { + Cause() error +} +``` +`errors.Cause` will recursively retrieve the topmost error which does not implement `causer`, which is assumed to be the original cause. For example: +```go +switch err := errors.Cause(err).(type) { +case *MyError: + // handle specifically +default: + // unknown error +} +``` + +[Read the package documentation for more information](https://godoc.org/github.com/pkg/errors). + +## Roadmap + +With the upcoming [Go2 error proposals](https://go.googlesource.com/proposal/+/master/design/go2draft.md) this package is moving into maintenance mode. The roadmap for a 1.0 release is as follows: + +- 0.9. Remove pre Go 1.9 and Go 1.10 support, address outstanding pull requests (if possible) +- 1.0. Final release. + +## Contributing + +Because of the Go2 errors changes, this package is not accepting proposals for new functionality. With that said, we welcome pull requests, bug fixes and issue reports. + +Before sending a PR, please discuss your change by raising an issue. + +## License + +BSD-2-Clause diff --git a/vendor/github.com/pkg/errors/appveyor.yml b/vendor/github.com/pkg/errors/appveyor.yml new file mode 100644 index 0000000..a932ead --- /dev/null +++ b/vendor/github.com/pkg/errors/appveyor.yml @@ -0,0 +1,32 @@ +version: build-{build}.{branch} + +clone_folder: C:\gopath\src\github.com\pkg\errors +shallow_clone: true # for startup speed + +environment: + GOPATH: C:\gopath + +platform: + - x64 + +# http://www.appveyor.com/docs/installed-software +install: + # some helpful output for debugging builds + - go version + - go env + # pre-installed MinGW at C:\MinGW is 32bit only + # but MSYS2 at C:\msys64 has mingw64 + - set PATH=C:\msys64\mingw64\bin;%PATH% + - gcc --version + - g++ --version + +build_script: + - go install -v ./... + +test_script: + - set PATH=C:\gopath\bin;%PATH% + - go test -v ./... + +#artifacts: +# - path: '%GOPATH%\bin\*.exe' +deploy: off diff --git a/vendor/github.com/pkg/errors/errors.go b/vendor/github.com/pkg/errors/errors.go new file mode 100644 index 0000000..161aea2 --- /dev/null +++ b/vendor/github.com/pkg/errors/errors.go @@ -0,0 +1,288 @@ +// Package errors provides simple error handling primitives. +// +// The traditional error handling idiom in Go is roughly akin to +// +// if err != nil { +// return err +// } +// +// which when applied recursively up the call stack results in error reports +// without context or debugging information. The errors package allows +// programmers to add context to the failure path in their code in a way +// that does not destroy the original value of the error. +// +// Adding context to an error +// +// The errors.Wrap function returns a new error that adds context to the +// original error by recording a stack trace at the point Wrap is called, +// together with the supplied message. For example +// +// _, err := ioutil.ReadAll(r) +// if err != nil { +// return errors.Wrap(err, "read failed") +// } +// +// If additional control is required, the errors.WithStack and +// errors.WithMessage functions destructure errors.Wrap into its component +// operations: annotating an error with a stack trace and with a message, +// respectively. +// +// Retrieving the cause of an error +// +// Using errors.Wrap constructs a stack of errors, adding context to the +// preceding error. Depending on the nature of the error it may be necessary +// to reverse the operation of errors.Wrap to retrieve the original error +// for inspection. Any error value which implements this interface +// +// type causer interface { +// Cause() error +// } +// +// can be inspected by errors.Cause. errors.Cause will recursively retrieve +// the topmost error that does not implement causer, which is assumed to be +// the original cause. For example: +// +// switch err := errors.Cause(err).(type) { +// case *MyError: +// // handle specifically +// default: +// // unknown error +// } +// +// Although the causer interface is not exported by this package, it is +// considered a part of its stable public interface. +// +// Formatted printing of errors +// +// All error values returned from this package implement fmt.Formatter and can +// be formatted by the fmt package. The following verbs are supported: +// +// %s print the error. If the error has a Cause it will be +// printed recursively. +// %v see %s +// %+v extended format. Each Frame of the error's StackTrace will +// be printed in detail. +// +// Retrieving the stack trace of an error or wrapper +// +// New, Errorf, Wrap, and Wrapf record a stack trace at the point they are +// invoked. This information can be retrieved with the following interface: +// +// type stackTracer interface { +// StackTrace() errors.StackTrace +// } +// +// The returned errors.StackTrace type is defined as +// +// type StackTrace []Frame +// +// The Frame type represents a call site in the stack trace. Frame supports +// the fmt.Formatter interface that can be used for printing information about +// the stack trace of this error. For example: +// +// if err, ok := err.(stackTracer); ok { +// for _, f := range err.StackTrace() { +// fmt.Printf("%+s:%d\n", f, f) +// } +// } +// +// Although the stackTracer interface is not exported by this package, it is +// considered a part of its stable public interface. +// +// See the documentation for Frame.Format for more details. +package errors + +import ( + "fmt" + "io" +) + +// New returns an error with the supplied message. +// New also records the stack trace at the point it was called. +func New(message string) error { + return &fundamental{ + msg: message, + stack: callers(), + } +} + +// Errorf formats according to a format specifier and returns the string +// as a value that satisfies error. +// Errorf also records the stack trace at the point it was called. +func Errorf(format string, args ...interface{}) error { + return &fundamental{ + msg: fmt.Sprintf(format, args...), + stack: callers(), + } +} + +// fundamental is an error that has a message and a stack, but no caller. +type fundamental struct { + msg string + *stack +} + +func (f *fundamental) Error() string { return f.msg } + +func (f *fundamental) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + io.WriteString(s, f.msg) + f.stack.Format(s, verb) + return + } + fallthrough + case 's': + io.WriteString(s, f.msg) + case 'q': + fmt.Fprintf(s, "%q", f.msg) + } +} + +// WithStack annotates err with a stack trace at the point WithStack was called. +// If err is nil, WithStack returns nil. +func WithStack(err error) error { + if err == nil { + return nil + } + return &withStack{ + err, + callers(), + } +} + +type withStack struct { + error + *stack +} + +func (w *withStack) Cause() error { return w.error } + +// Unwrap provides compatibility for Go 1.13 error chains. +func (w *withStack) Unwrap() error { return w.error } + +func (w *withStack) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + fmt.Fprintf(s, "%+v", w.Cause()) + w.stack.Format(s, verb) + return + } + fallthrough + case 's': + io.WriteString(s, w.Error()) + case 'q': + fmt.Fprintf(s, "%q", w.Error()) + } +} + +// Wrap returns an error annotating err with a stack trace +// at the point Wrap is called, and the supplied message. +// If err is nil, Wrap returns nil. +func Wrap(err error, message string) error { + if err == nil { + return nil + } + err = &withMessage{ + cause: err, + msg: message, + } + return &withStack{ + err, + callers(), + } +} + +// Wrapf returns an error annotating err with a stack trace +// at the point Wrapf is called, and the format specifier. +// If err is nil, Wrapf returns nil. +func Wrapf(err error, format string, args ...interface{}) error { + if err == nil { + return nil + } + err = &withMessage{ + cause: err, + msg: fmt.Sprintf(format, args...), + } + return &withStack{ + err, + callers(), + } +} + +// WithMessage annotates err with a new message. +// If err is nil, WithMessage returns nil. +func WithMessage(err error, message string) error { + if err == nil { + return nil + } + return &withMessage{ + cause: err, + msg: message, + } +} + +// WithMessagef annotates err with the format specifier. +// If err is nil, WithMessagef returns nil. +func WithMessagef(err error, format string, args ...interface{}) error { + if err == nil { + return nil + } + return &withMessage{ + cause: err, + msg: fmt.Sprintf(format, args...), + } +} + +type withMessage struct { + cause error + msg string +} + +func (w *withMessage) Error() string { return w.msg + ": " + w.cause.Error() } +func (w *withMessage) Cause() error { return w.cause } + +// Unwrap provides compatibility for Go 1.13 error chains. +func (w *withMessage) Unwrap() error { return w.cause } + +func (w *withMessage) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + if s.Flag('+') { + fmt.Fprintf(s, "%+v\n", w.Cause()) + io.WriteString(s, w.msg) + return + } + fallthrough + case 's', 'q': + io.WriteString(s, w.Error()) + } +} + +// Cause returns the underlying cause of the error, if possible. +// An error value has a cause if it implements the following +// interface: +// +// type causer interface { +// Cause() error +// } +// +// If the error does not implement Cause, the original error will +// be returned. If the error is nil, nil will be returned without further +// investigation. +func Cause(err error) error { + type causer interface { + Cause() error + } + + for err != nil { + cause, ok := err.(causer) + if !ok { + break + } + err = cause.Cause() + } + return err +} diff --git a/vendor/github.com/pkg/errors/go113.go b/vendor/github.com/pkg/errors/go113.go new file mode 100644 index 0000000..be0d10d --- /dev/null +++ b/vendor/github.com/pkg/errors/go113.go @@ -0,0 +1,38 @@ +// +build go1.13 + +package errors + +import ( + stderrors "errors" +) + +// Is reports whether any error in err's chain matches target. +// +// The chain consists of err itself followed by the sequence of errors obtained by +// repeatedly calling Unwrap. +// +// An error is considered to match a target if it is equal to that target or if +// it implements a method Is(error) bool such that Is(target) returns true. +func Is(err, target error) bool { return stderrors.Is(err, target) } + +// As finds the first error in err's chain that matches target, and if so, sets +// target to that error value and returns true. +// +// The chain consists of err itself followed by the sequence of errors obtained by +// repeatedly calling Unwrap. +// +// An error matches target if the error's concrete value is assignable to the value +// pointed to by target, or if the error has a method As(interface{}) bool such that +// As(target) returns true. In the latter case, the As method is responsible for +// setting target. +// +// As will panic if target is not a non-nil pointer to either a type that implements +// error, or to any interface type. As returns false if err is nil. +func As(err error, target interface{}) bool { return stderrors.As(err, target) } + +// Unwrap returns the result of calling the Unwrap method on err, if err's +// type contains an Unwrap method returning error. +// Otherwise, Unwrap returns nil. +func Unwrap(err error) error { + return stderrors.Unwrap(err) +} diff --git a/vendor/github.com/pkg/errors/stack.go b/vendor/github.com/pkg/errors/stack.go new file mode 100644 index 0000000..779a834 --- /dev/null +++ b/vendor/github.com/pkg/errors/stack.go @@ -0,0 +1,177 @@ +package errors + +import ( + "fmt" + "io" + "path" + "runtime" + "strconv" + "strings" +) + +// Frame represents a program counter inside a stack frame. +// For historical reasons if Frame is interpreted as a uintptr +// its value represents the program counter + 1. +type Frame uintptr + +// pc returns the program counter for this frame; +// multiple frames may have the same PC value. +func (f Frame) pc() uintptr { return uintptr(f) - 1 } + +// file returns the full path to the file that contains the +// function for this Frame's pc. +func (f Frame) file() string { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return "unknown" + } + file, _ := fn.FileLine(f.pc()) + return file +} + +// line returns the line number of source code of the +// function for this Frame's pc. +func (f Frame) line() int { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return 0 + } + _, line := fn.FileLine(f.pc()) + return line +} + +// name returns the name of this function, if known. +func (f Frame) name() string { + fn := runtime.FuncForPC(f.pc()) + if fn == nil { + return "unknown" + } + return fn.Name() +} + +// Format formats the frame according to the fmt.Formatter interface. +// +// %s source file +// %d source line +// %n function name +// %v equivalent to %s:%d +// +// Format accepts flags that alter the printing of some verbs, as follows: +// +// %+s function name and path of source file relative to the compile time +// GOPATH separated by \n\t (\n\t) +// %+v equivalent to %+s:%d +func (f Frame) Format(s fmt.State, verb rune) { + switch verb { + case 's': + switch { + case s.Flag('+'): + io.WriteString(s, f.name()) + io.WriteString(s, "\n\t") + io.WriteString(s, f.file()) + default: + io.WriteString(s, path.Base(f.file())) + } + case 'd': + io.WriteString(s, strconv.Itoa(f.line())) + case 'n': + io.WriteString(s, funcname(f.name())) + case 'v': + f.Format(s, 's') + io.WriteString(s, ":") + f.Format(s, 'd') + } +} + +// MarshalText formats a stacktrace Frame as a text string. The output is the +// same as that of fmt.Sprintf("%+v", f), but without newlines or tabs. +func (f Frame) MarshalText() ([]byte, error) { + name := f.name() + if name == "unknown" { + return []byte(name), nil + } + return []byte(fmt.Sprintf("%s %s:%d", name, f.file(), f.line())), nil +} + +// StackTrace is stack of Frames from innermost (newest) to outermost (oldest). +type StackTrace []Frame + +// Format formats the stack of Frames according to the fmt.Formatter interface. +// +// %s lists source files for each Frame in the stack +// %v lists the source file and line number for each Frame in the stack +// +// Format accepts flags that alter the printing of some verbs, as follows: +// +// %+v Prints filename, function, and line number for each Frame in the stack. +func (st StackTrace) Format(s fmt.State, verb rune) { + switch verb { + case 'v': + switch { + case s.Flag('+'): + for _, f := range st { + io.WriteString(s, "\n") + f.Format(s, verb) + } + case s.Flag('#'): + fmt.Fprintf(s, "%#v", []Frame(st)) + default: + st.formatSlice(s, verb) + } + case 's': + st.formatSlice(s, verb) + } +} + +// formatSlice will format this StackTrace into the given buffer as a slice of +// Frame, only valid when called with '%s' or '%v'. +func (st StackTrace) formatSlice(s fmt.State, verb rune) { + io.WriteString(s, "[") + for i, f := range st { + if i > 0 { + io.WriteString(s, " ") + } + f.Format(s, verb) + } + io.WriteString(s, "]") +} + +// stack represents a stack of program counters. +type stack []uintptr + +func (s *stack) Format(st fmt.State, verb rune) { + switch verb { + case 'v': + switch { + case st.Flag('+'): + for _, pc := range *s { + f := Frame(pc) + fmt.Fprintf(st, "\n%+v", f) + } + } + } +} + +func (s *stack) StackTrace() StackTrace { + f := make([]Frame, len(*s)) + for i := 0; i < len(f); i++ { + f[i] = Frame((*s)[i]) + } + return f +} + +func callers() *stack { + const depth = 32 + var pcs [depth]uintptr + n := runtime.Callers(3, pcs[:]) + var st stack = pcs[0:n] + return &st +} + +// funcname removes the path prefix component of a function's name reported by func.Name(). +func funcname(name string) string { + i := strings.LastIndex(name, "/") + name = name[i+1:] + i = strings.Index(name, ".") + return name[i+1:] +} diff --git a/vendor/modules.txt b/vendor/modules.txt index db0b893..910ac83 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,3 +1,6 @@ +# github.com/evanphx/json-patch v0.5.2 +## explicit; go 1.14 +github.com/evanphx/json-patch # github.com/fulldump/biff v1.3.0 ## explicit; go 1.14 github.com/fulldump/biff @@ -7,3 +10,6 @@ github.com/fulldump/box # github.com/google/uuid v1.3.0 ## explicit github.com/google/uuid +# github.com/pkg/errors v0.9.1 +## explicit +github.com/pkg/errors From ab0530bbe79c11bb0f15bafa50bffd6e5b672aa4 Mon Sep 17 00:00:00 2001 From: fulldump Date: Thu, 12 May 2022 01:34:42 +0200 Subject: [PATCH 012/163] feat: expose PatchBy via HTTP API --- api/0_build.go | 1 + api/indexPatchBy.go | 31 +++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 api/indexPatchBy.go diff --git a/api/0_build.go b/api/0_build.go index 520130d..5a2b52a 100644 --- a/api/0_build.go +++ b/api/0_build.go @@ -46,6 +46,7 @@ func Build(db *database.Database, dataDir string) *box.B { // TODO: remove datad WithActions( box.Get(indexFindBy(collections)), box.Delete(indexDeleteBy(collections)), + box.Patch(indexPatchBy(collections)), ) return b diff --git a/api/indexPatchBy.go b/api/indexPatchBy.go new file mode 100644 index 0000000..f5cfc28 --- /dev/null +++ b/api/indexPatchBy.go @@ -0,0 +1,31 @@ +package api + +import ( + "context" + "fmt" + + "inceptiondb/collection" +) + +func indexPatchBy(collections map[string]*collection.Collection) interface{} { + + return func(ctx context.Context, patch map[string]interface{}) (interface{}, error) { + + collectionName := getParam(ctx, "collection_name") + indexName := getParam(ctx, "index_name") + value := getParam(ctx, "value") + + err := collections[collectionName].PatchBy(indexName, value, patch) + if err != nil { + return nil, fmt.Errorf("item %s='%s' does not exist", indexName, value) + } + + result := map[string]interface{}{} + err = collections[collectionName].FindBy(indexName, value, &result) + if err != nil { + return nil, fmt.Errorf("item %s='%s' does not exist", indexName, value) + } + + return result, nil + } +} From 2b4cac0b19cf519e64751304b0565c82e6e560bc Mon Sep 17 00:00:00 2001 From: fulldump Date: Thu, 12 May 2022 01:36:00 +0200 Subject: [PATCH 013/163] fix: patch commands on OpenCollection Patch command was trying to persist also during the initial load phase. --- collection/collection.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/collection/collection.go b/collection/collection.go index 9ae1f00..3231c37 100644 --- a/collection/collection.go +++ b/collection/collection.go @@ -64,7 +64,7 @@ func OpenCollection(filename string) (*Collection, error) { json.Unmarshal(command.Payload, options) // Todo: handle error properly err := collection.indexRows(options) if err != nil { - fmt.Printf("WARNING: create index '%s': %s", options.Field, err.Error()) + fmt.Printf("WARNING: create index '%s': %s\n", options.Field, err.Error()) } case "delete": filter := struct { @@ -74,7 +74,7 @@ func OpenCollection(filename string) (*Collection, error) { json.Unmarshal(command.Payload, &filter) // Todo: handle error properly err := collection.DeleteBy(filter.Field, filter.Value) if err != nil { - fmt.Printf("WARNING: delete item '%s'='%s': %s", filter.Field, filter.Value, err.Error()) + fmt.Printf("WARNING: delete item '%s'='%s': %s\n", filter.Field, filter.Value, err.Error()) } case "patch": params := struct { @@ -83,9 +83,9 @@ func OpenCollection(filename string) (*Collection, error) { Diff map[string]interface{} }{} json.Unmarshal(command.Payload, ¶ms) - err := collection.PatchBy(params.Field, params.Value, params.Diff) + err := collection.patchRow(params.Field, params.Value, params.Diff, false) if err != nil { - fmt.Printf("WARNING: patch item '%s'='%s': %s", params.Field, params.Value, err.Error()) + fmt.Printf("WARNING: patch item '%s'='%s': %s\n", params.Field, params.Value, err.Error()) } } } @@ -377,6 +377,10 @@ func (c *Collection) DeleteBy(field string, value string) error { } func (c *Collection) PatchBy(field string, value string, patch map[string]interface{}) error { + return c.patchRow(field, value, patch, true) +} + +func (c *Collection) patchRow(field string, value string, patch map[string]interface{}, persist bool) error { index, ok := c.Indexes[field] if !ok { @@ -416,6 +420,10 @@ func (c *Collection) PatchBy(field string, value string, patch map[string]interf return fmt.Errorf("indexInsert: %w", err) } + if !persist { + return nil + } + // Persist payload, err := json.Marshal(map[string]interface{}{ "field": field, From 7eafc1e443c58068ee8ed981ec13f5851aa50128 Mon Sep 17 00:00:00 2001 From: fulldump Date: Sat, 14 May 2022 00:36:18 +0200 Subject: [PATCH 014/163] feat: Add static files --- api/0_build.go | 9 ++++++++- configuration/configuration.go | 1 + main.go | 2 +- statics/http.go | 33 +++++++++++++++++++++++++++++++++ statics/www/index.html | 1 + 5 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 statics/http.go create mode 100644 statics/www/index.html diff --git a/api/0_build.go b/api/0_build.go index 5a2b52a..602ae14 100644 --- a/api/0_build.go +++ b/api/0_build.go @@ -3,9 +3,10 @@ package api import ( "github.com/fulldump/box" "inceptiondb/database" + "inceptiondb/statics" ) -func Build(db *database.Database, dataDir string) *box.B { // TODO: remove datadir +func Build(db *database.Database, dataDir string, staticsDir string) *box.B { // TODO: remove datadir collections := db.Collections @@ -49,5 +50,11 @@ func Build(db *database.Database, dataDir string) *box.B { // TODO: remove datad box.Patch(indexPatchBy(collections)), ) + // Mount statics + b.Resource("/*"). + WithActions( + box.Get(statics.ServeStatics(staticsDir)), + ) + return b } diff --git a/configuration/configuration.go b/configuration/configuration.go index 507fbed..64604a3 100644 --- a/configuration/configuration.go +++ b/configuration/configuration.go @@ -3,4 +3,5 @@ package configuration type Configuration struct { HttpAddr string `usage:"HTTP address"` Dir string `usage:"data directory"` + Statics string `usage:"statics directory"` } diff --git a/main.go b/main.go index 9357571..aca32d2 100644 --- a/main.go +++ b/main.go @@ -31,7 +31,7 @@ func main() { c := configuration.Default() d := database.NewDatabase(c) - b := api.Build(d, c.Dir) + b := api.Build(d, c.Dir, c.Statics) s := &http.Server{ Addr: c.HttpAddr, Handler: box.Box2Http(b), diff --git a/statics/http.go b/statics/http.go new file mode 100644 index 0000000..5e1d86f --- /dev/null +++ b/statics/http.go @@ -0,0 +1,33 @@ +package statics + +import ( + "embed" + "net/http" + "net/url" +) + +// Serve static files +//go:embed www/* +var www embed.FS + +func ServeStatics(staticsDir string) http.HandlerFunc { + if staticsDir == "" { + return AddPrefix("../www", http.FileServer(http.FS(www))) + } + return http.FileServer(http.Dir(staticsDir)).ServeHTTP +} + +// Copied from http.StripPrefix +func AddPrefix(prefix string, h http.Handler) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { + p := prefix + r.URL.Path + rp := prefix + r.URL.Path + r2 := new(http.Request) + *r2 = *r + r2.URL = new(url.URL) + *r2.URL = *r.URL + r2.URL.Path = p + r2.URL.RawPath = rp + h.ServeHTTP(w, r2) + } +} diff --git a/statics/www/index.html b/statics/www/index.html new file mode 100644 index 0000000..8d26ffd --- /dev/null +++ b/statics/www/index.html @@ -0,0 +1 @@ +

Welcome to InceptionDB

\ No newline at end of file From b83d5650695812cf427cf64de59237f74977fc11 Mon Sep 17 00:00:00 2001 From: fulldump Date: Sat, 14 May 2022 04:20:32 +0200 Subject: [PATCH 015/163] feat: add configuration (goconfig) --- go.mod | 7 +- go.sum | 2 + main.go | 12 +- .../github.com/fulldump/goconfig/.gitignore | 6 + .../github.com/fulldump/goconfig/.travis.yml | 19 ++ vendor/github.com/fulldump/goconfig/LICENSE | 21 +++ vendor/github.com/fulldump/goconfig/Makefile | 25 +++ vendor/github.com/fulldump/goconfig/README.md | 165 ++++++++++++++++++ .../github.com/fulldump/goconfig/fill_args.go | 118 +++++++++++++ .../fulldump/goconfig/fill_environments.go | 97 ++++++++++ .../github.com/fulldump/goconfig/fill_json.go | 88 ++++++++++ .../github.com/fulldump/goconfig/goconfig.go | 42 +++++ vendor/github.com/fulldump/goconfig/icon.png | Bin 0 -> 55544 bytes vendor/github.com/fulldump/goconfig/logo.png | Bin 0 -> 16031 bytes .../github.com/fulldump/goconfig/traverse.go | 113 ++++++++++++ vendor/github.com/fulldump/goconfig/util.go | 30 ++++ vendor/modules.txt | 3 + 17 files changed, 740 insertions(+), 8 deletions(-) create mode 100644 vendor/github.com/fulldump/goconfig/.gitignore create mode 100644 vendor/github.com/fulldump/goconfig/.travis.yml create mode 100644 vendor/github.com/fulldump/goconfig/LICENSE create mode 100644 vendor/github.com/fulldump/goconfig/Makefile create mode 100644 vendor/github.com/fulldump/goconfig/README.md create mode 100644 vendor/github.com/fulldump/goconfig/fill_args.go create mode 100644 vendor/github.com/fulldump/goconfig/fill_environments.go create mode 100644 vendor/github.com/fulldump/goconfig/fill_json.go create mode 100644 vendor/github.com/fulldump/goconfig/goconfig.go create mode 100644 vendor/github.com/fulldump/goconfig/icon.png create mode 100644 vendor/github.com/fulldump/goconfig/logo.png create mode 100644 vendor/github.com/fulldump/goconfig/traverse.go create mode 100644 vendor/github.com/fulldump/goconfig/util.go diff --git a/go.mod b/go.mod index 42e2840..ac9d90f 100644 --- a/go.mod +++ b/go.mod @@ -3,12 +3,11 @@ module inceptiondb go 1.17 require ( + github.com/evanphx/json-patch v0.5.2 github.com/fulldump/biff v1.3.0 github.com/fulldump/box v0.1.3 + github.com/fulldump/goconfig v1.6.1 github.com/google/uuid v1.3.0 ) -require ( - github.com/evanphx/json-patch v0.5.2 // indirect - github.com/pkg/errors v0.9.1 // indirect -) +require github.com/pkg/errors v0.9.1 // indirect diff --git a/go.sum b/go.sum index 27b740d..7a3577e 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ github.com/fulldump/biff v1.3.0 h1:FZDqvP8lkrCMDv/oNEH+j2unpuAY+8aXZ44GIvXYOx4= github.com/fulldump/biff v1.3.0/go.mod h1:TnBce9eRITmnv3otdmITKeU/zmC08DxotA9s0VcJELg= github.com/fulldump/box v0.1.3 h1:Qz1tDf0giqC+EVj51CdFLHgaLAayboTT8/FnP6Zd1EA= github.com/fulldump/box v0.1.3/go.mod h1:UO8uDIKLNvnsp/ru2za7gev9VBlRIeAVH3bLl7xdfNg= +github.com/fulldump/goconfig v1.6.1 h1:3Xif582G08uAWfiLIYFLgdZ/H+lUu1kCgMaZdMM44WQ= +github.com/fulldump/goconfig v1.6.1/go.mod h1:qsSyOhlzhYkL2dJ3KWKxs1hX3Qv58Jzj8pRsIEkHmUY= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= diff --git a/main.go b/main.go index aca32d2..31cb6a1 100644 --- a/main.go +++ b/main.go @@ -3,15 +3,18 @@ package main import ( "context" "fmt" - "github.com/fulldump/box" - "inceptiondb/api" - "inceptiondb/configuration" - "inceptiondb/database" "net/http" "os" "os/signal" "sync" "syscall" + + "github.com/fulldump/box" + "github.com/fulldump/goconfig" + + "inceptiondb/api" + "inceptiondb/configuration" + "inceptiondb/database" ) var banner = ` @@ -30,6 +33,7 @@ func main() { fmt.Println(banner) c := configuration.Default() + goconfig.Read(&c) d := database.NewDatabase(c) b := api.Build(d, c.Dir, c.Statics) s := &http.Server{ diff --git a/vendor/github.com/fulldump/goconfig/.gitignore b/vendor/github.com/fulldump/goconfig/.gitignore new file mode 100644 index 0000000..c06b395 --- /dev/null +++ b/vendor/github.com/fulldump/goconfig/.gitignore @@ -0,0 +1,6 @@ +bin/ +pkg/ +.idea/ +src/ +coverage* +config.json \ No newline at end of file diff --git a/vendor/github.com/fulldump/goconfig/.travis.yml b/vendor/github.com/fulldump/goconfig/.travis.yml new file mode 100644 index 0000000..ab6f2ea --- /dev/null +++ b/vendor/github.com/fulldump/goconfig/.travis.yml @@ -0,0 +1,19 @@ +language: go + +go: + - 1.5 + - 1.6 + - 1.7 + - 1.8 + - 1.9 + - "1.10" + - "1.11" + - "1.12" + - "1.13" + - "1.14" + - "1.15" + - "1.16" + - "1.17" + +script: + - make setup && make test diff --git a/vendor/github.com/fulldump/goconfig/LICENSE b/vendor/github.com/fulldump/goconfig/LICENSE new file mode 100644 index 0000000..f6d7e44 --- /dev/null +++ b/vendor/github.com/fulldump/goconfig/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Fulldump + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/fulldump/goconfig/Makefile b/vendor/github.com/fulldump/goconfig/Makefile new file mode 100644 index 0000000..797bb16 --- /dev/null +++ b/vendor/github.com/fulldump/goconfig/Makefile @@ -0,0 +1,25 @@ +PROJECT = github.com/fulldump/goconfig + +GOCMD=go + +.PHONY: all setup test coverage example + +all: test + +setup: + mkdir -p src/$(PROJECT) + rmdir src/$(PROJECT) + ln -s ../../.. src/$(PROJECT) + +test: + $(GOCMD) version + $(GOCMD) env + $(GOCMD) test -v $(PROJECT) + +example: + $(GOCMD) install $(PROJECT)/example + +coverage: + $(GOCMD) test ./src/github.com/fulldump/goconfig -cover -covermode=count -coverprofile=coverage.out; \ + $(GOCMD) tool cover -html=coverage.out + diff --git a/vendor/github.com/fulldump/goconfig/README.md b/vendor/github.com/fulldump/goconfig/README.md new file mode 100644 index 0000000..0e9ff47 --- /dev/null +++ b/vendor/github.com/fulldump/goconfig/README.md @@ -0,0 +1,165 @@ + + +

+ + +GoDoc + +

+ +Goconfig is an extremely simple and powerful configuration library for your Go +programs that read values from environment vars, command line arguments and +configuration file in JSON. + +Make your configuration flags compact and easy to read. + +Arguments are parsed from command line with the standard `flag` library. + + + +- [How to use](#how-to-use) +- [Supported types](#supported-types) +- [Builtin flags](#builtin-flags) + - [-help](#-help) + - [-config](#-config) +- [Contribute](#contribute) +- [Testing](#testing) +- [Example project](#example-project) + + + + +# How to use + +Define your structure with **descriptions**: + +```go +type myconfig struct { + Name string `usage:"The name of something"` + EnableLog bool `usage:"Enable logging into logdb" json:"enable_log"` + MaxProcs int `usage:"Maximum number of procs"` + UsersDB db + LogDB db +} + +type db struct { + Host string `usage:"Host where db is located"` + User string `usage:"Database user"` + Pass string `usage:"Database password"` +} +``` + +Instance your config with **default values**: + +```go +c := &myconfig{ + EnableLog: true, + UsersDB: db{ + Host: "localhost", + User: "root", + Pass: "123456", + }, +} +``` + +**Fill** your config: +```go +goconfig.Read(c) +``` + +How the `-help` looks like: + +``` +Usage of example: + -enablelog + Enable logging into logdb (default true) + -logdb.host string + Host where db is located (default "localhost") + -logdb.pass string + Database password (default "123456") + -logdb.user string + Database user (default "root") + -maxprocs int + Maximum number of procs + -name string + The name of something + -usersdb.host string + Host where db is located + -usersdb.pass string + Database password + -usersdb.user string + Database user +``` + + +# Supported types + +Mainly almost all types from `flag` library are supported: + +* bool +* float64 +* int64 +* int +* string +* uint64 +* uint +* struct (hyerarchical keys) +* array (any type) + +For the moment `duration` type is not supported. + + +# Builtin flags + +## -help + +Since `flag` library is using the key `-help` to show usage, Goconf is behaving +in the same way. + +## -config + +Builtin flag `-config` allow read configuration from a file. For the example +configuration above, this is a sample config.json file: + +```json +{ + "name": "Fulanito", + "usersdb": { + "host": "localhost", + "user": "admin", + "pass": "123" + } +} +``` + +Configuration precedence is as follows (higher to lower): +* Arg command line +* Json config file +* Environment variable +* Default value + + +# Contribute + +Feel free to fork, make changes and pull-request to master branch. + +If you prefer, [create a new issue](https://github.com/fulldump/goconfig/releases/new) +or email me for new features, issues or whatever. + + +# Testing + +This command will pass all tests. + +```sh +make +``` + + +# Example project + +This project includes a sample project with a sample configuration. To make the binary: + +```sh +make example +``` diff --git a/vendor/github.com/fulldump/goconfig/fill_args.go b/vendor/github.com/fulldump/goconfig/fill_args.go new file mode 100644 index 0000000..9b3f7e6 --- /dev/null +++ b/vendor/github.com/fulldump/goconfig/fill_args.go @@ -0,0 +1,118 @@ +package goconfig + +import ( + "bytes" + "encoding/json" + "errors" + "flag" + "fmt" + "os" + "reflect" + "strings" + "time" +) + +var values = map[string]interface{}{} + +type postFillArgs struct { + item + Raw *string +} + +func FillArgs(c interface{}, args []string) error { + var f = flag.NewFlagSet(os.Args[0], flag.ContinueOnError) + f.Usage = func() {} + f.SetOutput(os.Stdout) + + // Default config flag + f.String("config", "", "Configuration JSON file") + + post := []postFillArgs{} + + traverse(c, func(i item) { + name_path := strings.ToLower(strings.Join(i.Path, ".")) + + if reflect.TypeOf(time.Duration(0)) == i.Value.Type() { + value := "" + f.StringVar(&value, name_path, i.Value.Interface().(time.Duration).String(), i.Usage) + + post = append(post, postFillArgs{ + Raw: &value, + item: i, + }) + + } else if reflect.Bool == i.Kind { + f.BoolVar(i.Ptr.(*bool), name_path, i.Value.Interface().(bool), i.Usage) + + } else if reflect.Float64 == i.Kind { + f.Float64Var(i.Ptr.(*float64), name_path, i.Value.Interface().(float64), i.Usage) + + } else if reflect.Int64 == i.Kind { + f.Int64Var(i.Ptr.(*int64), name_path, i.Value.Interface().(int64), i.Usage) + + } else if reflect.Int == i.Kind { + f.IntVar(i.Ptr.(*int), name_path, i.Value.Interface().(int), i.Usage) + + } else if reflect.String == i.Kind { + f.StringVar(i.Ptr.(*string), name_path, i.Value.Interface().(string), i.Usage) + + } else if reflect.Uint64 == i.Kind { + f.Uint64Var(i.Ptr.(*uint64), name_path, i.Value.Interface().(uint64), i.Usage) + + } else if reflect.Uint == i.Kind { + f.UintVar(i.Ptr.(*uint), name_path, i.Value.Interface().(uint), i.Usage) + + } else if reflect.Slice == i.Kind { + + b, _ := json.Marshal(i.Value.Interface()) + + value := "" + f.StringVar(&value, name_path, string(b), i.Usage) + + post = append(post, postFillArgs{ + Raw: &value, + item: i, + }) + + } else { + panic("Kind `" + i.Kind.String() + + "` is not supported by goconfig (field `" + i.FieldName + "`)") + } + + }) + + if err := f.Parse(args); err != nil && err == flag.ErrHelp { + m := bytes.NewBufferString("Usage of goconfig:\n\n") + f.SetOutput(m) + f.PrintDefaults() + return errors.New(m.String()) + } + + // Postprocess flags: unsupported flags needs to be declared as string + // and parsed later. Here is the place. + for _, p := range post { + // Special case for durations + if reflect.TypeOf(time.Duration(0)) == p.Value.Type() { + d, err := unmarshalDurationString(*p.Raw) + if err != nil { + return fmt.Errorf( + "'%s' should be nanoseconds or a time.Duration string: %s", + p.FieldName, err.Error(), + ) + } + p.Value.SetInt(int64(d)) + + continue + } + + err := json.Unmarshal([]byte(*p.Raw), p.Ptr) + if err != nil { + return errors.New(fmt.Sprintf( + "'%s' should be a JSON array: %s", + p.FieldName, err.Error(), + )) + } + } + + return nil +} diff --git a/vendor/github.com/fulldump/goconfig/fill_environments.go b/vendor/github.com/fulldump/goconfig/fill_environments.go new file mode 100644 index 0000000..baed516 --- /dev/null +++ b/vendor/github.com/fulldump/goconfig/fill_environments.go @@ -0,0 +1,97 @@ +package goconfig + +import ( + "encoding/json" + "errors" + "fmt" + "os" + "reflect" + "strconv" + "strings" + "time" +) + +func FillEnvironments(c interface{}) (err error) { + + traverse(c, func(i item) { + env := strings.ToUpper(strings.Join(i.Path, "_")) + value := os.Getenv(env) + + if "" == value { + return + } + + if reflect.TypeOf(time.Duration(0)) == i.Value.Type() { + if d, err := unmarshalDurationString(value); err == nil { + v := int64(d) + set(i.Ptr, &v) + } + + } else if reflect.Bool == i.Kind { + if v, err := strconv.ParseBool(value); nil == err { + set(i.Ptr, &v) + } + + } else if reflect.Float64 == i.Kind { + if v, err := strconv.ParseFloat(value, 64); nil == err { + set(i.Ptr, &v) + } + + } else if reflect.Float32 == i.Kind { + if v, err := strconv.ParseFloat(value, 32); nil == err { + w := float32(v) + set(i.Ptr, &w) + } + + } else if reflect.Int64 == i.Kind { + if v, err := strconv.ParseInt(value, 10, 64); nil == err { + set(i.Ptr, &v) + } + + } else if reflect.Int32 == i.Kind { + if v, err := strconv.ParseInt(value, 10, 64); nil == err { + w := int32(v) + set(i.Ptr, &w) + } + + } else if reflect.Int == i.Kind { + if v, err := strconv.ParseInt(value, 10, strconv.IntSize); nil == err { + w := int(v) + set(i.Ptr, &w) + } + + } else if reflect.String == i.Kind { + set(i.Ptr, &value) + + } else if reflect.Uint64 == i.Kind { + if v, err := strconv.ParseUint(value, 10, 64); nil == err { + set(i.Ptr, &v) + } + + } else if reflect.Uint32 == i.Kind { + if v, err := strconv.ParseUint(value, 10, 32); nil == err { + w := uint32(v) + set(i.Ptr, &w) + } + + } else if reflect.Uint == i.Kind { + if v, err := strconv.ParseUint(value, 10, strconv.IntSize); nil == err { + w := uint(v) + set(i.Ptr, &w) + } + + } else if reflect.Slice == i.Kind { + jsonErr := json.Unmarshal([]byte(value), i.Ptr) + if jsonErr != nil { + err = errors.New(fmt.Sprintf( + "'%s' should be a JSON array: %s", + env, jsonErr.Error(), + )) + } + + } + + }) + + return +} diff --git a/vendor/github.com/fulldump/goconfig/fill_json.go b/vendor/github.com/fulldump/goconfig/fill_json.go new file mode 100644 index 0000000..795df42 --- /dev/null +++ b/vendor/github.com/fulldump/goconfig/fill_json.go @@ -0,0 +1,88 @@ +package goconfig + +import ( + "encoding/json" + "errors" + "io/ioutil" + "reflect" + "strings" + "time" +) + +func FillJson(c interface{}, filename string) error { + + if "" == filename { + return nil + } + + data, err := ioutil.ReadFile(filename) + if nil != err { + return err + } + + return unmarshalJSON(data, c) +} + +func unmarshalJSON(data []byte, c interface{}) error { + if reflect.TypeOf(c).Implements(reflect.TypeOf(new(json.Unmarshaler)).Elem()) { + if err := json.Unmarshal(data, c); err != nil { + return errors.New("Bad json file: " + err.Error()) + } + + } else { + var values map[string]json.RawMessage + if err := json.Unmarshal(data, &values); err != nil { + return errors.New("Bad json file: " + err.Error()) + } + for k, v := range values { + k = strings.ToLower(k) + values[k] = v + } + + traverse_json(c, func(i item) { + tag := i.Tags.Get("json") + if len(tag) > 0 { + if i := strings.Index(tag, ","); i != -1 { + tag = tag[:i] + } + } + + var value json.RawMessage + if v, ok := values[tag]; ok { + value = v + } else if v, ok := values[i.FieldName]; ok { + value = v + } else if v, ok := values[strings.ToLower(i.FieldName)]; ok { + value = v + } else { + return + } + + if reflect.TypeOf(i.Value.Type()).Implements(reflect.TypeOf(new(json.Unmarshaler)).Elem()) { + unmarshalJSON(value, i.Ptr) + + } else if reflect.TypeOf(time.Duration(0)) == i.Value.Type() { + var d time.Duration + // try nanosecond int, then duration string + if err := json.Unmarshal(value, &d); err != nil { + tmp := "" + if err := json.Unmarshal(value, &tmp); err != nil { + return + } + + if d, err = unmarshalDurationString(tmp); err != nil { + return + } + } + + v := int64(d) + set(i.Ptr, &v) + + } else { + json.Unmarshal(value, i.Ptr) + } + }) + } + + return nil +} diff --git a/vendor/github.com/fulldump/goconfig/goconfig.go b/vendor/github.com/fulldump/goconfig/goconfig.go new file mode 100644 index 0000000..1dafe9f --- /dev/null +++ b/vendor/github.com/fulldump/goconfig/goconfig.go @@ -0,0 +1,42 @@ +package goconfig + +import ( + "errors" + "flag" + "io/ioutil" + "os" +) + +func Read(c interface{}) { + + if err := readWithError(c); err != nil { + os.Stderr.WriteString(err.Error()) + os.Exit(1) + } + +} + +func readWithError(c interface{}) error { + + f := flag.NewFlagSet(os.Args[0], flag.ContinueOnError) + f.SetOutput(ioutil.Discard) + filename := f.String("config", "", "-usage-") + f.Parse(os.Args[1:]) + + // Read from file JSON + if err := FillJson(c, *filename); err != nil { + return errors.New("Config file error: " + err.Error()) + } + + // Overwrite configuration with environment vars: + if err := FillEnvironments(c); err != nil { + return errors.New("Config env error: " + err.Error()) + } + + // Overwrite configuration with command line args: + if err := FillArgs(c, os.Args[1:]); err != nil { + return errors.New("Config arg error: " + err.Error()) + } + + return nil +} diff --git a/vendor/github.com/fulldump/goconfig/icon.png b/vendor/github.com/fulldump/goconfig/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..9ac79c4a4940c77e7b5a31dfa0fdbe3852b4696e GIT binary patch literal 55544 zcmbrlbyU>f7d1Te&>_+--3Un6NFyPNNFyE6okN32w;&~DP*Tz%4bl?QA>Gn2Fz@C2 zThHI`v)1zmi=Mfkd(YWt?|sgFr=_8Uk3)q6fk5z8l;yP{5LECbDg=rNKAn31zJ@>; zAS&{5FTH2?T73KsCO;sN(ia0anKQ>EY%b^Bjjm)cRP=F5?K&;htS4{uZS)=+`Fx)U zcWkqjxE+2bQG@0XoK{!E!lR~^uQl+B9XlcQ?Nn;A=(l9icNYQ4MSg+sE!F2S$*-%L z`=k%2)hGxq?FmtiZW1BU|FDU%ck81)ZO->YLEPLhWO6t4&6~1qH*U@AE5;1 zVQry{UwaFosh<~&-L>M|U{LW4+M#F8&dpJCadF+8iQ#uTLtv`5DB2T3+n!BDR8lzM zBoLhLs2+Yi#v;-nA{a!O5k0WYBrQGt!nRLURaIL^U5y-8h=QR#z|n4DO2Utk0V({` zBaO(99UV0N17kpp5&XtxjMGo^cm{RB6Xgv1oTgnTK@&G+)a2=rvwjU#p9rx}Nznde zn~9zxs>PlH(PEDnS)Mylg8UR3s`hO>*&LzYytuKRQEZuca@2i(ksKVtj#oC>OGCDq zF_$>(;S+awa}DRgd83U_gAs?4+AhGc{(Sj!`7YMrzyQ_o1A&CPQSA`fd@WSjd`hCB2cN?rl_(Ng+m4S@OZEfHF$2c{c6pF#m5KqHszk zIKqg2R&hrb+rlx+A8FB*a6Oe_3J<1Nh8T}}1jH^KUVKBB3t!!{xsN^T=e;nSVq5*p z;sBvvml9hNq-7MwN{ZEo>V!uEgH`2VORKom%3S`plrB_;`t-KU2}WznOQlw#1nq%I zNMpIdP8B<;DukW%mNi5E3H;6ng%ZQre)l7MDSNV3qWaK8(Ys!$e5i^tl z@8xRle@7`G86j;k&un;JqC2Uu-Q5s2BJr&P(?y?Ife|m|qT+vN#50II&1{6CJ;Vxt z7UO@b&?Z;Rk7DG+*2>rFF)=a-hham)VZ=O-*dG`hnS=BOAtLY+PtK~Tv1f;h>%R^7 zFY7@x{+IdS?EV9R`@sJX55Pa%|K@(D{;&S<{sI5@5+H>B`=9$2{J*`#s-);|7K-e8 zLte(B-$g0f(;kCW-1tP=s%^b%i90wPq|wfJokT$d%8X$cHrlGSRv>(%s^671EI8sG zh2PH5-!8F84zP{HIyy?64jz9d8wa=oT$7!YK!FMCxP`Cx>qS7DS zQmIco>*S|%Z(3;684DT3|4jlxn-3MCDD^c64tr zL|SPg&^<6ly0+bMWCig$8H>7F9iuOQ;tb<6hFLAWP@vz9nAqmVFs_b;9}0I8QnWva zcq6&u+AzNF@l-6H6E%-w1G`j=hM>WInm2c1+mIp5TUoVXK5$l(A)n3guZo|xz4kx} zUgu{K^%HzQ#-rGEd)r76;lZj9Y|X$Rh4*A)&Bjh&sT^E3&q`44oHd2H`-iA@#rS#C z2Uk3s0sxZb6RlRXTlhJQ-c;gt*n7WEam^WAZ=BfobTVr8{=T)dL&BNMe#iEn=`IF- z*}AaM4u|NSTTBkSIq}>G3%B>GPt1!>$p<;$p)&}nCtF%tzIu($pf*v%qCGH(y%%Y` zxO8E3S2F#3nMVFeX)9+^dK>oJ2e4*Ky{T96y%RM4W_O6I%%wx}I(E7jH?tkJo!job zB(P>EX}B2P=1=SQp-+~vif!+_4r`S`LYN)lr&~p?LYlmYXVZ7EY$B@oY9xgkRgY;+YQbRvSU^RM%A@4?n)&25H zOG|auNjX3+X0MRLphK??+=5I{2gR~K(7jiUTb3_pNqBH%EUB?-nxuQVl>PA|x7F}R zG5UJU*J!`cl6GT{Y3|g2-c#Muc7zby@j7?rI*Id$Cn=Q#vjicuP=V( z@4g!P?>~t_^+YIjvLxE7#B{#y;@Q<IZ zpAhv?URK*?K73=YL5SCoxJ1e(8kw!JiQ)NksY^~@CImYKzdc=j!hhq_dtMR;2Zyh! zve*!XLUf6nWhD(-9*Lq3v9-s3rZhTCN!l2~plq;wEB#&5)wN z_YP|^uXErgOpu$*7O4NC(2yMqDySGABa@Oy7{f*->EZzlDzrNVU7c3cwH;D(qkD=p z*`)Z#60NQQES$MPpP6G!Nl!PZ^AeO(ZNRxi53Xui%(+W&Zy<#^|EaF{+9840T)nYl z({nD1XK>ipp4FbqGkC4$rqv^7$s>iA1dFVl(egs!HU8{8RJ2GjlravsRJ9NnS^qTu=B80K4ci!P_xt2j(ANu{ywujuUr)Rqbj0Rm*ra1 zgI`+M!Ie_T?z&AAQLQz4jg&dw6(1BCPf;3^xmx@50bEdh-cU;{5L{_8C#=J1pFcU@ z))e)HFy)cGw#?jU3QeW3xmY8A6Cp!>nc;Amey;D0LMZ<0 zqyAKgR!*DkwGr<$3CFs)>8qL^o}`v7l($$yGl@a?-(%2EHTK-ZvlU=be5kpoR(N$Q zy2h;}=%MWB1Sp!gjC3AN3%xf~hXU?Qyfv}#XHv+;z3YvInwNX;o|t_?ONB*^)Gr)( zMVM#F=cBhfQwK5@;j!M7p$97E;XClVurht>m!(%`W_Zmcszprnwt?#O5-bIeW1Sqj zcf1+FLls)}qk`m;G&Dnb#?S97iZ@L4Dx-Hz45Fp4|8;JY@HJY-Q-TSh0Kn}{{$AWX z5}UDZKVcZ)30|g}MxVFJ+&C#JD#{F?O@RU!bT>bA!krM}>Ev&fW%x597jtogB}Ss9 zX)0m*RA>&xGo3#qUn_l3SXcFmWtr6!VM-auh>qG(#933Z73wu@7%JbRPjpI#8rv?u z{Cq;_0*{$#*1JiAv%2>r@p?;r6_A5I~&1A zv0{&wgAxcHgx62{RDy<1=njw)7{~g1bWXE~-B-MtZu^$eGu3WfJAvvkCuA!^9xzs_RWuFO(lH z;sr9rH#acoX`uzL&p!;|OVa1n$Aa{oOA8Ox1V$w^^RT>8z59fQT!TPJ8BNqw;{==j z%pF|U=PvROmYtE=z=l61!iPFJo);3WzIXi5^i&7Rj>69w+xCXyPx~RZ#kG><5$3nG zc}N`^)xL83^zgM2Lr$+O;bZLm`1$VDX+4C@bk`Vc!oyBx=f znnpTUO;xojoX7;cg9^h_O0)SiA^26~2e)3`-$5_%#Cws~Yy@RDI^0W-{ds>A_2K$< z2@PF47toWPx3xdHd-U@pJ`oBnex(mj9FF_PKdB zI0ZV(WEuc8^{ZE}tSBQAVR=f(fRDVJ@4xg`GzWA$D`8-AFlliVA>O6@6IowhFJob0 z(FaLsd3Z(W`(=sWC|&CVgL8px=3#>&uT=Ac&oJyD@1yIdY?G7`$Hb_XfolZHjLrw(y`nsW&XQFG^WLNn`J<(1J18F;+fRy~?GF-@HnsI*)e|Vz_L`49WtP%!N6( z{xf>eDq5KE#-+&IB^!xCG157QUj#y|rJjd7tMrrA{n^0PW9#(FWh|NJXwL-9{@f1v z*_5-#cN?7X()&|7qr9L9euJcVAGExA@`q_To|7aGy*;W|!xz{0P6RFf!OV2@CB;&r zbMsqLCfReU7if!{x{Ix5F-jP|Bl-1@@6bq5e%>K+0UyQgODgHHQXra5o~WNZZ?a9=q56#8 z-uL+KOJdC<#N+svg8GVs=oi<{JYblGxgFa$Cf z6i^|&HKq1j`R6-NXen=k$iqBXX&pMGFi<;-+E^)?^^Wq&X(1PbY-hUTJGH z38_DQ2Wme^XfJp7gq$2+L?E)WeSggR?s(^lz+FOi|AdRwP6J;S9sLoCjE5+Vmxq^E z!EPf7iYf#z_IO%Iotc?As=U0sHwexa{a|lZYL9j(Adx=-u^|gO z3FWl{)j60Xx@BB1>m$c7BhiCF=2M1di7=mP2?Ki(QJ z9rS~o8w~B3zCmU8pfV$i{4s}UOJ;Zxw<8MPTiP=RXR>4^qkh=IHO#OF*1rE>AIZ>g zy4i~_1iA{OBCQ~EC`%wVB{WJV7{jz^@1a_>MH~@;c8OxnzQU(n=YPzaCSB(kL;yF* zztqYz&ZkK3GINYIBPM!=hlsGvM6SCw2+4l6k^=`3waiEw@n#-ZQkk?U@awRYDoS3n zf4JGN$3Y|=e(Dnll_OD450+F&6otedz2wZMTKl7c6Z&W0Mn35I0NwpAXzLM5M>QHv zn&s8t;9x4-7#1h?(OYuG2_aD70PMe-yHvA8m>+HrE~nJKKeJ$<)-<947Qr?Z=g;{l zCBXfMgw%68SF}7e6d>2}Zzu@nPLLwY{rvfb_~Y%g)f#XcqOZ2@>=60N$YrP4f zT%7Xl9aT;gXAR&zb?$rK81hbgXb;GssK`@fyy`(Ba^TLF*!eN{|I`4m@Q*RaI#qn{ z$@KDUd3hlT#QFr`F(GsdiY&Qa+8JkpUA1%GzqAZe{CD!VwS6-8pAY+#$svGvT3W^C{ z-I?u_==>P;t@GKy)Rce6Eq)iOgH;r}o0u_rD5ew<3EDeNp8HiGjp&@Sq-1Hp3yfak z5{u_4-M{_(`P?xdF&PO|17BDqLKaPU{OnkTP3ioqr%TRXIu$C!;1H6Lk8zt>ogsTk+QMts?N#p)-CCJ}j6|7azF~o-wl)M<`Y_bY z@?2y8aM`Gd3SDfyXvSB)*0rji;)8-yIL-5rYI1Fxu1Dbn%NG%E4F7sI-2%7a*SjEr z+H1;){13&pscC6sAchF|W1)7pN;hKjV-@zn%x$s($YthhjF{Sp6teL{B^z=55Hwr8 zQ+LnR-5uGepfRP1BM08JXPk%&7;xTc0aBm-f34 z!H>2It6S3!>RuI&MCK$!<$R18MNbV#C=IqvCRsE?-%&9pPWQ6rx9{6!_}NhCY!LkN_AZqmveQW~?bKV+ z+$<^noq(k^!X9K3(4ZqhbI&P}0tOi{G+RjaZ3v^^;ATNd0qx*n$+s_w*8zSu(!Lqz zvsB`{cD53F3WLW5-g9$AnQ=8Hd}R}#jJCwbozAXw?c#zKlheX$Pz}i93YlqCIGE$< zHT8{Aw!;-HGAaD8)*qr4w#hT3e3gU=yf!2V;NI4%9r8CKr%n*zQ8$6pp z81d^q!wwQ<IF}Fvk_Pl(3Zw zjM*?xb^kr}angQ#PhMuQYSaT5A2g2z1;>Q&IapLwRAQ(_;z~=oNk~Y5QTC~&CHwr` zvoJ-JCO9vCdbbFCzp-JZudfe&DPD3>ye;l)MgsP*kX(ZaI6tnNlz)fal7t&FQe4EK zM%9mSzYf(i&DTZAiCz3mA|Z~!gtB0oF`zP!@)jW~d)NMmlMxV5O|G$IjIOwgzYinI z3hI|nI4y~R^Tj`^4^mh=!wt^KP_}+5Ri@MX`$MV9zTBE_v=?-eSLH9N44@bh3^>C^s+2C{Cfi={!hzeLv2dYK)_NIR>mB-~e{?!oi$y&z6w6BR;1^ge z2Y>%sXlrZt4GfsA{0s-ibFq4sqMRIxf`Y=c50Aq^YXcD|lUbaYkU*3?GPAIt?&l{_ zR#qk?Bor3bp8qsb)cc4*DUN2P>)ivPCr?(-d`E~dVACoC1NV`SLuS-hSTxutxRIQc zMLy$ubd``M9{f_^G{gvx*-+$(g6yawVra$LfBx`7tsOLZ3bgy(R<|DJqvNM1-iStp^VCEYPjKdk!w_7xz2z4hzAA-|Fi~3NO&DsEVTx_)YZMXlE9|?@K;QY z<8Bm()ySXYPxpk1vbxSg^yoJfZPs?jl^jyVQI(GwGDt6Ti){n_gf`J)2J(rl5V~k1 zpOWsF9(#A7OL4I1hkXm*a9+OfwH15S|HWud>}W^r<9uss&Uu~Sc~5duQeR)+atJQ3 zX?OHPZo~S7)m1aF&t$VD^zTkjPqhpT2Jdcf{(Lvb$`WxZE-j527*G|LkQj-`eSvTAE}w6V`5GSNxVee-`%~#Rx&RZ%fH7{w`wyuACo zscSNXL2fAP>G`DJ#fBo(%ycA2s`>VO?*7ADk}fkdbNJ{9$@qA`;D+FBbm}uB7nTpn z#SHR!BEiv6_6XG(tc@r6%*OpQbF@+X^sR;hF5M3eUHe%m zB8aPRZxUs#t=X^$>HaQwEv?6jESj$M#C{a_*!g8&v`fleAM`;Np7JH#L~bhO3&Xlz zGX2Boj*q)5O}^$TQ29jCdUfb=MVh4t4r8HhW`SaD{MQ>tiil3Y1K&`{ptr)%hXQ-- z#&ZKaHT%S(`Yw0HfsV=K;`(#*Q*h7kSPWExr}=@AopN?|oVrFH4@Ksk(QwImEp~o? z>8#6@d0A6i``uygiLw8&!lM6)IwvRR*1-V*Cu`E<#~lD=sdET-L}5V1G?6Fn!-+sppC1;iu9MFoe{9;8iIe#nPkW^C znG9zN+h6u6N&hTVN$x>p>VqX*pY8hWd@1q1J)5?5aF7aUNlNaUFteSsYkou7u%QMEJ?X%hR2QLA}F@a3JY5qvTc!vb~q_wU~_tSl@7Z+@cN z+1mqYWOZorqx$X;{Xn~?&1ntZNGGaQK%YNH551b6);=D0+q+v>d+G2eYKeMgdid9` zznS*sD-ugr1i5#oB^;cb+FZ8ug@uK{Gt+By=h)uf{yjA{J~I=QLgHk=CA{G{YyVY+ zQYPp3d~>@>lqx!V1Pd9_lMAI|>sVQIn>vRNA2KpPgKeV;Y?m+a=M6PS>&YN*Sm3;- zvqiSeqxGxnN*{Ti>Ji@}lAgxW$PDc%WGSbXb3u6WudD@)JABx>KExAqqM{N~s*=)`nJxb|s8~7Ni;}120qV_fXzlnVAhwgge|MEZ zqYO}hVaW**upA(b9NF}v%21ctz3qjK=k5t77Cd>4jV+`@XVuFGkKX*iwryvJj!Jo# zZsbX6a=W9e*W}3qRyP2Zm(|y&(kOY*$zRF+9>@$(n!fu>>&NMggH;xmx>%5^!!)01 zlfNybxZ)4xai^7HWOU}PU5Y=wd1`G8Rqy?GvAny3Q`4MAGPELFZNe7@rQvc&53A(75-e)tL+o%!j0m}H)R8O*|bpJ@; zxgaQ5Pi00pbK-WZ?On=SiIudI#4VjOHWG?=J^R0rxBVD=5>1wUh7SOQz1l?%^DMR?hqA0A6b{@rt7wzNnB91v#5>8tH3`*JI8 zw2eB7VW#zx-w|6upOwZ*-o>~bo>_dg?_G`)H7K#|E`F~lDI;7MD?46U+`4)yRUfCH z>$L#y{uQ)vNpqpq)$?v=ljic~y|s6F?#=vW{mHPzhbIo1kBJ0+?wVx&~_d_`k?h zIx+dd?~mMn2(2CN6qbC;D(~@ac5pD&vw8Op%6N3+TiLug+GO7tmy<#>GlfmJ#3dBg z2_yp5mj)k;XCLJ^3j4unv$mKr0a9N0_isW<&bu7atuziET#mB*hiIN%Oyw;-Gw*_y z$2z1r)*sTvTR&~~#Exn5v!#eQy7Grj)W|o@Emht5A`70JI;Eeh^xLk{mtF9oqslEv z6G{Z0L9tAH1YV}4<>rpugA94wrGl%gYf4L$fOKxW!(82e55M!RVtjFtK-TqK)KFw0 z;$%r$O>spY@kUXB7C4=1{=UV-g`uGuo!n2~z5|xi zNQbl3JJ24HJ6~W+&FV;{<{)`zgE!T|{{=f~Is$$aQOhFVS)o7zME;;?g-?fCI zB>egcvgo%cAy2Haj`%W*Yp-qtCFwK`*U`EPAW+YnCLHh73E@oh>6`R?nrxH;Hdbwe zgZu32>6kVqu)GE|AWPz~Ihoq2IdO0At)(y~_F&aagBu&6)G5)3^v&DPp{DwQC@l7l zKRwpU3gbq)|9vw?K@+HhK?^Mz-D3~Z$f!8a$Gx{8_*m!9bq|UIk*z^W@sduz6`*op zm+~?@ce_`dfsaQg-d*+W+xy{R4PRg1Q4iY4&fsF(%iF+aW4?ujh5RVZ+`|f6n>chp zLX?L-$Zn*V-9Pr5oQ}&6)hb*cmCf{hW$`WpLOIbbB&p+P6j6REv~vR2#;d`Q;Kv-T z?gK%(7crscM?aS{|KyGc=wFms%&gF%Eu2i#U<=WFHl22&TRY0C2#-OQ>Q&co=J>s? zwu*gx7aCJ+Yv|m8;lD<`B&*t=i$qvAHa6N+NO$MRAQKBq->OOMdPUmQCRU!-On(GNxKi4dm6Q}xzH5`I7#Ju$QLO%mk1zh7fxme1 zf&UFEAl>;}uSYA{SXq;jl7Lo31LDr;m!tFEpN7{=J_tZG1j6h1z_kg)LB z{QNTm18VSR$`bJQ!~NoK--9F2Zbbi0?J^e@B??<|`jm&0@*2Z(>``oeFqn%ALyGYS zE-7$%LKS8_m1G>ow3mJ6$hsdMb3@mafSf%TIp4&|Eq5wUzKey=YuY$dm}S zO*aY8$@nZU+tyu%R#m@bl*= zy=p4~r$tE|TwIlB&zQx;KDey)BxYt(Ib+`j7N!Ky**fS2^S6}eHW+N5T-n&#=9ibp z{ZB>7>F7L+B&6RsTIr;b3H%6t2pB8SjySi*K%9bi7NVOU8S}bRIhZG5|-&Y^w!N`)-S67I|B^**^6uv90*>_3S(3r~^+Ax06#Me_5Kbt2lK6DbAF)}u z@hs1IEF5a2%d}j&`ueGUU|6w>-37tdy0?EXH@vS!AlgXibtl4G<$UFc0mbk84Zq?KC+B7?FlB;yG4Q}k zdG|ikx*e7_h>x?po~?r#O@|JJ0Bb*le~65uNC92RK#fBw777hm4lYLE22V~rot#q7 zfFq3hF4@@F+y;4mRC?~~TOuM8|5RI_lsN^w2j-NqH=|ax-Y$pvW<{XJc)${~*STTJ z6*6x{>4Ce5$e1gF9n1KZTIuczbH79bSQIp^5e!8Zh|A;qRHKLhQ%bn^I!@5)7X+21 zqEkgt5fjARF91ed70pQz5tsz@(lK-UR&#X@do$ldL$L_Q=l!qUYO}wtj_wM-JoL{6 zddp&VUH5sVvrAr6AzkPc-zV9jIetV*rk- ztn{YrfsrM8E+TrYq`+*Nr#gSIr=$Bll1JvsDGqUh&oJ5)IQdn_A9=H64T%51Cp^J4 z%j3J7L$`|suWH+g;?a@Kz<(2l{uk5ALxb@SioP@>Sn6KO`!)sT^M$UR1~h>uIsg#^ zmy7iD^hh5*bUB;Ut@gii1!J8VqApCfwze)OE(Qh$V(wc+20t9v_kS!|4W{u}%VWZr z%vPDBdG&i@zR9SLg_9?ye0aBhLMX~aB>DQS&|7qQ8w7T>SQeNcAXlGg)*h(;h8oqR z5mcg){i(82%dKf)Dh&I<6I{k}tR9y}syq{#+Y|cLs)x=;TDhMyx(l9sAqc-0?RwWr1%vk1D(4?5# z#DuCp)hF}tliKq+upJ1z5XCaNq0aWO_O#*R<=vj79P`#+vj7w5i_ev4 zQgY`7aRF7j%eDf+{G~zNekqFMk+-A{AiMg_l*oEg2n5j5aUfudZKpsJu?5XPtX)OD z!!1jx2nuCg?D|9?;?21XI9R*?wO9V$CKzjKy`HRay;@Jt^%&6>IJL>_`Tga^@y+eq zrPgdl+@~fJq&&p+UX;YEpfJ?5lT3M5MMtOK{XAd`?C8+FQ=RMoDp=kN(CqSe ziFEg-^o5vX+pTAL{VLACfB#^5x5v^?fI;*@!i#s>&<&sd?kMtum>X-F<#Q7g`e^8- zxWwnBrpOmfizOvxv4t_ulB%jveVE6LkrZhRz3+|*)9xlo*pYK&73x9v(=rM{3nAa_ zeuiN?ffL23Qfh({3E(u?Vq^c#w0w)~*0hzZp)b70(5A!IES+z!!mBbLK8g$rL$~^s z(J@w_B%uC)gC+aSLwV3F#7&w52lFrvSI|C&-@s-^Q5<>du|Q_IZCI`(Z*wjEpet-N*@ zP(xF}%pv$b<+vd@I;ju&U3O2FD4iz+%LXCin7QGnQS#!#Thc{hd-YmKR)S=?QlVWY zoQR&XjH!Lm@%T+`ccN|sXM^NKXne&AjEOJ z+t|MLac9a8@A-UJ`4=4-&c&FxG4RMc$Ef#QiGKC>u*2z0_5(((*6V)Fz$7P7&gcD4 zh5({Sl%HQByf%|o3P)rQo*RNk~?Cfyl(Hg~VCEbG>rC0-VJ7ER-UWL}%dZ^8fV6U~(E<-+r9`9=jheCGVy700u37_Ti^j+l`T`np$cq?aH%EA#6tUU6Hr# za|^LcyG6i@MqJ^l3=L7A_24s#wq8lq$($3dkK6?hM>J~H%W!eMf5`Rf=&S;sEpbpE z+TgZH;Kzz|R*Gd+&+LkR$aRmR1d*ZOVYITgha-G7n%h34t)((sTP7PxN~LHLe}^(i z|3krCzM4kKWZK^kdf@@zxV+OesI@%TEyqF;4%HB6e%hg4XR*G zU0s#Snj&~Bsr`MxJTv@{EuoR-?*1A^O8R8Le*bef^OVN_tVpXny4W@)EzR|2|A!m+ z5QN_$qz#TZrP=A&G;?SM@PU16npg8uwK0>*R#YokHbJZ?qbueYyOZJGK&n#EF>=NR z=O$hsg@_adjA6t982j4X-24MO1!_fHF^AG2SZ~hotu_ojuK7&-CI?RDWRDBcHTGe- z*MNP4)C+#S1&RN2n86tN+U4<@^5Ca$U)~*8ZzKrXYp_#GJI1zIDV08PG`%q=bn{=s z^nL3DfIBsnOZQ(p8sQPhRBc*KS`1Jr4c*5SNXg0FR=cp{=%jGahbcZ+%!F1{Cx86o zrZVtr$Lrk}X!fAE>o0gI$(>@>F?}j3B(JV+s!9!A6BL)`M@2Dn(kQSl)?-4(t!*1? z!1fDzT5z{7z^VPby;vHlvK&m=BrW;7zW)5hi_WPs1K_#jmlVVnmU6SEq+r78UFNA} z^~qAH7tL-82why~HWKpshlbWSvYa*+{SMawVS8To6>F8$hU#Qv$Q;<|APOq2N9ec* ze@;&-#P+IdDoHfBsVWZ-?07ZhC8m&y5{;NZzuO5>BcF3Y_LPITIXGN0ihY{%@|GPq z)@zT+uoK1!3f^TjQwrxS>>(x}c^jZEBD={=eoeQY3s+TF>-;lm)-W|<b0o1b6^GBG>>U|?7?*|1x07yy0iCPX0Z_%ms6@erk_M*;3ipi26r;9 zuC5Lt(oBldvx+46En=-zz8 zgWM4%zafsEp$wG+LpI-!3Z7xbD~AWGM+Oz&_cvvV1LN6*B1%BHK_irGpRKjmsff<7 z=amD_G;rzURQq$J{7{)7HsbyXIdKHwRTNY;9NXk&^9&zQ6c@Imc;5-bqT-tt4u%RA zF5`VsOqEc%OD4AY)la+w1Me_bKRZ+sl^C28psRb00#&*(p*ij=SvjJst^G6GYmxHg z7o3BIhlj`A(-X+DPGHP)@bPW-B|ZZE4KqD5Y;0`Kx72IwvcLo8YePH+%@9VyxBrTe zBrdEM&eX`9VGYjdw*Fxg8P)7wTHj%Qyt_4-yU;Ah#&;t@P*b90$t>ZF11*cyS&jBudB?V(@26kS#Zs3r{_18n=;|Hcm5 zP!&A;N=%qcx8h0ac5OjHMN`Vr|Lr?qBVI!fwLsxBsEYyBpl7dQ|KQ-ht8jaBQ4REg zBpqQS0qxJji#2CaIm8ude<_qu9j9;q$~=-?^-CSn>1PbtzA&V+sH{Pe+V^Suc6ij#g^eKoSt+E>5Gt zs+sf4$ZJ7CNeN3+@)&)8f8SmBNhPvqQza`Q{`gB4Xx?78JW)Zl#>7*AwL^a1mAqYR zvn-`R3Os`Rln*-aZ2Hk;SgunCWrx0iKT<^RO9-@DR=TsJT1+F;Xl)Az)r>*w`XZk& zAnMnzUnod$7G-Z|Yu*6_3Xi20ab0dlp`xPt*`>4u_fu8HdBCP&(VxU(=r$zGZPNM? zH1kqpC|H1Hj@WIx;~J4X!2>=iU}0jdS4PEmyAOjqjTOClgPga3n~XL`f7?b#fpiKy z$tr#Q9Y%>{v;gTg@3j=*-C~^M|4#)pY{1}o_*L&x;&Y)7+q#CdEBJI{IdGm}0=W6W zWhcc9Dzv1o?p0b!il`*r$ae$+aTc>o((!tTTz%SB+!cBfuQ_V`0>8Z|-FTqv$Kn7$ z%Yr}?;I-7+lizzSgfa+zr{-f!rFc60ed-)SkVWR^kBV&pZjk{o^vueN^+kyWxL)zu zJtv~r_MW#ED8)0~2GRg_&E1?(Gv7=cpqw!3kr_|7a+`}}PS|%+;s&bY^882J5a|Du zHD2SRb$kp0+8M-%w(5OLx#A0}mm@Z?4v5PvQPw0y=TOm(5#dDlgvUq7rtT2>a@hFItGKlEE#0~l7= z*htkTb1f*KZ(zUyibKoAVnKCvGPsBpxAR~Q^F6p2GB7YO1Dayms)Z?kDNiV6+|HxO zAV#tHdzX_Ve=ZQ!%3S<$_q5cj^@P^3eg$*=itv$`nAo#i>1?0}UAMIIZp?tu;43bk zQSXiY?siB3n~sdxj2-SHqg7KuHqM%c-*sxul0A%(;~yVfgH&Ywtw~VaC&B@?@_$u&v-b( zoRf^EDke-akGe?q{``Iku%7;p0ttS6S*OMlOI0946H;xtqSA^f&!waK*D z6r?TfVqIE-YztU%!+zb|%NclBLPA2jWx7#Mdh01mRaI38?!ygwxZ3)h6DA4j&5uS5 zytt?#eUYF_qzYq~QfN`2`Q&B1EefRM8yc9YHBr67MlGWkHeexm4~6<51)@-)j5cDv zVN+jax4~>yJAcgQ*}J;h+T&dlWM(9NU7kEfAx9xF3*rR%!tRJoBv9)eV1ufMO)6sUM$ z^s$~+pJ!72DuFAl<)aa2%W}L$L1D5ZCMw#jApU%U1eanK#ivN_QXr!5p_M<-p3Ef% zC6CFYnvtPIpK^2bu31RxSG zL|oAy`~0(TY`wBd<1rw6{f%2@vdPQ&XtHf=Vv+9oqITtTt(v)Pv?D|XkoZ||dq+ez zrg4Q!(dJO0u^vAZX6Zj6lw16&^5-PRn-CJ z9eL{!1{ts6XUA2j6+qW{=T7{;o119C=);{(EsC)vZ%qj_(RYo)>>Wl=DllJRNFl$| zoa>_I5k8wZ1vd#qL6X-&8OwiQ#VneXNLoD)T0U+Q$SWzx-gp!Vi~Rg7gFhHxg5oLN z+1XjC!H>9$B{;nP02hAQNe^qmAlTZhcwu$PI(vS1 zdb1kYi;GvMB{D-E(?%tYh*)PZa8iGFy{p&i@AEnOb5G0r0_r;zUAGq&&x73j$qQ;< zxr(pwz1%60go9g0l-Qu)8iWziV-Q@Becd!0B*!u)CVD^B)}b=KFbkXelC0b$?*u9V z@gcGeyeb0tekGmK5gwn9ulx~f^hnd&>odEc^JGAq1%21OM1;qln~>W;g_?JmwNn4> zECE5c(2iPY%dKA%W#HIwKXKu4vKqwkqS$#tT_n6GV{)Q1e;TRiww@R@sQy$6^}6AT zvV)4+zw;@$75W=oHG7@|u4&yy-{0Yyn?xy=QXG-^8cxI=@OB~Uibh)%gw<2Vsje>h z=;&x=P0ho$63}3B8bQm=a2%%S8CKm2IdVN4QX74gr_Tg&vr&HVj~H(KuhxyGoix#th34F zdZy7d{`XyTHsluog)$)Wb?;ZX+0Bm!rY_sSB{{PCw23!`i~_UY_20^kd+wmOmh(aY z;o{)t-n%dWuD|EOd_U+rgbJf(_Pt$bOa(I$_X`Ad9L#p)V~Urt#jWk`bsbW-o@q{a zh-YPqp7wD4p6$I6ry<~NL%5c=oa=i`tl~v>c34ZFP=3^~-m+F&yu$*yBmdBrWYvB5 ztQ@$+kdq` zCXC&pS0qpMw!~{U9OE1=*#axbgMeM?)`>5oWoTIHsQ;08kHZzHyznq9zcUVyWAEBZ z*uF}#j%lLNcj()rWTyPX(kKy}tl|bkKH@iq*g@62A7=p<=2o9@#3UOX2@NS;sPPON z0u)|`r=1|fDT zDvo+V)0gk?gGmPL@y3AlmyVZgQrW>4A-ARGbsd(m@nt;x3s@9$w(j|bb&nxL=!_z zzR8Whkr5z_PxBrXr2rE#RoA%xXE^SpoQ#VGP%!tV-_zmnA1$%Tb3eu>U&oJrYWsoe zWBg@yq*kOp)_}VRp{<}0o}$g!1h$cKv5hsX5>Pappe6@D|3&);gHQeB`7k79y(mp| z$)$|4l?P!^IWsdedCOO98Xxn@${geuO2BA4F`KF)Cn!q!JP=zwWw|^OS-dg`5p2|V zRkU|e!l*~@<;yNG2T>p$`_KP%nlSt3OZH*2sB}K-^SwX$=FOUpORAm0I0&gl6bXeY z`n7Mlo*JlL_d}emktR&W=tl+x3c{rxfeY55ETt|(A z8O4BZItHz{M*pjSa(`}QJa$L{@Pk&@b0edn{$#f8tt|%NX@Xh1==bkG0M!y69&U%| zN(c!-eN}EqxidL7siirYc9G#eR)qFE4(fAv00CFOIH_NmOwuC<2%mvT)xZg%!x#NQ z;AU$xoTEqx&z#Je`u5j#{1qz69)u!PQ5H4sRRa`m5(09_6gt=F&nKmbry zx4C70E?{f)29#+aTB0>r>#u_BqexJ8Uid}?SOv3bA9~*mUP3P<3(yZ z7_b8F<@fszTqM7>ObfwYOj%~_nOp5NKv&Z!Gvk##T7lx+AR?ZnR6DHT1(M> zHN&!GF8*yMAG4xs?N}Kc5Ju0$$S-8#%kv94FvGKAY=8oW5<#B{9--td{ZxYV&v|C? zb1khPq7Xo4#rFkqhdnAV75rmiLh6qIOK=_v%WJBoTv&-(c=Do6zUuyn?4%AxN62{e zNgo_8?Vg!svV!ZoYeOPSf49a8z-eV4XD+f70F&wdQO|a#PksrGfW{8FGnfN%>wc)q z4;W2qJkePI*|Ti%RLj9MjA7iwq_`bByM?CyoX7ZWB8?leRQXTQ%!x6eIr~ZY^G=x? zn|PfRC%F9?WDq$F8wy2iXRh1%n~{%FQ6;I}h4{|PG9AA5(6F$8+qdXz;I^6$1mqv+ zi#2Ry+TRl@FcF=gM~S#H`Lcn-0y+tI=*N_W-l~(o!qy9 z<{W-}x9BHf;VobWj2u<3`xO25?pK#@*CX{A9xT0rn1-6bF?p|rG0 zNlSxBr*uBEf7kPWcg}TQoXs8comuO%<`yi*e}$ZcV2JtwRGb=QtJYTG50!woLu$X1 z)c=LNwXJQ~?`f_9`Ys$@R1cOS^CK_$5e9{j+dNOT@%2A!cTSK;Y(eMdK4tSk*^ky! ze7fFwby$^t;)~nBq1au{olVY)nr|5Va3SG zxw;C7&3cR&$A!|#21rd!O+BmKwj*PgT1rfQ7rAnBR3GPqCtR%TzCuh*cn86$h-nF- z>#G6f^?qQ|z921I(9G%nb$k?cp8Gk`dhHpoA8i)Xd-=f&#ttE$w_q6S8a`zHMQz^6 zd7sI1uGb*c=5=Yh&MG7blgqKGYUoX{c|0Fw`~3&N^Wy#m;Kq!v{Qv6BT3my{;SJc=A2Pu zD=Z+KZT;kM>g!WHy_{3Sd%rFvDXAN(!JyClAiM2&h1p;+_KYIj?(E9IX%c&9lWPnX{*e$FVqrId-eK&5^`%#6i3NyVXwy}ts_Y23QcFk~Lm zb&6mk#EOSx5vth8VDnA;!L?O0`(2p#DzH@D7)Cc-#F-ABflvjBm5^#|p!=fNM~bqJ z&R{l&>I*Z}+P_!K>$c)afH?!F5Z*FDN;*u3qxb*=hDOkWYgcTHuG^@-4-VSxFLmkl zzdbl`yW_VfKL;3|{LQp(rc_^Cz#+4#@3x3Y1k>?7Ze&6EPJi&qQ}4+Rfzv(wUd!ls zA<{e%$@jPb!WB(=(bpkkrStfly1+>yX zo!IxeDC+clS_CafTi;{!`YV zi1<9A|In;T>@W|BcfZ=1iJ0F2q(JI&ucPVm;D@4HfBt(~Nsow|tN9z7o9nA*M9P+0 zutvpGal~`u?lER;z?}tZ%0A~4t4{<;=(e{QTWDX9f|tHR6~+hy4{-dEZNINg!{vD{ za8(eN>wn)@EBKl-M9K*GCDB-xHatAK||iq~KfQ)N@y zpk9|ccA-Y?@Lgi{q$j4Eq~T|8-p0SDKYolGQD=A$xF28dTy0&=5~z0O{o2{taUZ8B zF3~VFoS+n>fVB)TmtfaSl^OpgN}pUWqKck-ezp3X}0`v!4V?G zgcqmXzVnIx)vK7zeEip)HF1hWMuAGFiyQ0aM8!mzBbB|Alaw~%5L3%_zc@;8ry~em&$_ZPvE^^!!oS4KOdS~JsiqBA zKyP=Ns$OZ@h+o#QZZZX?MgIBoPjxv8o*cPqAgIJ6@Ls-L*l}N?q5irjiC|o+MYOBG z&gGcXfApx()X6v~TEn0K9w=UJ?)O0z#Fo?ZUYge`mCZJziMhQpPBjO?1oJCyt_OeA zp}YR}9Tf>+!u1!*wemt%RHVuW(*g88?b|PFV1Ep98ij#b#ormi5g?B6)z}S=!=K|@ zQdr2w@i)KzaioUMKhhxAM<`@0k*~)oFz@N=K2H~Mq;Pe0Wo!)xpr?aS_78L;i7iR+ z0B_ATr+xdTpv}Oc1U}5_p{YLEd0N_ivXyUn+MfEO9;TrJ9BLRXb@s!#c7(Lz3}#v< z8@Y@KKv@z`nPZ2wlmW!>XSWc z^jCO{P}a+B{X(o8Xyy{Qy1E)}&@?qVN|w$oM&3ILJ4w9v+OcZik}vtd(i#iVG*`(C z?A(Z&90EQ1*AiqXAKzm?30_QUV}+PC*FuJcnX8_)6K`R2N>&yhW^N$1#YWbExB&U; zGi)wH3z;|9?q+Fk*ADRzn3$NwpFZI~k>`<=OhsY~rR86rnS{O$lRZf}oN)$;_m1}; zVle7Oeo$k6ZESRS@q&N^VdcTC^5n7;50xal^$U)fd6s&D(u)@_jC_%<4M0&^k+o## zuYvHOrlI-YZ@&FjW$UG{i(36f4z42a*O)^Q75^r++rYrfpIu~6cf*DMNm*glh zP_X$WyX=K2P$t5tuguPW&9g;_xTlV*l#mluvand_sNqRYc6WCR+^&ZCqXle|xF2{Y zH5R}S_-#I!XJThpjlS++(MOVd^%oEK66S(LSn)g1$?tPjD+D?w-u$F&EmUkc*&E7H z1XEWh z9K4iW_Px=pXha-!ly^|IwCeT-SDk|y(#P%N?!PZQ^;0S+v{F~Ydx;9#_)bQV88Jc&EvW*GxPs1eO-c)y_hnDKC~q?S-Bn}X(B$PsW(iSp@}sYT zK9LvI@$L1n{h#A$=T(p-uZ_cxOgLgxm}x|v7FusETEFaf(}-wFVm8dafT=uKL8Gw2 z%Spl;`xm;@MsSc_HvRk%LrE?JHfmK>m6EDzxK=S1CZ@f;J=KK6`^DUfin#Xnc1}<2 zhqvJk&BM-GI?n#CVWe~6cfp;W^T^^B+U!#k6DPu6w87KS z&^n6gmk`P+J(B>Z=M}wXjCnDk+&G@p|4(kM`3Z zb-_g(U7qLi4{`%Td*hywqeJD3NRt*jCEA**39qZG!}XaKaU9<}U5@*g zM}HLoB#BA9T%2izU4N>G)bXU%Ng6{Z>+l7SF}U--(`|S2{OXK?^P^ z$IHc)I&2)2^1|Vb99IuY8UD-yXWvGs9J?G#4Ph`xu2@c|UD>Pm zF)=Z?f`{P9`>otHvjAZVd0_gTE{20(Ju)#t`D@V5);0n7Yh;cI+-)R6d|yhbV^Z(Y zBT)@ah)$Z|=?Mhj2SxI8s4P>)xCaIX_R7xB&(rHIe*E7H%fiY!JTjtaU@+!Ac8!UJ zB|;ZOw7Bn8Iprjen*Cj{C5I7RuW5mMwUV|nivLdPqX%uOWg=8~UF78C1+UK#LUnh{ zRY-$~OLWX``8_)GLuaVVc6m@P!hu8EccM@B({+C`n$j}r;-2DchR3T{HO})gU|EG# zKB%z^?zgpq%MJYhd(i6K_7>!U`#9OzZsO0+uB}PX;m&AS*~d0mK{NBYOu2 zC%A1x`T6DGk_2T)-4-w7NiNGsEW6hSt}RNs4!dcE*q`N30M2+wKE2N< zm|z3~v)H`fKaN$|7$hErWbv6*WBug0?@CHaiZ7cgV(Tau(Rh+^Y#cGD6SrhBa(;=! zuxvB@gB|QL2KiD`oYD9*+ok5zKINc}|CCO`&(RVeUQ)7f6MJ4V&rg`YWqQ6|p!Qnm z7!bd8N%&7s?GNtsZ#?8;F~wmMSXd}zzKe}LHY zMoZYr(z5%F)MIW6z&%oH##`Y2{vFH;?9Ne!e%9K?#vzS6hM{fU12fv&Kv~6EG*9Jo zgS*p!EG?o_R+(ksHsdQP)kX=HVG%JtBY^(z;OL?UG>$p;zNI$ z7*=$*u$CFp^&ylwWiYy;|B$BpeW_p#uiN!*@x5u8L=PXl;bAH(OO(JTX1cnB&CSh> z&2MyyAgMqYxccJH!jaF%PoJbWZ!Y{~bTTBBU%fghhN+*_xXSX)qtDcsP_05969h9b zq-aG*`gaC%%M+n0k62AlGyXoeI6K9M(_hj!X=Q0?>GlVVWLI=KBJ?WgW5za*l-t}C zs4j*63+s>h&`hE9?(|Y3pU>>J-zQ-Ffo@uO*(g#fq7C~UC#!8VT}Es2ClXuySgWQa z_;D^Czq8?1@55$(unh=@g?7%*zP?DK@bCOkbq0+VWn<>%Ol8j3^7NhsN@m2q!KHkl zub=XhqipMXmj**ag_tU@;Y7(wY@ow|U9mrdbl#L53Llglf$VYpdYlm8b zh82o-$+UW`%E!csNv|0A^j4QDX9_Y(A)ZnOui%cm zmn`->uhRHuDEbQn;D6!zGzWAO2i-0}gO(*9di1G9momgHMJ9xnG%7=JjOO_x12XQ& zmw#tmaNqjA6dW3l%H?P}_FFUAVqhWs z<-bWnHs@luH4EOX=Xq}+x&L=T{8`Z2A@_}3wc6*rl9E7bb99rcAeRnE^$u@7v|heh?uo-P`|;RaBGNtj@?E{8M0?aNHB9E=^^|r6IT+;pPF*H< zJ-2HokXWbc;W^B1FxXjupMr!U{9rOObGe4+963ONci^P(m*l4~7*-e)`hrvu8tTe1 z`9$&5Ded1FCj%H46!B291ef`(u!A}liJ+0T4)3r)El(ZD(p5g1-gOmR%FWMPD4ee1 zGolh6a&Em6g^YUU11lqubUOH_zaaVJsobc?V*D}lMe9zU#Cd*oFVQs#PX%sTu8<%~ zAc`qWKb6`7`jbwgEeHQqxBz0WyQYe?;Ju-LtD189t%6I+`ym=uBLH@U@Np#jdx{>a zM#^cBQB3e*kz?ySys*@Fxwvfg#oDyKBe1WtjeD^u-W;42Zk)B=eC>92$IVr9dP#sG zHWSqRhs{ zL#Mg4&eU>#eqM?3x{dGF;aXl?wael+oG-`7-Z*2|FC4~XZQ~36vAmnGu&`|PSPh*n zGRNqx7CbUCm8I*@y*nL0Z6XI(e-KKAP@^Q5pvxJIIih84Hsx&Tmt5a8y`$G%&f~fj zR%~BAQO4}PlDeSRQIr+%J({mY&E37mwy%%_B4u93Q+BYQpmAPu9p9SUOnYZ+f_c4A zMLmw|u2NBNmX`h24G;NS-z zR@^hoYPaN9(@#5Xt+jKOX~^Zt)J-d~QQysj9F!GszMd@14t*Uoo|HErt*9d#yb4h+ zuBbHUmXuxpUEjxJHyBGGv$o^J=eEmTrhi%MPwJ(!!)!z_*|jR}VRTRiKKJyjE#dec zvyPcejNS$#I;1+HIu?7;C}MtM+lyo2+`kk;J*a3FD4w62nfVDmE7tXXT%cB2RC?Gy z%afg1DR>0h$x8budeUiLurB`^BYqpjl!y_pp4l4Tur;&bNbi5BH@O|uQTh1O4A?xE z2OnDhKw_nXte4xQcySO&-?aHlHrdNXLI@deOB4i)q|?Be3h?BD99M`)>Zyb~vFG{v z3(YWp^+@<-_xw&Pp+C0mR7v4%^gnekLnqnOMU`5$ODkEw8F~Nkt@y~*5})31iWb?7 zA681~IA#$BEO`G{YIYR`8olGA=TQqagEGsIChaPwew*%TDCpbqHrdjN<6t!P5my8g z+Y?$&%_I!7d*G46FZ!B&(hP+W;u!%QNQ(-zKvfVgljo3l@brtuw@6su@1$|Q0K!1` z{BEzg8vl!z$iX=`FR!PX z{+j>)Sb)p9myH>G{_C3DfU^byQG?_f!A5}P`5~J^g5N)|;A9Pk_gQ~ibh!@%FVb#@ zB?M~f>cgT&p)QxFu1G24Vp#Mpy`$UX&gYX}6KWZohPc5ltb0q?S$V{;21ZZs9rX9* z6ciLH%g#5X$%SJ#dB!`ZFHvKHnG&1KiCGB)-s<}Ju9eHg_AD=g#l|cunuIj+?K1Mp zE&PT-?L=O0qy&nEyOuj5#L;qNk#H-TJ(MNZlWnz9q+Hn5Qs?IWMnxA=qBLX}I}$*NzUbFOX4%GJw&Eh53kmxR)K9A; zk_%1ds6@#vVkuej+^}H)UET;2?}RR6HMme5g-Q=yoY5Fzp!}Ytdcx#&X-(o);HlYp znhxyC@rJ`DA~JHZasH}Yl*BU_Vy8bJ>PmO*2Aniz{}b!#dgwQ=_{Y@yQ)}(BTUnpD zO!-{D)WC}sas1uQ`UE)#`9W=iJBXLJ|812VG!o<*9YFAAzk0$V0r*RSvQkS>($^m? z9{whop)+l?90bCu3;=!$6XSpM;c60ll2oh4=2ubEC(@*HF~|v4j>6^`L$L941YaNjIiA#&B&*#@OcIMqt)_?4a z9!7EJ38kg@&uX7yK7B89^~8kkISkyH0p|{Qm}qe_=Nuq7q}HY}tln(Z3?4wYBqOdq zNJRRhX?n+Er~g&1Q0jfZxsS~u2IS3M3cp!@eDt}%H`!K%KM5SJr!T&&P}ugTlJ9d2 zJgc>=eO!w#+zv|ui$Ik*^|X{{tV@>~b$Gzvx-s@@(zR!7^`g#V-pyE9xl1&>BS+^Y zW_EG08bV2r>_3QIR93J$q4G0rGot;>xMx@~vd$)yPXQCO^1`qCZ#zALWLcH1aqS0G zQT=3MhO2%yuju%NgwXo!#5MRfu5WHGL%@g*&RFNbi)mQ;;z;34I}A7|+`oI7J{up+ zHUASB?{0dsn|nIPXQizpf2$UD(cp5B_A&@FU2=j z6bos8P%+wt2x0EQb)mT*6S%dL&mCG)62%c^Yd(3mpJRUqm(KV0@$N(G7=bFX*(-#p zp{(llIVI-3doGe0mq05Q6cvRBUepKDd>x|FxxlLm7C-PLP zdAVXW?3jtwq& z_2x=GXdxO(C!!(YKUFYkyP_J9ML>u`UBtb!VK|S!LS;TFxT^JTh3}EU(F+Kn z&BGGxR(LJO?-1Z%b3P_WKf{Hw98*2PltAgarO#d0u9JL`muWie=dJ_Fu@&toF!W3&4-TFX7+#q(?S? zVUu*371)(3?9Xf?Pc-!8)9K8MRGHb3rK5#?FDfc3^Rt5$7B;qLY39@l1_luEJX_Zd zMD7tmusxB&rN6dY`gg!SF&RgERMAX((38bfFOPGXQdIIRa`wedDMwTbpo~!bnH3RR zFB|oZjX&lW7S7>bVlCiX6NWuuF8r1ik2ieCgt&$JEx!t3pvOPu#M1AcE%szh(v{XZ zySAjIC9@-(Ft3CQ0|O(u9|Wpcl>YwyCM3KGW~D9cJLr9_9`MNbmh#CZDf}SG!iG?X z8wu7BVtC1&w{d))>7riOEW^)$y-8(bs9MPP9Xz;BSlq%+rYt|Yscu#kwRCYu$(gn% z+nm-Dv0)a-NlPF|evNlwPR~zc4@F@9LqY;mZEbDFhx||_1pGZ?$YCgfhjRhxK**LmTuIun49bUPj0!etS`$n3D3*xQmGKhhUcf z@onS*iNqTE0VoLO%sQQNcb&OlrF|tG#5#Ou%6(KQKN(&AlBowz%OslYOy7XqN{icc z0znu4W1j#IR%d||`7=w_Y$PQ)41VM)NZNRXH=p`A$!5zaIYhBn{#x&M?0ZWpY3 z-BrPCKL(-B4tNzsLF!djq&sEr=*SG=8B8y{%1}~IUDh)1=bN7!OB0KIM555uBeV9Px>2q`W(f~sl zV>DZ^fFbHNmh7c8=lW6dGRd0MODa1>x{_REywJpiHky)eyjpDp8?rBUS^h~tNG|T5 zMn7;Lf7%B{zS}xFTFQaW0}0I~96%;zU?3$+ z>n~g{4J63&JHP>*TX?tiM#j_AvwZ?zPw(f~CF)zdO<8}=aG^;g{&-BuN*BS@B2Ni- zEQ(ubbT+bPpR7FgC7iG&gW=pidKE*wu%2cYujSM%f40(A?;@iK7huEevsbX= zXV73O$vvGeRNy{-;3s1#Sd-%WDo{XrPs_AV!_kr$4&%b4#U#{q8-0Az&rEriuSr5(btKp14g)b}ljm@cqCzy+8o;Llu! zb&UXF2Su?8;*Cgo8p^|MYU+deCwfexb`<|!G)G_JixFp}#xg{4SPAELoD&DHvBye# zvagrO=$KidHG9^27|<2ew}{H|qWi)1IKK$?)%>+de1wUvexQ1p5IW$b;#_5^T}}F5 zf{Mu;?UEg(`HzA2XS(vvZxx7?^bK&ZTZ>m2P)yx;pXe@rdgZ?G(F`qs{0lNB0Sq28 z=#g^38H;qiy%sSbRI8&p;8?+6vq++(PCd_Dic?)b;^C8n8c|a67b{p)VDGUKcH3p! zyx@pSSlj1kHy2W87@;^1(D7JFgUW~!Gy5(t=$Ue|TN{ONS~=x#}FcJD#TNl6LQ#u3?^r|h;Wm;K}sF#_<#KZdR6 zfH79StOva58x|_vkG#zzR25bcZq6Z6} z1Ab4S*=C5!{XIS&2Iag}?s|_eMWaE?cSr~*;S{i*MH)od^RLbhkZBzGkuLj~ceD`%}{m#XN=_gg4pW^S#W%hDZ zf8;9RX7-(F$Y>VV9UJj+kv(dZ$#sDFT_vM|yXc0!GZFN0uu947`jqv2%zxTVGQ5VvdJZvLz-#v4(TudYw# zfywOp<&1@#uZ@U2q-Zd5Vk~ayUk=ADsJQR{`wuzHfsDYPq6Jl1$(rh?zT5fL)fI9b zN>Qdy6nmyuFmxghOik~^yhk84wK+F-Rzm-brn_S1(DOA1MLEb5?R}XY-Ja}jw+m41 z`Y|~4_)(Oz{a)~#SQ*d4zr|pJOJShPv)PvIus} zsowQ7*C2Y$FIz*jw@Cx@N(Q)9OJ9DY44w`Af98RXKviJxwmCy6bvcm z_W5e@$yx2qoU8QH9*T`uenF-F!WU%9?_iG#Z}Z^;-o`D#B;KbQFJA0?{)BkdI19hz zA+&uECF+Iw7?f;)*3p>*Wi zXk+Ymh^+F^uV3G8u6J(^pqwUw7>yvorQ^Q%u*>gwC1Tc4Pg#IZYZvOmofCWN>i`45 zwFSLQAzskz3rhGt(tFoMow)ZjJzssNp2QhL*onE5;`cTgBap?7c zb@%LQ=k^x9Bq?K~jw@jF$xfD;&?ZHh*dWP4 z_CdQ{t9LroEsNq(ca2Ypud=i14njWD&cr#}V>{;3)$r`#`u-+!>`1UKD&6q9Zr z^BR5{3^SkC6CW;``ZPD~eZ!SH8hr@p1FE@>77QO)DYe(s9+=CgsyDB%+yvv~5p`Knls7~szehHU?J3Bu=6dq9+MkC(7?Q#-j zj#|p!=2O-KN+7$jF%4Ge01n?VkZ})5(l<9ti=*4HEFJuff7$2u>w}p__rch8gqfZk z(d9s3*@YaL9@(3RzOQO-Ug-{fb{>2`S1--3>#{aAKk~#kVd9C$wvAM#;bnqATzVMXSE0PH~aL!f%hY{n8F;5!&U+?GGZkr=D%+_I(Jf$`pREO zIMEcJz5eIPfyeiNxH1y^GYV>`3PRXBXrG`mg;%|DF+$F{HsL`v)pVIu^`lQKqnS_B zf7c`qEUWGloMO%46%B7fw+(yP_^IDQ(=2E=Knp^(i#RQql<_Gk>d+3^)_9xbTNxO1 zt$V=2ynQbzfJR(S_blL`6Zbw~LHe)~ZlM59@JU7Fn#Sde#q-z4|1{X_HK|IS{K;L$ z`LS5$%p=r;(czceJ^SUk*{zHIo4ne?8i)9X2QHqU-TnTtVK&|~KHZZAWrZ=AAvS8m z_Nh01d93L1_3q%lxGx;{mSBBQy;Zq}RBkk^o9Rc$%0sL6Xr>9w24 z#`b5VdVBEvJ^^=dj|1h|9qbT9=%1QF2W1=MJJ;H=gUw&U)P;)~v`Vq$<5{>P)s>3! z6JSzB_EfQX{|VEdz8??4TKjab&)kq*Bm3&-_REJ)o@l}-0!xKD?1h0dQna*W0{sb+ z+mN=0lEetxHbzJFEv>AEH;?k}5H*{|O*wU23+9w?p`oE29vuNOK%`8pp0KvPT>yhd zcee^q5kN_0>I`kwwt*BC9nXgHT4n z>6kmY_ln(qaYe0>RAoq}4zCR+#W(OhAy4?S*l<*CD9h-l+-I-sXBc&4sVVH~|Mdw5 zGZ1B@s_47ja`?bq)VfYa*AqMBxA5@y-R~}zBcK z^v^>&vqN8)ypZX`rFf@rq~&)_yL7N>9uvEN&rd!=YkGEdVSRVC*RkUM#iDHCtlEM_ z`tN46u1#gfTH1U{t7C?#-z?AZ<$RanGWC9)v%?F%p!eStJ5ZhcE~e)TK`IhL zS=Ju+lL5cG9T|{?6z-jw3z$~mQ5EYoYT?T56wUu>xNxe)j9B&G38&=z0v2^nZf3k32oH#C1*}p}gsM+up&pxQJ$Vfg!3VB$oq72(WeFnPM#HL6?_HVDSS38AC}J zL$5P6A&RvIMR-w6k^=huj0(5|WkRuD=hGd$;t@B2xF8>);1DqK{r@5@9}H_s;y9U=<5;{+itl z_y9YD9Bj?g`Gti$A&A#e5pZ-DE)TJvG~3;@1gut-fvXf@^YO zGk>Qt+;%xRh8x+jN!=zU9Im}XG{Ch`+Ub?vC9Aj0M`+<Owwh|_ol9n4TvhMrK#5(zO$=1jvd4s98zPO6uXPjKq!!8Gn`%4j%H9!HnT;>x`IaCS;@IByC#`fnJ#KecZ4SIxl&Pq zg?e?K*pp%7vgGCE$iDCGBv}htsqE`RPq?vzBbY8!$p3vTk+O($f6^tNda|1#aACCX zBXN8(q5kA+`sc16EqgcPf5>Q00&b(fwhHw9?;5cWPPFbR#0u0L9q%Id`~V1>x*drC z*yMC1>$+2DPzpdPgLp0WpFGx!9XDF=C=5Sg>pR1WXbO8LvylEmLO-Oy#dH)-7+%YIQeFa{sAQsqnrPUQV|1#2=9${Px=K}uyZ-qAQ+@skL-`{yM8T}V)LdNUHCIt-nzp{J$g$?K$H-|JP&p$y9 z3Or9_vcI^ZV)d@s*)2#3QC_uu6d)B)&H+(!{`$DS2{tUZ504@u-*j=GZ;167`(j8& z+gNg44?1mq#*;g}jX=E(LM@&dmYg)*oOMQ=vVAfO1$F=lz$ASE*R&E;j6}IYRKqmd z*l=Lg%IjTryh6>3d9$cj^E%5AEyN*=P(Q5nm#P$cqTkR`tBj#94fNQ{KBN4cSvpA$ zleHNjY3gY7=K^B=vy>qI z;dzLQWchs~)s-c zNGTbJFBW!otNQ8Ma0YY$`D$S54>+#LsshJoM0Ga z%jxamA1>2ScZv!3-nYBwN$*s!xSSz$;C_*JN%nw}PuaOD(=(JmNTg3^sfh`uyepo5 zuk-YvR0{9NfthH@g$X4v>_!*36g0aFw%IQs#Oc9=BdqB*y-IIMQ;oYqI9-V4MAkTvOqf z#1SdFUT0E<=Kd8SaYj=6JMD_0fCkjAO$&4LAi8@mhlYmQK+@_46bW)hnxA!EXA>GA zsWiTpQzlrt#S4Q0?m!trje{fq^g6uvX&`=5{-2M_m#Q*%sS$yVSKg0N!gWPRUvp$X zhfjHxmz3=I8B@s<^){X9Y!7HDP{={o{V7>IFQYT&tFt5>99#8+>Vb-MbXhw&C-xX2 z+n>BLn!S8{<&J*f-i?ZCthcjOE%?O4ap#r&l6tV5z6O`Ob8~b<(-iI1rixC}2|;Fy zskL$8*RQE&H|G;bwsXc~M0>_(^AoJh-^=A8fv?BHs`se+IiF~xRtMcVEW&ZXGOyy_ zj4CzWYco_!eiAxhe``Zf3#UhRIkmP?srYgK6+1XZ=)#Nx zq~CMBn$>)yZ+|Ff%Q3&R{=?99tAE1bcRjli2kfX)x3OWZXgTLXBFbMk%aJ>vKglaw0&vk4X9zXTfDctiXQT8jBmn7WIUCBFHm-GnAi50!MB1I=tZZ9o1 z`iWU)%Pgt>&GMI(ZbVKEd#oiqT>h3+*f4@|URmN&DG<(TP;PAU{{Ud^aJSoG7mTzW{^W@P!&?O@xQLqi4%oA*jE-{Vw&wCyqYn_-*S@)h?-DIf_yS zfwG>!?Ze4j?XoALaVxCee{5ObC0R4GeUB-FHa3_6yR*xv!EMDac1(Ej_|$fWTDhPQ z)FYWs^ZY6>0rWktA@JQtb>nSJNF}WMRr!w@S^$g;;FR!T-fBAj?FhDS6)3aFeRE4) z(XhK7Pv^gM0^}D(V5<|{2!MF|;Q-JA)M`~LBuIksU4HHv*^JBGln>?Y_BD*__z%qt znc`Xr22kL%F}6U9KkLeU5Q+lReT!9ANa-h8`3uUFaGge@h>-Aq z1dm`wdiJwZVAgLaoMyfx!{%+9iIH{G#w`pVXvTTWS91L}-TCS25AEw95$2lZe)e8dvs{igi@21OK6Qmy?(7&o*s0ZjQOi3ssatQ0jX$Up__^ zt1#;f6Y2V6J4K8hujt}aQer8*@nt=VRk;3SZ}UUluiwM>n8$x`L>+EjB|oea|5edI zVWkjkM$E6^Yeb~{a~}r;LA6Y?Z(I0c7?^kcH5`pOn`_yxj9(*Qg*S3xwWw${WC8*4 zh>VWz0PpJEJ9*gB56xwMK>;$L3Hy`4V1$c+9!CI98iBZpdV&faCrpWeU7?414VENoyVt)kv&_{k?0a*9OPb!wP3x>K!t z(&N#!676rIF}3er-^0S&k8HR%J@_TK106nJI30BC?mt3#A5M61l~m<0ujS?Jcv7DJ z`~7G;R_iS-g6nz;5nK${FKqY$`J1r%8r+DRjB0*O;Y~T5;7AZ$KIdMNKB~R=NopNFW zbYybU2&ywIaqE0`q^n0yMU@RoU&TbUPLU!JI=nnk>);X%O>a){14>c^pBw@K4#A>M z%{nghU4Y3v?Z0Rq_79!j%9E7bOrDDm4sB}!Ti!6}d`fB&AVCFPLl=-!8Ue+;fm%!p+8f zKy`60ZAy>hI_$UHQrfGdyF^HH(>`$3vv>Aqy-q8CYFWL8eR#zqkK@{P=+iyf{MPcY zEgI*Z7B`+k3!66V?Ui?=Y^)(DPjq;zQl_j;zlL4^Rry*HH{$v^VP|9cP(*ZLW25sP zf6&s=XR7UU?ogd@RvDNV>3m&rpwDU?jI618otelEmUen3sllP>=;DTb z&*p~e%SJtGiR9d4%EfbB+(ES&sXMT0&O1XYi1)q~Xw*Q`I8D_=X!J8kioBgnHzB!o zs|xcOn+F?dFh@>r%g8`rx9c;W<0d&Hv=%8Tq;2E0a5I>4J@H~_G^P^uRy#GDWwY|T zoN^uNIq$>)v)VFzHzTxvA-Vc2#%1lU5h-y?ikC*VweI z%|suCmBrZ77r*TtSmX34LWlj`v->)Th`v%Z1@d6M>JZ`W!liCLYaV;d37fL`{lOW(b;_^9A5>pdehp7QbWqD%qgNpUv6wvTy&6ZQdml?Q`ew9# zZHpdMCrmc1JdD*BW5!0^Dc4Dz^QF->WS4*zNTF|OL2VVsHv%1EFdf+=7L`U-zY`-r z*t9NXZYdHd%~nspb#@_ol|}kWS4`1g$4`+L-9Sz-1$>);6@@sgsa0h`D*mo+izr<= zXVDAaSysC4Nu!wI0N^(KuD_Y8ql#qvi3>jA&&zTYS!)+IAqw+DyMDmj0Gg(1%%A&@j( zVch-S-RO@c&;Ks};*#N%9>}pe`uy^cf5A;PJv5z=Q@x68u+Q?UpsVcZV>Js?NuTDx zx=ELlkm+S+-tq6V6FkgfLXoo0b3atG?sSOcGoAW<-nqtp^@_4)r-}X|YD|rdbVJFY zHjB}bdCemrkCSRRjzeEj?=ng3jQ5mr`F7?Jc*Fsmi+0Ja%dcul(4963l1Rj~zBWeu?pS#`n++ z#m2?VlyKbYRC&U`=JCgdy~a%sG&dKc#Ym}JzQSZvhBEPRN;FKcOLzUdP{{~xXp_1G0qY9sSQZK7lhDlD_PdC%Si5kIV z+uSKqN=vp<#C+>psOl^tmx1WHfJNGW-AywdFPbcIuF4r7OZpT`kLegZ#qDuSl^7n< zc<3nhwD}6QllnykQ`_qv_6aJ!aE@#XSD!3p(*CZ*x;m75wMyl8i55ln zFEL{!Qj}7(lVg1FFr`5fH^U})LWJ)2i`08>++Z@7=W_cB?X!nCF$<2NA|1P!OdJDt zepw4`#+YRDuZ7=w>2*5G=7FO_#mmW!2!&ND>OwXYvZ3FoJvdQJ%Vm&O=m4Afeuq@v~&v=(0L)6rzQ#}hfG`4?2@I~9Zz@!>76<)I28@6z0u zrr{tW_E{rQ|A@hAbJt`K_RSSu_gZz7%JLlhK ztE>yU-FJ_EL_`Yi|J-a`TU%E(Fu+VpEA~IKLDysQUU}?65nYF4$AUHFWY9~fX|E|# zh$mO}cge2zdgxB^zFO6SJO0p9S8Ow9zBLvt73m)oKjBAOaG8lpf^$JCKz*cS+|Sb8 z#ecVBCyR;dwG8;k=#QPCr>_HX6XCcQ6I;xHIM6mj>>u}_*~Sb#D*wY zW$|S6{j)SlmZH>N*UpU+rqfaK?89beira;SP&I6E zH}Vr~44G!r@OB1tTuoI;Z+Y{dxhkt6x{vr%@Mb=@pqF;FJ*?Qt-kRjp!j$%BFDdrk zwqZ$38U0Zn(_on$*2n&lnklO0+-*M?Cy2sC2klld=J`y^7V6{|H)KcO|261urKUu@5KGv;Xq(4OIp5 z<+f+*&D>r(!syS@l0aohpT2PvbMg@{YZEFQ-t4X90)OzB#ng@u`}+NPQ)gAQJWj=yoO%x@x-y*X z=C${wAWXaJ6m1pM11gd-quruU?vrPhW`pb_s@Y ztUA7_y4}m$7Yh*95)6{cwvA)M=xiW@YTnsa$_C_?&~c;6fo$3dhBkSZ40Qp^lqi1$ zhoh6kzpLkw3kxGZ+)r57ZP->%z7%tLWd?snt}N#b4S8prA@@A@>|0ny8+nbFcL(w| z66x%;hEYOO!)IB{iJQx9mxcixTM;=II$0 zRBo*wl1dCr5+O}udFfu`Iw#wc!wb39hadla{2=b)dS7OeFU5u-`bJ{mNULy)EcqEm zuiBnrEG6et@_!}2FKm7zr4^pdrq7Naqr>Gla~L$ePx$#c4B>B?DSQmufB;X2ExyPs zxxEd)?;jR+(XO%Hkyq)>*yDEiOw_|t6VNb)T!S6#dSzH}45h#L)kVdK)8n<;q467b zB{HoGEA6h`PTS`^g>e=fW3lR;i$>xf?CcsYSO==a&+Ttf6PpDqHO2-}y~Ml($j#=5I@Y66n};TastaTOe`>tP4BDya;{>SUnmTrgXm@Ya9@=nK z)mFY#VSP+FR>_mz9@j@pSB@MIXy&dGJnU<=uiziJ0RFHIWQ|)~uGFb8+R~S^(Bd24 zNzVP&HIe9DEt@q*qIdr9g{-CTA?13xC{KtBt8xsDpI9mKo_NpNlQk5+wEX#V;L87Z zYVo!yyIMnzH57483F}*cL=K<*S~Y~n8sd)y%zRCs$W742(F*8lb|9SSA}eSehpThq zDYC8@b7JLS{I)-N9XEa+QxHfW7iHI~>iaOthFIPg zdN+E}D_L9FSc*4)!gk|ZtcyL8Is60eKog-6-d7^_^JDnHO@>QjzubLo^3y9D_otVKR7$nKs;!yUs5%;8ZH0X3A$a>&leX%OeM6*o=J@-vpL)Br;Z1e7L&46ik#XuHok>%I;QtdcxNbrV~-|kKC55-Ip_v2V0QWW z5yv=rHR;*Xea_;8H!qEb8_$Ab3?{@oefE?-JlOQdJU>%4t#FwTc=>@d%zImb#%^9$ z$8FguxZuTadoc#5J1REP>sB+|$ z+E%jY@umiZHCV$RJ=+xn7ifYiA1RLdXZ9fb>COiy#6C3PH*c6|$?~}@el1j_kKlXJ zOa9BI+EAd#ugOl=&@ifmtTxMjApLul=PTuV7Kf&;hq$RR z7PaS`x~k1ZVQ3A{;p=UG!Cz^w)GD)hU7@nn%TC9Q@pl&E)imJfxZK{k2h5UtZ58AE zqly5>JJN9Bo0&G|d1}LIgNJ!Vkx- zRKDU7Fh~tM(VC)$NXm06+dN}UINF$+UwX_#(SJmugx<2IG|<%V}C_#2sbDSU93IySGAN zRmmTCA-`eP=Ju2QcbNr6xbaClmYEqsdzX1{a(m!b6SB%zaXpo1!}1!tB!{@u{+AeA zyR?Ym*E7Dix1BqF*-`R_!y01Gks^>#MX4-eZ5Sqp$4D8PP7OW#%D{Z1KTd`y%0=N;F}kGB#shrb8`(&^=^-NljH zpeym!4viYh1Z(UkW&JtlsnUK|$CeZRx9R!3P&N zvgZGu&3hc4%x}fMEZh4|3{0|b!@cIsP+h7oy;z(YuIE^VJDme-?otzFrCniP-hEMe zN&vu6(V#huAJl1qI%vaYU*h-A>i}cB;^{Me9aB@ypc@U1qw5M6FR#hvWf^A2H*c!f zzi`*BkDq-cgabK6O*+}}I|bmillg^4KOxLhMo`p78$M8>I$mf+x4mU)wG+JTt{1_5 ze>7XqEL;G8bN5LVrS2OZHcfKmEK<#|rbP>?|C^P(w^#A5BO$eu47o zQ)Y?LqG&Pvfi%-?LFd^&3xu^Ky|F3#+1^15K|N0lA!ZuixsKbq0MWuKuI}Xa9a@WSMUC1Ya40Qa!~GGsfB-_d_qy zo`v?bpO}*E2VXL}HZ#2qxG8lg1k*(D_2hq?_#XB+KU5_o@gc2w?3iWtDWAKxulgMc zjNgV-h834-eMS!^mX!dBrA--Qv5M3v{jTE7)<}yrYk>$ z)e%dS#a$_7(d|M}L!}?Dw}?n~iJ)-qVWZ$5yIDOmSzI-`dtC+9+iqQlBbsS1u z#|udDfV7j(Xqd-#7Zw*5CTieWzRnaq`s9lIFSltgtR83|qs=jJXMDZ>lGQC;b(~YhZo^8G7c(N86$Igh&sSUI0}Tq|w2J;Y0dD@dg~#5t@4 zX8#2Rj<0J#_Bs%24mwt;Cht|y8V^yz+-qFN^d8xuQ$+B1ftb1!MZd80^ID^kg1r#F z7LgV!l|#}e4SRqR1gI+Dk%3?aIxa>0#U9uDcLqKSHr1fAcKM{x=V+Z&YyTUmRwI}z zr6zjx4+PDg2zv#rt}EUw_nXVdIihU@ifDlXeEda+7$W>Qu-wm1+Cmy7|MOkT zSkD1Pe50V3NoS5$s6et+=FA3ihT*IhoNrkJ=bz5wWIrA^K~xx^zRgR?_%$IObf=cy zGH?3W<%!E;O5j60eEbHGQvkRhm9B0;_y>X?)3*|%`qc>tLd>V5WvQ2?l@4zGa-eMXrC-qFJ+{IxhON{w=X}-3csl6Ytq) z;vsu?SsvGwZsI!7`Unz`vDWnRouI(M^EXfu1SwUc2gXBujK0YLYcaX9qNU+PKrcDS zCo$0wwBo*eTgl(!86*^!Yip6qR+#s*LB4`yiR5Dt>a}e@LGUr+ar3Suu;}gX?{X;` zwp!2l&%QWC8BgNqk=z7$Y{;&Ryu)C^BG;gnwa_3=mOA5BF_(f(1|9R;5$r*p zfjnZy(V2|zJ`>P^p*dK*lUi(bHk>o_4Avvv3tG8qzdq#lJ$!9;u+^VL`t8gY+R;hsQSA>_q?3OFoTY|R2= zVmpouj%y_i_UDW_-{)~oA=3x5f@gc$k%5C$zei^%A)}op^|$RvaZ3FC^-}zMQ^^Yc zA@Oll+YL_GnI&o;q%3_Vui0n2+heVcgN?k4 z-#}bAZ!_d;NjN4aA8v_=^O9e>bhPH78Lf}U(d_#Usjl=2Tw9^Cca{Uc6~7>hG#8$8 z2|Y>l_G<7Rl4n5HJr6$mLm-9R>oSP{6B%ddaRo{_#+R_az9HU}#ixc0k7g=*KD(e; zC%4av?&v|%av$+pk~4Yt?F#Zq+WB8PC;zijeSPA^Ze|QX0gX;(<6hC5#0e@BOUO2! zxI1{j;uUCFXhL`vSzR2G-zmu!Hr!3E5rTFHqu<3`wmD4 zH#x3tC+ogV&W{=hGC3w_9UFJs04-f`QhhX{1FjHza?0;^Nmj^M0We%?m6et8X9%HI z2vl9Rvz;qwl=S z4_~C7;5w@-WfX1bun3~;^JjjlEs6f#1q%TNucJ{Bpj`F$=Lbr@3eIsL=3xNj0ZX*) zr&solw`cOVB@~eeh+RUnzP=+e2Ol4=P)xJBO45(k!gYlyfUw9KIW@liHCj>8-DG}H z_=$#)dt8YEsY02;y>}ZT0(+Il_WnnE+D+DmtL;T%NKVSj^JV3HYNlj2E72f=Q-Wo*4CEw@87_~=p6@|+U~^Jo&d=pOhPxu(H*b7pI;H%nq7VJ%{wGX z0RzIffjSY_S42|tibIwWj*f4EzdyEtb>tdM+cXy?z|BqSsgaLtj?D6S^8NT1K7Qp{ zOffq}oJ}|SWko;TtA8joQk%jOjlWT@}3 zludTgS|^zq8Fz&f;4m&oJBuG$%a;_r_RReHrsU;C#W%Cg+^EbGuf3)I1$v#%v)z)O zHME27?s9p(sL)q0ns;;s+EO~nvvk`WdA+l$EjMy)zc@Ok0Wex})x?DpepKoKA9M+d z7U(C~P@&0tYwXtSDA$^y>Lp5O#i1q33{jU+d%v?~LvG9e^_j&0PX?5o3aqyL=^6Ew z{r|N9e;k1fU$}$jE%u6%B2(7#@^ZPNsKPx3@@G`{(iUyF-@FLsd?MPD;cLbhUZo}A zdr5Xf&mL1C8BIyc{8aDpGpqXz>M>?;ZdT7!DSJpQdU82`^Abcax=% zzN=QC#<2CHHkB&$5KsD4U0vGgaBubrZjMcjV$SPV-H<8I!+hdN8a{?JbdjIT4-Gze zU>n?Xw--c%Gl6k{_!>UM>3pJw_$W|qh<0}95Bx#9wB}0N$B#E%1aRQIn|O?{iu?8W zN{s^;b2Pq&GI~iY&QlJw`~F8ezurtEp9L)Y5qJOC$SQI&;>aEnt|S!w{2HMIVUd2` zmyF!j6=76o<*US|GXcd)V9Ld1QG5X=0l>XwR$BmRr4b-U^FP}CiBx=79e=pkc_1V^ z-7UIT7P32X#3|Q!XR{TVFC#|M%5rt9X$S_AB!$!CeHBZHUM8M3waneg$N{+)3K*Yh zS2mCz+fdu#0PU^O`0*CH&tz%ovp!xayt-9qeR)Hss%^lg?^N&++Lzj6A!5{m>}$lY z_iqH(KeelrKefUVBKx$vcmJs=adl08exFta*eXE6E1rl3c>X-5U1D~|goT^#K!bPv zK*5HCicH(})?>FfZDJ_t;`U$L$lIn<_@b=j3_ggG0l_+@}oh*Ya zCVVQJ-|Y=lm3zK`3kld;R8s}6jp9I1ImVu@xVt-8_Y!FqUs7@p^q=bCe8%IloqO!x z!;d<=zDN$>GF`wnZD-RducFCKD51%T_6J}|e7#`Cq9lin2q{&tpfqbTMC0PU-So4s zp{Ao7TUaUtL}@7)@aFULE0E#U{h+=>cRh!kJc5h+9wvTtgvsc0hoiKfSHcO=n9+Fs zTjS~wI5W71+%ZDKwnGoY;x3Uv-qR^NhN_TCg?5j=lQA+gn-7xe6Lm9 zt-pVzv>;F~mkg|XzAe3F!>{`R8A3ZcQJloqu+ESg*4ohtUxlipbBLg&we{4F6&m~X z;9m4J!0IRuimZI(%c7xZUg51*iYTK^(i5t~zF%=M4(Ng&MwkGfGXiRERJ&S%idw!m zgm%{va)4+rntLiTCQluO1rLiCw!yP@PEgbGK5poZgy2)u2y1J ze3LKMk9skjp}|YOsr}FjBLlyG`wTQ}0p~F_?Tm(wPGfnP@~-*_@H0J)eB8jLgSfQJ z@suw9&|`9Xim&J67c9d2YdYtZEwajxFEuV)o1^^3>V?q$psFvId3`NNSVuRq!xN(7L0uliP78LEd%OjJ#;aN;s#ZY*D(Qlw*t_-K!36isMUJ$ zVnjA;{+sUe7YR9QuZ9%na9za^ltIt=TRo)o){y?CH8s2GHild@3APWQ>n&N|HhT$b zNIrXb;5p}bBh^8a<(NyNdF2nN9cW?`a~_RYd%?_s$pP4F@v95qfC#NJz~4nJJpDFX z=Ha$m&o7=5QryP+hmQtEG?^JcI>hwzYp=c0(WB!~e(G0`$g8j>jf8emALo4R6u-z_ z6LbF2?moRTD*W|G5UO;To}5f>DynO0@{EAq6fUVL|J2Bt^pj!?-xV!ME;KZvhPQfk zFZ-e}Wz#OPZ|9LlxU=V{BzG#avuEgL+mQ3LA1IPuCOd52`wn^{=7#9K zK(U^y&c6w~Q%s%mG5_4f_QlEZH$_LF7fX(6x`G8Ze5>3BO4n6|-}@|%gGEiwiS@}q z=pzTEtKSp+Bfby$or8}<2TX4Od&3x@vMV0>CH?#N@(zQ4hr^0*$1va=1au`^f|� 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

cqx*<6dH zJ(c5NE@sL{4i;W8O=A2Yl-d>>Z~Yc>cV|}T6rG>F79T^i4pMTQu$|Uh|lTE3qE^7vzg_660%DM$jJYfRY2;F7ozM z6a@}`Tf-WBV#tph@``Pu`DpqOnn2Y8_rk+Beq_@|A0kcLGp7aWBbAzpN4^uN-JBuO z0{h(a89F}SO(1PmW|syB9YK#O=ZOo6T~L!Vo$!t7jmHx=R)OM{ISKxCfWEE4i&Ox| z?jE!ZQv%+Xlo3(63yU(eo&mLgKiYwuNuD?q9!t5J3DfLi+JkIn;U>D%2zRr+Yys1! z#_AKf=c;I`uQ_K3>2ATwy$UM1Hy8|t)nsaVx)g-0Kta!j3Udmp(T*$3Cq8yVDE9hB z8j0WWXFtm>te0uZ#qIcEUj<7ies{vat3Ig_i0l~#0&O2}pKc#i-F{G2z3lGi`*ZV0 z4xzPGz4^O6w_S_iUNM#l&u)%k|H6D)I>AVXWEV^DE@RFd1#Wxg(JN|qn zdl*tf#*Fm(JWZ*2Keo%r$VlU^SRf^p!S!nll>G{X<;qZjifESea@WFRzt3OHNKN1B zS0;5T^NTT4oeinJ=sm!cALM4yy>&HlA0&6Qw^!@Dd<_vDS_nk<{{nW$ET#Q%aq6$N zC-UW_VjWiMZzcb)wX2Ov@`~eN5KK|QS}GETHdDjUa>mqD2*FvA(T*j{k!gYo%RSHAz4y6q z|KIQTfBu5L*S_#DSKMY*vlB`IO^53M-HL>wYtPrT4b7iv?k8tbx;@enw3u`R@wVhC zlC~8$BUVps1N*orVb#r&T}jDW^x_Cg(-B@U_9xu%MfO+=lr-D5_79TbsAYJ-t_9eg zLBL9VV$6>PatnMM>RTGX4TaE(!YyO{G|NWr$lfJ4HbV>=_`VtwI&3A{nC)-M=(povtg_MW!X#4!5 zsJvwxTWeUc)v{Q6gN-2DxX|iI0(lf9({ilkr&0oY)8O6wQGAA>--|Pcoidgm$DX;w zTWPAW>?w$`e<+wFr+z}FEU~xNTDvkSaBf|qhNz0e)g@hu5#-k8NH?abIt&K!* zcyA0#`89Z5kEoEPd@LuIPvDY9=cjpB&c+RAZ%dATRl~V=a>lJn?Y5M|_X%Kk!4Ck@ zM!}V1UQv`Akn4i6nB_`fH7-H)W^s?ZI(?=80T~hg*0|3*l4Z#u9$ORtXHti_WNuDj zdiE*eLxJHNK_+em2*BF_WI_bncOrN*JQ}13vv#2D{OWt2r!3WAMVRqoq|3q94_(j+ ztnBjL?$vQ4TMU@9sG?%Dc7x{qY?lu#d~xvud<*f(I0r!Xpj~UFfi0S*n0is7#p1Vq7Zz_vq)p4>$Esr>JL ziMuc+yjHgM1v1vOK>XR+L!Wv&_4{yqeg)b8mQpwwWY!B0JIYLe!#4*63x{Xn~+lLzG6(8|H(Ds+f3A&M= zTXpOj*$dYV3{J*xyH8B)y+h}(TkrP@Nz}1PL8#aPe$<)7K5x*yu&+-Wx|!Ky`7EdRY~MMuc`sptsma#PQG)cfX2ztXVtn~=<4 zpgJhdlEZIF*335!OYrKcn+B13>eM>1Ne<%7V5hk|T7Inh5Z*Oeb+>L&;=$T{D>w7s b2{IEoaIq|nv-z+T0*-H^nY2sPo%#O&M&HP7 literal 0 HcmV?d00001 diff --git a/vendor/github.com/fulldump/goconfig/logo.png b/vendor/github.com/fulldump/goconfig/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..421163455867f8745f8c67954dc0e62628612c5d GIT binary patch literal 16031 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 Date: Sat, 14 May 2022 04:20:51 +0200 Subject: [PATCH 016/163] feat: implement delete collection --- api/0_build.go | 1 + api/deleteCollection.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 api/deleteCollection.go diff --git a/api/0_build.go b/api/0_build.go index 602ae14..5a4a003 100644 --- a/api/0_build.go +++ b/api/0_build.go @@ -24,6 +24,7 @@ func Build(db *database.Database, dataDir string, staticsDir string) *box.B { // b.Resource("collections/{collection_name}"). WithActions( box.Get(listItems(collections)), + box.Delete(deleteCollection(collections)), box.Post(insertItem(collections)), ) diff --git a/api/deleteCollection.go b/api/deleteCollection.go new file mode 100644 index 0000000..30d32c0 --- /dev/null +++ b/api/deleteCollection.go @@ -0,0 +1,29 @@ +package api + +import ( + "context" + "fmt" + "inceptiondb/collection" + "net/http" +) + +func deleteCollection(collections map[string]*collection.Collection) interface{} { + return func(ctx context.Context, w http.ResponseWriter) error { + + collectionName := getParam(ctx, "collection_name") + + collection, found := collections[collectionName] + if !found { + return fmt.Errorf("collection '%s' not found", collectionName) + } + + err := collection.Drop() + if err != nil { + return err + } + + delete(collections, collectionName) + + return nil + } +} From 00e430fda22bc0d03ce367960b449f2af9b6b934 Mon Sep 17 00:00:00 2001 From: fulldump Date: Sat, 14 May 2022 04:21:14 +0200 Subject: [PATCH 017/163] fix: createCollection (avoid name duplication) --- api/createCollection.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/api/createCollection.go b/api/createCollection.go index e47b4ef..8959c02 100644 --- a/api/createCollection.go +++ b/api/createCollection.go @@ -1,6 +1,8 @@ package api import ( + "fmt" + "net/http" "path" "inceptiondb/collection" @@ -11,7 +13,13 @@ type createCollectionRequest struct { } func createCollection(collections map[string]*collection.Collection, dir string) interface{} { - return func(input *createCollectionRequest) (*createCollectionRequest, error) { + return func(w http.ResponseWriter, input *createCollectionRequest) (*createCollectionRequest, error) { + + _, exist := collections[input.Name] + if exist { + w.WriteHeader(http.StatusConflict) + return nil, fmt.Errorf("collection '%s' already exists", input.Name) + } filename := path.Join(dir, input.Name) From 2bddb11486c711fc306ed4588d19950e007c6705 Mon Sep 17 00:00:00 2001 From: fulldump Date: Sat, 14 May 2022 04:21:33 +0200 Subject: [PATCH 018/163] feat: WIP working UI --- statics/www/index.html | 180 +- statics/www/lib/axios.js | 1603 +++++ statics/www/lib/axios.min.js | 8 + statics/www/lib/vue-router.js | 2626 +++++++ statics/www/lib/vue.js | 11855 ++++++++++++++++++++++++++++++++ statics/www/style.css | 40 + 6 files changed, 16311 insertions(+), 1 deletion(-) create mode 100644 statics/www/lib/axios.js create mode 100644 statics/www/lib/axios.min.js create mode 100644 statics/www/lib/vue-router.js create mode 100644 statics/www/lib/vue.js create mode 100644 statics/www/style.css diff --git a/statics/www/index.html b/statics/www/index.html index 8d26ffd..24adeb4 100644 --- a/statics/www/index.html +++ b/statics/www/index.html @@ -1 +1,179 @@ -

Welcome to InceptionDB

\ No newline at end of file + + + + InceptionDB - αlpha + + + + + +
+
+

Welcome to InceptionDB

+
+
+
+

Collections

+ + New collection +
+
+

{{ collection_name }}

+ +
+ + + + +
{{ row }}
+
+ +
+ Find by index + + : +
+
{{ item }}
+ +
+ Patch: + + +
+
+ +
+ Press the following button to delete the collection: + +
+ +
+
+
+ + + + + + + + diff --git a/statics/www/lib/axios.js b/statics/www/lib/axios.js new file mode 100644 index 0000000..c4401c4 --- /dev/null +++ b/statics/www/lib/axios.js @@ -0,0 +1,1603 @@ +/* axios v0.18.0 | (c) 2018 by Matt Zabriskie */ +(function webpackUniversalModuleDefinition(root, factory) { + if(typeof exports === 'object' && typeof module === 'object') + module.exports = factory(); + else if(typeof define === 'function' && define.amd) + define([], factory); + else if(typeof exports === 'object') + exports["axios"] = factory(); + else + root["axios"] = factory(); +})(this, function() { +return /******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) +/******/ return installedModules[moduleId].exports; +/******/ +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ exports: {}, +/******/ id: moduleId, +/******/ loaded: false +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.loaded = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(0); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ (function(module, exports, __webpack_require__) { + + module.exports = __webpack_require__(1); + +/***/ }), +/* 1 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var utils = __webpack_require__(2); + var bind = __webpack_require__(3); + var Axios = __webpack_require__(5); + var defaults = __webpack_require__(6); + + /** + * Create an instance of Axios + * + * @param {Object} defaultConfig The default config for the instance + * @return {Axios} A new instance of Axios + */ + function createInstance(defaultConfig) { + var context = new Axios(defaultConfig); + var instance = bind(Axios.prototype.request, context); + + // Copy axios.prototype to instance + utils.extend(instance, Axios.prototype, context); + + // Copy context to instance + utils.extend(instance, context); + + return instance; + } + + // Create the default instance to be exported + var axios = createInstance(defaults); + + // Expose Axios class to allow class inheritance + axios.Axios = Axios; + + // Factory for creating new instances + axios.create = function create(instanceConfig) { + return createInstance(utils.merge(defaults, instanceConfig)); + }; + + // Expose Cancel & CancelToken + axios.Cancel = __webpack_require__(23); + axios.CancelToken = __webpack_require__(24); + axios.isCancel = __webpack_require__(20); + + // Expose all/spread + axios.all = function all(promises) { + return Promise.all(promises); + }; + axios.spread = __webpack_require__(25); + + module.exports = axios; + + // Allow use of default import syntax in TypeScript + module.exports.default = axios; + + +/***/ }), +/* 2 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var bind = __webpack_require__(3); + var isBuffer = __webpack_require__(4); + + /*global toString:true*/ + + // utils is a library of generic helper functions non-specific to axios + + var toString = Object.prototype.toString; + + /** + * Determine if a value is an Array + * + * @param {Object} val The value to test + * @returns {boolean} True if value is an Array, otherwise false + */ + function isArray(val) { + return toString.call(val) === '[object Array]'; + } + + /** + * Determine if a value is an ArrayBuffer + * + * @param {Object} val The value to test + * @returns {boolean} True if value is an ArrayBuffer, otherwise false + */ + function isArrayBuffer(val) { + return toString.call(val) === '[object ArrayBuffer]'; + } + + /** + * Determine if a value is a FormData + * + * @param {Object} val The value to test + * @returns {boolean} True if value is an FormData, otherwise false + */ + function isFormData(val) { + return (typeof FormData !== 'undefined') && (val instanceof FormData); + } + + /** + * Determine if a value is a view on an ArrayBuffer + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a view on an ArrayBuffer, otherwise false + */ + function isArrayBufferView(val) { + var result; + if ((typeof ArrayBuffer !== 'undefined') && (ArrayBuffer.isView)) { + result = ArrayBuffer.isView(val); + } else { + result = (val) && (val.buffer) && (val.buffer instanceof ArrayBuffer); + } + return result; + } + + /** + * Determine if a value is a String + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a String, otherwise false + */ + function isString(val) { + return typeof val === 'string'; + } + + /** + * Determine if a value is a Number + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a Number, otherwise false + */ + function isNumber(val) { + return typeof val === 'number'; + } + + /** + * Determine if a value is undefined + * + * @param {Object} val The value to test + * @returns {boolean} True if the value is undefined, otherwise false + */ + function isUndefined(val) { + return typeof val === 'undefined'; + } + + /** + * Determine if a value is an Object + * + * @param {Object} val The value to test + * @returns {boolean} True if value is an Object, otherwise false + */ + function isObject(val) { + return val !== null && typeof val === 'object'; + } + + /** + * Determine if a value is a Date + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a Date, otherwise false + */ + function isDate(val) { + return toString.call(val) === '[object Date]'; + } + + /** + * Determine if a value is a File + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a File, otherwise false + */ + function isFile(val) { + return toString.call(val) === '[object File]'; + } + + /** + * Determine if a value is a Blob + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a Blob, otherwise false + */ + function isBlob(val) { + return toString.call(val) === '[object Blob]'; + } + + /** + * Determine if a value is a Function + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a Function, otherwise false + */ + function isFunction(val) { + return toString.call(val) === '[object Function]'; + } + + /** + * Determine if a value is a Stream + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a Stream, otherwise false + */ + function isStream(val) { + return isObject(val) && isFunction(val.pipe); + } + + /** + * Determine if a value is a URLSearchParams object + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a URLSearchParams object, otherwise false + */ + function isURLSearchParams(val) { + return typeof URLSearchParams !== 'undefined' && val instanceof URLSearchParams; + } + + /** + * Trim excess whitespace off the beginning and end of a string + * + * @param {String} str The String to trim + * @returns {String} The String freed of excess whitespace + */ + function trim(str) { + return str.replace(/^\s*/, '').replace(/\s*$/, ''); + } + + /** + * Determine if we're running in a standard browser environment + * + * This allows axios to run in a web worker, and react-native. + * Both environments support XMLHttpRequest, but not fully standard globals. + * + * web workers: + * typeof window -> undefined + * typeof document -> undefined + * + * react-native: + * navigator.product -> 'ReactNative' + */ + function isStandardBrowserEnv() { + if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') { + return false; + } + return ( + typeof window !== 'undefined' && + typeof document !== 'undefined' + ); + } + + /** + * Iterate over an Array or an Object invoking a function for each item. + * + * If `obj` is an Array callback will be called passing + * the value, index, and complete array for each item. + * + * If 'obj' is an Object callback will be called passing + * the value, key, and complete object for each property. + * + * @param {Object|Array} obj The object to iterate + * @param {Function} fn The callback to invoke for each item + */ + function forEach(obj, fn) { + // Don't bother if no value provided + if (obj === null || typeof obj === 'undefined') { + return; + } + + // Force an array if not already something iterable + if (typeof obj !== 'object') { + /*eslint no-param-reassign:0*/ + obj = [obj]; + } + + if (isArray(obj)) { + // Iterate over array values + for (var i = 0, l = obj.length; i < l; i++) { + fn.call(null, obj[i], i, obj); + } + } else { + // Iterate over object keys + for (var key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + fn.call(null, obj[key], key, obj); + } + } + } + } + + /** + * Accepts varargs expecting each argument to be an object, then + * immutably merges the properties of each object and returns result. + * + * When multiple objects contain the same key the later object in + * the arguments list will take precedence. + * + * Example: + * + * ```js + * var result = merge({foo: 123}, {foo: 456}); + * console.log(result.foo); // outputs 456 + * ``` + * + * @param {Object} obj1 Object to merge + * @returns {Object} Result of all merge properties + */ + function merge(/* obj1, obj2, obj3, ... */) { + var result = {}; + function assignValue(val, key) { + if (typeof result[key] === 'object' && typeof val === 'object') { + result[key] = merge(result[key], val); + } else { + result[key] = val; + } + } + + for (var i = 0, l = arguments.length; i < l; i++) { + forEach(arguments[i], assignValue); + } + return result; + } + + /** + * Extends object a by mutably adding to it the properties of object b. + * + * @param {Object} a The object to be extended + * @param {Object} b The object to copy properties from + * @param {Object} thisArg The object to bind function to + * @return {Object} The resulting value of object a + */ + function extend(a, b, thisArg) { + forEach(b, function assignValue(val, key) { + if (thisArg && typeof val === 'function') { + a[key] = bind(val, thisArg); + } else { + a[key] = val; + } + }); + return a; + } + + module.exports = { + isArray: isArray, + isArrayBuffer: isArrayBuffer, + isBuffer: isBuffer, + isFormData: isFormData, + isArrayBufferView: isArrayBufferView, + isString: isString, + isNumber: isNumber, + isObject: isObject, + isUndefined: isUndefined, + isDate: isDate, + isFile: isFile, + isBlob: isBlob, + isFunction: isFunction, + isStream: isStream, + isURLSearchParams: isURLSearchParams, + isStandardBrowserEnv: isStandardBrowserEnv, + forEach: forEach, + merge: merge, + extend: extend, + trim: trim + }; + + +/***/ }), +/* 3 */ +/***/ (function(module, exports) { + + 'use strict'; + + module.exports = function bind(fn, thisArg) { + return function wrap() { + var args = new Array(arguments.length); + for (var i = 0; i < args.length; i++) { + args[i] = arguments[i]; + } + return fn.apply(thisArg, args); + }; + }; + + +/***/ }), +/* 4 */ +/***/ (function(module, exports) { + + /*! + * Determine if an object is a Buffer + * + * @author Feross Aboukhadijeh + * @license MIT + */ + + // The _isBuffer check is for Safari 5-7 support, because it's missing + // Object.prototype.constructor. Remove this eventually + module.exports = function (obj) { + return obj != null && (isBuffer(obj) || isSlowBuffer(obj) || !!obj._isBuffer) + } + + function isBuffer (obj) { + return !!obj.constructor && typeof obj.constructor.isBuffer === 'function' && obj.constructor.isBuffer(obj) + } + + // For Node v0.10 support. Remove this eventually. + function isSlowBuffer (obj) { + return typeof obj.readFloatLE === 'function' && typeof obj.slice === 'function' && isBuffer(obj.slice(0, 0)) + } + + +/***/ }), +/* 5 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var defaults = __webpack_require__(6); + var utils = __webpack_require__(2); + var InterceptorManager = __webpack_require__(17); + var dispatchRequest = __webpack_require__(18); + + /** + * Create a new instance of Axios + * + * @param {Object} instanceConfig The default config for the instance + */ + function Axios(instanceConfig) { + this.defaults = instanceConfig; + this.interceptors = { + request: new InterceptorManager(), + response: new InterceptorManager() + }; + } + + /** + * Dispatch a request + * + * @param {Object} config The config specific for this request (merged with this.defaults) + */ + Axios.prototype.request = function request(config) { + /*eslint no-param-reassign:0*/ + // Allow for axios('example/url'[, config]) a la fetch API + if (typeof config === 'string') { + config = utils.merge({ + url: arguments[0] + }, arguments[1]); + } + + config = utils.merge(defaults, {method: 'get'}, this.defaults, config); + config.method = config.method.toLowerCase(); + + // Hook up interceptors middleware + var chain = [dispatchRequest, undefined]; + var promise = Promise.resolve(config); + + this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { + chain.unshift(interceptor.fulfilled, interceptor.rejected); + }); + + this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) { + chain.push(interceptor.fulfilled, interceptor.rejected); + }); + + while (chain.length) { + promise = promise.then(chain.shift(), chain.shift()); + } + + return promise; + }; + + // Provide aliases for supported request methods + utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) { + /*eslint func-names:0*/ + Axios.prototype[method] = function(url, config) { + return this.request(utils.merge(config || {}, { + method: method, + url: url + })); + }; + }); + + utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) { + /*eslint func-names:0*/ + Axios.prototype[method] = function(url, data, config) { + return this.request(utils.merge(config || {}, { + method: method, + url: url, + data: data + })); + }; + }); + + module.exports = Axios; + + +/***/ }), +/* 6 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var utils = __webpack_require__(2); + var normalizeHeaderName = __webpack_require__(7); + + var DEFAULT_CONTENT_TYPE = { + 'Content-Type': 'application/x-www-form-urlencoded' + }; + + function setContentTypeIfUnset(headers, value) { + if (!utils.isUndefined(headers) && utils.isUndefined(headers['Content-Type'])) { + headers['Content-Type'] = value; + } + } + + function getDefaultAdapter() { + var adapter; + if (typeof XMLHttpRequest !== 'undefined') { + // For browsers use XHR adapter + adapter = __webpack_require__(8); + } else if (typeof process !== 'undefined') { + // For node use HTTP adapter + adapter = __webpack_require__(8); + } + return adapter; + } + + var defaults = { + adapter: getDefaultAdapter(), + + transformRequest: [function transformRequest(data, headers) { + normalizeHeaderName(headers, 'Content-Type'); + if (utils.isFormData(data) || + utils.isArrayBuffer(data) || + utils.isBuffer(data) || + utils.isStream(data) || + utils.isFile(data) || + utils.isBlob(data) + ) { + return data; + } + if (utils.isArrayBufferView(data)) { + return data.buffer; + } + if (utils.isURLSearchParams(data)) { + setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8'); + return data.toString(); + } + if (utils.isObject(data)) { + setContentTypeIfUnset(headers, 'application/json;charset=utf-8'); + return JSON.stringify(data); + } + return data; + }], + + transformResponse: [function transformResponse(data) { + /*eslint no-param-reassign:0*/ + if (typeof data === 'string') { + try { + data = JSON.parse(data); + } catch (e) { /* Ignore */ } + } + return data; + }], + + /** + * A timeout in milliseconds to abort a request. If set to 0 (default) a + * timeout is not created. + */ + timeout: 0, + + xsrfCookieName: 'XSRF-TOKEN', + xsrfHeaderName: 'X-XSRF-TOKEN', + + maxContentLength: -1, + + validateStatus: function validateStatus(status) { + return status >= 200 && status < 300; + } + }; + + defaults.headers = { + common: { + 'Accept': 'application/json, text/plain, */*' + } + }; + + utils.forEach(['delete', 'get', 'head'], function forEachMethodNoData(method) { + defaults.headers[method] = {}; + }); + + utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) { + defaults.headers[method] = utils.merge(DEFAULT_CONTENT_TYPE); + }); + + module.exports = defaults; + + +/***/ }), +/* 7 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var utils = __webpack_require__(2); + + module.exports = function normalizeHeaderName(headers, normalizedName) { + utils.forEach(headers, function processHeader(value, name) { + if (name !== normalizedName && name.toUpperCase() === normalizedName.toUpperCase()) { + headers[normalizedName] = value; + delete headers[name]; + } + }); + }; + + +/***/ }), +/* 8 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var utils = __webpack_require__(2); + var settle = __webpack_require__(9); + var buildURL = __webpack_require__(12); + var parseHeaders = __webpack_require__(13); + var isURLSameOrigin = __webpack_require__(14); + var createError = __webpack_require__(10); + var btoa = (typeof window !== 'undefined' && window.btoa && window.btoa.bind(window)) || __webpack_require__(15); + + module.exports = function xhrAdapter(config) { + return new Promise(function dispatchXhrRequest(resolve, reject) { + var requestData = config.data; + var requestHeaders = config.headers; + + if (utils.isFormData(requestData)) { + delete requestHeaders['Content-Type']; // Let the browser set it + } + + var request = new XMLHttpRequest(); + var loadEvent = 'onreadystatechange'; + var xDomain = false; + + // For IE 8/9 CORS support + // Only supports POST and GET calls and doesn't returns the response headers. + // DON'T do this for testing b/c XMLHttpRequest is mocked, not XDomainRequest. + if (("production") !== 'test' && + typeof window !== 'undefined' && + window.XDomainRequest && !('withCredentials' in request) && + !isURLSameOrigin(config.url)) { + request = new window.XDomainRequest(); + loadEvent = 'onload'; + xDomain = true; + request.onprogress = function handleProgress() {}; + request.ontimeout = function handleTimeout() {}; + } + + // HTTP basic authentication + if (config.auth) { + var username = config.auth.username || ''; + var password = config.auth.password || ''; + requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password); + } + + request.open(config.method.toUpperCase(), buildURL(config.url, config.params, config.paramsSerializer), true); + + // Set the request timeout in MS + request.timeout = config.timeout; + + // Listen for ready state + request[loadEvent] = function handleLoad() { + if (!request || (request.readyState !== 4 && !xDomain)) { + return; + } + + // The request errored out and we didn't get a response, this will be + // handled by onerror instead + // With one exception: request that using file: protocol, most browsers + // will return status as 0 even though it's a successful request + if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) { + return; + } + + // Prepare the response + var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null; + var responseData = !config.responseType || config.responseType === 'text' ? request.responseText : request.response; + var response = { + data: responseData, + // IE sends 1223 instead of 204 (https://github.com/axios/axios/issues/201) + status: request.status === 1223 ? 204 : request.status, + statusText: request.status === 1223 ? 'No Content' : request.statusText, + headers: responseHeaders, + config: config, + request: request + }; + + settle(resolve, reject, response); + + // Clean up request + request = null; + }; + + // Handle low level network errors + request.onerror = function handleError() { + // Real errors are hidden from us by the browser + // onerror should only fire if it's a network error + reject(createError('Network Error', config, null, request)); + + // Clean up request + request = null; + }; + + // Handle timeout + request.ontimeout = function handleTimeout() { + reject(createError('timeout of ' + config.timeout + 'ms exceeded', config, 'ECONNABORTED', + request)); + + // Clean up request + request = null; + }; + + // Add xsrf header + // This is only done if running in a standard browser environment. + // Specifically not if we're in a web worker, or react-native. + if (utils.isStandardBrowserEnv()) { + var cookies = __webpack_require__(16); + + // Add xsrf header + var xsrfValue = (config.withCredentials || isURLSameOrigin(config.url)) && config.xsrfCookieName ? + cookies.read(config.xsrfCookieName) : + undefined; + + if (xsrfValue) { + requestHeaders[config.xsrfHeaderName] = xsrfValue; + } + } + + // Add headers to the request + if ('setRequestHeader' in request) { + utils.forEach(requestHeaders, function setRequestHeader(val, key) { + if (typeof requestData === 'undefined' && key.toLowerCase() === 'content-type') { + // Remove Content-Type if upload is undefined + delete requestHeaders[key]; + } else { + // Otherwise add header to the request + request.setRequestHeader(key, val); + } + }); + } + + // Add withCredentials to request if needed + if (config.withCredentials) { + request.withCredentials = true; + } + + // Add responseType to request if needed + if (config.responseType) { + try { + request.responseType = config.responseType; + } catch (e) { + // Expected DOMException thrown by browsers not compatible XMLHttpRequest Level 2. + // But, this can be suppressed for 'json' type as it can be parsed by default 'transformResponse' function. + if (config.responseType !== 'json') { + throw e; + } + } + } + + // Handle progress if needed + if (typeof config.onDownloadProgress === 'function') { + request.addEventListener('progress', config.onDownloadProgress); + } + + // Not all browsers support upload events + if (typeof config.onUploadProgress === 'function' && request.upload) { + request.upload.addEventListener('progress', config.onUploadProgress); + } + + if (config.cancelToken) { + // Handle cancellation + config.cancelToken.promise.then(function onCanceled(cancel) { + if (!request) { + return; + } + + request.abort(); + reject(cancel); + // Clean up request + request = null; + }); + } + + if (requestData === undefined) { + requestData = null; + } + + // Send the request + request.send(requestData); + }); + }; + + +/***/ }), +/* 9 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var createError = __webpack_require__(10); + + /** + * Resolve or reject a Promise based on response status. + * + * @param {Function} resolve A function that resolves the promise. + * @param {Function} reject A function that rejects the promise. + * @param {object} response The response. + */ + module.exports = function settle(resolve, reject, response) { + var validateStatus = response.config.validateStatus; + // Note: status is not exposed by XDomainRequest + if (!response.status || !validateStatus || validateStatus(response.status)) { + resolve(response); + } else { + reject(createError( + 'Request failed with status code ' + response.status, + response.config, + null, + response.request, + response + )); + } + }; + + +/***/ }), +/* 10 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var enhanceError = __webpack_require__(11); + + /** + * Create an Error with the specified message, config, error code, request and response. + * + * @param {string} message The error message. + * @param {Object} config The config. + * @param {string} [code] The error code (for example, 'ECONNABORTED'). + * @param {Object} [request] The request. + * @param {Object} [response] The response. + * @returns {Error} The created error. + */ + module.exports = function createError(message, config, code, request, response) { + var error = new Error(message); + return enhanceError(error, config, code, request, response); + }; + + +/***/ }), +/* 11 */ +/***/ (function(module, exports) { + + 'use strict'; + + /** + * Update an Error with the specified config, error code, and response. + * + * @param {Error} error The error to update. + * @param {Object} config The config. + * @param {string} [code] The error code (for example, 'ECONNABORTED'). + * @param {Object} [request] The request. + * @param {Object} [response] The response. + * @returns {Error} The error. + */ + module.exports = function enhanceError(error, config, code, request, response) { + error.config = config; + if (code) { + error.code = code; + } + error.request = request; + error.response = response; + return error; + }; + + +/***/ }), +/* 12 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var utils = __webpack_require__(2); + + function encode(val) { + return encodeURIComponent(val). + replace(/%40/gi, '@'). + replace(/%3A/gi, ':'). + replace(/%24/g, '$'). + replace(/%2C/gi, ','). + replace(/%20/g, '+'). + replace(/%5B/gi, '['). + replace(/%5D/gi, ']'); + } + + /** + * Build a URL by appending params to the end + * + * @param {string} url The base of the url (e.g., http://www.google.com) + * @param {object} [params] The params to be appended + * @returns {string} The formatted url + */ + module.exports = function buildURL(url, params, paramsSerializer) { + /*eslint no-param-reassign:0*/ + if (!params) { + return url; + } + + var serializedParams; + if (paramsSerializer) { + serializedParams = paramsSerializer(params); + } else if (utils.isURLSearchParams(params)) { + serializedParams = params.toString(); + } else { + var parts = []; + + utils.forEach(params, function serialize(val, key) { + if (val === null || typeof val === 'undefined') { + return; + } + + if (utils.isArray(val)) { + key = key + '[]'; + } else { + val = [val]; + } + + utils.forEach(val, function parseValue(v) { + if (utils.isDate(v)) { + v = v.toISOString(); + } else if (utils.isObject(v)) { + v = JSON.stringify(v); + } + parts.push(encode(key) + '=' + encode(v)); + }); + }); + + serializedParams = parts.join('&'); + } + + if (serializedParams) { + url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams; + } + + return url; + }; + + +/***/ }), +/* 13 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var utils = __webpack_require__(2); + + // Headers whose duplicates are ignored by node + // c.f. https://nodejs.org/api/http.html#http_message_headers + var ignoreDuplicateOf = [ + 'age', 'authorization', 'content-length', 'content-type', 'etag', + 'expires', 'from', 'host', 'if-modified-since', 'if-unmodified-since', + 'last-modified', 'location', 'max-forwards', 'proxy-authorization', + 'referer', 'retry-after', 'user-agent' + ]; + + /** + * Parse headers into an object + * + * ``` + * Date: Wed, 27 Aug 2014 08:58:49 GMT + * Content-Type: application/json + * Connection: keep-alive + * Transfer-Encoding: chunked + * ``` + * + * @param {String} headers Headers needing to be parsed + * @returns {Object} Headers parsed into an object + */ + module.exports = function parseHeaders(headers) { + var parsed = {}; + var key; + var val; + var i; + + if (!headers) { return parsed; } + + utils.forEach(headers.split('\n'), function parser(line) { + i = line.indexOf(':'); + key = utils.trim(line.substr(0, i)).toLowerCase(); + val = utils.trim(line.substr(i + 1)); + + if (key) { + if (parsed[key] && ignoreDuplicateOf.indexOf(key) >= 0) { + return; + } + if (key === 'set-cookie') { + parsed[key] = (parsed[key] ? parsed[key] : []).concat([val]); + } else { + parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val; + } + } + }); + + return parsed; + }; + + +/***/ }), +/* 14 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var utils = __webpack_require__(2); + + module.exports = ( + utils.isStandardBrowserEnv() ? + + // Standard browser envs have full support of the APIs needed to test + // whether the request URL is of the same origin as current location. + (function standardBrowserEnv() { + var msie = /(msie|trident)/i.test(navigator.userAgent); + var urlParsingNode = document.createElement('a'); + var originURL; + + /** + * Parse a URL to discover it's components + * + * @param {String} url The URL to be parsed + * @returns {Object} + */ + function resolveURL(url) { + var href = url; + + if (msie) { + // IE needs attribute set twice to normalize properties + urlParsingNode.setAttribute('href', href); + href = urlParsingNode.href; + } + + urlParsingNode.setAttribute('href', href); + + // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils + return { + href: urlParsingNode.href, + protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', + host: urlParsingNode.host, + search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', + hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', + hostname: urlParsingNode.hostname, + port: urlParsingNode.port, + pathname: (urlParsingNode.pathname.charAt(0) === '/') ? + urlParsingNode.pathname : + '/' + urlParsingNode.pathname + }; + } + + originURL = resolveURL(window.location.href); + + /** + * Determine if a URL shares the same origin as the current location + * + * @param {String} requestURL The URL to test + * @returns {boolean} True if URL shares the same origin, otherwise false + */ + return function isURLSameOrigin(requestURL) { + var parsed = (utils.isString(requestURL)) ? resolveURL(requestURL) : requestURL; + return (parsed.protocol === originURL.protocol && + parsed.host === originURL.host); + }; + })() : + + // Non standard browser envs (web workers, react-native) lack needed support. + (function nonStandardBrowserEnv() { + return function isURLSameOrigin() { + return true; + }; + })() + ); + + +/***/ }), +/* 15 */ +/***/ (function(module, exports) { + + 'use strict'; + + // btoa polyfill for IE<10 courtesy https://github.com/davidchambers/Base64.js + + var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/='; + + function E() { + this.message = 'String contains an invalid character'; + } + E.prototype = new Error; + E.prototype.code = 5; + E.prototype.name = 'InvalidCharacterError'; + + function btoa(input) { + var str = String(input); + var output = ''; + for ( + // initialize result and counter + var block, charCode, idx = 0, map = chars; + // if the next str index does not exist: + // change the mapping table to "=" + // check if d has no fractional digits + str.charAt(idx | 0) || (map = '=', idx % 1); + // "8 - idx % 1 * 8" generates the sequence 2, 4, 6, 8 + output += map.charAt(63 & block >> 8 - idx % 1 * 8) + ) { + charCode = str.charCodeAt(idx += 3 / 4); + if (charCode > 0xFF) { + throw new E(); + } + block = block << 8 | charCode; + } + return output; + } + + module.exports = btoa; + + +/***/ }), +/* 16 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var utils = __webpack_require__(2); + + module.exports = ( + utils.isStandardBrowserEnv() ? + + // Standard browser envs support document.cookie + (function standardBrowserEnv() { + return { + write: function write(name, value, expires, path, domain, secure) { + var cookie = []; + cookie.push(name + '=' + encodeURIComponent(value)); + + if (utils.isNumber(expires)) { + cookie.push('expires=' + new Date(expires).toGMTString()); + } + + if (utils.isString(path)) { + cookie.push('path=' + path); + } + + if (utils.isString(domain)) { + cookie.push('domain=' + domain); + } + + if (secure === true) { + cookie.push('secure'); + } + + document.cookie = cookie.join('; '); + }, + + read: function read(name) { + var match = document.cookie.match(new RegExp('(^|;\\s*)(' + name + ')=([^;]*)')); + return (match ? decodeURIComponent(match[3]) : null); + }, + + remove: function remove(name) { + this.write(name, '', Date.now() - 86400000); + } + }; + })() : + + // Non standard browser env (web workers, react-native) lack needed support. + (function nonStandardBrowserEnv() { + return { + write: function write() {}, + read: function read() { return null; }, + remove: function remove() {} + }; + })() + ); + + +/***/ }), +/* 17 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var utils = __webpack_require__(2); + + function InterceptorManager() { + this.handlers = []; + } + + /** + * Add a new interceptor to the stack + * + * @param {Function} fulfilled The function to handle `then` for a `Promise` + * @param {Function} rejected The function to handle `reject` for a `Promise` + * + * @return {Number} An ID used to remove interceptor later + */ + InterceptorManager.prototype.use = function use(fulfilled, rejected) { + this.handlers.push({ + fulfilled: fulfilled, + rejected: rejected + }); + return this.handlers.length - 1; + }; + + /** + * Remove an interceptor from the stack + * + * @param {Number} id The ID that was returned by `use` + */ + InterceptorManager.prototype.eject = function eject(id) { + if (this.handlers[id]) { + this.handlers[id] = null; + } + }; + + /** + * Iterate over all the registered interceptors + * + * This method is particularly useful for skipping over any + * interceptors that may have become `null` calling `eject`. + * + * @param {Function} fn The function to call for each interceptor + */ + InterceptorManager.prototype.forEach = function forEach(fn) { + utils.forEach(this.handlers, function forEachHandler(h) { + if (h !== null) { + fn(h); + } + }); + }; + + module.exports = InterceptorManager; + + +/***/ }), +/* 18 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var utils = __webpack_require__(2); + var transformData = __webpack_require__(19); + var isCancel = __webpack_require__(20); + var defaults = __webpack_require__(6); + var isAbsoluteURL = __webpack_require__(21); + var combineURLs = __webpack_require__(22); + + /** + * Throws a `Cancel` if cancellation has been requested. + */ + function throwIfCancellationRequested(config) { + if (config.cancelToken) { + config.cancelToken.throwIfRequested(); + } + } + + /** + * Dispatch a request to the server using the configured adapter. + * + * @param {object} config The config that is to be used for the request + * @returns {Promise} The Promise to be fulfilled + */ + module.exports = function dispatchRequest(config) { + throwIfCancellationRequested(config); + + // Support baseURL config + if (config.baseURL && !isAbsoluteURL(config.url)) { + config.url = combineURLs(config.baseURL, config.url); + } + + // Ensure headers exist + config.headers = config.headers || {}; + + // Transform request upload + config.data = transformData( + config.data, + config.headers, + config.transformRequest + ); + + // Flatten headers + config.headers = utils.merge( + config.headers.common || {}, + config.headers[config.method] || {}, + config.headers || {} + ); + + utils.forEach( + ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'], + function cleanHeaderConfig(method) { + delete config.headers[method]; + } + ); + + var adapter = config.adapter || defaults.adapter; + + return adapter(config).then(function onAdapterResolution(response) { + throwIfCancellationRequested(config); + + // Transform response upload + response.data = transformData( + response.data, + response.headers, + config.transformResponse + ); + + return response; + }, function onAdapterRejection(reason) { + if (!isCancel(reason)) { + throwIfCancellationRequested(config); + + // Transform response upload + if (reason && reason.response) { + reason.response.data = transformData( + reason.response.data, + reason.response.headers, + config.transformResponse + ); + } + } + + return Promise.reject(reason); + }); + }; + + +/***/ }), +/* 19 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var utils = __webpack_require__(2); + + /** + * Transform the upload for a request or a response + * + * @param {Object|String} data The upload to be transformed + * @param {Array} headers The headers for the request or response + * @param {Array|Function} fns A single function or Array of functions + * @returns {*} The resulting transformed upload + */ + module.exports = function transformData(data, headers, fns) { + /*eslint no-param-reassign:0*/ + utils.forEach(fns, function transform(fn) { + data = fn(data, headers); + }); + + return data; + }; + + +/***/ }), +/* 20 */ +/***/ (function(module, exports) { + + 'use strict'; + + module.exports = function isCancel(value) { + return !!(value && value.__CANCEL__); + }; + + +/***/ }), +/* 21 */ +/***/ (function(module, exports) { + + 'use strict'; + + /** + * Determines whether the specified URL is absolute + * + * @param {string} url The URL to test + * @returns {boolean} True if the specified URL is absolute, otherwise false + */ + module.exports = function isAbsoluteURL(url) { + // A URL is considered absolute if it begins with "://" or "//" (protocol-relative URL). + // RFC 3986 defines scheme name as a sequence of characters beginning with a letter and followed + // by any combination of letters, digits, plus, period, or hyphen. + return /^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(url); + }; + + +/***/ }), +/* 22 */ +/***/ (function(module, exports) { + + 'use strict'; + + /** + * Creates a new URL by combining the specified URLs + * + * @param {string} baseURL The base URL + * @param {string} relativeURL The relative URL + * @returns {string} The combined URL + */ + module.exports = function combineURLs(baseURL, relativeURL) { + return relativeURL + ? baseURL.replace(/\/+$/, '') + '/' + relativeURL.replace(/^\/+/, '') + : baseURL; + }; + + +/***/ }), +/* 23 */ +/***/ (function(module, exports) { + + 'use strict'; + + /** + * A `Cancel` is an object that is thrown when an operation is canceled. + * + * @class + * @param {string=} message The message. + */ + function Cancel(message) { + this.message = message; + } + + Cancel.prototype.toString = function toString() { + return 'Cancel' + (this.message ? ': ' + this.message : ''); + }; + + Cancel.prototype.__CANCEL__ = true; + + module.exports = Cancel; + + +/***/ }), +/* 24 */ +/***/ (function(module, exports, __webpack_require__) { + + 'use strict'; + + var Cancel = __webpack_require__(23); + + /** + * A `CancelToken` is an object that can be used to request cancellation of an operation. + * + * @class + * @param {Function} executor The executor function. + */ + function CancelToken(executor) { + if (typeof executor !== 'function') { + throw new TypeError('executor must be a function.'); + } + + var resolvePromise; + this.promise = new Promise(function promiseExecutor(resolve) { + resolvePromise = resolve; + }); + + var token = this; + executor(function cancel(message) { + if (token.reason) { + // Cancellation has already been requested + return; + } + + token.reason = new Cancel(message); + resolvePromise(token.reason); + }); + } + + /** + * Throws a `Cancel` if cancellation has been requested. + */ + CancelToken.prototype.throwIfRequested = function throwIfRequested() { + if (this.reason) { + throw this.reason; + } + }; + + /** + * Returns an object that contains a new `CancelToken` and a function that, when called, + * cancels the `CancelToken`. + */ + CancelToken.source = function source() { + var cancel; + var token = new CancelToken(function executor(c) { + cancel = c; + }); + return { + token: token, + cancel: cancel + }; + }; + + module.exports = CancelToken; + + +/***/ }), +/* 25 */ +/***/ (function(module, exports) { + + 'use strict'; + + /** + * Syntactic sugar for invoking a function and expanding an array for arguments. + * + * Common use case would be to use `Function.prototype.apply`. + * + * ```js + * function f(x, y, z) {} + * var args = [1, 2, 3]; + * f.apply(null, args); + * ``` + * + * With `spread` this example can be re-written. + * + * ```js + * spread(function(x, y, z) {})([1, 2, 3]); + * ``` + * + * @param {Function} callback + * @returns {Function} + */ + module.exports = function spread(callback) { + return function wrap(arr) { + return callback.apply(null, arr); + }; + }; + + +/***/ }) +/******/ ]) +}); +; +//# sourceMappingURL=axios.map \ No newline at end of file diff --git a/statics/www/lib/axios.min.js b/statics/www/lib/axios.min.js new file mode 100644 index 0000000..2c5634d --- /dev/null +++ b/statics/www/lib/axios.min.js @@ -0,0 +1,8 @@ +/* axios v0.18.0 | (c) 2018 by Matt Zabriskie */ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.axios=t():e.axios=t()}(this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return e[r].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){e.exports=n(1)},function(e,t,n){"use strict";function r(e){var t=new s(e),n=i(s.prototype.request,t);return o.extend(n,s.prototype,t),o.extend(n,t),n}var o=n(2),i=n(3),s=n(5),u=n(6),a=r(u);a.Axios=s,a.create=function(e){return r(o.merge(u,e))},a.Cancel=n(23),a.CancelToken=n(24),a.isCancel=n(20),a.all=function(e){return Promise.all(e)},a.spread=n(25),e.exports=a,e.exports.default=a},function(e,t,n){"use strict";function r(e){return"[object Array]"===R.call(e)}function o(e){return"[object ArrayBuffer]"===R.call(e)}function i(e){return"undefined"!=typeof FormData&&e instanceof FormData}function s(e){var t;return t="undefined"!=typeof ArrayBuffer&&ArrayBuffer.isView?ArrayBuffer.isView(e):e&&e.buffer&&e.buffer instanceof ArrayBuffer}function u(e){return"string"==typeof e}function a(e){return"number"==typeof e}function c(e){return"undefined"==typeof e}function f(e){return null!==e&&"object"==typeof e}function p(e){return"[object Date]"===R.call(e)}function d(e){return"[object File]"===R.call(e)}function l(e){return"[object Blob]"===R.call(e)}function h(e){return"[object Function]"===R.call(e)}function m(e){return f(e)&&h(e.pipe)}function y(e){return"undefined"!=typeof URLSearchParams&&e instanceof URLSearchParams}function w(e){return e.replace(/^\s*/,"").replace(/\s*$/,"")}function g(){return("undefined"==typeof navigator||"ReactNative"!==navigator.product)&&("undefined"!=typeof window&&"undefined"!=typeof document)}function v(e,t){if(null!==e&&"undefined"!=typeof e)if("object"!=typeof e&&(e=[e]),r(e))for(var n=0,o=e.length;n + * @license MIT + */ +e.exports=function(e){return null!=e&&(n(e)||r(e)||!!e._isBuffer)}},function(e,t,n){"use strict";function r(e){this.defaults=e,this.interceptors={request:new s,response:new s}}var o=n(6),i=n(2),s=n(17),u=n(18);r.prototype.request=function(e){"string"==typeof e&&(e=i.merge({url:arguments[0]},arguments[1])),e=i.merge(o,{method:"get"},this.defaults,e),e.method=e.method.toLowerCase();var t=[u,void 0],n=Promise.resolve(e);for(this.interceptors.request.forEach(function(e){t.unshift(e.fulfilled,e.rejected)}),this.interceptors.response.forEach(function(e){t.push(e.fulfilled,e.rejected)});t.length;)n=n.then(t.shift(),t.shift());return n},i.forEach(["delete","get","head","options"],function(e){r.prototype[e]=function(t,n){return this.request(i.merge(n||{},{method:e,url:t}))}}),i.forEach(["post","put","patch"],function(e){r.prototype[e]=function(t,n,r){return this.request(i.merge(r||{},{method:e,url:t,data:n}))}}),e.exports=r},function(e,t,n){"use strict";function r(e,t){!i.isUndefined(e)&&i.isUndefined(e["Content-Type"])&&(e["Content-Type"]=t)}function o(){var e;return"undefined"!=typeof XMLHttpRequest?e=n(8):"undefined"!=typeof process&&(e=n(8)),e}var i=n(2),s=n(7),u={"Content-Type":"application/x-www-form-urlencoded"},a={adapter:o(),transformRequest:[function(e,t){return s(t,"Content-Type"),i.isFormData(e)||i.isArrayBuffer(e)||i.isBuffer(e)||i.isStream(e)||i.isFile(e)||i.isBlob(e)?e:i.isArrayBufferView(e)?e.buffer:i.isURLSearchParams(e)?(r(t,"application/x-www-form-urlencoded;charset=utf-8"),e.toString()):i.isObject(e)?(r(t,"application/json;charset=utf-8"),JSON.stringify(e)):e}],transformResponse:[function(e){if("string"==typeof e)try{e=JSON.parse(e)}catch(e){}return e}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,validateStatus:function(e){return e>=200&&e<300}};a.headers={common:{Accept:"application/json, text/plain, */*"}},i.forEach(["delete","get","head"],function(e){a.headers[e]={}}),i.forEach(["post","put","patch"],function(e){a.headers[e]=i.merge(u)}),e.exports=a},function(e,t,n){"use strict";var r=n(2);e.exports=function(e,t){r.forEach(e,function(n,r){r!==t&&r.toUpperCase()===t.toUpperCase()&&(e[t]=n,delete e[r])})}},function(e,t,n){"use strict";var r=n(2),o=n(9),i=n(12),s=n(13),u=n(14),a=n(10),c="undefined"!=typeof window&&window.btoa&&window.btoa.bind(window)||n(15);e.exports=function(e){return new Promise(function(t,f){var p=e.data,d=e.headers;r.isFormData(p)&&delete d["Content-Type"];var l=new XMLHttpRequest,h="onreadystatechange",m=!1;if("undefined"==typeof window||!window.XDomainRequest||"withCredentials"in l||u(e.url)||(l=new window.XDomainRequest,h="onload",m=!0,l.onprogress=function(){},l.ontimeout=function(){}),e.auth){var y=e.auth.username||"",w=e.auth.password||"";d.Authorization="Basic "+c(y+":"+w)}if(l.open(e.method.toUpperCase(),i(e.url,e.params,e.paramsSerializer),!0),l.timeout=e.timeout,l[h]=function(){if(l&&(4===l.readyState||m)&&(0!==l.status||l.responseURL&&0===l.responseURL.indexOf("file:"))){var n="getAllResponseHeaders"in l?s(l.getAllResponseHeaders()):null,r=e.responseType&&"text"!==e.responseType?l.response:l.responseText,i={data:r,status:1223===l.status?204:l.status,statusText:1223===l.status?"No Content":l.statusText,headers:n,config:e,request:l};o(t,f,i),l=null}},l.onerror=function(){f(a("Network Error",e,null,l)),l=null},l.ontimeout=function(){f(a("timeout of "+e.timeout+"ms exceeded",e,"ECONNABORTED",l)),l=null},r.isStandardBrowserEnv()){var g=n(16),v=(e.withCredentials||u(e.url))&&e.xsrfCookieName?g.read(e.xsrfCookieName):void 0;v&&(d[e.xsrfHeaderName]=v)}if("setRequestHeader"in l&&r.forEach(d,function(e,t){"undefined"==typeof p&&"content-type"===t.toLowerCase()?delete d[t]:l.setRequestHeader(t,e)}),e.withCredentials&&(l.withCredentials=!0),e.responseType)try{l.responseType=e.responseType}catch(t){if("json"!==e.responseType)throw t}"function"==typeof e.onDownloadProgress&&l.addEventListener("progress",e.onDownloadProgress),"function"==typeof e.onUploadProgress&&l.upload&&l.upload.addEventListener("progress",e.onUploadProgress),e.cancelToken&&e.cancelToken.promise.then(function(e){l&&(l.abort(),f(e),l=null)}),void 0===p&&(p=null),l.send(p)})}},function(e,t,n){"use strict";var r=n(10);e.exports=function(e,t,n){var o=n.config.validateStatus;n.status&&o&&!o(n.status)?t(r("Request failed with status code "+n.status,n.config,null,n.request,n)):e(n)}},function(e,t,n){"use strict";var r=n(11);e.exports=function(e,t,n,o,i){var s=new Error(e);return r(s,t,n,o,i)}},function(e,t){"use strict";e.exports=function(e,t,n,r,o){return e.config=t,n&&(e.code=n),e.request=r,e.response=o,e}},function(e,t,n){"use strict";function r(e){return encodeURIComponent(e).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+").replace(/%5B/gi,"[").replace(/%5D/gi,"]")}var o=n(2);e.exports=function(e,t,n){if(!t)return e;var i;if(n)i=n(t);else if(o.isURLSearchParams(t))i=t.toString();else{var s=[];o.forEach(t,function(e,t){null!==e&&"undefined"!=typeof e&&(o.isArray(e)?t+="[]":e=[e],o.forEach(e,function(e){o.isDate(e)?e=e.toISOString():o.isObject(e)&&(e=JSON.stringify(e)),s.push(r(t)+"="+r(e))}))}),i=s.join("&")}return i&&(e+=(e.indexOf("?")===-1?"?":"&")+i),e}},function(e,t,n){"use strict";var r=n(2),o=["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"];e.exports=function(e){var t,n,i,s={};return e?(r.forEach(e.split("\n"),function(e){if(i=e.indexOf(":"),t=r.trim(e.substr(0,i)).toLowerCase(),n=r.trim(e.substr(i+1)),t){if(s[t]&&o.indexOf(t)>=0)return;"set-cookie"===t?s[t]=(s[t]?s[t]:[]).concat([n]):s[t]=s[t]?s[t]+", "+n:n}}),s):s}},function(e,t,n){"use strict";var r=n(2);e.exports=r.isStandardBrowserEnv()?function(){function e(e){var t=e;return n&&(o.setAttribute("href",t),t=o.href),o.setAttribute("href",t),{href:o.href,protocol:o.protocol?o.protocol.replace(/:$/,""):"",host:o.host,search:o.search?o.search.replace(/^\?/,""):"",hash:o.hash?o.hash.replace(/^#/,""):"",hostname:o.hostname,port:o.port,pathname:"/"===o.pathname.charAt(0)?o.pathname:"/"+o.pathname}}var t,n=/(msie|trident)/i.test(navigator.userAgent),o=document.createElement("a");return t=e(window.location.href),function(n){var o=r.isString(n)?e(n):n;return o.protocol===t.protocol&&o.host===t.host}}():function(){return function(){return!0}}()},function(e,t){"use strict";function n(){this.message="String contains an invalid character"}function r(e){for(var t,r,i=String(e),s="",u=0,a=o;i.charAt(0|u)||(a="=",u%1);s+=a.charAt(63&t>>8-u%1*8)){if(r=i.charCodeAt(u+=.75),r>255)throw new n;t=t<<8|r}return s}var o="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";n.prototype=new Error,n.prototype.code=5,n.prototype.name="InvalidCharacterError",e.exports=r},function(e,t,n){"use strict";var r=n(2);e.exports=r.isStandardBrowserEnv()?function(){return{write:function(e,t,n,o,i,s){var u=[];u.push(e+"="+encodeURIComponent(t)),r.isNumber(n)&&u.push("expires="+new Date(n).toGMTString()),r.isString(o)&&u.push("path="+o),r.isString(i)&&u.push("domain="+i),s===!0&&u.push("secure"),document.cookie=u.join("; ")},read:function(e){var t=document.cookie.match(new RegExp("(^|;\\s*)("+e+")=([^;]*)"));return t?decodeURIComponent(t[3]):null},remove:function(e){this.write(e,"",Date.now()-864e5)}}}():function(){return{write:function(){},read:function(){return null},remove:function(){}}}()},function(e,t,n){"use strict";function r(){this.handlers=[]}var o=n(2);r.prototype.use=function(e,t){return this.handlers.push({fulfilled:e,rejected:t}),this.handlers.length-1},r.prototype.eject=function(e){this.handlers[e]&&(this.handlers[e]=null)},r.prototype.forEach=function(e){o.forEach(this.handlers,function(t){null!==t&&e(t)})},e.exports=r},function(e,t,n){"use strict";function r(e){e.cancelToken&&e.cancelToken.throwIfRequested()}var o=n(2),i=n(19),s=n(20),u=n(6),a=n(21),c=n(22);e.exports=function(e){r(e),e.baseURL&&!a(e.url)&&(e.url=c(e.baseURL,e.url)),e.headers=e.headers||{},e.data=i(e.data,e.headers,e.transformRequest),e.headers=o.merge(e.headers.common||{},e.headers[e.method]||{},e.headers||{}),o.forEach(["delete","get","head","post","put","patch","common"],function(t){delete e.headers[t]});var t=e.adapter||u.adapter;return t(e).then(function(t){return r(e),t.data=i(t.data,t.headers,e.transformResponse),t},function(t){return s(t)||(r(e),t&&t.response&&(t.response.data=i(t.response.data,t.response.headers,e.transformResponse))),Promise.reject(t)})}},function(e,t,n){"use strict";var r=n(2);e.exports=function(e,t,n){return r.forEach(n,function(n){e=n(e,t)}),e}},function(e,t){"use strict";e.exports=function(e){return!(!e||!e.__CANCEL__)}},function(e,t){"use strict";e.exports=function(e){return/^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(e)}},function(e,t){"use strict";e.exports=function(e,t){return t?e.replace(/\/+$/,"")+"/"+t.replace(/^\/+/,""):e}},function(e,t){"use strict";function n(e){this.message=e}n.prototype.toString=function(){return"Cancel"+(this.message?": "+this.message:"")},n.prototype.__CANCEL__=!0,e.exports=n},function(e,t,n){"use strict";function r(e){if("function"!=typeof e)throw new TypeError("executor must be a function.");var t;this.promise=new Promise(function(e){t=e});var n=this;e(function(e){n.reason||(n.reason=new o(e),t(n.reason))})}var o=n(23);r.prototype.throwIfRequested=function(){if(this.reason)throw this.reason},r.source=function(){var e,t=new r(function(t){e=t});return{token:t,cancel:e}},e.exports=r},function(e,t){"use strict";e.exports=function(e){return function(t){return e.apply(null,t)}}}])}); diff --git a/statics/www/lib/vue-router.js b/statics/www/lib/vue-router.js new file mode 100644 index 0000000..ca8645e --- /dev/null +++ b/statics/www/lib/vue-router.js @@ -0,0 +1,2626 @@ +/*! + * vue-router v3.0.2 + * (c) 2018 Evan You + * @license MIT + */ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global.VueRouter = factory()); +}(this, (function () { 'use strict'; + +/* */ + +function assert (condition, message) { + if (!condition) { + throw new Error(("[vue-router] " + message)) + } +} + +function warn (condition, message) { + if ("development" !== 'production' && !condition) { + typeof console !== 'undefined' && console.warn(("[vue-router] " + message)); + } +} + +function isError (err) { + return Object.prototype.toString.call(err).indexOf('Error') > -1 +} + +function extend (a, b) { + for (var key in b) { + a[key] = b[key]; + } + return a +} + +var View = { + name: 'RouterView', + functional: true, + props: { + name: { + type: String, + default: 'default' + } + }, + render: function render (_, ref) { + var props = ref.props; + var children = ref.children; + var parent = ref.parent; + var data = ref.data; + + // used by devtools to display a router-view badge + data.routerView = true; + + // directly use parent context's createElement() function + // so that components rendered by router-view can resolve named slots + var h = parent.$createElement; + var name = props.name; + var route = parent.$route; + var cache = parent._routerViewCache || (parent._routerViewCache = {}); + + // determine current view depth, also check to see if the tree + // has been toggled inactive but kept-alive. + var depth = 0; + var inactive = false; + while (parent && parent._routerRoot !== parent) { + if (parent.$vnode && parent.$vnode.data.routerView) { + depth++; + } + if (parent._inactive) { + inactive = true; + } + parent = parent.$parent; + } + data.routerViewDepth = depth; + + // render previous view if the tree is inactive and kept-alive + if (inactive) { + return h(cache[name], data, children) + } + + var matched = route.matched[depth]; + // render empty node if no matched route + if (!matched) { + cache[name] = null; + return h() + } + + var component = cache[name] = matched.components[name]; + + // attach instance registration hook + // this will be called in the instance's injected lifecycle hooks + data.registerRouteInstance = function (vm, val) { + // val could be undefined for unregistration + var current = matched.instances[name]; + if ( + (val && current !== vm) || + (!val && current === vm) + ) { + matched.instances[name] = val; + } + } + + // also register instance in prepatch hook + // in case the same component instance is reused across different routes + ;(data.hook || (data.hook = {})).prepatch = function (_, vnode) { + matched.instances[name] = vnode.componentInstance; + }; + + // resolve props + var propsToPass = data.props = resolveProps(route, matched.props && matched.props[name]); + if (propsToPass) { + // clone to prevent mutation + propsToPass = data.props = extend({}, propsToPass); + // pass non-declared props as attrs + var attrs = data.attrs = data.attrs || {}; + for (var key in propsToPass) { + if (!component.props || !(key in component.props)) { + attrs[key] = propsToPass[key]; + delete propsToPass[key]; + } + } + } + + return h(component, data, children) + } +} + +function resolveProps (route, config) { + switch (typeof config) { + case 'undefined': + return + case 'object': + return config + case 'function': + return config(route) + case 'boolean': + return config ? route.params : undefined + default: + { + warn( + false, + "props in \"" + (route.path) + "\" is a " + (typeof config) + ", " + + "expecting an object, function or boolean." + ); + } + } +} + +/* */ + +var encodeReserveRE = /[!'()*]/g; +var encodeReserveReplacer = function (c) { return '%' + c.charCodeAt(0).toString(16); }; +var commaRE = /%2C/g; + +// fixed encodeURIComponent which is more conformant to RFC3986: +// - escapes [!'()*] +// - preserve commas +var encode = function (str) { return encodeURIComponent(str) + .replace(encodeReserveRE, encodeReserveReplacer) + .replace(commaRE, ','); }; + +var decode = decodeURIComponent; + +function resolveQuery ( + query, + extraQuery, + _parseQuery +) { + if ( extraQuery === void 0 ) extraQuery = {}; + + var parse = _parseQuery || parseQuery; + var parsedQuery; + try { + parsedQuery = parse(query || ''); + } catch (e) { + "development" !== 'production' && warn(false, e.message); + parsedQuery = {}; + } + for (var key in extraQuery) { + parsedQuery[key] = extraQuery[key]; + } + return parsedQuery +} + +function parseQuery (query) { + var res = {}; + + query = query.trim().replace(/^(\?|#|&)/, ''); + + if (!query) { + return res + } + + query.split('&').forEach(function (param) { + var parts = param.replace(/\+/g, ' ').split('='); + var key = decode(parts.shift()); + var val = parts.length > 0 + ? decode(parts.join('=')) + : null; + + if (res[key] === undefined) { + res[key] = val; + } else if (Array.isArray(res[key])) { + res[key].push(val); + } else { + res[key] = [res[key], val]; + } + }); + + return res +} + +function stringifyQuery (obj) { + var res = obj ? Object.keys(obj).map(function (key) { + var val = obj[key]; + + if (val === undefined) { + return '' + } + + if (val === null) { + return encode(key) + } + + if (Array.isArray(val)) { + var result = []; + val.forEach(function (val2) { + if (val2 === undefined) { + return + } + if (val2 === null) { + result.push(encode(key)); + } else { + result.push(encode(key) + '=' + encode(val2)); + } + }); + return result.join('&') + } + + return encode(key) + '=' + encode(val) + }).filter(function (x) { return x.length > 0; }).join('&') : null; + return res ? ("?" + res) : '' +} + +/* */ + +var trailingSlashRE = /\/?$/; + +function createRoute ( + record, + location, + redirectedFrom, + router +) { + var stringifyQuery$$1 = router && router.options.stringifyQuery; + + var query = location.query || {}; + try { + query = clone(query); + } catch (e) {} + + var route = { + name: location.name || (record && record.name), + meta: (record && record.meta) || {}, + path: location.path || '/', + hash: location.hash || '', + query: query, + params: location.params || {}, + fullPath: getFullPath(location, stringifyQuery$$1), + matched: record ? formatMatch(record) : [] + }; + if (redirectedFrom) { + route.redirectedFrom = getFullPath(redirectedFrom, stringifyQuery$$1); + } + return Object.freeze(route) +} + +function clone (value) { + if (Array.isArray(value)) { + return value.map(clone) + } else if (value && typeof value === 'object') { + var res = {}; + for (var key in value) { + res[key] = clone(value[key]); + } + return res + } else { + return value + } +} + +// the starting route that represents the initial state +var START = createRoute(null, { + path: '/' +}); + +function formatMatch (record) { + var res = []; + while (record) { + res.unshift(record); + record = record.parent; + } + return res +} + +function getFullPath ( + ref, + _stringifyQuery +) { + var path = ref.path; + var query = ref.query; if ( query === void 0 ) query = {}; + var hash = ref.hash; if ( hash === void 0 ) hash = ''; + + var stringify = _stringifyQuery || stringifyQuery; + return (path || '/') + stringify(query) + hash +} + +function isSameRoute (a, b) { + if (b === START) { + return a === b + } else if (!b) { + return false + } else if (a.path && b.path) { + return ( + a.path.replace(trailingSlashRE, '') === b.path.replace(trailingSlashRE, '') && + a.hash === b.hash && + isObjectEqual(a.query, b.query) + ) + } else if (a.name && b.name) { + return ( + a.name === b.name && + a.hash === b.hash && + isObjectEqual(a.query, b.query) && + isObjectEqual(a.params, b.params) + ) + } else { + return false + } +} + +function isObjectEqual (a, b) { + if ( a === void 0 ) a = {}; + if ( b === void 0 ) b = {}; + + // handle null value #1566 + if (!a || !b) { return a === b } + var aKeys = Object.keys(a); + var bKeys = Object.keys(b); + if (aKeys.length !== bKeys.length) { + return false + } + return aKeys.every(function (key) { + var aVal = a[key]; + var bVal = b[key]; + // check nested equality + if (typeof aVal === 'object' && typeof bVal === 'object') { + return isObjectEqual(aVal, bVal) + } + return String(aVal) === String(bVal) + }) +} + +function isIncludedRoute (current, target) { + return ( + current.path.replace(trailingSlashRE, '/').indexOf( + target.path.replace(trailingSlashRE, '/') + ) === 0 && + (!target.hash || current.hash === target.hash) && + queryIncludes(current.query, target.query) + ) +} + +function queryIncludes (current, target) { + for (var key in target) { + if (!(key in current)) { + return false + } + } + return true +} + +/* */ + +// work around weird flow bug +var toTypes = [String, Object]; +var eventTypes = [String, Array]; + +var Link = { + name: 'RouterLink', + props: { + to: { + type: toTypes, + required: true + }, + tag: { + type: String, + default: 'a' + }, + exact: Boolean, + append: Boolean, + replace: Boolean, + activeClass: String, + exactActiveClass: String, + event: { + type: eventTypes, + default: 'click' + } + }, + render: function render (h) { + var this$1 = this; + + var router = this.$router; + var current = this.$route; + var ref = router.resolve(this.to, current, this.append); + var location = ref.location; + var route = ref.route; + var href = ref.href; + + var classes = {}; + var globalActiveClass = router.options.linkActiveClass; + var globalExactActiveClass = router.options.linkExactActiveClass; + // Support global empty active class + var activeClassFallback = globalActiveClass == null + ? 'router-link-active' + : globalActiveClass; + var exactActiveClassFallback = globalExactActiveClass == null + ? 'router-link-exact-active' + : globalExactActiveClass; + var activeClass = this.activeClass == null + ? activeClassFallback + : this.activeClass; + var exactActiveClass = this.exactActiveClass == null + ? exactActiveClassFallback + : this.exactActiveClass; + var compareTarget = location.path + ? createRoute(null, location, null, router) + : route; + + classes[exactActiveClass] = isSameRoute(current, compareTarget); + classes[activeClass] = this.exact + ? classes[exactActiveClass] + : isIncludedRoute(current, compareTarget); + + var handler = function (e) { + if (guardEvent(e)) { + if (this$1.replace) { + router.replace(location); + } else { + router.push(location); + } + } + }; + + var on = { click: guardEvent }; + if (Array.isArray(this.event)) { + this.event.forEach(function (e) { on[e] = handler; }); + } else { + on[this.event] = handler; + } + + var data = { + class: classes + }; + + if (this.tag === 'a') { + data.on = on; + data.attrs = { href: href }; + } else { + // find the first child and apply listener and href + var a = findAnchor(this.$slots.default); + if (a) { + // in case the is a static node + a.isStatic = false; + var aData = a.data = extend({}, a.data); + aData.on = on; + var aAttrs = a.data.attrs = extend({}, a.data.attrs); + aAttrs.href = href; + } else { + // doesn't have child, apply listener to self + data.on = on; + } + } + + return h(this.tag, data, this.$slots.default) + } +} + +function guardEvent (e) { + // don't redirect with control keys + if (e.metaKey || e.altKey || e.ctrlKey || e.shiftKey) { return } + // don't redirect when preventDefault called + if (e.defaultPrevented) { return } + // don't redirect on right click + if (e.button !== undefined && e.button !== 0) { return } + // don't redirect if `target="_blank"` + if (e.currentTarget && e.currentTarget.getAttribute) { + var target = e.currentTarget.getAttribute('target'); + if (/\b_blank\b/i.test(target)) { return } + } + // this may be a Weex event which doesn't have this method + if (e.preventDefault) { + e.preventDefault(); + } + return true +} + +function findAnchor (children) { + if (children) { + var child; + for (var i = 0; i < children.length; i++) { + child = children[i]; + if (child.tag === 'a') { + return child + } + if (child.children && (child = findAnchor(child.children))) { + return child + } + } + } +} + +var _Vue; + +function install (Vue) { + if (install.installed && _Vue === Vue) { return } + install.installed = true; + + _Vue = Vue; + + var isDef = function (v) { return v !== undefined; }; + + var registerInstance = function (vm, callVal) { + var i = vm.$options._parentVnode; + if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) { + i(vm, callVal); + } + }; + + Vue.mixin({ + beforeCreate: function beforeCreate () { + if (isDef(this.$options.router)) { + this._routerRoot = this; + this._router = this.$options.router; + this._router.init(this); + Vue.util.defineReactive(this, '_route', this._router.history.current); + } else { + this._routerRoot = (this.$parent && this.$parent._routerRoot) || this; + } + registerInstance(this, this); + }, + destroyed: function destroyed () { + registerInstance(this); + } + }); + + Object.defineProperty(Vue.prototype, '$router', { + get: function get () { return this._routerRoot._router } + }); + + Object.defineProperty(Vue.prototype, '$route', { + get: function get () { return this._routerRoot._route } + }); + + Vue.component('RouterView', View); + Vue.component('RouterLink', Link); + + var strats = Vue.config.optionMergeStrategies; + // use the same hook merging strategy for route hooks + strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created; +} + +/* */ + +var inBrowser = typeof window !== 'undefined'; + +/* */ + +function resolvePath ( + relative, + base, + append +) { + var firstChar = relative.charAt(0); + if (firstChar === '/') { + return relative + } + + if (firstChar === '?' || firstChar === '#') { + return base + relative + } + + var stack = base.split('/'); + + // remove trailing segment if: + // - not appending + // - appending to trailing slash (last segment is empty) + if (!append || !stack[stack.length - 1]) { + stack.pop(); + } + + // resolve relative path + var segments = relative.replace(/^\//, '').split('/'); + for (var i = 0; i < segments.length; i++) { + var segment = segments[i]; + if (segment === '..') { + stack.pop(); + } else if (segment !== '.') { + stack.push(segment); + } + } + + // ensure leading slash + if (stack[0] !== '') { + stack.unshift(''); + } + + return stack.join('/') +} + +function parsePath (path) { + var hash = ''; + var query = ''; + + var hashIndex = path.indexOf('#'); + if (hashIndex >= 0) { + hash = path.slice(hashIndex); + path = path.slice(0, hashIndex); + } + + var queryIndex = path.indexOf('?'); + if (queryIndex >= 0) { + query = path.slice(queryIndex + 1); + path = path.slice(0, queryIndex); + } + + return { + path: path, + query: query, + hash: hash + } +} + +function cleanPath (path) { + return path.replace(/\/\//g, '/') +} + +var isarray = Array.isArray || function (arr) { + return Object.prototype.toString.call(arr) == '[object Array]'; +}; + +/** + * Expose `pathToRegexp`. + */ +var pathToRegexp_1 = pathToRegexp; +var parse_1 = parse; +var compile_1 = compile; +var tokensToFunction_1 = tokensToFunction; +var tokensToRegExp_1 = tokensToRegExp; + +/** + * The main path matching regexp utility. + * + * @type {RegExp} + */ +var PATH_REGEXP = new RegExp([ + // Match escaped characters that would otherwise appear in future matches. + // This allows the user to escape special characters that won't transform. + '(\\\\.)', + // Match Express-style parameters and un-named parameters with a prefix + // and optional suffixes. Matches appear as: + // + // "/:test(\\d+)?" => ["/", "test", "\d+", undefined, "?", undefined] + // "/route(\\d+)" => [undefined, undefined, undefined, "\d+", undefined, undefined] + // "/*" => ["/", undefined, undefined, undefined, undefined, "*"] + '([\\/.])?(?:(?:\\:(\\w+)(?:\\(((?:\\\\.|[^\\\\()])+)\\))?|\\(((?:\\\\.|[^\\\\()])+)\\))([+*?])?|(\\*))' +].join('|'), 'g'); + +/** + * Parse a string for the raw tokens. + * + * @param {string} str + * @param {Object=} options + * @return {!Array} + */ +function parse (str, options) { + var tokens = []; + var key = 0; + var index = 0; + var path = ''; + var defaultDelimiter = options && options.delimiter || '/'; + var res; + + while ((res = PATH_REGEXP.exec(str)) != null) { + var m = res[0]; + var escaped = res[1]; + var offset = res.index; + path += str.slice(index, offset); + index = offset + m.length; + + // Ignore already escaped sequences. + if (escaped) { + path += escaped[1]; + continue + } + + var next = str[index]; + var prefix = res[2]; + var name = res[3]; + var capture = res[4]; + var group = res[5]; + var modifier = res[6]; + var asterisk = res[7]; + + // Push the current path onto the tokens. + if (path) { + tokens.push(path); + path = ''; + } + + var partial = prefix != null && next != null && next !== prefix; + var repeat = modifier === '+' || modifier === '*'; + var optional = modifier === '?' || modifier === '*'; + var delimiter = res[2] || defaultDelimiter; + var pattern = capture || group; + + tokens.push({ + name: name || key++, + prefix: prefix || '', + delimiter: delimiter, + optional: optional, + repeat: repeat, + partial: partial, + asterisk: !!asterisk, + pattern: pattern ? escapeGroup(pattern) : (asterisk ? '.*' : '[^' + escapeString(delimiter) + ']+?') + }); + } + + // Match any characters still remaining. + if (index < str.length) { + path += str.substr(index); + } + + // If the path exists, push it onto the end. + if (path) { + tokens.push(path); + } + + return tokens +} + +/** + * Compile a string to a template function for the path. + * + * @param {string} str + * @param {Object=} options + * @return {!function(Object=, Object=)} + */ +function compile (str, options) { + return tokensToFunction(parse(str, options)) +} + +/** + * Prettier encoding of URI path segments. + * + * @param {string} + * @return {string} + */ +function encodeURIComponentPretty (str) { + return encodeURI(str).replace(/[\/?#]/g, function (c) { + return '%' + c.charCodeAt(0).toString(16).toUpperCase() + }) +} + +/** + * Encode the asterisk parameter. Similar to `pretty`, but allows slashes. + * + * @param {string} + * @return {string} + */ +function encodeAsterisk (str) { + return encodeURI(str).replace(/[?#]/g, function (c) { + return '%' + c.charCodeAt(0).toString(16).toUpperCase() + }) +} + +/** + * Expose a method for transforming tokens into the path function. + */ +function tokensToFunction (tokens) { + // Compile all the tokens into regexps. + var matches = new Array(tokens.length); + + // Compile all the patterns before compilation. + for (var i = 0; i < tokens.length; i++) { + if (typeof tokens[i] === 'object') { + matches[i] = new RegExp('^(?:' + tokens[i].pattern + ')$'); + } + } + + return function (obj, opts) { + var path = ''; + var data = obj || {}; + var options = opts || {}; + var encode = options.pretty ? encodeURIComponentPretty : encodeURIComponent; + + for (var i = 0; i < tokens.length; i++) { + var token = tokens[i]; + + if (typeof token === 'string') { + path += token; + + continue + } + + var value = data[token.name]; + var segment; + + if (value == null) { + if (token.optional) { + // Prepend partial segment prefixes. + if (token.partial) { + path += token.prefix; + } + + continue + } else { + throw new TypeError('Expected "' + token.name + '" to be defined') + } + } + + if (isarray(value)) { + if (!token.repeat) { + throw new TypeError('Expected "' + token.name + '" to not repeat, but received `' + JSON.stringify(value) + '`') + } + + if (value.length === 0) { + if (token.optional) { + continue + } else { + throw new TypeError('Expected "' + token.name + '" to not be empty') + } + } + + for (var j = 0; j < value.length; j++) { + segment = encode(value[j]); + + if (!matches[i].test(segment)) { + throw new TypeError('Expected all "' + token.name + '" to match "' + token.pattern + '", but received `' + JSON.stringify(segment) + '`') + } + + path += (j === 0 ? token.prefix : token.delimiter) + segment; + } + + continue + } + + segment = token.asterisk ? encodeAsterisk(value) : encode(value); + + if (!matches[i].test(segment)) { + throw new TypeError('Expected "' + token.name + '" to match "' + token.pattern + '", but received "' + segment + '"') + } + + path += token.prefix + segment; + } + + return path + } +} + +/** + * Escape a regular expression string. + * + * @param {string} str + * @return {string} + */ +function escapeString (str) { + return str.replace(/([.+*?=^!:${}()[\]|\/\\])/g, '\\$1') +} + +/** + * Escape the capturing group by escaping special characters and meaning. + * + * @param {string} group + * @return {string} + */ +function escapeGroup (group) { + return group.replace(/([=!:$\/()])/g, '\\$1') +} + +/** + * Attach the keys as a property of the regexp. + * + * @param {!RegExp} re + * @param {Array} keys + * @return {!RegExp} + */ +function attachKeys (re, keys) { + re.keys = keys; + return re +} + +/** + * Get the flags for a regexp from the options. + * + * @param {Object} options + * @return {string} + */ +function flags (options) { + return options.sensitive ? '' : 'i' +} + +/** + * Pull out keys from a regexp. + * + * @param {!RegExp} path + * @param {!Array} keys + * @return {!RegExp} + */ +function regexpToRegexp (path, keys) { + // Use a negative lookahead to match only capturing groups. + var groups = path.source.match(/\((?!\?)/g); + + if (groups) { + for (var i = 0; i < groups.length; i++) { + keys.push({ + name: i, + prefix: null, + delimiter: null, + optional: false, + repeat: false, + partial: false, + asterisk: false, + pattern: null + }); + } + } + + return attachKeys(path, keys) +} + +/** + * Transform an array into a regexp. + * + * @param {!Array} path + * @param {Array} keys + * @param {!Object} options + * @return {!RegExp} + */ +function arrayToRegexp (path, keys, options) { + var parts = []; + + for (var i = 0; i < path.length; i++) { + parts.push(pathToRegexp(path[i], keys, options).source); + } + + var regexp = new RegExp('(?:' + parts.join('|') + ')', flags(options)); + + return attachKeys(regexp, keys) +} + +/** + * Create a path regexp from string input. + * + * @param {string} path + * @param {!Array} keys + * @param {!Object} options + * @return {!RegExp} + */ +function stringToRegexp (path, keys, options) { + return tokensToRegExp(parse(path, options), keys, options) +} + +/** + * Expose a function for taking tokens and returning a RegExp. + * + * @param {!Array} tokens + * @param {(Array|Object)=} keys + * @param {Object=} options + * @return {!RegExp} + */ +function tokensToRegExp (tokens, keys, options) { + if (!isarray(keys)) { + options = /** @type {!Object} */ (keys || options); + keys = []; + } + + options = options || {}; + + var strict = options.strict; + var end = options.end !== false; + var route = ''; + + // Iterate over the tokens and create our regexp string. + for (var i = 0; i < tokens.length; i++) { + var token = tokens[i]; + + if (typeof token === 'string') { + route += escapeString(token); + } else { + var prefix = escapeString(token.prefix); + var capture = '(?:' + token.pattern + ')'; + + keys.push(token); + + if (token.repeat) { + capture += '(?:' + prefix + capture + ')*'; + } + + if (token.optional) { + if (!token.partial) { + capture = '(?:' + prefix + '(' + capture + '))?'; + } else { + capture = prefix + '(' + capture + ')?'; + } + } else { + capture = prefix + '(' + capture + ')'; + } + + route += capture; + } + } + + var delimiter = escapeString(options.delimiter || '/'); + var endsWithDelimiter = route.slice(-delimiter.length) === delimiter; + + // In non-strict mode we allow a slash at the end of match. If the path to + // match already ends with a slash, we remove it for consistency. The slash + // is valid at the end of a path match, not in the middle. This is important + // in non-ending mode, where "/test/" shouldn't match "/test//route". + if (!strict) { + route = (endsWithDelimiter ? route.slice(0, -delimiter.length) : route) + '(?:' + delimiter + '(?=$))?'; + } + + if (end) { + route += '$'; + } else { + // In non-ending mode, we need the capturing groups to match as much as + // possible by using a positive lookahead to the end or next path segment. + route += strict && endsWithDelimiter ? '' : '(?=' + delimiter + '|$)'; + } + + return attachKeys(new RegExp('^' + route, flags(options)), keys) +} + +/** + * Normalize the given path string, returning a regular expression. + * + * An empty array can be passed in for the keys, which will hold the + * placeholder key descriptions. For example, using `/user/:id`, `keys` will + * contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`. + * + * @param {(string|RegExp|Array)} path + * @param {(Array|Object)=} keys + * @param {Object=} options + * @return {!RegExp} + */ +function pathToRegexp (path, keys, options) { + if (!isarray(keys)) { + options = /** @type {!Object} */ (keys || options); + keys = []; + } + + options = options || {}; + + if (path instanceof RegExp) { + return regexpToRegexp(path, /** @type {!Array} */ (keys)) + } + + if (isarray(path)) { + return arrayToRegexp(/** @type {!Array} */ (path), /** @type {!Array} */ (keys), options) + } + + return stringToRegexp(/** @type {string} */ (path), /** @type {!Array} */ (keys), options) +} +pathToRegexp_1.parse = parse_1; +pathToRegexp_1.compile = compile_1; +pathToRegexp_1.tokensToFunction = tokensToFunction_1; +pathToRegexp_1.tokensToRegExp = tokensToRegExp_1; + +/* */ + +// $flow-disable-line +var regexpCompileCache = Object.create(null); + +function fillParams ( + path, + params, + routeMsg +) { + try { + var filler = + regexpCompileCache[path] || + (regexpCompileCache[path] = pathToRegexp_1.compile(path)); + return filler(params || {}, { pretty: true }) + } catch (e) { + { + warn(false, ("missing param for " + routeMsg + ": " + (e.message))); + } + return '' + } +} + +/* */ + +function createRouteMap ( + routes, + oldPathList, + oldPathMap, + oldNameMap +) { + // the path list is used to control path matching priority + var pathList = oldPathList || []; + // $flow-disable-line + var pathMap = oldPathMap || Object.create(null); + // $flow-disable-line + var nameMap = oldNameMap || Object.create(null); + + routes.forEach(function (route) { + addRouteRecord(pathList, pathMap, nameMap, route); + }); + + // ensure wildcard routes are always at the end + for (var i = 0, l = pathList.length; i < l; i++) { + if (pathList[i] === '*') { + pathList.push(pathList.splice(i, 1)[0]); + l--; + i--; + } + } + + return { + pathList: pathList, + pathMap: pathMap, + nameMap: nameMap + } +} + +function addRouteRecord ( + pathList, + pathMap, + nameMap, + route, + parent, + matchAs +) { + var path = route.path; + var name = route.name; + { + assert(path != null, "\"path\" is required in a route configuration."); + assert( + typeof route.component !== 'string', + "route config \"component\" for path: " + (String(path || name)) + " cannot be a " + + "string id. Use an actual component instead." + ); + } + + var pathToRegexpOptions = route.pathToRegexpOptions || {}; + var normalizedPath = normalizePath( + path, + parent, + pathToRegexpOptions.strict + ); + + if (typeof route.caseSensitive === 'boolean') { + pathToRegexpOptions.sensitive = route.caseSensitive; + } + + var record = { + path: normalizedPath, + regex: compileRouteRegex(normalizedPath, pathToRegexpOptions), + components: route.components || { default: route.component }, + instances: {}, + name: name, + parent: parent, + matchAs: matchAs, + redirect: route.redirect, + beforeEnter: route.beforeEnter, + meta: route.meta || {}, + props: route.props == null + ? {} + : route.components + ? route.props + : { default: route.props } + }; + + if (route.children) { + // Warn if route is named, does not redirect and has a default child route. + // If users navigate to this route by name, the default child will + // not be rendered (GH Issue #629) + { + if (route.name && !route.redirect && route.children.some(function (child) { return /^\/?$/.test(child.path); })) { + warn( + false, + "Named Route '" + (route.name) + "' has a default child route. " + + "When navigating to this named route (:to=\"{name: '" + (route.name) + "'\"), " + + "the default child route will not be rendered. Remove the name from " + + "this route and use the name of the default child route for named " + + "links instead." + ); + } + } + route.children.forEach(function (child) { + var childMatchAs = matchAs + ? cleanPath((matchAs + "/" + (child.path))) + : undefined; + addRouteRecord(pathList, pathMap, nameMap, child, record, childMatchAs); + }); + } + + if (route.alias !== undefined) { + var aliases = Array.isArray(route.alias) + ? route.alias + : [route.alias]; + + aliases.forEach(function (alias) { + var aliasRoute = { + path: alias, + children: route.children + }; + addRouteRecord( + pathList, + pathMap, + nameMap, + aliasRoute, + parent, + record.path || '/' // matchAs + ); + }); + } + + if (!pathMap[record.path]) { + pathList.push(record.path); + pathMap[record.path] = record; + } + + if (name) { + if (!nameMap[name]) { + nameMap[name] = record; + } else if ("development" !== 'production' && !matchAs) { + warn( + false, + "Duplicate named routes definition: " + + "{ name: \"" + name + "\", path: \"" + (record.path) + "\" }" + ); + } + } +} + +function compileRouteRegex (path, pathToRegexpOptions) { + var regex = pathToRegexp_1(path, [], pathToRegexpOptions); + { + var keys = Object.create(null); + regex.keys.forEach(function (key) { + warn(!keys[key.name], ("Duplicate param keys in route with path: \"" + path + "\"")); + keys[key.name] = true; + }); + } + return regex +} + +function normalizePath (path, parent, strict) { + if (!strict) { path = path.replace(/\/$/, ''); } + if (path[0] === '/') { return path } + if (parent == null) { return path } + return cleanPath(((parent.path) + "/" + path)) +} + +/* */ + +function normalizeLocation ( + raw, + current, + append, + router +) { + var next = typeof raw === 'string' ? { path: raw } : raw; + // named target + if (next.name || next._normalized) { + return next + } + + // relative params + if (!next.path && next.params && current) { + next = extend({}, next); + next._normalized = true; + var params = extend(extend({}, current.params), next.params); + if (current.name) { + next.name = current.name; + next.params = params; + } else if (current.matched.length) { + var rawPath = current.matched[current.matched.length - 1].path; + next.path = fillParams(rawPath, params, ("path " + (current.path))); + } else { + warn(false, "relative params navigation requires a current route."); + } + return next + } + + var parsedPath = parsePath(next.path || ''); + var basePath = (current && current.path) || '/'; + var path = parsedPath.path + ? resolvePath(parsedPath.path, basePath, append || next.append) + : basePath; + + var query = resolveQuery( + parsedPath.query, + next.query, + router && router.options.parseQuery + ); + + var hash = next.hash || parsedPath.hash; + if (hash && hash.charAt(0) !== '#') { + hash = "#" + hash; + } + + return { + _normalized: true, + path: path, + query: query, + hash: hash + } +} + +/* */ + + + +function createMatcher ( + routes, + router +) { + var ref = createRouteMap(routes); + var pathList = ref.pathList; + var pathMap = ref.pathMap; + var nameMap = ref.nameMap; + + function addRoutes (routes) { + createRouteMap(routes, pathList, pathMap, nameMap); + } + + function match ( + raw, + currentRoute, + redirectedFrom + ) { + var location = normalizeLocation(raw, currentRoute, false, router); + var name = location.name; + + if (name) { + var record = nameMap[name]; + { + warn(record, ("Route with name '" + name + "' does not exist")); + } + if (!record) { return _createRoute(null, location) } + var paramNames = record.regex.keys + .filter(function (key) { return !key.optional; }) + .map(function (key) { return key.name; }); + + if (typeof location.params !== 'object') { + location.params = {}; + } + + if (currentRoute && typeof currentRoute.params === 'object') { + for (var key in currentRoute.params) { + if (!(key in location.params) && paramNames.indexOf(key) > -1) { + location.params[key] = currentRoute.params[key]; + } + } + } + + if (record) { + location.path = fillParams(record.path, location.params, ("named route \"" + name + "\"")); + return _createRoute(record, location, redirectedFrom) + } + } else if (location.path) { + location.params = {}; + for (var i = 0; i < pathList.length; i++) { + var path = pathList[i]; + var record$1 = pathMap[path]; + if (matchRoute(record$1.regex, location.path, location.params)) { + return _createRoute(record$1, location, redirectedFrom) + } + } + } + // no match + return _createRoute(null, location) + } + + function redirect ( + record, + location + ) { + var originalRedirect = record.redirect; + var redirect = typeof originalRedirect === 'function' + ? originalRedirect(createRoute(record, location, null, router)) + : originalRedirect; + + if (typeof redirect === 'string') { + redirect = { path: redirect }; + } + + if (!redirect || typeof redirect !== 'object') { + { + warn( + false, ("invalid redirect option: " + (JSON.stringify(redirect))) + ); + } + return _createRoute(null, location) + } + + var re = redirect; + var name = re.name; + var path = re.path; + var query = location.query; + var hash = location.hash; + var params = location.params; + query = re.hasOwnProperty('query') ? re.query : query; + hash = re.hasOwnProperty('hash') ? re.hash : hash; + params = re.hasOwnProperty('params') ? re.params : params; + + if (name) { + // resolved named direct + var targetRecord = nameMap[name]; + { + assert(targetRecord, ("redirect failed: named route \"" + name + "\" not found.")); + } + return match({ + _normalized: true, + name: name, + query: query, + hash: hash, + params: params + }, undefined, location) + } else if (path) { + // 1. resolve relative redirect + var rawPath = resolveRecordPath(path, record); + // 2. resolve params + var resolvedPath = fillParams(rawPath, params, ("redirect route with path \"" + rawPath + "\"")); + // 3. rematch with existing query and hash + return match({ + _normalized: true, + path: resolvedPath, + query: query, + hash: hash + }, undefined, location) + } else { + { + warn(false, ("invalid redirect option: " + (JSON.stringify(redirect)))); + } + return _createRoute(null, location) + } + } + + function alias ( + record, + location, + matchAs + ) { + var aliasedPath = fillParams(matchAs, location.params, ("aliased route with path \"" + matchAs + "\"")); + var aliasedMatch = match({ + _normalized: true, + path: aliasedPath + }); + if (aliasedMatch) { + var matched = aliasedMatch.matched; + var aliasedRecord = matched[matched.length - 1]; + location.params = aliasedMatch.params; + return _createRoute(aliasedRecord, location) + } + return _createRoute(null, location) + } + + function _createRoute ( + record, + location, + redirectedFrom + ) { + if (record && record.redirect) { + return redirect(record, redirectedFrom || location) + } + if (record && record.matchAs) { + return alias(record, location, record.matchAs) + } + return createRoute(record, location, redirectedFrom, router) + } + + return { + match: match, + addRoutes: addRoutes + } +} + +function matchRoute ( + regex, + path, + params +) { + var m = path.match(regex); + + if (!m) { + return false + } else if (!params) { + return true + } + + for (var i = 1, len = m.length; i < len; ++i) { + var key = regex.keys[i - 1]; + var val = typeof m[i] === 'string' ? decodeURIComponent(m[i]) : m[i]; + if (key) { + // Fix #1994: using * with props: true generates a param named 0 + params[key.name || 'pathMatch'] = val; + } + } + + return true +} + +function resolveRecordPath (path, record) { + return resolvePath(path, record.parent ? record.parent.path : '/', true) +} + +/* */ + +var positionStore = Object.create(null); + +function setupScroll () { + // Fix for #1585 for Firefox + // Fix for #2195 Add optional third attribute to workaround a bug in safari https://bugs.webkit.org/show_bug.cgi?id=182678 + window.history.replaceState({ key: getStateKey() }, '', window.location.href.replace(window.location.origin, '')); + window.addEventListener('popstate', function (e) { + saveScrollPosition(); + if (e.state && e.state.key) { + setStateKey(e.state.key); + } + }); +} + +function handleScroll ( + router, + to, + from, + isPop +) { + if (!router.app) { + return + } + + var behavior = router.options.scrollBehavior; + if (!behavior) { + return + } + + { + assert(typeof behavior === 'function', "scrollBehavior must be a function"); + } + + // wait until re-render finishes before scrolling + router.app.$nextTick(function () { + var position = getScrollPosition(); + var shouldScroll = behavior.call(router, to, from, isPop ? position : null); + + if (!shouldScroll) { + return + } + + if (typeof shouldScroll.then === 'function') { + shouldScroll.then(function (shouldScroll) { + scrollToPosition((shouldScroll), position); + }).catch(function (err) { + { + assert(false, err.toString()); + } + }); + } else { + scrollToPosition(shouldScroll, position); + } + }); +} + +function saveScrollPosition () { + var key = getStateKey(); + if (key) { + positionStore[key] = { + x: window.pageXOffset, + y: window.pageYOffset + }; + } +} + +function getScrollPosition () { + var key = getStateKey(); + if (key) { + return positionStore[key] + } +} + +function getElementPosition (el, offset) { + var docEl = document.documentElement; + var docRect = docEl.getBoundingClientRect(); + var elRect = el.getBoundingClientRect(); + return { + x: elRect.left - docRect.left - offset.x, + y: elRect.top - docRect.top - offset.y + } +} + +function isValidPosition (obj) { + return isNumber(obj.x) || isNumber(obj.y) +} + +function normalizePosition (obj) { + return { + x: isNumber(obj.x) ? obj.x : window.pageXOffset, + y: isNumber(obj.y) ? obj.y : window.pageYOffset + } +} + +function normalizeOffset (obj) { + return { + x: isNumber(obj.x) ? obj.x : 0, + y: isNumber(obj.y) ? obj.y : 0 + } +} + +function isNumber (v) { + return typeof v === 'number' +} + +function scrollToPosition (shouldScroll, position) { + var isObject = typeof shouldScroll === 'object'; + if (isObject && typeof shouldScroll.selector === 'string') { + var el = document.querySelector(shouldScroll.selector); + if (el) { + var offset = shouldScroll.offset && typeof shouldScroll.offset === 'object' ? shouldScroll.offset : {}; + offset = normalizeOffset(offset); + position = getElementPosition(el, offset); + } else if (isValidPosition(shouldScroll)) { + position = normalizePosition(shouldScroll); + } + } else if (isObject && isValidPosition(shouldScroll)) { + position = normalizePosition(shouldScroll); + } + + if (position) { + window.scrollTo(position.x, position.y); + } +} + +/* */ + +var supportsPushState = inBrowser && (function () { + var ua = window.navigator.userAgent; + + if ( + (ua.indexOf('Android 2.') !== -1 || ua.indexOf('Android 4.0') !== -1) && + ua.indexOf('Mobile Safari') !== -1 && + ua.indexOf('Chrome') === -1 && + ua.indexOf('Windows Phone') === -1 + ) { + return false + } + + return window.history && 'pushState' in window.history +})(); + +// use User Timing server (if present) for more accurate key precision +var Time = inBrowser && window.performance && window.performance.now + ? window.performance + : Date; + +var _key = genKey(); + +function genKey () { + return Time.now().toFixed(3) +} + +function getStateKey () { + return _key +} + +function setStateKey (key) { + _key = key; +} + +function pushState (url, replace) { + saveScrollPosition(); + // try...catch the pushState call to get around Safari + // DOM Exception 18 where it limits to 100 pushState calls + var history = window.history; + try { + if (replace) { + history.replaceState({ key: _key }, '', url); + } else { + _key = genKey(); + history.pushState({ key: _key }, '', url); + } + } catch (e) { + window.location[replace ? 'replace' : 'assign'](url); + } +} + +function replaceState (url) { + pushState(url, true); +} + +/* */ + +function runQueue (queue, fn, cb) { + var step = function (index) { + if (index >= queue.length) { + cb(); + } else { + if (queue[index]) { + fn(queue[index], function () { + step(index + 1); + }); + } else { + step(index + 1); + } + } + }; + step(0); +} + +/* */ + +function resolveAsyncComponents (matched) { + return function (to, from, next) { + var hasAsync = false; + var pending = 0; + var error = null; + + flatMapComponents(matched, function (def, _, match, key) { + // if it's a function and doesn't have cid attached, + // assume it's an async component resolve function. + // we are not using Vue's default async resolving mechanism because + // we want to halt the navigation until the incoming component has been + // resolved. + if (typeof def === 'function' && def.cid === undefined) { + hasAsync = true; + pending++; + + var resolve = once(function (resolvedDef) { + if (isESModule(resolvedDef)) { + resolvedDef = resolvedDef.default; + } + // save resolved on async factory in case it's used elsewhere + def.resolved = typeof resolvedDef === 'function' + ? resolvedDef + : _Vue.extend(resolvedDef); + match.components[key] = resolvedDef; + pending--; + if (pending <= 0) { + next(); + } + }); + + var reject = once(function (reason) { + var msg = "Failed to resolve async component " + key + ": " + reason; + "development" !== 'production' && warn(false, msg); + if (!error) { + error = isError(reason) + ? reason + : new Error(msg); + next(error); + } + }); + + var res; + try { + res = def(resolve, reject); + } catch (e) { + reject(e); + } + if (res) { + if (typeof res.then === 'function') { + res.then(resolve, reject); + } else { + // new syntax in Vue 2.3 + var comp = res.component; + if (comp && typeof comp.then === 'function') { + comp.then(resolve, reject); + } + } + } + } + }); + + if (!hasAsync) { next(); } + } +} + +function flatMapComponents ( + matched, + fn +) { + return flatten(matched.map(function (m) { + return Object.keys(m.components).map(function (key) { return fn( + m.components[key], + m.instances[key], + m, key + ); }) + })) +} + +function flatten (arr) { + return Array.prototype.concat.apply([], arr) +} + +var hasSymbol = + typeof Symbol === 'function' && + typeof Symbol.toStringTag === 'symbol'; + +function isESModule (obj) { + return obj.__esModule || (hasSymbol && obj[Symbol.toStringTag] === 'Module') +} + +// in Webpack 2, require.ensure now also returns a Promise +// so the resolve/reject functions may get called an extra time +// if the user uses an arrow function shorthand that happens to +// return that Promise. +function once (fn) { + var called = false; + return function () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + if (called) { return } + called = true; + return fn.apply(this, args) + } +} + +/* */ + +var History = function History (router, base) { + this.router = router; + this.base = normalizeBase(base); + // start with a route object that stands for "nowhere" + this.current = START; + this.pending = null; + this.ready = false; + this.readyCbs = []; + this.readyErrorCbs = []; + this.errorCbs = []; +}; + +History.prototype.listen = function listen (cb) { + this.cb = cb; +}; + +History.prototype.onReady = function onReady (cb, errorCb) { + if (this.ready) { + cb(); + } else { + this.readyCbs.push(cb); + if (errorCb) { + this.readyErrorCbs.push(errorCb); + } + } +}; + +History.prototype.onError = function onError (errorCb) { + this.errorCbs.push(errorCb); +}; + +History.prototype.transitionTo = function transitionTo (location, onComplete, onAbort) { + var this$1 = this; + + var route = this.router.match(location, this.current); + this.confirmTransition(route, function () { + this$1.updateRoute(route); + onComplete && onComplete(route); + this$1.ensureURL(); + + // fire ready cbs once + if (!this$1.ready) { + this$1.ready = true; + this$1.readyCbs.forEach(function (cb) { cb(route); }); + } + }, function (err) { + if (onAbort) { + onAbort(err); + } + if (err && !this$1.ready) { + this$1.ready = true; + this$1.readyErrorCbs.forEach(function (cb) { cb(err); }); + } + }); +}; + +History.prototype.confirmTransition = function confirmTransition (route, onComplete, onAbort) { + var this$1 = this; + + var current = this.current; + var abort = function (err) { + if (isError(err)) { + if (this$1.errorCbs.length) { + this$1.errorCbs.forEach(function (cb) { cb(err); }); + } else { + warn(false, 'uncaught error during route navigation:'); + console.error(err); + } + } + onAbort && onAbort(err); + }; + if ( + isSameRoute(route, current) && + // in the case the route map has been dynamically appended to + route.matched.length === current.matched.length + ) { + this.ensureURL(); + return abort() + } + + var ref = resolveQueue(this.current.matched, route.matched); + var updated = ref.updated; + var deactivated = ref.deactivated; + var activated = ref.activated; + + var queue = [].concat( + // in-component leave guards + extractLeaveGuards(deactivated), + // global before hooks + this.router.beforeHooks, + // in-component update hooks + extractUpdateHooks(updated), + // in-config enter guards + activated.map(function (m) { return m.beforeEnter; }), + // async components + resolveAsyncComponents(activated) + ); + + this.pending = route; + var iterator = function (hook, next) { + if (this$1.pending !== route) { + return abort() + } + try { + hook(route, current, function (to) { + if (to === false || isError(to)) { + // next(false) -> abort navigation, ensure current URL + this$1.ensureURL(true); + abort(to); + } else if ( + typeof to === 'string' || + (typeof to === 'object' && ( + typeof to.path === 'string' || + typeof to.name === 'string' + )) + ) { + // next('/') or next({ path: '/' }) -> redirect + abort(); + if (typeof to === 'object' && to.replace) { + this$1.replace(to); + } else { + this$1.push(to); + } + } else { + // confirm transition and pass on the value + next(to); + } + }); + } catch (e) { + abort(e); + } + }; + + runQueue(queue, iterator, function () { + var postEnterCbs = []; + var isValid = function () { return this$1.current === route; }; + // wait until async components are resolved before + // extracting in-component enter guards + var enterGuards = extractEnterGuards(activated, postEnterCbs, isValid); + var queue = enterGuards.concat(this$1.router.resolveHooks); + runQueue(queue, iterator, function () { + if (this$1.pending !== route) { + return abort() + } + this$1.pending = null; + onComplete(route); + if (this$1.router.app) { + this$1.router.app.$nextTick(function () { + postEnterCbs.forEach(function (cb) { cb(); }); + }); + } + }); + }); +}; + +History.prototype.updateRoute = function updateRoute (route) { + var prev = this.current; + this.current = route; + this.cb && this.cb(route); + this.router.afterHooks.forEach(function (hook) { + hook && hook(route, prev); + }); +}; + +function normalizeBase (base) { + if (!base) { + if (inBrowser) { + // respect tag + var baseEl = document.querySelector('base'); + base = (baseEl && baseEl.getAttribute('href')) || '/'; + // strip full URL origin + base = base.replace(/^https?:\/\/[^\/]+/, ''); + } else { + base = '/'; + } + } + // make sure there's the starting slash + if (base.charAt(0) !== '/') { + base = '/' + base; + } + // remove trailing slash + return base.replace(/\/$/, '') +} + +function resolveQueue ( + current, + next +) { + var i; + var max = Math.max(current.length, next.length); + for (i = 0; i < max; i++) { + if (current[i] !== next[i]) { + break + } + } + return { + updated: next.slice(0, i), + activated: next.slice(i), + deactivated: current.slice(i) + } +} + +function extractGuards ( + records, + name, + bind, + reverse +) { + var guards = flatMapComponents(records, function (def, instance, match, key) { + var guard = extractGuard(def, name); + if (guard) { + return Array.isArray(guard) + ? guard.map(function (guard) { return bind(guard, instance, match, key); }) + : bind(guard, instance, match, key) + } + }); + return flatten(reverse ? guards.reverse() : guards) +} + +function extractGuard ( + def, + key +) { + if (typeof def !== 'function') { + // extend now so that global mixins are applied. + def = _Vue.extend(def); + } + return def.options[key] +} + +function extractLeaveGuards (deactivated) { + return extractGuards(deactivated, 'beforeRouteLeave', bindGuard, true) +} + +function extractUpdateHooks (updated) { + return extractGuards(updated, 'beforeRouteUpdate', bindGuard) +} + +function bindGuard (guard, instance) { + if (instance) { + return function boundRouteGuard () { + return guard.apply(instance, arguments) + } + } +} + +function extractEnterGuards ( + activated, + cbs, + isValid +) { + return extractGuards(activated, 'beforeRouteEnter', function (guard, _, match, key) { + return bindEnterGuard(guard, match, key, cbs, isValid) + }) +} + +function bindEnterGuard ( + guard, + match, + key, + cbs, + isValid +) { + return function routeEnterGuard (to, from, next) { + return guard(to, from, function (cb) { + next(cb); + if (typeof cb === 'function') { + cbs.push(function () { + // #750 + // if a router-view is wrapped with an out-in transition, + // the instance may not have been registered at this time. + // we will need to poll for registration until current route + // is no longer valid. + poll(cb, match.instances, key, isValid); + }); + } + }) + } +} + +function poll ( + cb, // somehow flow cannot infer this is a function + instances, + key, + isValid +) { + if ( + instances[key] && + !instances[key]._isBeingDestroyed // do not reuse being destroyed instance + ) { + cb(instances[key]); + } else if (isValid()) { + setTimeout(function () { + poll(cb, instances, key, isValid); + }, 16); + } +} + +/* */ + +var HTML5History = (function (History$$1) { + function HTML5History (router, base) { + var this$1 = this; + + History$$1.call(this, router, base); + + var expectScroll = router.options.scrollBehavior; + var supportsScroll = supportsPushState && expectScroll; + + if (supportsScroll) { + setupScroll(); + } + + var initLocation = getLocation(this.base); + window.addEventListener('popstate', function (e) { + var current = this$1.current; + + // Avoiding first `popstate` event dispatched in some browsers but first + // history route not updated since async guard at the same time. + var location = getLocation(this$1.base); + if (this$1.current === START && location === initLocation) { + return + } + + this$1.transitionTo(location, function (route) { + if (supportsScroll) { + handleScroll(router, route, current, true); + } + }); + }); + } + + if ( History$$1 ) HTML5History.__proto__ = History$$1; + HTML5History.prototype = Object.create( History$$1 && History$$1.prototype ); + HTML5History.prototype.constructor = HTML5History; + + HTML5History.prototype.go = function go (n) { + window.history.go(n); + }; + + HTML5History.prototype.push = function push (location, onComplete, onAbort) { + var this$1 = this; + + var ref = this; + var fromRoute = ref.current; + this.transitionTo(location, function (route) { + pushState(cleanPath(this$1.base + route.fullPath)); + handleScroll(this$1.router, route, fromRoute, false); + onComplete && onComplete(route); + }, onAbort); + }; + + HTML5History.prototype.replace = function replace (location, onComplete, onAbort) { + var this$1 = this; + + var ref = this; + var fromRoute = ref.current; + this.transitionTo(location, function (route) { + replaceState(cleanPath(this$1.base + route.fullPath)); + handleScroll(this$1.router, route, fromRoute, false); + onComplete && onComplete(route); + }, onAbort); + }; + + HTML5History.prototype.ensureURL = function ensureURL (push) { + if (getLocation(this.base) !== this.current.fullPath) { + var current = cleanPath(this.base + this.current.fullPath); + push ? pushState(current) : replaceState(current); + } + }; + + HTML5History.prototype.getCurrentLocation = function getCurrentLocation () { + return getLocation(this.base) + }; + + return HTML5History; +}(History)); + +function getLocation (base) { + var path = decodeURI(window.location.pathname); + if (base && path.indexOf(base) === 0) { + path = path.slice(base.length); + } + return (path || '/') + window.location.search + window.location.hash +} + +/* */ + +var HashHistory = (function (History$$1) { + function HashHistory (router, base, fallback) { + History$$1.call(this, router, base); + // check history fallback deeplinking + if (fallback && checkFallback(this.base)) { + return + } + ensureSlash(); + } + + if ( History$$1 ) HashHistory.__proto__ = History$$1; + HashHistory.prototype = Object.create( History$$1 && History$$1.prototype ); + HashHistory.prototype.constructor = HashHistory; + + // this is delayed until the app mounts + // to avoid the hashchange listener being fired too early + HashHistory.prototype.setupListeners = function setupListeners () { + var this$1 = this; + + var router = this.router; + var expectScroll = router.options.scrollBehavior; + var supportsScroll = supportsPushState && expectScroll; + + if (supportsScroll) { + setupScroll(); + } + + window.addEventListener(supportsPushState ? 'popstate' : 'hashchange', function () { + var current = this$1.current; + if (!ensureSlash()) { + return + } + this$1.transitionTo(getHash(), function (route) { + if (supportsScroll) { + handleScroll(this$1.router, route, current, true); + } + if (!supportsPushState) { + replaceHash(route.fullPath); + } + }); + }); + }; + + HashHistory.prototype.push = function push (location, onComplete, onAbort) { + var this$1 = this; + + var ref = this; + var fromRoute = ref.current; + this.transitionTo(location, function (route) { + pushHash(route.fullPath); + handleScroll(this$1.router, route, fromRoute, false); + onComplete && onComplete(route); + }, onAbort); + }; + + HashHistory.prototype.replace = function replace (location, onComplete, onAbort) { + var this$1 = this; + + var ref = this; + var fromRoute = ref.current; + this.transitionTo(location, function (route) { + replaceHash(route.fullPath); + handleScroll(this$1.router, route, fromRoute, false); + onComplete && onComplete(route); + }, onAbort); + }; + + HashHistory.prototype.go = function go (n) { + window.history.go(n); + }; + + HashHistory.prototype.ensureURL = function ensureURL (push) { + var current = this.current.fullPath; + if (getHash() !== current) { + push ? pushHash(current) : replaceHash(current); + } + }; + + HashHistory.prototype.getCurrentLocation = function getCurrentLocation () { + return getHash() + }; + + return HashHistory; +}(History)); + +function checkFallback (base) { + var location = getLocation(base); + if (!/^\/#/.test(location)) { + window.location.replace( + cleanPath(base + '/#' + location) + ); + return true + } +} + +function ensureSlash () { + var path = getHash(); + if (path.charAt(0) === '/') { + return true + } + replaceHash('/' + path); + return false +} + +function getHash () { + // We can't use window.location.hash here because it's not + // consistent across browsers - Firefox will pre-decode it! + var href = window.location.href; + var index = href.indexOf('#'); + return index === -1 ? '' : decodeURI(href.slice(index + 1)) +} + +function getUrl (path) { + var href = window.location.href; + var i = href.indexOf('#'); + var base = i >= 0 ? href.slice(0, i) : href; + return (base + "#" + path) +} + +function pushHash (path) { + if (supportsPushState) { + pushState(getUrl(path)); + } else { + window.location.hash = path; + } +} + +function replaceHash (path) { + if (supportsPushState) { + replaceState(getUrl(path)); + } else { + window.location.replace(getUrl(path)); + } +} + +/* */ + +var AbstractHistory = (function (History$$1) { + function AbstractHistory (router, base) { + History$$1.call(this, router, base); + this.stack = []; + this.index = -1; + } + + if ( History$$1 ) AbstractHistory.__proto__ = History$$1; + AbstractHistory.prototype = Object.create( History$$1 && History$$1.prototype ); + AbstractHistory.prototype.constructor = AbstractHistory; + + AbstractHistory.prototype.push = function push (location, onComplete, onAbort) { + var this$1 = this; + + this.transitionTo(location, function (route) { + this$1.stack = this$1.stack.slice(0, this$1.index + 1).concat(route); + this$1.index++; + onComplete && onComplete(route); + }, onAbort); + }; + + AbstractHistory.prototype.replace = function replace (location, onComplete, onAbort) { + var this$1 = this; + + this.transitionTo(location, function (route) { + this$1.stack = this$1.stack.slice(0, this$1.index).concat(route); + onComplete && onComplete(route); + }, onAbort); + }; + + AbstractHistory.prototype.go = function go (n) { + var this$1 = this; + + var targetIndex = this.index + n; + if (targetIndex < 0 || targetIndex >= this.stack.length) { + return + } + var route = this.stack[targetIndex]; + this.confirmTransition(route, function () { + this$1.index = targetIndex; + this$1.updateRoute(route); + }); + }; + + AbstractHistory.prototype.getCurrentLocation = function getCurrentLocation () { + var current = this.stack[this.stack.length - 1]; + return current ? current.fullPath : '/' + }; + + AbstractHistory.prototype.ensureURL = function ensureURL () { + // noop + }; + + return AbstractHistory; +}(History)); + +/* */ + + + +var VueRouter = function VueRouter (options) { + if ( options === void 0 ) options = {}; + + this.app = null; + this.apps = []; + this.options = options; + this.beforeHooks = []; + this.resolveHooks = []; + this.afterHooks = []; + this.matcher = createMatcher(options.routes || [], this); + + var mode = options.mode || 'hash'; + this.fallback = mode === 'history' && !supportsPushState && options.fallback !== false; + if (this.fallback) { + mode = 'hash'; + } + if (!inBrowser) { + mode = 'abstract'; + } + this.mode = mode; + + switch (mode) { + case 'history': + this.history = new HTML5History(this, options.base); + break + case 'hash': + this.history = new HashHistory(this, options.base, this.fallback); + break + case 'abstract': + this.history = new AbstractHistory(this, options.base); + break + default: + { + assert(false, ("invalid mode: " + mode)); + } + } +}; + +var prototypeAccessors = { currentRoute: { configurable: true } }; + +VueRouter.prototype.match = function match ( + raw, + current, + redirectedFrom +) { + return this.matcher.match(raw, current, redirectedFrom) +}; + +prototypeAccessors.currentRoute.get = function () { + return this.history && this.history.current +}; + +VueRouter.prototype.init = function init (app /* Vue component instance */) { + var this$1 = this; + + "development" !== 'production' && assert( + install.installed, + "not installed. Make sure to call `Vue.use(VueRouter)` " + + "before creating root instance." + ); + + this.apps.push(app); + + // main app already initialized. + if (this.app) { + return + } + + this.app = app; + + var history = this.history; + + if (history instanceof HTML5History) { + history.transitionTo(history.getCurrentLocation()); + } else if (history instanceof HashHistory) { + var setupHashListener = function () { + history.setupListeners(); + }; + history.transitionTo( + history.getCurrentLocation(), + setupHashListener, + setupHashListener + ); + } + + history.listen(function (route) { + this$1.apps.forEach(function (app) { + app._route = route; + }); + }); +}; + +VueRouter.prototype.beforeEach = function beforeEach (fn) { + return registerHook(this.beforeHooks, fn) +}; + +VueRouter.prototype.beforeResolve = function beforeResolve (fn) { + return registerHook(this.resolveHooks, fn) +}; + +VueRouter.prototype.afterEach = function afterEach (fn) { + return registerHook(this.afterHooks, fn) +}; + +VueRouter.prototype.onReady = function onReady (cb, errorCb) { + this.history.onReady(cb, errorCb); +}; + +VueRouter.prototype.onError = function onError (errorCb) { + this.history.onError(errorCb); +}; + +VueRouter.prototype.push = function push (location, onComplete, onAbort) { + this.history.push(location, onComplete, onAbort); +}; + +VueRouter.prototype.replace = function replace (location, onComplete, onAbort) { + this.history.replace(location, onComplete, onAbort); +}; + +VueRouter.prototype.go = function go (n) { + this.history.go(n); +}; + +VueRouter.prototype.back = function back () { + this.go(-1); +}; + +VueRouter.prototype.forward = function forward () { + this.go(1); +}; + +VueRouter.prototype.getMatchedComponents = function getMatchedComponents (to) { + var route = to + ? to.matched + ? to + : this.resolve(to).route + : this.currentRoute; + if (!route) { + return [] + } + return [].concat.apply([], route.matched.map(function (m) { + return Object.keys(m.components).map(function (key) { + return m.components[key] + }) + })) +}; + +VueRouter.prototype.resolve = function resolve ( + to, + current, + append +) { + var location = normalizeLocation( + to, + current || this.history.current, + append, + this + ); + var route = this.match(location, current); + var fullPath = route.redirectedFrom || route.fullPath; + var base = this.history.base; + var href = createHref(base, fullPath, this.mode); + return { + location: location, + route: route, + href: href, + // for backwards compat + normalizedTo: location, + resolved: route + } +}; + +VueRouter.prototype.addRoutes = function addRoutes (routes) { + this.matcher.addRoutes(routes); + if (this.history.current !== START) { + this.history.transitionTo(this.history.getCurrentLocation()); + } +}; + +Object.defineProperties( VueRouter.prototype, prototypeAccessors ); + +function registerHook (list, fn) { + list.push(fn); + return function () { + var i = list.indexOf(fn); + if (i > -1) { list.splice(i, 1); } + } +} + +function createHref (base, fullPath, mode) { + var path = mode === 'hash' ? '#' + fullPath : fullPath; + return base ? cleanPath(base + '/' + path) : path +} + +VueRouter.install = install; +VueRouter.version = '3.0.2'; + +if (inBrowser && window.Vue) { + window.Vue.use(VueRouter); +} + +return VueRouter; + +}))); diff --git a/statics/www/lib/vue.js b/statics/www/lib/vue.js new file mode 100644 index 0000000..4be2ab4 --- /dev/null +++ b/statics/www/lib/vue.js @@ -0,0 +1,11855 @@ +/*! + * Vue.js v2.6.6 + * (c) 2014-2019 Evan You + * Released under the MIT License. + */ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global = global || self, global.Vue = factory()); +}(this, function () { 'use strict'; + + /* */ + + var emptyObject = Object.freeze({}); + + // These helpers produce better VM code in JS engines due to their + // explicitness and function inlining. + function isUndef (v) { + return v === undefined || v === null + } + + function isDef (v) { + return v !== undefined && v !== null + } + + function isTrue (v) { + return v === true + } + + function isFalse (v) { + return v === false + } + + /** + * Check if value is primitive. + */ + function isPrimitive (value) { + return ( + typeof value === 'string' || + typeof value === 'number' || + // $flow-disable-line + typeof value === 'symbol' || + typeof value === 'boolean' + ) + } + + /** + * Quick object check - this is primarily used to tell + * Objects from primitive values when we know the value + * is a JSON-compliant type. + */ + function isObject (obj) { + return obj !== null && typeof obj === 'object' + } + + /** + * Get the raw type string of a value, e.g., [object Object]. + */ + var _toString = Object.prototype.toString; + + function toRawType (value) { + return _toString.call(value).slice(8, -1) + } + + /** + * Strict object type check. Only returns true + * for plain JavaScript objects. + */ + function isPlainObject (obj) { + return _toString.call(obj) === '[object Object]' + } + + function isRegExp (v) { + return _toString.call(v) === '[object RegExp]' + } + + /** + * Check if val is a valid array index. + */ + function isValidArrayIndex (val) { + var n = parseFloat(String(val)); + return n >= 0 && Math.floor(n) === n && isFinite(val) + } + + function isPromise (val) { + return ( + isDef(val) && + typeof val.then === 'function' && + typeof val.catch === 'function' + ) + } + + /** + * Convert a value to a string that is actually rendered. + */ + function toString (val) { + return val == null + ? '' + : Array.isArray(val) || (isPlainObject(val) && val.toString === _toString) + ? JSON.stringify(val, null, 2) + : String(val) + } + + /** + * Convert an input value to a number for persistence. + * If the conversion fails, return original string. + */ + function toNumber (val) { + var n = parseFloat(val); + return isNaN(n) ? val : n + } + + /** + * Make a map and return a function for checking if a key + * is in that map. + */ + function makeMap ( + str, + expectsLowerCase + ) { + var map = Object.create(null); + var list = str.split(','); + for (var i = 0; i < list.length; i++) { + map[list[i]] = true; + } + return expectsLowerCase + ? function (val) { return map[val.toLowerCase()]; } + : function (val) { return map[val]; } + } + + /** + * Check if a tag is a built-in tag. + */ + var isBuiltInTag = makeMap('slot,component', true); + + /** + * Check if an attribute is a reserved attribute. + */ + var isReservedAttribute = makeMap('key,ref,slot,slot-scope,is'); + + /** + * Remove an item from an array. + */ + function remove (arr, item) { + if (arr.length) { + var index = arr.indexOf(item); + if (index > -1) { + return arr.splice(index, 1) + } + } + } + + /** + * Check whether an object has the property. + */ + var hasOwnProperty = Object.prototype.hasOwnProperty; + function hasOwn (obj, key) { + return hasOwnProperty.call(obj, key) + } + + /** + * Create a cached version of a pure function. + */ + function cached (fn) { + var cache = Object.create(null); + return (function cachedFn (str) { + var hit = cache[str]; + return hit || (cache[str] = fn(str)) + }) + } + + /** + * Camelize a hyphen-delimited string. + */ + var camelizeRE = /-(\w)/g; + var camelize = cached(function (str) { + return str.replace(camelizeRE, function (_, c) { return c ? c.toUpperCase() : ''; }) + }); + + /** + * Capitalize a string. + */ + var capitalize = cached(function (str) { + return str.charAt(0).toUpperCase() + str.slice(1) + }); + + /** + * Hyphenate a camelCase string. + */ + var hyphenateRE = /\B([A-Z])/g; + var hyphenate = cached(function (str) { + return str.replace(hyphenateRE, '-$1').toLowerCase() + }); + + /** + * Simple bind polyfill for environments that do not support it, + * e.g., PhantomJS 1.x. Technically, we don't need this anymore + * since native bind is now performant enough in most browsers. + * But removing it would mean breaking code that was able to run in + * PhantomJS 1.x, so this must be kept for backward compatibility. + */ + + /* istanbul ignore next */ + function polyfillBind (fn, ctx) { + function boundFn (a) { + var l = arguments.length; + return l + ? l > 1 + ? fn.apply(ctx, arguments) + : fn.call(ctx, a) + : fn.call(ctx) + } + + boundFn._length = fn.length; + return boundFn + } + + function nativeBind (fn, ctx) { + return fn.bind(ctx) + } + + var bind = Function.prototype.bind + ? nativeBind + : polyfillBind; + + /** + * Convert an Array-like object to a real Array. + */ + function toArray (list, start) { + start = start || 0; + var i = list.length - start; + var ret = new Array(i); + while (i--) { + ret[i] = list[i + start]; + } + return ret + } + + /** + * Mix properties into target object. + */ + function extend (to, _from) { + for (var key in _from) { + to[key] = _from[key]; + } + return to + } + + /** + * Merge an Array of Objects into a single Object. + */ + function toObject (arr) { + var res = {}; + for (var i = 0; i < arr.length; i++) { + if (arr[i]) { + extend(res, arr[i]); + } + } + return res + } + + /* eslint-disable no-unused-vars */ + + /** + * Perform no operation. + * Stubbing args to make Flow happy without leaving useless transpiled code + * with ...rest (https://flow.org/blog/2017/05/07/Strict-Function-Call-Arity/). + */ + function noop (a, b, c) {} + + /** + * Always return false. + */ + var no = function (a, b, c) { return false; }; + + /* eslint-enable no-unused-vars */ + + /** + * Return the same value. + */ + var identity = function (_) { return _; }; + + /** + * Generate a string containing static keys from compiler modules. + */ + function genStaticKeys (modules) { + return modules.reduce(function (keys, m) { + return keys.concat(m.staticKeys || []) + }, []).join(',') + } + + /** + * Check if two values are loosely equal - that is, + * if they are plain objects, do they have the same shape? + */ + function looseEqual (a, b) { + if (a === b) { return true } + var isObjectA = isObject(a); + var isObjectB = isObject(b); + if (isObjectA && isObjectB) { + try { + var isArrayA = Array.isArray(a); + var isArrayB = Array.isArray(b); + if (isArrayA && isArrayB) { + return a.length === b.length && a.every(function (e, i) { + return looseEqual(e, b[i]) + }) + } else if (a instanceof Date && b instanceof Date) { + return a.getTime() === b.getTime() + } else if (!isArrayA && !isArrayB) { + var keysA = Object.keys(a); + var keysB = Object.keys(b); + return keysA.length === keysB.length && keysA.every(function (key) { + return looseEqual(a[key], b[key]) + }) + } else { + /* istanbul ignore next */ + return false + } + } catch (e) { + /* istanbul ignore next */ + return false + } + } else if (!isObjectA && !isObjectB) { + return String(a) === String(b) + } else { + return false + } + } + + /** + * Return the first index at which a loosely equal value can be + * found in the array (if value is a plain object, the array must + * contain an object of the same shape), or -1 if it is not present. + */ + function looseIndexOf (arr, val) { + for (var i = 0; i < arr.length; i++) { + if (looseEqual(arr[i], val)) { return i } + } + return -1 + } + + /** + * Ensure a function is called only once. + */ + function once (fn) { + var called = false; + return function () { + if (!called) { + called = true; + fn.apply(this, arguments); + } + } + } + + var SSR_ATTR = 'upload-server-rendered'; + + var ASSET_TYPES = [ + 'component', + 'directive', + 'filter' + ]; + + var LIFECYCLE_HOOKS = [ + 'beforeCreate', + 'created', + 'beforeMount', + 'mounted', + 'beforeUpdate', + 'updated', + 'beforeDestroy', + 'destroyed', + 'activated', + 'deactivated', + 'errorCaptured', + 'serverPrefetch' + ]; + + /* */ + + + + var config = ({ + /** + * Option merge strategies (used in core/util/options) + */ + // $flow-disable-line + optionMergeStrategies: Object.create(null), + + /** + * Whether to suppress warnings. + */ + silent: false, + + /** + * Show production mode tip message on boot? + */ + productionTip: "development" !== 'production', + + /** + * Whether to enable devtools + */ + devtools: "development" !== 'production', + + /** + * Whether to record perf + */ + performance: false, + + /** + * Error handler for watcher errors + */ + errorHandler: null, + + /** + * Warn handler for watcher warns + */ + warnHandler: null, + + /** + * Ignore certain ext elements + */ + ignoredElements: [], + + /** + * Custom user key aliases for v-on + */ + // $flow-disable-line + keyCodes: Object.create(null), + + /** + * Check if a tag is reserved so that it cannot be registered as a + * component. This is platform-dependent and may be overwritten. + */ + isReservedTag: no, + + /** + * Check if an attribute is reserved so that it cannot be used as a component + * prop. This is platform-dependent and may be overwritten. + */ + isReservedAttr: no, + + /** + * Check if a tag is an unknown element. + * Platform-dependent. + */ + isUnknownElement: no, + + /** + * Get the namespace of an element + */ + getTagNamespace: noop, + + /** + * Parse the real tag name for the specific platform. + */ + parsePlatformTagName: identity, + + /** + * Check if an attribute must be bound using property, e.g. value + * Platform-dependent. + */ + mustUseProp: no, + + /** + * Perform updates asynchronously. Intended to be used by Vue Test Utils + * This will significantly reduce performance if set to false. + */ + async: true, + + /** + * Exposed for legacy reasons + */ + _lifecycleHooks: LIFECYCLE_HOOKS + }); + + /* */ + + /** + * unicode letters used for parsing html tags, component names and property paths. + * using https://www.w3.org/TR/html53/semantics-scripting.html#potentialcustomelementname + * skipping \u10000-\uEFFFF due to it freezing up PhantomJS + */ + var unicodeLetters = 'a-zA-Z\u00B7\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u037D\u037F-\u1FFF\u200C-\u200D\u203F-\u2040\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD'; + + /** + * Check if a string starts with $ or _ + */ + function isReserved (str) { + var c = (str + '').charCodeAt(0); + return c === 0x24 || c === 0x5F + } + + /** + * Define a property. + */ + function def (obj, key, val, enumerable) { + Object.defineProperty(obj, key, { + value: val, + enumerable: !!enumerable, + writable: true, + configurable: true + }); + } + + /** + * Parse simple path. + */ + var bailRE = new RegExp(("[^" + unicodeLetters + ".$_\\d]")); + function parsePath (path) { + if (bailRE.test(path)) { + return + } + var segments = path.split('.'); + return function (obj) { + for (var i = 0; i < segments.length; i++) { + if (!obj) { return } + obj = obj[segments[i]]; + } + return obj + } + } + + /* */ + + // can we use __proto__? + var hasProto = '__proto__' in {}; + + // Browser environment sniffing + var inBrowser = typeof window !== 'undefined'; + var inWeex = typeof WXEnvironment !== 'undefined' && !!WXEnvironment.platform; + var weexPlatform = inWeex && WXEnvironment.platform.toLowerCase(); + var UA = inBrowser && window.navigator.userAgent.toLowerCase(); + var isIE = UA && /msie|trident/.test(UA); + var isIE9 = UA && UA.indexOf('msie 9.0') > 0; + var isEdge = UA && UA.indexOf('edge/') > 0; + var isAndroid = (UA && UA.indexOf('android') > 0) || (weexPlatform === 'android'); + var isIOS = (UA && /iphone|ipad|ipod|ios/.test(UA)) || (weexPlatform === 'ios'); + var isChrome = UA && /chrome\/\d+/.test(UA) && !isEdge; + var isPhantomJS = UA && /phantomjs/.test(UA); + var isFF = UA && UA.match(/firefox\/(\d+)/); + + // Firefox has a "watch" function on Object.prototype... + var nativeWatch = ({}).watch; + + var supportsPassive = false; + if (inBrowser) { + try { + var opts = {}; + Object.defineProperty(opts, 'passive', ({ + get: function get () { + /* istanbul ignore next */ + supportsPassive = true; + } + })); // https://github.com/facebook/flow/issues/285 + window.addEventListener('test-passive', null, opts); + } catch (e) {} + } + + // this needs to be lazy-evaled because vue may be required before + // vue-server-renderer can set VUE_ENV + var _isServer; + var isServerRendering = function () { + if (_isServer === undefined) { + /* istanbul ignore if */ + if (!inBrowser && !inWeex && typeof global !== 'undefined') { + // detect presence of vue-server-renderer and avoid + // Webpack shimming the process + _isServer = global['process'] && global['process'].env.VUE_ENV === 'server'; + } else { + _isServer = false; + } + } + return _isServer + }; + + // detect devtools + var devtools = inBrowser && window.__VUE_DEVTOOLS_GLOBAL_HOOK__; + + /* istanbul ignore next */ + function isNative (Ctor) { + return typeof Ctor === 'function' && /native code/.test(Ctor.toString()) + } + + var hasSymbol = + typeof Symbol !== 'undefined' && isNative(Symbol) && + typeof Reflect !== 'undefined' && isNative(Reflect.ownKeys); + + var _Set; + /* istanbul ignore if */ // $flow-disable-line + if (typeof Set !== 'undefined' && isNative(Set)) { + // use native Set when available. + _Set = Set; + } else { + // a non-standard Set polyfill that only works with primitive keys. + _Set = /*@__PURE__*/(function () { + function Set () { + this.set = Object.create(null); + } + Set.prototype.has = function has (key) { + return this.set[key] === true + }; + Set.prototype.add = function add (key) { + this.set[key] = true; + }; + Set.prototype.clear = function clear () { + this.set = Object.create(null); + }; + + return Set; + }()); + } + + /* */ + + var warn = noop; + var tip = noop; + var generateComponentTrace = (noop); // work around flow check + var formatComponentName = (noop); + + { + var hasConsole = typeof console !== 'undefined'; + var classifyRE = /(?:^|[-_])(\w)/g; + var classify = function (str) { return str + .replace(classifyRE, function (c) { return c.toUpperCase(); }) + .replace(/[-_]/g, ''); }; + + warn = function (msg, vm) { + var trace = vm ? generateComponentTrace(vm) : ''; + + if (config.warnHandler) { + config.warnHandler.call(null, msg, vm, trace); + } else if (hasConsole && (!config.silent)) { + console.error(("[Vue warn]: " + msg + trace)); + } + }; + + tip = function (msg, vm) { + if (hasConsole && (!config.silent)) { + console.warn("[Vue tip]: " + msg + ( + vm ? generateComponentTrace(vm) : '' + )); + } + }; + + formatComponentName = function (vm, includeFile) { + if (vm.$root === vm) { + return '' + } + var options = typeof vm === 'function' && vm.cid != null + ? vm.options + : vm._isVue + ? vm.$options || vm.constructor.options + : vm; + var name = options.name || options._componentTag; + var file = options.__file; + if (!name && file) { + var match = file.match(/([^/\\]+)\.vue$/); + name = match && match[1]; + } + + return ( + (name ? ("<" + (classify(name)) + ">") : "") + + (file && includeFile !== false ? (" at " + file) : '') + ) + }; + + var repeat = function (str, n) { + var res = ''; + while (n) { + if (n % 2 === 1) { res += str; } + if (n > 1) { str += str; } + n >>= 1; + } + return res + }; + + generateComponentTrace = function (vm) { + if (vm._isVue && vm.$parent) { + var tree = []; + var currentRecursiveSequence = 0; + while (vm) { + if (tree.length > 0) { + var last = tree[tree.length - 1]; + if (last.constructor === vm.constructor) { + currentRecursiveSequence++; + vm = vm.$parent; + continue + } else if (currentRecursiveSequence > 0) { + tree[tree.length - 1] = [last, currentRecursiveSequence]; + currentRecursiveSequence = 0; + } + } + tree.push(vm); + vm = vm.$parent; + } + return '\n\nfound in\n\n' + tree + .map(function (vm, i) { return ("" + (i === 0 ? '---> ' : repeat(' ', 5 + i * 2)) + (Array.isArray(vm) + ? ((formatComponentName(vm[0])) + "... (" + (vm[1]) + " recursive calls)") + : formatComponentName(vm))); }) + .join('\n') + } else { + return ("\n\n(found in " + (formatComponentName(vm)) + ")") + } + }; + } + + /* */ + + var uid = 0; + + /** + * A dep is an observable that can have multiple + * directives subscribing to it. + */ + var Dep = function Dep () { + this.id = uid++; + this.subs = []; + }; + + Dep.prototype.addSub = function addSub (sub) { + this.subs.push(sub); + }; + + Dep.prototype.removeSub = function removeSub (sub) { + remove(this.subs, sub); + }; + + Dep.prototype.depend = function depend () { + if (Dep.target) { + Dep.target.addDep(this); + } + }; + + Dep.prototype.notify = function notify () { + // stabilize the subscriber list first + var subs = this.subs.slice(); + if (!config.async) { + // subs aren't sorted in scheduler if not running async + // we need to sort them now to make sure they fire in correct + // order + subs.sort(function (a, b) { return a.id - b.id; }); + } + for (var i = 0, l = subs.length; i < l; i++) { + subs[i].update(); + } + }; + + // The current target watcher being evaluated. + // This is globally unique because only one watcher + // can be evaluated at a time. + Dep.target = null; + var targetStack = []; + + function pushTarget (target) { + targetStack.push(target); + Dep.target = target; + } + + function popTarget () { + targetStack.pop(); + Dep.target = targetStack[targetStack.length - 1]; + } + + /* */ + + var VNode = function VNode ( + tag, + data, + children, + text, + elm, + context, + componentOptions, + asyncFactory + ) { + this.tag = tag; + this.data = data; + this.children = children; + this.text = text; + this.elm = elm; + this.ns = undefined; + this.context = context; + this.fnContext = undefined; + this.fnOptions = undefined; + this.fnScopeId = undefined; + this.key = data && data.key; + this.componentOptions = componentOptions; + this.componentInstance = undefined; + this.parent = undefined; + this.raw = false; + this.isStatic = false; + this.isRootInsert = true; + this.isComment = false; + this.isCloned = false; + this.isOnce = false; + this.asyncFactory = asyncFactory; + this.asyncMeta = undefined; + this.isAsyncPlaceholder = false; + }; + + var prototypeAccessors = { child: { configurable: true } }; + + // DEPRECATED: alias for componentInstance for backwards compat. + /* istanbul ignore next */ + prototypeAccessors.child.get = function () { + return this.componentInstance + }; + + Object.defineProperties( VNode.prototype, prototypeAccessors ); + + var createEmptyVNode = function (text) { + if ( text === void 0 ) text = ''; + + var node = new VNode(); + node.text = text; + node.isComment = true; + return node + }; + + function createTextVNode (val) { + return new VNode(undefined, undefined, undefined, String(val)) + } + + // optimized shallow clone + // used for static nodes and slot nodes because they may be reused across + // multiple renders, cloning them avoids errors when DOM manipulations rely + // on their elm reference. + function cloneVNode (vnode) { + var cloned = new VNode( + vnode.tag, + vnode.data, + // #7975 + // clone children array to avoid mutating original in case of cloning + // a child. + vnode.children && vnode.children.slice(), + vnode.text, + vnode.elm, + vnode.context, + vnode.componentOptions, + vnode.asyncFactory + ); + cloned.ns = vnode.ns; + cloned.isStatic = vnode.isStatic; + cloned.key = vnode.key; + cloned.isComment = vnode.isComment; + cloned.fnContext = vnode.fnContext; + cloned.fnOptions = vnode.fnOptions; + cloned.fnScopeId = vnode.fnScopeId; + cloned.asyncMeta = vnode.asyncMeta; + cloned.isCloned = true; + return cloned + } + + /* + * not type checking this file because flow doesn't play well with + * dynamically accessing methods on Array prototype + */ + + var arrayProto = Array.prototype; + var arrayMethods = Object.create(arrayProto); + + var methodsToPatch = [ + 'push', + 'pop', + 'shift', + 'unshift', + 'splice', + 'sort', + 'reverse' + ]; + + /** + * Intercept mutating methods and emit events + */ + methodsToPatch.forEach(function (method) { + // cache original method + var original = arrayProto[method]; + def(arrayMethods, method, function mutator () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + var result = original.apply(this, args); + var ob = this.__ob__; + var inserted; + switch (method) { + case 'push': + case 'unshift': + inserted = args; + break + case 'splice': + inserted = args.slice(2); + break + } + if (inserted) { ob.observeArray(inserted); } + // notify change + ob.dep.notify(); + return result + }); + }); + + /* */ + + var arrayKeys = Object.getOwnPropertyNames(arrayMethods); + + /** + * In some cases we may want to disable observation inside a component's + * update computation. + */ + var shouldObserve = true; + + function toggleObserving (value) { + shouldObserve = value; + } + + /** + * Observer class that is attached to each observed + * object. Once attached, the observer converts the target + * object's property keys into getter/setters that + * collect dependencies and dispatch updates. + */ + var Observer = function Observer (value) { + this.value = value; + this.dep = new Dep(); + this.vmCount = 0; + def(value, '__ob__', this); + if (Array.isArray(value)) { + if (hasProto) { + protoAugment(value, arrayMethods); + } else { + copyAugment(value, arrayMethods, arrayKeys); + } + this.observeArray(value); + } else { + this.walk(value); + } + }; + + /** + * Walk through all properties and convert them into + * getter/setters. This method should only be called when + * value type is Object. + */ + Observer.prototype.walk = function walk (obj) { + var keys = Object.keys(obj); + for (var i = 0; i < keys.length; i++) { + defineReactive$$1(obj, keys[i]); + } + }; + + /** + * Observe a list of Array items. + */ + Observer.prototype.observeArray = function observeArray (items) { + for (var i = 0, l = items.length; i < l; i++) { + observe(items[i]); + } + }; + + // helpers + + /** + * Augment a target Object or Array by intercepting + * the prototype chain using __proto__ + */ + function protoAugment (target, src) { + /* eslint-disable no-proto */ + target.__proto__ = src; + /* eslint-enable no-proto */ + } + + /** + * Augment a target Object or Array by defining + * hidden properties. + */ + /* istanbul ignore next */ + function copyAugment (target, src, keys) { + for (var i = 0, l = keys.length; i < l; i++) { + var key = keys[i]; + def(target, key, src[key]); + } + } + + /** + * Attempt to create an observer instance for a value, + * returns the new observer if successfully observed, + * or the existing observer if the value already has one. + */ + function observe (value, asRootData) { + if (!isObject(value) || value instanceof VNode) { + return + } + var ob; + if (hasOwn(value, '__ob__') && value.__ob__ instanceof Observer) { + ob = value.__ob__; + } else if ( + shouldObserve && + !isServerRendering() && + (Array.isArray(value) || isPlainObject(value)) && + Object.isExtensible(value) && + !value._isVue + ) { + ob = new Observer(value); + } + if (asRootData && ob) { + ob.vmCount++; + } + return ob + } + + /** + * Define a reactive property on an Object. + */ + function defineReactive$$1 ( + obj, + key, + val, + customSetter, + shallow + ) { + var dep = new Dep(); + + var property = Object.getOwnPropertyDescriptor(obj, key); + if (property && property.configurable === false) { + return + } + + // cater for pre-defined getter/setters + var getter = property && property.get; + var setter = property && property.set; + if ((!getter || setter) && arguments.length === 2) { + val = obj[key]; + } + + var childOb = !shallow && observe(val); + Object.defineProperty(obj, key, { + enumerable: true, + configurable: true, + get: function reactiveGetter () { + var value = getter ? getter.call(obj) : val; + if (Dep.target) { + dep.depend(); + if (childOb) { + childOb.dep.depend(); + if (Array.isArray(value)) { + dependArray(value); + } + } + } + return value + }, + set: function reactiveSetter (newVal) { + var value = getter ? getter.call(obj) : val; + /* eslint-disable no-self-compare */ + if (newVal === value || (newVal !== newVal && value !== value)) { + return + } + /* eslint-enable no-self-compare */ + if (customSetter) { + customSetter(); + } + // #7981: for accessor properties without setter + if (getter && !setter) { return } + if (setter) { + setter.call(obj, newVal); + } else { + val = newVal; + } + childOb = !shallow && observe(newVal); + dep.notify(); + } + }); + } + + /** + * Set a property on an object. Adds the new property and + * triggers change notification if the property doesn't + * already exist. + */ + function set (target, key, val) { + if (isUndef(target) || isPrimitive(target) + ) { + warn(("Cannot set reactive property on undefined, null, or primitive value: " + ((target)))); + } + if (Array.isArray(target) && isValidArrayIndex(key)) { + target.length = Math.max(target.length, key); + target.splice(key, 1, val); + return val + } + if (key in target && !(key in Object.prototype)) { + target[key] = val; + return val + } + var ob = (target).__ob__; + if (target._isVue || (ob && ob.vmCount)) { + warn( + 'Avoid adding reactive properties to a Vue instance or its root $upload ' + + 'at runtime - declare it upfront in the upload option.' + ); + return val + } + if (!ob) { + target[key] = val; + return val + } + defineReactive$$1(ob.value, key, val); + ob.dep.notify(); + return val + } + + /** + * Delete a property and trigger change if necessary. + */ + function del (target, key) { + if (isUndef(target) || isPrimitive(target) + ) { + warn(("Cannot delete reactive property on undefined, null, or primitive value: " + ((target)))); + } + if (Array.isArray(target) && isValidArrayIndex(key)) { + target.splice(key, 1); + return + } + var ob = (target).__ob__; + if (target._isVue || (ob && ob.vmCount)) { + warn( + 'Avoid deleting properties on a Vue instance or its root $upload ' + + '- just set it to null.' + ); + return + } + if (!hasOwn(target, key)) { + return + } + delete target[key]; + if (!ob) { + return + } + ob.dep.notify(); + } + + /** + * Collect dependencies on array elements when the array is touched, since + * we cannot intercept array element access like property getters. + */ + function dependArray (value) { + for (var e = (void 0), i = 0, l = value.length; i < l; i++) { + e = value[i]; + e && e.__ob__ && e.__ob__.dep.depend(); + if (Array.isArray(e)) { + dependArray(e); + } + } + } + + /* */ + + /** + * Option overwriting strategies are functions that handle + * how to merge a parent option value and a child option + * value into the final value. + */ + var strats = config.optionMergeStrategies; + + /** + * Options with restrictions + */ + { + strats.el = strats.propsData = function (parent, child, vm, key) { + if (!vm) { + warn( + "option \"" + key + "\" can only be used during instance " + + 'creation with the `new` keyword.' + ); + } + return defaultStrat(parent, child) + }; + } + + /** + * Helper that recursively merges two upload objects together. + */ + function mergeData (to, from) { + if (!from) { return to } + var key, toVal, fromVal; + + var keys = hasSymbol + ? Reflect.ownKeys(from) + : Object.keys(from); + + for (var i = 0; i < keys.length; i++) { + key = keys[i]; + // in case the object is already observed... + if (key === '__ob__') { continue } + toVal = to[key]; + fromVal = from[key]; + if (!hasOwn(to, key)) { + set(to, key, fromVal); + } else if ( + toVal !== fromVal && + isPlainObject(toVal) && + isPlainObject(fromVal) + ) { + mergeData(toVal, fromVal); + } + } + return to + } + + /** + * Data + */ + function mergeDataOrFn ( + parentVal, + childVal, + vm + ) { + if (!vm) { + // in a Vue.extend merge, both should be functions + if (!childVal) { + return parentVal + } + if (!parentVal) { + return childVal + } + // when parentVal & childVal are both present, + // we need to return a function that returns the + // merged result of both functions... no need to + // check if parentVal is a function here because + // it has to be a function to pass previous merges. + return function mergedDataFn () { + return mergeData( + typeof childVal === 'function' ? childVal.call(this, this) : childVal, + typeof parentVal === 'function' ? parentVal.call(this, this) : parentVal + ) + } + } else { + return function mergedInstanceDataFn () { + // instance merge + var instanceData = typeof childVal === 'function' + ? childVal.call(vm, vm) + : childVal; + var defaultData = typeof parentVal === 'function' + ? parentVal.call(vm, vm) + : parentVal; + if (instanceData) { + return mergeData(instanceData, defaultData) + } else { + return defaultData + } + } + } + } + + strats.data = function ( + parentVal, + childVal, + vm + ) { + if (!vm) { + if (childVal && typeof childVal !== 'function') { + warn( + 'The "upload" option should be a function ' + + 'that returns a per-instance value in component ' + + 'definitions.', + vm + ); + + return parentVal + } + return mergeDataOrFn(parentVal, childVal) + } + + return mergeDataOrFn(parentVal, childVal, vm) + }; + + /** + * Hooks and props are merged as arrays. + */ + function mergeHook ( + parentVal, + childVal + ) { + var res = childVal + ? parentVal + ? parentVal.concat(childVal) + : Array.isArray(childVal) + ? childVal + : [childVal] + : parentVal; + return res + ? dedupeHooks(res) + : res + } + + function dedupeHooks (hooks) { + var res = []; + for (var i = 0; i < hooks.length; i++) { + if (res.indexOf(hooks[i]) === -1) { + res.push(hooks[i]); + } + } + return res + } + + LIFECYCLE_HOOKS.forEach(function (hook) { + strats[hook] = mergeHook; + }); + + /** + * Assets + * + * When a vm is present (instance creation), we need to do + * a three-way merge between constructor options, instance + * options and parent options. + */ + function mergeAssets ( + parentVal, + childVal, + vm, + key + ) { + var res = Object.create(parentVal || null); + if (childVal) { + assertObjectType(key, childVal, vm); + return extend(res, childVal) + } else { + return res + } + } + + ASSET_TYPES.forEach(function (type) { + strats[type + 's'] = mergeAssets; + }); + + /** + * Watchers. + * + * Watchers hashes should not overwrite one + * another, so we merge them as arrays. + */ + strats.watch = function ( + parentVal, + childVal, + vm, + key + ) { + // work around Firefox's Object.prototype.watch... + if (parentVal === nativeWatch) { parentVal = undefined; } + if (childVal === nativeWatch) { childVal = undefined; } + /* istanbul ignore if */ + if (!childVal) { return Object.create(parentVal || null) } + { + assertObjectType(key, childVal, vm); + } + if (!parentVal) { return childVal } + var ret = {}; + extend(ret, parentVal); + for (var key$1 in childVal) { + var parent = ret[key$1]; + var child = childVal[key$1]; + if (parent && !Array.isArray(parent)) { + parent = [parent]; + } + ret[key$1] = parent + ? parent.concat(child) + : Array.isArray(child) ? child : [child]; + } + return ret + }; + + /** + * Other object hashes. + */ + strats.props = + strats.methods = + strats.inject = + strats.computed = function ( + parentVal, + childVal, + vm, + key + ) { + if (childVal && "development" !== 'production') { + assertObjectType(key, childVal, vm); + } + if (!parentVal) { return childVal } + var ret = Object.create(null); + extend(ret, parentVal); + if (childVal) { extend(ret, childVal); } + return ret + }; + strats.provide = mergeDataOrFn; + + /** + * Default strategy. + */ + var defaultStrat = function (parentVal, childVal) { + return childVal === undefined + ? parentVal + : childVal + }; + + /** + * Validate component names + */ + function checkComponents (options) { + for (var key in options.components) { + validateComponentName(key); + } + } + + function validateComponentName (name) { + if (!new RegExp(("^[a-zA-Z][\\-\\.0-9_" + unicodeLetters + "]*$")).test(name)) { + warn( + 'Invalid component name: "' + name + '". Component names ' + + 'should conform to valid ext element name in html5 specification.' + ); + } + if (isBuiltInTag(name) || config.isReservedTag(name)) { + warn( + 'Do not use built-in or reserved HTML elements as component ' + + 'id: ' + name + ); + } + } + + /** + * Ensure all props option syntax are normalized into the + * Object-based format. + */ + function normalizeProps (options, vm) { + var props = options.props; + if (!props) { return } + var res = {}; + var i, val, name; + if (Array.isArray(props)) { + i = props.length; + while (i--) { + val = props[i]; + if (typeof val === 'string') { + name = camelize(val); + res[name] = { type: null }; + } else { + warn('props must be strings when using array syntax.'); + } + } + } else if (isPlainObject(props)) { + for (var key in props) { + val = props[key]; + name = camelize(key); + res[name] = isPlainObject(val) + ? val + : { type: val }; + } + } else { + warn( + "Invalid value for option \"props\": expected an Array or an Object, " + + "but got " + (toRawType(props)) + ".", + vm + ); + } + options.props = res; + } + + /** + * Normalize all injections into Object-based format + */ + function normalizeInject (options, vm) { + var inject = options.inject; + if (!inject) { return } + var normalized = options.inject = {}; + if (Array.isArray(inject)) { + for (var i = 0; i < inject.length; i++) { + normalized[inject[i]] = { from: inject[i] }; + } + } else if (isPlainObject(inject)) { + for (var key in inject) { + var val = inject[key]; + normalized[key] = isPlainObject(val) + ? extend({ from: key }, val) + : { from: val }; + } + } else { + warn( + "Invalid value for option \"inject\": expected an Array or an Object, " + + "but got " + (toRawType(inject)) + ".", + vm + ); + } + } + + /** + * Normalize raw function directives into object format. + */ + function normalizeDirectives (options) { + var dirs = options.directives; + if (dirs) { + for (var key in dirs) { + var def$$1 = dirs[key]; + if (typeof def$$1 === 'function') { + dirs[key] = { bind: def$$1, update: def$$1 }; + } + } + } + } + + function assertObjectType (name, value, vm) { + if (!isPlainObject(value)) { + warn( + "Invalid value for option \"" + name + "\": expected an Object, " + + "but got " + (toRawType(value)) + ".", + vm + ); + } + } + + /** + * Merge two option objects into a new one. + * Core utility used in both instantiation and inheritance. + */ + function mergeOptions ( + parent, + child, + vm + ) { + { + checkComponents(child); + } + + if (typeof child === 'function') { + child = child.options; + } + + normalizeProps(child, vm); + normalizeInject(child, vm); + normalizeDirectives(child); + + // Apply extends and mixins on the child options, + // but only if it is a raw options object that isn't + // the result of another mergeOptions call. + // Only merged options has the _base property. + if (!child._base) { + if (child.extends) { + parent = mergeOptions(parent, child.extends, vm); + } + if (child.mixins) { + for (var i = 0, l = child.mixins.length; i < l; i++) { + parent = mergeOptions(parent, child.mixins[i], vm); + } + } + } + + var options = {}; + var key; + for (key in parent) { + mergeField(key); + } + for (key in child) { + if (!hasOwn(parent, key)) { + mergeField(key); + } + } + function mergeField (key) { + var strat = strats[key] || defaultStrat; + options[key] = strat(parent[key], child[key], vm, key); + } + return options + } + + /** + * Resolve an asset. + * This function is used because child instances need access + * to assets defined in its ancestor chain. + */ + function resolveAsset ( + options, + type, + id, + warnMissing + ) { + /* istanbul ignore if */ + if (typeof id !== 'string') { + return + } + var assets = options[type]; + // check local registration variations first + if (hasOwn(assets, id)) { return assets[id] } + var camelizedId = camelize(id); + if (hasOwn(assets, camelizedId)) { return assets[camelizedId] } + var PascalCaseId = capitalize(camelizedId); + if (hasOwn(assets, PascalCaseId)) { return assets[PascalCaseId] } + // fallback to prototype chain + var res = assets[id] || assets[camelizedId] || assets[PascalCaseId]; + if (warnMissing && !res) { + warn( + 'Failed to resolve ' + type.slice(0, -1) + ': ' + id, + options + ); + } + return res + } + + /* */ + + + + function validateProp ( + key, + propOptions, + propsData, + vm + ) { + var prop = propOptions[key]; + var absent = !hasOwn(propsData, key); + var value = propsData[key]; + // boolean casting + var booleanIndex = getTypeIndex(Boolean, prop.type); + if (booleanIndex > -1) { + if (absent && !hasOwn(prop, 'default')) { + value = false; + } else if (value === '' || value === hyphenate(key)) { + // only cast empty string / same name to boolean if + // boolean has higher priority + var stringIndex = getTypeIndex(String, prop.type); + if (stringIndex < 0 || booleanIndex < stringIndex) { + value = true; + } + } + } + // check default value + if (value === undefined) { + value = getPropDefaultValue(vm, prop, key); + // since the default value is a fresh copy, + // make sure to observe it. + var prevShouldObserve = shouldObserve; + toggleObserving(true); + observe(value); + toggleObserving(prevShouldObserve); + } + { + assertProp(prop, key, value, vm, absent); + } + return value + } + + /** + * Get the default value of a prop. + */ + function getPropDefaultValue (vm, prop, key) { + // no default, return undefined + if (!hasOwn(prop, 'default')) { + return undefined + } + var def = prop.default; + // warn against non-factory defaults for Object & Array + if (isObject(def)) { + warn( + 'Invalid default value for prop "' + key + '": ' + + 'Props with type Object/Array must use a factory function ' + + 'to return the default value.', + vm + ); + } + // the raw prop value was also undefined from previous render, + // return previous default value to avoid unnecessary watcher trigger + if (vm && vm.$options.propsData && + vm.$options.propsData[key] === undefined && + vm._props[key] !== undefined + ) { + return vm._props[key] + } + // call factory function for non-Function types + // a value is Function if its prototype is function even across different execution context + return typeof def === 'function' && getType(prop.type) !== 'Function' + ? def.call(vm) + : def + } + + /** + * Assert whether a prop is valid. + */ + function assertProp ( + prop, + name, + value, + vm, + absent + ) { + if (prop.required && absent) { + warn( + 'Missing required prop: "' + name + '"', + vm + ); + return + } + if (value == null && !prop.required) { + return + } + var type = prop.type; + var valid = !type || type === true; + var expectedTypes = []; + if (type) { + if (!Array.isArray(type)) { + type = [type]; + } + for (var i = 0; i < type.length && !valid; i++) { + var assertedType = assertType(value, type[i]); + expectedTypes.push(assertedType.expectedType || ''); + valid = assertedType.valid; + } + } + + if (!valid) { + warn( + getInvalidTypeMessage(name, value, expectedTypes), + vm + ); + return + } + var validator = prop.validator; + if (validator) { + if (!validator(value)) { + warn( + 'Invalid prop: ext validator check failed for prop "' + name + '".', + vm + ); + } + } + } + + var simpleCheckRE = /^(String|Number|Boolean|Function|Symbol)$/; + + function assertType (value, type) { + var valid; + var expectedType = getType(type); + if (simpleCheckRE.test(expectedType)) { + var t = typeof value; + valid = t === expectedType.toLowerCase(); + // for primitive wrapper objects + if (!valid && t === 'object') { + valid = value instanceof type; + } + } else if (expectedType === 'Object') { + valid = isPlainObject(value); + } else if (expectedType === 'Array') { + valid = Array.isArray(value); + } else { + valid = value instanceof type; + } + return { + valid: valid, + expectedType: expectedType + } + } + + /** + * Use function string name to check built-in types, + * because a simple equality check will fail when running + * across different vms / iframes. + */ + function getType (fn) { + var match = fn && fn.toString().match(/^\s*function (\w+)/); + return match ? match[1] : '' + } + + function isSameType (a, b) { + return getType(a) === getType(b) + } + + function getTypeIndex (type, expectedTypes) { + if (!Array.isArray(expectedTypes)) { + return isSameType(expectedTypes, type) ? 0 : -1 + } + for (var i = 0, len = expectedTypes.length; i < len; i++) { + if (isSameType(expectedTypes[i], type)) { + return i + } + } + return -1 + } + + function getInvalidTypeMessage (name, value, expectedTypes) { + var message = "Invalid prop: type check failed for prop \"" + name + "\"." + + " Expected " + (expectedTypes.map(capitalize).join(', ')); + var expectedType = expectedTypes[0]; + var receivedType = toRawType(value); + var expectedValue = styleValue(value, expectedType); + var receivedValue = styleValue(value, receivedType); + // check if we need to specify expected value + if (expectedTypes.length === 1 && + isExplicable(expectedType) && + !isBoolean(expectedType, receivedType)) { + message += " with value " + expectedValue; + } + message += ", got " + receivedType + " "; + // check if we need to specify received value + if (isExplicable(receivedType)) { + message += "with value " + receivedValue + "."; + } + return message + } + + function styleValue (value, type) { + if (type === 'String') { + return ("\"" + value + "\"") + } else if (type === 'Number') { + return ("" + (Number(value))) + } else { + return ("" + value) + } + } + + function isExplicable (value) { + var explicitTypes = ['string', 'number', 'boolean']; + return explicitTypes.some(function (elem) { return value.toLowerCase() === elem; }) + } + + function isBoolean () { + var args = [], len = arguments.length; + while ( len-- ) args[ len ] = arguments[ len ]; + + return args.some(function (elem) { return elem.toLowerCase() === 'boolean'; }) + } + + /* */ + + function handleError (err, vm, info) { + if (vm) { + var cur = vm; + while ((cur = cur.$parent)) { + var hooks = cur.$options.errorCaptured; + if (hooks) { + for (var i = 0; i < hooks.length; i++) { + try { + var capture = hooks[i].call(cur, err, vm, info) === false; + if (capture) { return } + } catch (e) { + globalHandleError(e, cur, 'errorCaptured hook'); + } + } + } + } + } + globalHandleError(err, vm, info); + } + + function invokeWithErrorHandling ( + handler, + context, + args, + vm, + info + ) { + var res; + try { + res = args ? handler.apply(context, args) : handler.call(context); + if (res && !res._isVue && isPromise(res)) { + res.catch(function (e) { return handleError(e, vm, info + " (Promise/async)"); }); + } + } catch (e) { + handleError(e, vm, info); + } + return res + } + + function globalHandleError (err, vm, info) { + if (config.errorHandler) { + try { + return config.errorHandler.call(null, err, vm, info) + } catch (e) { + // if the user intentionally throws the original error in the handler, + // do not log it twice + if (e !== err) { + logError(e, null, 'config.errorHandler'); + } + } + } + logError(err, vm, info); + } + + function logError (err, vm, info) { + { + warn(("Error in " + info + ": \"" + (err.toString()) + "\""), vm); + } + /* istanbul ignore else */ + if ((inBrowser || inWeex) && typeof console !== 'undefined') { + console.error(err); + } else { + throw err + } + } + + /* */ + + var isUsingMicroTask = false; + + var callbacks = []; + var pending = false; + + function flushCallbacks () { + pending = false; + var copies = callbacks.slice(0); + callbacks.length = 0; + for (var i = 0; i < copies.length; i++) { + copies[i](); + } + } + + // Here we have async deferring wrappers using microtasks. + // In 2.5 we used (macro) tasks (in combination with microtasks). + // However, it has subtle problems when state is changed right before repaint + // (e.g. #6813, out-in transitions). + // Also, using (macro) tasks in event handler would cause some weird behaviors + // that cannot be circumvented (e.g. #7109, #7153, #7546, #7834, #8109). + // So we now use microtasks everywhere, again. + // A major drawback of this tradeoff is that there are some scenarios + // where microtasks have too high a priority and fire in between supposedly + // sequential events (e.g. #4521, #6690, which have workarounds) + // or even between bubbling of the same event (#6566). + var timerFunc; + + // The nextTick behavior leverages the microtask queue, which can be accessed + // via either native Promise.then or MutationObserver. + // MutationObserver has wider support, however it is seriously bugged in + // UIWebView in iOS >= 9.3.3 when triggered in touch event handlers. It + // completely stops working after triggering a few times... so, if native + // Promise is available, we will use it: + /* istanbul ignore next, $flow-disable-line */ + if (typeof Promise !== 'undefined' && isNative(Promise)) { + var p = Promise.resolve(); + timerFunc = function () { + p.then(flushCallbacks); + // In problematic UIWebViews, Promise.then doesn't completely break, but + // it can get stuck in a weird state where callbacks are pushed into the + // microtask queue but the queue isn't being flushed, until the browser + // needs to do some other work, e.g. handle a timer. Therefore we can + // "force" the microtask queue to be flushed by adding an empty timer. + if (isIOS) { setTimeout(noop); } + }; + isUsingMicroTask = true; + } else if (!isIE && typeof MutationObserver !== 'undefined' && ( + isNative(MutationObserver) || + // PhantomJS and iOS 7.x + MutationObserver.toString() === '[object MutationObserverConstructor]' + )) { + // Use MutationObserver where native Promise is not available, + // e.g. PhantomJS, iOS7, Android 4.4 + // (#6466 MutationObserver is unreliable in IE11) + var counter = 1; + var observer = new MutationObserver(flushCallbacks); + var textNode = document.createTextNode(String(counter)); + observer.observe(textNode, { + characterData: true + }); + timerFunc = function () { + counter = (counter + 1) % 2; + textNode.data = String(counter); + }; + isUsingMicroTask = true; + } else if (typeof setImmediate !== 'undefined' && isNative(setImmediate)) { + // Fallback to setImmediate. + // Techinically it leverages the (macro) task queue, + // but it is still a better choice than setTimeout. + timerFunc = function () { + setImmediate(flushCallbacks); + }; + } else { + // Fallback to setTimeout. + timerFunc = function () { + setTimeout(flushCallbacks, 0); + }; + } + + function nextTick (cb, ctx) { + var _resolve; + callbacks.push(function () { + if (cb) { + try { + cb.call(ctx); + } catch (e) { + handleError(e, ctx, 'nextTick'); + } + } else if (_resolve) { + _resolve(ctx); + } + }); + if (!pending) { + pending = true; + timerFunc(); + } + // $flow-disable-line + if (!cb && typeof Promise !== 'undefined') { + return new Promise(function (resolve) { + _resolve = resolve; + }) + } + } + + /* */ + + var mark; + var measure; + + { + var perf = inBrowser && window.performance; + /* istanbul ignore if */ + if ( + perf && + perf.mark && + perf.measure && + perf.clearMarks && + perf.clearMeasures + ) { + mark = function (tag) { return perf.mark(tag); }; + measure = function (name, startTag, endTag) { + perf.measure(name, startTag, endTag); + perf.clearMarks(startTag); + perf.clearMarks(endTag); + // perf.clearMeasures(name) + }; + } + } + + /* not type checking this file because flow doesn't play well with Proxy */ + + var initProxy; + + { + var allowedGlobals = makeMap( + 'Infinity,undefined,NaN,isFinite,isNaN,' + + 'parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,' + + 'Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,' + + 'require' // for Webpack/Browserify + ); + + var warnNonPresent = function (target, key) { + warn( + "Property or method \"" + key + "\" is not defined on the instance but " + + 'referenced during render. Make sure that this property is reactive, ' + + 'either in the upload option, or for class-based components, by ' + + 'initializing the property. ' + + 'See: https://vuejs.org/v2/guide/reactivity.html#Declaring-Reactive-Properties.', + target + ); + }; + + var warnReservedPrefix = function (target, key) { + warn( + "Property \"" + key + "\" must be accessed with \"$upload." + key + "\" because " + + 'properties starting with "$" or "_" are not proxied in the Vue instance to ' + + 'prevent conflicts with Vue internals' + + 'See: https://vuejs.org/v2/api/#data', + target + ); + }; + + var hasProxy = + typeof Proxy !== 'undefined' && isNative(Proxy); + + if (hasProxy) { + var isBuiltInModifier = makeMap('stop,prevent,self,ctrl,shift,alt,meta,exact'); + config.keyCodes = new Proxy(config.keyCodes, { + set: function set (target, key, value) { + if (isBuiltInModifier(key)) { + warn(("Avoid overwriting built-in modifier in config.keyCodes: ." + key)); + return false + } else { + target[key] = value; + return true + } + } + }); + } + + var hasHandler = { + has: function has (target, key) { + var has = key in target; + var isAllowed = allowedGlobals(key) || + (typeof key === 'string' && key.charAt(0) === '_' && !(key in target.$data)); + if (!has && !isAllowed) { + if (key in target.$data) { warnReservedPrefix(target, key); } + else { warnNonPresent(target, key); } + } + return has || !isAllowed + } + }; + + var getHandler = { + get: function get (target, key) { + if (typeof key === 'string' && !(key in target)) { + if (key in target.$data) { warnReservedPrefix(target, key); } + else { warnNonPresent(target, key); } + } + return target[key] + } + }; + + initProxy = function initProxy (vm) { + if (hasProxy) { + // determine which proxy handler to use + var options = vm.$options; + var handlers = options.render && options.render._withStripped + ? getHandler + : hasHandler; + vm._renderProxy = new Proxy(vm, handlers); + } else { + vm._renderProxy = vm; + } + }; + } + + /* */ + + var seenObjects = new _Set(); + + /** + * Recursively traverse an object to evoke all converted + * getters, so that every nested property inside the object + * is collected as a "deep" dependency. + */ + function traverse (val) { + _traverse(val, seenObjects); + seenObjects.clear(); + } + + function _traverse (val, seen) { + var i, keys; + var isA = Array.isArray(val); + if ((!isA && !isObject(val)) || Object.isFrozen(val) || val instanceof VNode) { + return + } + if (val.__ob__) { + var depId = val.__ob__.dep.id; + if (seen.has(depId)) { + return + } + seen.add(depId); + } + if (isA) { + i = val.length; + while (i--) { _traverse(val[i], seen); } + } else { + keys = Object.keys(val); + i = keys.length; + while (i--) { _traverse(val[keys[i]], seen); } + } + } + + /* */ + + var normalizeEvent = cached(function (name) { + var passive = name.charAt(0) === '&'; + name = passive ? name.slice(1) : name; + var once$$1 = name.charAt(0) === '~'; // Prefixed last, checked first + name = once$$1 ? name.slice(1) : name; + var capture = name.charAt(0) === '!'; + name = capture ? name.slice(1) : name; + return { + name: name, + once: once$$1, + capture: capture, + passive: passive + } + }); + + function createFnInvoker (fns, vm) { + function invoker () { + var arguments$1 = arguments; + + var fns = invoker.fns; + if (Array.isArray(fns)) { + var cloned = fns.slice(); + for (var i = 0; i < cloned.length; i++) { + invokeWithErrorHandling(cloned[i], null, arguments$1, vm, "v-on handler"); + } + } else { + // return handler return value for single handlers + return invokeWithErrorHandling(fns, null, arguments, vm, "v-on handler") + } + } + invoker.fns = fns; + return invoker + } + + function updateListeners ( + on, + oldOn, + add, + remove$$1, + createOnceHandler, + vm + ) { + var name, def$$1, cur, old, event; + for (name in on) { + def$$1 = cur = on[name]; + old = oldOn[name]; + event = normalizeEvent(name); + if (isUndef(cur)) { + warn( + "Invalid handler for event \"" + (event.name) + "\": got " + String(cur), + vm + ); + } else if (isUndef(old)) { + if (isUndef(cur.fns)) { + cur = on[name] = createFnInvoker(cur, vm); + } + if (isTrue(event.once)) { + cur = on[name] = createOnceHandler(event.name, cur, event.capture); + } + add(event.name, cur, event.capture, event.passive, event.params); + } else if (cur !== old) { + old.fns = cur; + on[name] = old; + } + } + for (name in oldOn) { + if (isUndef(on[name])) { + event = normalizeEvent(name); + remove$$1(event.name, oldOn[name], event.capture); + } + } + } + + /* */ + + function mergeVNodeHook (def, hookKey, hook) { + if (def instanceof VNode) { + def = def.data.hook || (def.data.hook = {}); + } + var invoker; + var oldHook = def[hookKey]; + + function wrappedHook () { + hook.apply(this, arguments); + // important: remove merged hook to ensure it's called only once + // and prevent memory leak + remove(invoker.fns, wrappedHook); + } + + if (isUndef(oldHook)) { + // no existing hook + invoker = createFnInvoker([wrappedHook]); + } else { + /* istanbul ignore if */ + if (isDef(oldHook.fns) && isTrue(oldHook.merged)) { + // already a merged invoker + invoker = oldHook; + invoker.fns.push(wrappedHook); + } else { + // existing plain hook + invoker = createFnInvoker([oldHook, wrappedHook]); + } + } + + invoker.merged = true; + def[hookKey] = invoker; + } + + /* */ + + function extractPropsFromVNodeData ( + data, + Ctor, + tag + ) { + // we are only extracting raw values here. + // validation and default values are handled in the child + // component itself. + var propOptions = Ctor.options.props; + if (isUndef(propOptions)) { + return + } + var res = {}; + var attrs = data.attrs; + var props = data.props; + if (isDef(attrs) || isDef(props)) { + for (var key in propOptions) { + var altKey = hyphenate(key); + { + var keyInLowerCase = key.toLowerCase(); + if ( + key !== keyInLowerCase && + attrs && hasOwn(attrs, keyInLowerCase) + ) { + tip( + "Prop \"" + keyInLowerCase + "\" is passed to component " + + (formatComponentName(tag || Ctor)) + ", but the declared prop name is" + + " \"" + key + "\". " + + "Note that HTML attributes are case-insensitive and camelCased " + + "props need to use their kebab-case equivalents when using in-DOM " + + "templates. You should probably use \"" + altKey + "\" instead of \"" + key + "\"." + ); + } + } + checkProp(res, props, key, altKey, true) || + checkProp(res, attrs, key, altKey, false); + } + } + return res + } + + function checkProp ( + res, + hash, + key, + altKey, + preserve + ) { + if (isDef(hash)) { + if (hasOwn(hash, key)) { + res[key] = hash[key]; + if (!preserve) { + delete hash[key]; + } + return true + } else if (hasOwn(hash, altKey)) { + res[key] = hash[altKey]; + if (!preserve) { + delete hash[altKey]; + } + return true + } + } + return false + } + + /* */ + + // The template compiler attempts to minimize the need for normalization by + // statically analyzing the template at compile time. + // + // For plain HTML markup, normalization can be completely skipped because the + // generated render function is guaranteed to return Array. There are + // two cases where extra normalization is needed: + + // 1. When the children contains components - because a functional component + // may return an Array instead of a single root. In this case, just a simple + // normalization is needed - if any child is an Array, we flatten the whole + // thing with Array.prototype.concat. It is guaranteed to be only 1-level deep + // because functional components already normalize their own children. + function simpleNormalizeChildren (children) { + for (var i = 0; i < children.length; i++) { + if (Array.isArray(children[i])) { + return Array.prototype.concat.apply([], children) + } + } + return children + } + + // 2. When the children contains constructs that always generated nested Arrays, + // e.g.