Handle events
- Adds interfaces for developers to build handlers that update data in response to log events - Resolves #29
This commit is contained in:
parent
ed907535e3
commit
06f78e0083
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,5 +1,4 @@
|
|||||||
.idea
|
.idea
|
||||||
Gododir/godobin-*
|
|
||||||
test_data_dir/
|
test_data_dir/
|
||||||
contracts/*
|
contracts/*
|
||||||
environments/*.toml
|
environments/*.toml
|
||||||
|
39
Gopkg.lock
generated
39
Gopkg.lock
generated
@ -1,12 +1,6 @@
|
|||||||
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
|
||||||
|
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/BurntSushi/toml"
|
|
||||||
packages = ["."]
|
|
||||||
revision = "b26d9c308763d68093482582cea63d69be07a0f0"
|
|
||||||
version = "v0.3.0"
|
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "github.com/aristanetworks/goarista"
|
name = "github.com/aristanetworks/goarista"
|
||||||
@ -152,27 +146,6 @@
|
|||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "b4575eea38cca1123ec2dc90c26529b5c5acfcff"
|
revision = "b4575eea38cca1123ec2dc90c26529b5c5acfcff"
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/neelance/graphql-go"
|
|
||||||
packages = [
|
|
||||||
".",
|
|
||||||
"errors",
|
|
||||||
"internal/common",
|
|
||||||
"internal/exec",
|
|
||||||
"internal/exec/packer",
|
|
||||||
"internal/exec/resolvable",
|
|
||||||
"internal/exec/selected",
|
|
||||||
"internal/query",
|
|
||||||
"internal/schema",
|
|
||||||
"internal/validation",
|
|
||||||
"introspection",
|
|
||||||
"log",
|
|
||||||
"relay",
|
|
||||||
"trace"
|
|
||||||
]
|
|
||||||
revision = "b46637030579abd312c5eea21d36845b6e9e7ca4"
|
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/onsi/ginkgo"
|
name = "github.com/onsi/ginkgo"
|
||||||
packages = [
|
packages = [
|
||||||
@ -218,16 +191,6 @@
|
|||||||
revision = "c893efa28eb45626cdaa76c9f653b62488858837"
|
revision = "c893efa28eb45626cdaa76c9f653b62488858837"
|
||||||
version = "v1.2.0"
|
version = "v1.2.0"
|
||||||
|
|
||||||
[[projects]]
|
|
||||||
name = "github.com/opentracing/opentracing-go"
|
|
||||||
packages = [
|
|
||||||
".",
|
|
||||||
"ext",
|
|
||||||
"log"
|
|
||||||
]
|
|
||||||
revision = "1949ddbfd147afd4d964a9f00b24eb291e0e7c38"
|
|
||||||
version = "v1.0.2"
|
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
name = "github.com/pelletier/go-toml"
|
name = "github.com/pelletier/go-toml"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
@ -379,6 +342,6 @@
|
|||||||
[solve-meta]
|
[solve-meta]
|
||||||
analyzer-name = "dep"
|
analyzer-name = "dep"
|
||||||
analyzer-version = 1
|
analyzer-version = 1
|
||||||
inputs-digest = "4d1806bf6a5678261abb604455d0076906ca5aba42e8625ee09cae019fb21d40"
|
inputs-digest = "61dea45437b8efb926e1f7446fef75449f89530b201c2ab1893469d13a37b0c9"
|
||||||
solver-name = "gps-cdcl"
|
solver-name = "gps-cdcl"
|
||||||
solver-version = 1
|
solver-version = 1
|
||||||
|
@ -40,7 +40,3 @@
|
|||||||
[[constraint]]
|
[[constraint]]
|
||||||
name = "github.com/ethereum/go-ethereum"
|
name = "github.com/ethereum/go-ethereum"
|
||||||
version = "1.7.3"
|
version = "1.7.3"
|
||||||
|
|
||||||
[[constraint]]
|
|
||||||
branch = "master"
|
|
||||||
name = "github.com/neelance/graphql-go"
|
|
||||||
|
2
Makefile
2
Makefile
@ -65,6 +65,8 @@ checkdbvars:
|
|||||||
test -n "$(HOST_NAME)" # $$HOST_NAME
|
test -n "$(HOST_NAME)" # $$HOST_NAME
|
||||||
test -n "$(PORT)" # $$PORT
|
test -n "$(PORT)" # $$PORT
|
||||||
test -n "$(NAME)" # $$NAME
|
test -n "$(NAME)" # $$NAME
|
||||||
|
@echo $(CONNECT_STRING)
|
||||||
|
|
||||||
|
|
||||||
.PHONY: rollback
|
.PHONY: rollback
|
||||||
rollback: $(MIGRATE) checkdbvars
|
rollback: $(MIGRATE) checkdbvars
|
||||||
|
10
README.md
10
README.md
@ -39,16 +39,6 @@ The default location for Ethereum is:
|
|||||||
|
|
||||||
* see `./environments` for example config
|
* see `./environments` for example config
|
||||||
|
|
||||||
## Watch specific events
|
|
||||||
1. Start geth
|
|
||||||
2. In a separate terminal start vulcanize_db
|
|
||||||
- `vulcanizedb sync --config <config.toml> --starting-block-number <block-number>`
|
|
||||||
3. Create event filter
|
|
||||||
- `vulcanizedb addFilter --config <config.toml> --filter-filepath <filter.json>`
|
|
||||||
* see `./filters` for example filter
|
|
||||||
4. The filters are tracked in the `log_filters` table and the filtered events
|
|
||||||
will show up in the `watched_log_events` view
|
|
||||||
|
|
||||||
## Running the Tests
|
## Running the Tests
|
||||||
|
|
||||||
### Unit Tests
|
### Unit Tests
|
||||||
|
@ -1,77 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/filters"
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/geth"
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/repositories/postgres"
|
|
||||||
"github.com/vulcanize/vulcanizedb/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
// addFilterCmd represents the addFilter command
|
|
||||||
var addFilterCmd = &cobra.Command{
|
|
||||||
Use: "addFilter",
|
|
||||||
Short: "Adds event filter to vulcanizedb",
|
|
||||||
Long: `An event filter is added to the vulcanize_db.
|
|
||||||
All events matching the filter conitions will be tracked
|
|
||||||
in vulcanizedb.
|
|
||||||
|
|
||||||
vulcanizedb addFilter --config config.toml --filter-filepath filter.json
|
|
||||||
|
|
||||||
The event filters are expected to match
|
|
||||||
the format described in the ethereum RPC wiki:
|
|
||||||
|
|
||||||
https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newfilter
|
|
||||||
|
|
||||||
[{
|
|
||||||
"fromBlock": "0x1",
|
|
||||||
"toBlock": "0x2",
|
|
||||||
"address": "0x8888f1f195afa192cfee860698584c030f4c9db1",
|
|
||||||
"topics": ["0x000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b",
|
|
||||||
null,
|
|
||||||
"0x000000000000000000000000a94f5374fce5edbc8e2a8697c15331677e6ebf0b",
|
|
||||||
"0x0000000000000000000000000aff3454fce5edbc8cca8697c15331677e6ebccc"]
|
|
||||||
}]
|
|
||||||
`,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
addFilter()
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
var filterFilepath string
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rootCmd.AddCommand(addFilterCmd)
|
|
||||||
|
|
||||||
addFilterCmd.PersistentFlags().StringVar(&filterFilepath, "filter-filepath", "", "path/to/filter.json")
|
|
||||||
addFilterCmd.MarkFlagRequired("filter-filepath")
|
|
||||||
}
|
|
||||||
|
|
||||||
func addFilter() {
|
|
||||||
if filterFilepath == "" {
|
|
||||||
log.Fatal("filter-filepath required")
|
|
||||||
}
|
|
||||||
var logFilters filters.LogFilters
|
|
||||||
blockchain := geth.NewBlockchain(ipc)
|
|
||||||
db := utils.LoadPostgres(databaseConfig, blockchain.Node())
|
|
||||||
filterRepository := postgres.FilterRepository{DB: &db}
|
|
||||||
absFilePath := utils.AbsFilePath(filterFilepath)
|
|
||||||
logFilterBytes, err := ioutil.ReadFile(absFilePath)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
err = json.Unmarshal(logFilterBytes, &logFilters)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
for _, filter := range logFilters {
|
|
||||||
err = filterRepository.CreateFilter(filter)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
107
cmd/graphql.go
107
cmd/graphql.go
@ -1,107 +0,0 @@
|
|||||||
package cmd
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
_ "net/http/pprof"
|
|
||||||
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/neelance/graphql-go"
|
|
||||||
"github.com/neelance/graphql-go/relay"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/geth"
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/graphql_server"
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/repositories/postgres"
|
|
||||||
"github.com/vulcanize/vulcanizedb/utils"
|
|
||||||
)
|
|
||||||
|
|
||||||
var graphqlCmd = &cobra.Command{
|
|
||||||
Use: "graphql",
|
|
||||||
Short: "Starts Vulcanize graphql server",
|
|
||||||
Long: `Starts vulcanize graphql server
|
|
||||||
and usage of using your command. For example:
|
|
||||||
|
|
||||||
vulcanizedb graphql --port 9090 --host localhost
|
|
||||||
`,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
schema := parseSchema()
|
|
||||||
serve(schema)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
var (
|
|
||||||
port int
|
|
||||||
host string
|
|
||||||
)
|
|
||||||
rootCmd.AddCommand(graphqlCmd)
|
|
||||||
|
|
||||||
syncCmd.Flags().IntVar(&port, "port", 9090, "graphql: port")
|
|
||||||
syncCmd.Flags().StringVar(&host, "host", "localhost", "graphql: host")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseSchema() *graphql.Schema {
|
|
||||||
|
|
||||||
blockchain := geth.NewBlockchain(ipc)
|
|
||||||
db := utils.LoadPostgres(databaseConfig, blockchain.Node())
|
|
||||||
blockRepository := &postgres.BlockRepository{DB: &db}
|
|
||||||
logRepository := &postgres.LogRepository{DB: &db}
|
|
||||||
filterRepository := &postgres.FilterRepository{DB: &db}
|
|
||||||
watchedEventRepository := &postgres.WatchedEventRepository{DB: &db}
|
|
||||||
graphQLRepositories := graphql_server.GraphQLRepositories{
|
|
||||||
WatchedEventRepository: watchedEventRepository,
|
|
||||||
BlockRepository: blockRepository,
|
|
||||||
LogRepository: logRepository,
|
|
||||||
FilterRepository: filterRepository,
|
|
||||||
}
|
|
||||||
schema := graphql.MustParseSchema(graphql_server.Schema, graphql_server.NewResolver(graphQLRepositories))
|
|
||||||
return schema
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func serve(schema *graphql.Schema) {
|
|
||||||
http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Write(page)
|
|
||||||
}))
|
|
||||||
http.Handle("/query", &relay.Handler{Schema: schema})
|
|
||||||
|
|
||||||
log.Fatal(http.ListenAndServe(":9090", nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
var page = []byte(`
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/graphiql/0.10.2/graphiql.css" />
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/1.1.0/fetch.min.js"></script>
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.5.4/react.min.js"></script>
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.5.4/react-dom.min.js"></script>
|
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/graphiql/0.10.2/graphiql.js"></script>
|
|
||||||
</head>
|
|
||||||
<body style="width: 100%; height: 100%; margin: 0; overflow: hidden;">
|
|
||||||
<div id="graphiql" style="height: 100vh;">Loading...</div>
|
|
||||||
<script>
|
|
||||||
function graphQLFetcher(graphQLParams) {
|
|
||||||
return fetch("/query", {
|
|
||||||
method: "post",
|
|
||||||
body: JSON.stringify(graphQLParams),
|
|
||||||
credentials: "include",
|
|
||||||
}).then(function (response) {
|
|
||||||
return response.text();
|
|
||||||
}).then(function (responseBody) {
|
|
||||||
try {
|
|
||||||
return JSON.parse(responseBody);
|
|
||||||
} catch (error) {
|
|
||||||
return responseBody;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
ReactDOM.render(
|
|
||||||
React.createElement(GraphiQL, {fetcher: graphQLFetcher}),
|
|
||||||
document.getElementById("graphiql")
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
`)
|
|
@ -9,10 +9,10 @@ import (
|
|||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/datastore"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres/repositories"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/geth"
|
"github.com/vulcanize/vulcanizedb/pkg/geth"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/history"
|
"github.com/vulcanize/vulcanizedb/pkg/history"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/repositories"
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/repositories/postgres"
|
|
||||||
"github.com/vulcanize/vulcanizedb/utils"
|
"github.com/vulcanize/vulcanizedb/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -50,7 +50,7 @@ func init() {
|
|||||||
syncCmd.Flags().IntVarP(&startingBlockNumber, "starting-block-number", "s", 0, "Block number to start syncing from")
|
syncCmd.Flags().IntVarP(&startingBlockNumber, "starting-block-number", "s", 0, "Block number to start syncing from")
|
||||||
}
|
}
|
||||||
|
|
||||||
func backFillAllBlocks(blockchain core.Blockchain, blockRepository repositories.BlockRepository, missingBlocksPopulated chan int, startingBlockNumber int64) {
|
func backFillAllBlocks(blockchain core.Blockchain, blockRepository datastore.BlockRepository, missingBlocksPopulated chan int, startingBlockNumber int64) {
|
||||||
go func() {
|
go func() {
|
||||||
missingBlocksPopulated <- history.PopulateMissingBlocks(blockchain, blockRepository, startingBlockNumber)
|
missingBlocksPopulated <- history.PopulateMissingBlocks(blockchain, blockRepository, startingBlockNumber)
|
||||||
}()
|
}()
|
||||||
@ -65,7 +65,7 @@ func sync() {
|
|||||||
log.Fatal("geth initial: state sync not finished")
|
log.Fatal("geth initial: state sync not finished")
|
||||||
}
|
}
|
||||||
db := utils.LoadPostgres(databaseConfig, blockchain.Node())
|
db := utils.LoadPostgres(databaseConfig, blockchain.Node())
|
||||||
blockRepository := postgres.BlockRepository{DB: &db}
|
blockRepository := repositories.BlockRepository{DB: &db}
|
||||||
validator := history.NewBlockValidator(blockchain, blockRepository, 15)
|
validator := history.NewBlockValidator(blockchain, blockRepository, 15)
|
||||||
|
|
||||||
missingBlocksPopulated := make(chan int)
|
missingBlocksPopulated := make(chan int)
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
[
|
|
||||||
{
|
|
||||||
"name": "TransferFilter",
|
|
||||||
"fromBlock": "0x488290",
|
|
||||||
"toBlock": "0x488678",
|
|
||||||
"address": "0x06012c8cf97bead5deae237070f9587f8e7a266d",
|
|
||||||
"topics": [
|
|
||||||
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "NewFilter",
|
|
||||||
"fromBlock": "0x4B34AD",
|
|
||||||
"address": "0x06012c8cf97bead5deae237070f9587f8e7a266d",
|
|
||||||
"topics": [
|
|
||||||
"0x241ea03ca20251805084d27d4440371c34a0b85ff108f6bb5611248f73818b80"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
@ -1,32 +1,22 @@
|
|||||||
package integration
|
package integration
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
cfg "github.com/vulcanize/vulcanizedb/pkg/config"
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/geth"
|
"github.com/vulcanize/vulcanizedb/pkg/geth"
|
||||||
|
"github.com/vulcanize/vulcanizedb/test_config"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("Rewards calculations", func() {
|
var _ = Describe("Rewards calculations", func() {
|
||||||
|
|
||||||
It("calculates a block reward for a real block", func() {
|
It("calculates a block reward for a real block", func() {
|
||||||
config, err := cfg.NewConfig("infura")
|
blockchain := geth.NewBlockchain(test_config.InfuraClient.IPCPath)
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
blockchain := geth.NewBlockchain(config.Client.IPCPath)
|
|
||||||
block := blockchain.GetBlockByNumber(1071819)
|
block := blockchain.GetBlockByNumber(1071819)
|
||||||
Expect(block.Reward).To(Equal(5.31355))
|
Expect(block.Reward).To(Equal(5.31355))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("calculates an uncle reward for a real block", func() {
|
It("calculates an uncle reward for a real block", func() {
|
||||||
config, err := cfg.NewConfig("infura")
|
blockchain := geth.NewBlockchain(test_config.InfuraClient.IPCPath)
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
blockchain := geth.NewBlockchain(config.Client.IPCPath)
|
|
||||||
block := blockchain.GetBlockByNumber(1071819)
|
block := blockchain.GetBlockByNumber(1071819)
|
||||||
Expect(block.UnclesReward).To(Equal(6.875))
|
Expect(block.UnclesReward).To(Equal(6.875))
|
||||||
})
|
})
|
||||||
|
@ -3,25 +3,20 @@ package integration
|
|||||||
import (
|
import (
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"log"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
cfg "github.com/vulcanize/vulcanizedb/pkg/config"
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/geth"
|
"github.com/vulcanize/vulcanizedb/pkg/geth"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/geth/testing"
|
"github.com/vulcanize/vulcanizedb/pkg/geth/testing"
|
||||||
|
"github.com/vulcanize/vulcanizedb/test_config"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("Reading contracts", func() {
|
var _ = Describe("Reading contracts", func() {
|
||||||
|
|
||||||
Describe("Reading the list of attributes", func() {
|
Describe("Reading the list of attributes", func() {
|
||||||
It("returns a string attribute for a real contract", func() {
|
It("returns a string attribute for a real contract", func() {
|
||||||
config, err := cfg.NewConfig("infura")
|
blockchain := geth.NewBlockchain(test_config.InfuraClient.IPCPath)
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
blockchain := geth.NewBlockchain(config.Client.IPCPath)
|
|
||||||
contract := testing.SampleContract()
|
contract := testing.SampleContract()
|
||||||
|
|
||||||
contractAttributes, err := blockchain.GetAttributes(contract)
|
contractAttributes, err := blockchain.GetAttributes(contract)
|
||||||
@ -34,8 +29,7 @@ var _ = Describe("Reading contracts", func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
It("does not return an attribute that takes an input", func() {
|
It("does not return an attribute that takes an input", func() {
|
||||||
config, err := cfg.NewConfig("infura")
|
blockchain := geth.NewBlockchain(test_config.InfuraClient.IPCPath)
|
||||||
blockchain := geth.NewBlockchain(config.Client.IPCPath)
|
|
||||||
contract := testing.SampleContract()
|
contract := testing.SampleContract()
|
||||||
|
|
||||||
contractAttributes, err := blockchain.GetAttributes(contract)
|
contractAttributes, err := blockchain.GetAttributes(contract)
|
||||||
@ -46,8 +40,7 @@ var _ = Describe("Reading contracts", func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
It("does not return an attribute that is not constant", func() {
|
It("does not return an attribute that is not constant", func() {
|
||||||
config, _ := cfg.NewConfig("infura")
|
blockchain := geth.NewBlockchain(test_config.InfuraClient.IPCPath)
|
||||||
blockchain := geth.NewBlockchain(config.Client.IPCPath)
|
|
||||||
contract := testing.SampleContract()
|
contract := testing.SampleContract()
|
||||||
|
|
||||||
contractAttributes, err := blockchain.GetAttributes(contract)
|
contractAttributes, err := blockchain.GetAttributes(contract)
|
||||||
@ -60,8 +53,7 @@ var _ = Describe("Reading contracts", func() {
|
|||||||
|
|
||||||
Describe("Getting a contract attribute", func() {
|
Describe("Getting a contract attribute", func() {
|
||||||
It("returns the correct attribute for a real contract", func() {
|
It("returns the correct attribute for a real contract", func() {
|
||||||
config, _ := cfg.NewConfig("infura")
|
blockchain := geth.NewBlockchain(test_config.InfuraClient.IPCPath)
|
||||||
blockchain := geth.NewBlockchain(config.Client.IPCPath)
|
|
||||||
|
|
||||||
contract := testing.SampleContract()
|
contract := testing.SampleContract()
|
||||||
name, err := blockchain.GetAttribute(contract, "name", nil)
|
name, err := blockchain.GetAttribute(contract, "name", nil)
|
||||||
@ -71,8 +63,7 @@ var _ = Describe("Reading contracts", func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
It("returns the correct attribute for a real contract", func() {
|
It("returns the correct attribute for a real contract", func() {
|
||||||
config, _ := cfg.NewConfig("infura")
|
blockchain := geth.NewBlockchain(test_config.InfuraClient.IPCPath)
|
||||||
blockchain := geth.NewBlockchain(config.Client.IPCPath)
|
|
||||||
contract := testing.SampleContract()
|
contract := testing.SampleContract()
|
||||||
|
|
||||||
name, err := blockchain.GetAttribute(contract, "name", nil)
|
name, err := blockchain.GetAttribute(contract, "name", nil)
|
||||||
@ -82,8 +73,7 @@ var _ = Describe("Reading contracts", func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
It("returns the correct attribute for a real contract at a specific block height", func() {
|
It("returns the correct attribute for a real contract at a specific block height", func() {
|
||||||
config, _ := cfg.NewConfig("infura")
|
blockchain := geth.NewBlockchain(test_config.InfuraClient.IPCPath)
|
||||||
blockchain := geth.NewBlockchain(config.Client.IPCPath)
|
|
||||||
contract := testing.SampleContract()
|
contract := testing.SampleContract()
|
||||||
|
|
||||||
name, err := blockchain.GetAttribute(contract, "name", big.NewInt(4701536))
|
name, err := blockchain.GetAttribute(contract, "name", big.NewInt(4701536))
|
||||||
@ -93,8 +83,7 @@ var _ = Describe("Reading contracts", func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
It("returns an error when asking for an attribute that does not exist", func() {
|
It("returns an error when asking for an attribute that does not exist", func() {
|
||||||
config, _ := cfg.NewConfig("infura")
|
blockchain := geth.NewBlockchain(test_config.InfuraClient.IPCPath)
|
||||||
blockchain := geth.NewBlockchain(config.Client.IPCPath)
|
|
||||||
contract := testing.SampleContract()
|
contract := testing.SampleContract()
|
||||||
|
|
||||||
name, err := blockchain.GetAttribute(contract, "missing_attribute", nil)
|
name, err := blockchain.GetAttribute(contract, "missing_attribute", nil)
|
||||||
@ -115,8 +104,7 @@ var _ = Describe("Reading contracts", func() {
|
|||||||
},
|
},
|
||||||
Index: 19,
|
Index: 19,
|
||||||
Data: "0x0000000000000000000000000000000000000000000000000c7d713b49da0000"}
|
Data: "0x0000000000000000000000000000000000000000000000000c7d713b49da0000"}
|
||||||
config, _ := cfg.NewConfig("infura")
|
blockchain := geth.NewBlockchain(test_config.InfuraClient.IPCPath)
|
||||||
blockchain := geth.NewBlockchain(config.Client.IPCPath)
|
|
||||||
contract := testing.SampleContract()
|
contract := testing.SampleContract()
|
||||||
|
|
||||||
logs, err := blockchain.GetLogs(contract, big.NewInt(4703824), nil)
|
logs, err := blockchain.GetLogs(contract, big.NewInt(4703824), nil)
|
||||||
@ -128,8 +116,7 @@ var _ = Describe("Reading contracts", func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
It("returns and empty log array when no events for a given block / contract combo", func() {
|
It("returns and empty log array when no events for a given block / contract combo", func() {
|
||||||
config, _ := cfg.NewConfig("infura")
|
blockchain := geth.NewBlockchain(test_config.InfuraClient.IPCPath)
|
||||||
blockchain := geth.NewBlockchain(config.Client.IPCPath)
|
|
||||||
|
|
||||||
logs, err := blockchain.GetLogs(core.Contract{Hash: "x123"}, big.NewInt(4703824), nil)
|
logs, err := blockchain.GetLogs(core.Contract{Hash: "x123"}, big.NewInt(4703824), nil)
|
||||||
|
|
||||||
@ -140,4 +127,19 @@ var _ = Describe("Reading contracts", func() {
|
|||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Describe("Fetching Contract data", func() {
|
||||||
|
It("returns the correct attribute for a real contract", func() {
|
||||||
|
blockchain := geth.NewBlockchain(test_config.InfuraClient.IPCPath)
|
||||||
|
|
||||||
|
contract := testing.SampleContract()
|
||||||
|
var balance = new(big.Int)
|
||||||
|
args := common.HexToHash("0xd26114cd6ee289accf82350c8d8487fedb8a0c07")
|
||||||
|
err := blockchain.FetchContractData(contract.Abi, "0xd26114cd6ee289accf82350c8d8487fedb8a0c07", "balanceOf", args, &balance, 5167471)
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
expected := new(big.Int)
|
||||||
|
expected.SetString("10897295492887612977137", 10)
|
||||||
|
Expect(balance).To(Equal(expected))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
@ -1,29 +1,21 @@
|
|||||||
package integration_test
|
package integration_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/config"
|
"github.com/vulcanize/vulcanizedb/pkg/datastore/inmemory"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/geth"
|
"github.com/vulcanize/vulcanizedb/pkg/geth"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/history"
|
"github.com/vulcanize/vulcanizedb/pkg/history"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/repositories/inmemory"
|
"github.com/vulcanize/vulcanizedb/test_config"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
|
||||||
log.SetOutput(ioutil.Discard)
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = Describe("Reading from the Geth blockchain", func() {
|
var _ = Describe("Reading from the Geth blockchain", func() {
|
||||||
|
|
||||||
var blockchain *geth.Blockchain
|
var blockchain *geth.Blockchain
|
||||||
var inMemory *inmemory.InMemory
|
var inMemory *inmemory.InMemory
|
||||||
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
cfg, _ := config.NewConfig("private")
|
blockchain = geth.NewBlockchain(test_config.TestClientConfig.IPCPath)
|
||||||
blockchain = geth.NewBlockchain(cfg.Client.IPCPath)
|
|
||||||
inMemory = inmemory.NewInMemory()
|
inMemory = inmemory.NewInMemory()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -52,8 +44,8 @@ var _ = Describe("Reading from the Geth blockchain", func() {
|
|||||||
devNetworkNodeId := float64(1)
|
devNetworkNodeId := float64(1)
|
||||||
|
|
||||||
Expect(node.GenesisBlock).To(Equal(devNetworkGenesisBlock))
|
Expect(node.GenesisBlock).To(Equal(devNetworkGenesisBlock))
|
||||||
Expect(node.NetworkId).To(Equal(devNetworkNodeId))
|
Expect(node.NetworkID).To(Equal(devNetworkNodeId))
|
||||||
Expect(len(node.Id)).To(Equal(128))
|
Expect(len(node.ID)).To(Equal(128))
|
||||||
Expect(node.ClientName).To(ContainSubstring("Geth"))
|
Expect(node.ClientName).To(ContainSubstring("Geth"))
|
||||||
|
|
||||||
close(done)
|
close(done)
|
||||||
|
27
libraries/shared/HandlerREADME.md
Normal file
27
libraries/shared/HandlerREADME.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# Handlers
|
||||||
|
|
||||||
|
## Description
|
||||||
|
Handlers must be defined in order to define what events should trigger data updates and how those are performed.
|
||||||
|
|
||||||
|
## Interface
|
||||||
|
|
||||||
|
### Initializer
|
||||||
|
Accepts DB and Blockchain from Vulcanize and returns a new handler. E.g. for a new object "Cup":
|
||||||
|
`func NewCupHandler(db *postgres.DB, blockchain core.ContractDataFetcher) handlers.Handler`
|
||||||
|
|
||||||
|
### Execute
|
||||||
|
Triggers operations to take in response to a given log event.
|
||||||
|
Can persist data from logs, fetch and persist arbitrary data from outside services (e.g. contract state), or take any number of other actions. E.g.:
|
||||||
|
`func (cupHandler *CupHandler) Execute() error`
|
||||||
|
|
||||||
|
## Additional Requirements
|
||||||
|
Handlers must define log filters and create them so that relevant watched events can be identified and retrieved. E.g.:
|
||||||
|
```$xslt
|
||||||
|
{
|
||||||
|
Name: "CupsBite",
|
||||||
|
FromBlock: 0,
|
||||||
|
ToBlock: -1,
|
||||||
|
Address: "0x448a5065aebb8e423f0896e6c5d525c040f59af3",
|
||||||
|
Topics: core.Topics{"0x40cc885400000000000000000000000000000000000000000000000000000000"},
|
||||||
|
},
|
||||||
|
```
|
23
libraries/shared/handler_interface.go
Normal file
23
libraries/shared/handler_interface.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package shared
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Handler interface {
|
||||||
|
Execute() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type HandlerInitializer func(db *postgres.DB, blockchain core.ContractDataFetcher) Handler
|
||||||
|
|
||||||
|
func HexToInt64(byteString string) int64 {
|
||||||
|
intHash := common.HexToHash(byteString)
|
||||||
|
return intHash.Big().Int64()
|
||||||
|
}
|
||||||
|
|
||||||
|
func HexToString(byteString string) string {
|
||||||
|
value := common.HexToHash(byteString)
|
||||||
|
return value.Big().String()
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package graphql_server_test
|
package shared_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
@ -7,7 +7,7 @@ import (
|
|||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGraphqlServer(t *testing.T) {
|
func TestShared(t *testing.T) {
|
||||||
RegisterFailHandler(Fail)
|
RegisterFailHandler(Fail)
|
||||||
RunSpecs(t, "GraphqlServer Suite")
|
RunSpecs(t, "Shared Suite")
|
||||||
}
|
}
|
27
libraries/shared/watcher.go
Normal file
27
libraries/shared/watcher.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
package shared
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Watcher struct {
|
||||||
|
Handlers []Handler
|
||||||
|
DB postgres.DB
|
||||||
|
Blockchain core.ContractDataFetcher
|
||||||
|
}
|
||||||
|
|
||||||
|
func (watcher *Watcher) AddHandlers(us []HandlerInitializer) {
|
||||||
|
for _, handlerInitializer := range us {
|
||||||
|
handler := handlerInitializer(&watcher.DB, watcher.Blockchain)
|
||||||
|
watcher.Handlers = append(watcher.Handlers, handler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (watcher *Watcher) Execute() error {
|
||||||
|
var err error
|
||||||
|
for _, handler := range watcher.Handlers {
|
||||||
|
err = handler.Execute()
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
69
libraries/shared/watcher_test.go
Normal file
69
libraries/shared/watcher_test.go
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
package shared_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
"github.com/vulcanize/vulcanizedb/libraries/shared"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MockHandler struct {
|
||||||
|
executeWasCalled bool
|
||||||
|
executeError error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mh *MockHandler) Execute() error {
|
||||||
|
if mh.executeError != nil {
|
||||||
|
return mh.executeError
|
||||||
|
}
|
||||||
|
mh.executeWasCalled = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fakeHandlerInitializer(db *postgres.DB, blockchain core.ContractDataFetcher) shared.Handler {
|
||||||
|
return &MockHandler{}
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ = Describe("Watcher", func() {
|
||||||
|
It("Adds handlers", func() {
|
||||||
|
watcher := shared.Watcher{}
|
||||||
|
|
||||||
|
watcher.AddHandlers([]shared.HandlerInitializer{fakeHandlerInitializer})
|
||||||
|
|
||||||
|
Expect(len(watcher.Handlers)).To(Equal(1))
|
||||||
|
Expect(watcher.Handlers).To(ConsistOf(&MockHandler{}))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Adds handlers from multiple sources", func() {
|
||||||
|
watcher := shared.Watcher{}
|
||||||
|
|
||||||
|
watcher.AddHandlers([]shared.HandlerInitializer{fakeHandlerInitializer})
|
||||||
|
watcher.AddHandlers([]shared.HandlerInitializer{fakeHandlerInitializer})
|
||||||
|
|
||||||
|
Expect(len(watcher.Handlers)).To(Equal(2))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Executes each handler", func() {
|
||||||
|
watcher := shared.Watcher{}
|
||||||
|
fakeHandler := &MockHandler{}
|
||||||
|
watcher.Handlers = []shared.Handler{fakeHandler}
|
||||||
|
|
||||||
|
watcher.Execute()
|
||||||
|
|
||||||
|
Expect(fakeHandler.executeWasCalled).To(BeTrue())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Returns an error if handler returns an error", func() {
|
||||||
|
watcher := shared.Watcher{}
|
||||||
|
fakeHandler := &MockHandler{executeError: errors.New("Something bad happened")}
|
||||||
|
watcher.Handlers = []shared.Handler{fakeHandler}
|
||||||
|
|
||||||
|
err := watcher.Execute()
|
||||||
|
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
Expect(fakeHandler.executeWasCalled).To(BeFalse())
|
||||||
|
})
|
||||||
|
})
|
@ -1,79 +1,6 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"path"
|
|
||||||
"runtime"
|
|
||||||
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"net/url"
|
|
||||||
|
|
||||||
"github.com/BurntSushi/toml"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Database Database
|
Database Database
|
||||||
Client Client
|
Client Client
|
||||||
}
|
}
|
||||||
|
|
||||||
var NewErrConfigFileNotFound = func(environment string) error {
|
|
||||||
return errors.New(fmt.Sprintf("No configuration found for environment: %v", environment))
|
|
||||||
}
|
|
||||||
|
|
||||||
var NewErrBadConnectionString = func(connectionString string) error {
|
|
||||||
return errors.New(fmt.Sprintf("connection string is invalid: %v", connectionString))
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConfig(environment string) (Config, error) {
|
|
||||||
filenameWithExtension := fmt.Sprintf("%s.toml", environment)
|
|
||||||
absolutePath := filepath.Join(ProjectRoot(), "environments", filenameWithExtension)
|
|
||||||
config, err := parseConfigFile(absolutePath)
|
|
||||||
if err != nil {
|
|
||||||
return Config{}, NewErrConfigFileNotFound(environment)
|
|
||||||
} else {
|
|
||||||
if !filepath.IsAbs(config.Client.IPCPath) && !isUrl(config.Client.IPCPath) {
|
|
||||||
config.Client.IPCPath = filepath.Join(ProjectRoot(), config.Client.IPCPath)
|
|
||||||
}
|
|
||||||
return config, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ProjectRoot() string {
|
|
||||||
var _, filename, _, _ = runtime.Caller(0)
|
|
||||||
return path.Join(path.Dir(filename), "..", "..")
|
|
||||||
}
|
|
||||||
|
|
||||||
func isUrl(s string) bool {
|
|
||||||
_, err := url.ParseRequestURI(s)
|
|
||||||
if err == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func fileExists(s string) bool {
|
|
||||||
_, err := os.Stat(s)
|
|
||||||
if err == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseConfigFile(filePath string) (Config, error) {
|
|
||||||
var cfg Config
|
|
||||||
if !isUrl(filePath) && !fileExists(filePath) {
|
|
||||||
return Config{}, NewErrBadConnectionString(filePath)
|
|
||||||
} else {
|
|
||||||
_, err := toml.DecodeFile(filePath, &cfg)
|
|
||||||
if err != nil {
|
|
||||||
return Config{}, err
|
|
||||||
}
|
|
||||||
return cfg, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -1,41 +1,24 @@
|
|||||||
package config_test
|
package config_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
cfg "github.com/vulcanize/vulcanizedb/pkg/config"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("Loading the config", func() {
|
var _ = Describe("Loading the config", func() {
|
||||||
|
|
||||||
It("reads the private config using the environment", func() {
|
It("reads the private config using the environment", func() {
|
||||||
privateConfig, err := cfg.NewConfig("private")
|
testConfig := viper.New()
|
||||||
|
testConfig.SetConfigName("private")
|
||||||
|
testConfig.AddConfigPath("$GOPATH/src/github.com/vulcanize/vulcanizedb/environments/")
|
||||||
|
err := testConfig.ReadInConfig()
|
||||||
|
Expect(viper.Get("client.ipcpath")).To(BeNil())
|
||||||
Expect(err).To(BeNil())
|
Expect(err).To(BeNil())
|
||||||
Expect(privateConfig.Database.Hostname).To(Equal("localhost"))
|
Expect(testConfig.Get("database.hostname")).To(Equal("localhost"))
|
||||||
Expect(privateConfig.Database.Name).To(Equal("vulcanize_private"))
|
Expect(testConfig.Get("database.name")).To(Equal("vulcanize_private"))
|
||||||
Expect(privateConfig.Database.Port).To(Equal(5432))
|
Expect(testConfig.Get("database.port")).To(Equal(int64(5432)))
|
||||||
expandedPath := filepath.Join(cfg.ProjectRoot(), "test_data_dir/geth.ipc")
|
Expect(testConfig.Get("client.ipcpath")).To(Equal("test_data_dir/geth.ipc"))
|
||||||
Expect(privateConfig.Client.IPCPath).To(Equal(expandedPath))
|
|
||||||
})
|
|
||||||
|
|
||||||
It("returns an error when there is no matching config file", func() {
|
|
||||||
config, err := cfg.NewConfig("bad-config")
|
|
||||||
|
|
||||||
Expect(config).To(Equal(cfg.Config{}))
|
|
||||||
Expect(err).NotTo(BeNil())
|
|
||||||
})
|
|
||||||
|
|
||||||
It("reads the infura config using the environment", func() {
|
|
||||||
infuraConfig, err := cfg.NewConfig("infura")
|
|
||||||
|
|
||||||
Expect(err).To(BeNil())
|
|
||||||
Expect(infuraConfig.Database.Hostname).To(Equal("localhost"))
|
|
||||||
Expect(infuraConfig.Database.Name).To(Equal("vulcanize_private"))
|
|
||||||
Expect(infuraConfig.Database.Port).To(Equal(5432))
|
|
||||||
Expect(infuraConfig.Client.IPCPath).To(Equal("https://mainnet.infura.io/J5Vd2fRtGsw0zZ0Ov3BL"))
|
|
||||||
})
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
@ -30,11 +30,10 @@ func template() string {
|
|||||||
func transactionToString(transaction *core.Transaction) string {
|
func transactionToString(transaction *core.Transaction) string {
|
||||||
if transaction == nil {
|
if transaction == nil {
|
||||||
return "NONE"
|
return "NONE"
|
||||||
} else {
|
}
|
||||||
return fmt.Sprintf(`Hash: %s
|
return fmt.Sprintf(`Hash: %s
|
||||||
To: %s
|
To: %s
|
||||||
From: %s`, transaction.Hash, transaction.To, transaction.From)
|
From: %s`, transaction.Hash, transaction.To, transaction.From)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func attributesString(summary ContractSummary) string {
|
func attributesString(summary ContractSummary) string {
|
||||||
|
@ -4,7 +4,7 @@ import (
|
|||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/repositories"
|
"github.com/vulcanize/vulcanizedb/pkg/datastore"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ContractSummary struct {
|
type ContractSummary struct {
|
||||||
@ -17,13 +17,12 @@ type ContractSummary struct {
|
|||||||
blockChain core.Blockchain
|
blockChain core.Blockchain
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSummary(blockchain core.Blockchain, contractRepository repositories.ContractRepository, contractHash string, blockNumber *big.Int) (ContractSummary, error) {
|
func NewSummary(blockchain core.Blockchain, contractRepository datastore.ContractRepository, contractHash string, blockNumber *big.Int) (ContractSummary, error) {
|
||||||
contract, err := contractRepository.GetContract(contractHash)
|
contract, err := contractRepository.GetContract(contractHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ContractSummary{}, err
|
return ContractSummary{}, err
|
||||||
} else {
|
|
||||||
return newContractSummary(blockchain, contract, blockNumber), nil
|
|
||||||
}
|
}
|
||||||
|
return newContractSummary(blockchain, contract, blockNumber), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (contractSummary ContractSummary) GetStateAttribute(attributeName string) interface{} {
|
func (contractSummary ContractSummary) GetStateAttribute(attributeName string) interface{} {
|
||||||
@ -48,7 +47,6 @@ func newContractSummary(blockchain core.Blockchain, contract core.Contract, bloc
|
|||||||
func lastTransaction(contract core.Contract) *core.Transaction {
|
func lastTransaction(contract core.Contract) *core.Transaction {
|
||||||
if len(contract.Transactions) > 0 {
|
if len(contract.Transactions) > 0 {
|
||||||
return &contract.Transactions[0]
|
return &contract.Transactions[0]
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -7,12 +7,12 @@ import (
|
|||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/contract_summary"
|
"github.com/vulcanize/vulcanizedb/pkg/contract_summary"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/datastore"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/datastore/inmemory"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/fakes"
|
"github.com/vulcanize/vulcanizedb/pkg/fakes"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/repositories"
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/repositories/inmemory"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewCurrentContractSummary(blockchain core.Blockchain, contractRepository repositories.ContractRepository, contractHash string) (contract_summary.ContractSummary, error) {
|
func NewCurrentContractSummary(blockchain core.Blockchain, contractRepository datastore.ContractRepository, contractHash string) (contract_summary.ContractSummary, error) {
|
||||||
return contract_summary.NewSummary(blockchain, contractRepository, contractHash, nil)
|
return contract_summary.NewSummary(blockchain, contractRepository, contractHash, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,6 +143,7 @@ var _ = Describe("The contract summary", func() {
|
|||||||
},
|
},
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
@ -3,10 +3,14 @@ package core
|
|||||||
import "math/big"
|
import "math/big"
|
||||||
|
|
||||||
type Blockchain interface {
|
type Blockchain interface {
|
||||||
|
GetAttribute(contract Contract, attributeName string, blockNumber *big.Int) (interface{}, error)
|
||||||
|
GetAttributes(contract Contract) (ContractAttributes, error)
|
||||||
GetBlockByNumber(blockNumber int64) Block
|
GetBlockByNumber(blockNumber int64) Block
|
||||||
|
GetLogs(contract Contract, startingBlockNumber *big.Int, endingBlockNumber *big.Int) ([]Log, error)
|
||||||
LastBlock() *big.Int
|
LastBlock() *big.Int
|
||||||
Node() Node
|
Node() Node
|
||||||
GetAttributes(contract Contract) (ContractAttributes, error)
|
}
|
||||||
GetAttribute(contract Contract, attributeName string, blockNumber *big.Int) (interface{}, error)
|
|
||||||
GetLogs(contract Contract, startingBlockNumber *big.Int, endingBlockNumber *big.Int) ([]Log, error)
|
type ContractDataFetcher interface {
|
||||||
|
FetchContractData(abiJSON string, address string, method string, methodArg interface{}, result interface{}, blockNumber int64) error
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ package core
|
|||||||
|
|
||||||
type Node struct {
|
type Node struct {
|
||||||
GenesisBlock string
|
GenesisBlock string
|
||||||
NetworkId float64
|
NetworkID float64
|
||||||
Id string
|
ID string
|
||||||
ClientName string
|
ClientName string
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
package core
|
package core
|
||||||
|
|
||||||
type WatchedEvent struct {
|
type WatchedEvent struct {
|
||||||
Name string `json:"name"` // name
|
LogID int64 `json:"log_id" db:"id"`
|
||||||
BlockNumber int64 `json:"block_number" db:"block_number"` // block_number
|
Name string `json:"name"`
|
||||||
Address string `json:"address"` // address
|
BlockNumber int64 `json:"block_number" db:"block_number"`
|
||||||
TxHash string `json:"tx_hash" db:"tx_hash"` // tx_hash
|
Address string `json:"address"`
|
||||||
Index int64 `json:"index"` // index
|
TxHash string `json:"tx_hash" db:"tx_hash"`
|
||||||
Topic0 string `json:"topic0"` // topic0
|
Index int64 `json:"index"`
|
||||||
Topic1 string `json:"topic1"` // topic1
|
Topic0 string `json:"topic0"`
|
||||||
Topic2 string `json:"topic2"` // topic2
|
Topic1 string `json:"topic1"`
|
||||||
Topic3 string `json:"topic3"` // topic3
|
Topic2 string `json:"topic2"`
|
||||||
Data string `json:"data"` // data
|
Topic3 string `json:"topic3"`
|
||||||
|
Data string `json:"data"`
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ package inmemory
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/repositories"
|
"github.com/vulcanize/vulcanizedb/pkg/datastore"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BlockRepository struct {
|
type BlockRepository struct {
|
||||||
@ -23,7 +23,7 @@ func (blockRepository *BlockRepository) GetBlock(blockNumber int64) (core.Block,
|
|||||||
if block, ok := blockRepository.blocks[blockNumber]; ok {
|
if block, ok := blockRepository.blocks[blockNumber]; ok {
|
||||||
return block, nil
|
return block, nil
|
||||||
}
|
}
|
||||||
return core.Block{}, repositories.ErrBlockDoesNotExist(blockNumber)
|
return core.Block{}, datastore.ErrBlockDoesNotExist(blockNumber)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (blockRepository *BlockRepository) MissingBlockNumbers(startingBlockNumber int64, endingBlockNumber int64) []int64 {
|
func (blockRepository *BlockRepository) MissingBlockNumbers(startingBlockNumber int64, endingBlockNumber int64) []int64 {
|
@ -2,7 +2,7 @@ package inmemory
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/repositories"
|
"github.com/vulcanize/vulcanizedb/pkg/datastore"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ContractRepostiory struct {
|
type ContractRepostiory struct {
|
||||||
@ -17,7 +17,7 @@ func (contractRepository *ContractRepostiory) ContractExists(contractHash string
|
|||||||
func (contractRepository *ContractRepostiory) GetContract(contractHash string) (core.Contract, error) {
|
func (contractRepository *ContractRepostiory) GetContract(contractHash string) (core.Contract, error) {
|
||||||
contract, ok := contractRepository.contracts[contractHash]
|
contract, ok := contractRepository.contracts[contractHash]
|
||||||
if !ok {
|
if !ok {
|
||||||
return core.Contract{}, repositories.ErrContractDoesNotExist(contractHash)
|
return core.Contract{}, datastore.ErrContractDoesNotExist(contractHash)
|
||||||
}
|
}
|
||||||
for _, block := range contractRepository.blocks {
|
for _, block := range contractRepository.blocks {
|
||||||
for _, transaction := range block.Transactions {
|
for _, transaction := range block.Transactions {
|
@ -4,15 +4,15 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
_ "github.com/lib/pq"
|
_ "github.com/lib/pq" //postgres driver
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/config"
|
"github.com/vulcanize/vulcanizedb/pkg/config"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DB struct {
|
type DB struct {
|
||||||
*sqlx.DB
|
*sqlx.DB
|
||||||
node core.Node
|
Node core.Node
|
||||||
nodeId int64
|
NodeID int64
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -28,7 +28,7 @@ func NewDB(databaseConfig config.Database, node core.Node) (*DB, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return &DB{}, ErrDBConnectionFailed
|
return &DB{}, ErrDBConnectionFailed
|
||||||
}
|
}
|
||||||
pg := DB{DB: db, node: node}
|
pg := DB{DB: db, Node: node}
|
||||||
err = pg.CreateNode(&node)
|
err = pg.CreateNode(&node)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &DB{}, ErrUnableToSetNode
|
return &DB{}, ErrUnableToSetNode
|
||||||
@ -48,10 +48,10 @@ func (db *DB) CreateNode(node *core.Node) error {
|
|||||||
node_id = $3,
|
node_id = $3,
|
||||||
client_name = $4
|
client_name = $4
|
||||||
RETURNING id`,
|
RETURNING id`,
|
||||||
node.GenesisBlock, node.NetworkId, node.Id, node.ClientName).Scan(&nodeId)
|
node.GenesisBlock, node.NetworkID, node.ID, node.ClientName).Scan(&nodeId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ErrUnableToSetNode
|
return ErrUnableToSetNode
|
||||||
}
|
}
|
||||||
db.nodeId = nodeId
|
db.NodeID = nodeId
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
@ -15,7 +15,9 @@ import (
|
|||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/config"
|
"github.com/vulcanize/vulcanizedb/pkg/config"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/repositories/postgres"
|
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres/repositories"
|
||||||
|
"github.com/vulcanize/vulcanizedb/test_config"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -23,24 +25,14 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var _ = Describe("Postgres DB", func() {
|
var _ = Describe("Postgres DB", func() {
|
||||||
var db *postgres.DB
|
var sqlxdb *sqlx.DB
|
||||||
|
|
||||||
It("connects to the database", func() {
|
It("connects to the database", func() {
|
||||||
cfg, _ := config.NewConfig("private")
|
var err error
|
||||||
pgConfig := config.DbConnectionString(cfg.Database)
|
pgConfig := config.DbConnectionString(test_config.DBConfig)
|
||||||
db, err := sqlx.Connect("postgres", pgConfig)
|
sqlxdb, err = sqlx.Connect("postgres", pgConfig)
|
||||||
Expect(err).Should(BeNil())
|
Expect(err).Should(BeNil())
|
||||||
Expect(db).ShouldNot(BeNil())
|
Expect(sqlxdb).ShouldNot(BeNil())
|
||||||
})
|
|
||||||
|
|
||||||
BeforeEach(func() {
|
|
||||||
node := core.Node{
|
|
||||||
GenesisBlock: "GENESIS",
|
|
||||||
NetworkId: 1,
|
|
||||||
Id: "b6f90c0fdd8ec9607aed8ee45c69322e47b7063f0bfb7a29c8ecafab24d0a22d24dd2329b5ee6ed4125a03cb14e57fd584e67f9e53e6c631055cbbd82f080845",
|
|
||||||
ClientName: "Geth/v1.7.2-stable-1db4ecdc/darwin-amd64/go1.9",
|
|
||||||
}
|
|
||||||
db = postgres.NewTestDB(node)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
It("serializes big.Int to db", func() {
|
It("serializes big.Int to db", func() {
|
||||||
@ -51,9 +43,9 @@ var _ = Describe("Postgres DB", func() {
|
|||||||
// sized int, so use string representation of big.Int
|
// sized int, so use string representation of big.Int
|
||||||
// and cast on insert
|
// and cast on insert
|
||||||
|
|
||||||
cfg, _ := config.NewConfig("private")
|
pgConnectString := config.DbConnectionString(test_config.DBConfig)
|
||||||
pgConfig := config.DbConnectionString(cfg.Database)
|
db, err := sqlx.Connect("postgres", pgConnectString)
|
||||||
db, err := sqlx.Connect("postgres", pgConfig)
|
Expect(err).NotTo(HaveOccurred())
|
||||||
|
|
||||||
bi := new(big.Int)
|
bi := new(big.Int)
|
||||||
bi.SetString("34940183920000000000", 10)
|
bi.SetString("34940183920000000000", 10)
|
||||||
@ -87,10 +79,9 @@ var _ = Describe("Postgres DB", func() {
|
|||||||
Nonce: badNonce,
|
Nonce: badNonce,
|
||||||
Transactions: []core.Transaction{},
|
Transactions: []core.Transaction{},
|
||||||
}
|
}
|
||||||
cfg, _ := config.NewConfig("private")
|
node := core.Node{GenesisBlock: "GENESIS", NetworkID: 1, ID: "x123", ClientName: "geth"}
|
||||||
node := core.Node{GenesisBlock: "GENESIS", NetworkId: 1, Id: "x123", ClientName: "geth"}
|
db := test_config.NewTestDB(node)
|
||||||
db, _ := postgres.NewDB(cfg.Database, node)
|
blocksRepository := repositories.BlockRepository{DB: db}
|
||||||
blocksRepository := postgres.BlockRepository{DB: db}
|
|
||||||
|
|
||||||
err1 := blocksRepository.CreateOrUpdateBlock(badBlock)
|
err1 := blocksRepository.CreateOrUpdateBlock(badBlock)
|
||||||
savedBlock, err2 := blocksRepository.GetBlock(123)
|
savedBlock, err2 := blocksRepository.GetBlock(123)
|
||||||
@ -102,16 +93,15 @@ var _ = Describe("Postgres DB", func() {
|
|||||||
|
|
||||||
It("throws error when can't connect to the database", func() {
|
It("throws error when can't connect to the database", func() {
|
||||||
invalidDatabase := config.Database{}
|
invalidDatabase := config.Database{}
|
||||||
node := core.Node{GenesisBlock: "GENESIS", NetworkId: 1, Id: "x123", ClientName: "geth"}
|
node := core.Node{GenesisBlock: "GENESIS", NetworkID: 1, ID: "x123", ClientName: "geth"}
|
||||||
_, err := postgres.NewDB(invalidDatabase, node)
|
_, err := postgres.NewDB(invalidDatabase, node)
|
||||||
Expect(err).To(Equal(postgres.ErrDBConnectionFailed))
|
Expect(err).To(Equal(postgres.ErrDBConnectionFailed))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("throws error when can't create node", func() {
|
It("throws error when can't create node", func() {
|
||||||
cfg, _ := config.NewConfig("private")
|
|
||||||
badHash := fmt.Sprintf("x %s", strings.Repeat("1", 100))
|
badHash := fmt.Sprintf("x %s", strings.Repeat("1", 100))
|
||||||
node := core.Node{GenesisBlock: badHash, NetworkId: 1, Id: "x123", ClientName: "geth"}
|
node := core.Node{GenesisBlock: badHash, NetworkID: 1, ID: "x123", ClientName: "geth"}
|
||||||
_, err := postgres.NewDB(cfg.Database, node)
|
_, err := postgres.NewDB(test_config.DBConfig, node)
|
||||||
Expect(err).To(Equal(postgres.ErrUnableToSetNode))
|
Expect(err).To(Equal(postgres.ErrUnableToSetNode))
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -123,10 +113,9 @@ var _ = Describe("Postgres DB", func() {
|
|||||||
BlockNumber: 1,
|
BlockNumber: 1,
|
||||||
TxHash: badTxHash,
|
TxHash: badTxHash,
|
||||||
}
|
}
|
||||||
cfg, _ := config.NewConfig("private")
|
node := core.Node{GenesisBlock: "GENESIS", NetworkID: 1, ID: "x123", ClientName: "geth"}
|
||||||
node := core.Node{GenesisBlock: "GENESIS", NetworkId: 1, Id: "x123", ClientName: "geth"}
|
db, _ := postgres.NewDB(test_config.DBConfig, node)
|
||||||
db, _ := postgres.NewDB(cfg.Database, node)
|
logRepository := repositories.LogRepository{DB: db}
|
||||||
logRepository := postgres.LogRepository{DB: db}
|
|
||||||
|
|
||||||
err := logRepository.CreateLogs([]core.Log{badLog})
|
err := logRepository.CreateLogs([]core.Log{badLog})
|
||||||
savedBlock := logRepository.GetLogs("x123", 1)
|
savedBlock := logRepository.GetLogs("x123", 1)
|
||||||
@ -143,10 +132,9 @@ var _ = Describe("Postgres DB", func() {
|
|||||||
Number: 123,
|
Number: 123,
|
||||||
Transactions: []core.Transaction{badTransaction},
|
Transactions: []core.Transaction{badTransaction},
|
||||||
}
|
}
|
||||||
cfg, _ := config.NewConfig("private")
|
node := core.Node{GenesisBlock: "GENESIS", NetworkID: 1, ID: "x123", ClientName: "geth"}
|
||||||
node := core.Node{GenesisBlock: "GENESIS", NetworkId: 1, Id: "x123", ClientName: "geth"}
|
db, _ := postgres.NewDB(test_config.DBConfig, node)
|
||||||
db, _ := postgres.NewDB(cfg.Database, node)
|
blockRepository := repositories.BlockRepository{DB: db}
|
||||||
blockRepository := postgres.BlockRepository{DB: db}
|
|
||||||
|
|
||||||
err1 := blockRepository.CreateOrUpdateBlock(block)
|
err1 := blockRepository.CreateOrUpdateBlock(block)
|
||||||
savedBlock, err2 := blockRepository.GetBlock(123)
|
savedBlock, err2 := blockRepository.GetBlock(123)
|
@ -1,15 +1,15 @@
|
|||||||
package postgres
|
package repositories
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/repositories"
|
"github.com/vulcanize/vulcanizedb/pkg/datastore"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -17,7 +17,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type BlockRepository struct {
|
type BlockRepository struct {
|
||||||
*DB
|
*postgres.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
func (blockRepository BlockRepository) SetBlocksStatus(chainHead int64) {
|
func (blockRepository BlockRepository) SetBlocksStatus(chainHead int64) {
|
||||||
@ -79,12 +79,12 @@ func (blockRepository BlockRepository) GetBlock(blockNumber int64) (core.Block,
|
|||||||
reward,
|
reward,
|
||||||
uncles_reward
|
uncles_reward
|
||||||
FROM blocks
|
FROM blocks
|
||||||
WHERE node_id = $1 AND number = $2`, blockRepository.nodeId, blockNumber)
|
WHERE node_id = $1 AND number = $2`, blockRepository.NodeID, blockNumber)
|
||||||
savedBlock, err := blockRepository.loadBlock(blockRows)
|
savedBlock, err := blockRepository.loadBlock(blockRows)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch err {
|
switch err {
|
||||||
case sql.ErrNoRows:
|
case sql.ErrNoRows:
|
||||||
return core.Block{}, repositories.ErrBlockDoesNotExist(blockNumber)
|
return core.Block{}, datastore.ErrBlockDoesNotExist(blockNumber)
|
||||||
default:
|
default:
|
||||||
return savedBlock, err
|
return savedBlock, err
|
||||||
}
|
}
|
||||||
@ -100,16 +100,16 @@ func (blockRepository BlockRepository) insertBlock(block core.Block) error {
|
|||||||
(node_id, number, gaslimit, gasused, time, difficulty, hash, nonce, parenthash, size, uncle_hash, is_final, miner, extra_data, reward, uncles_reward)
|
(node_id, number, gaslimit, gasused, time, difficulty, hash, nonce, parenthash, size, uncle_hash, is_final, miner, extra_data, reward, uncles_reward)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16)
|
||||||
RETURNING id `,
|
RETURNING id `,
|
||||||
blockRepository.nodeId, block.Number, block.GasLimit, block.GasUsed, block.Time, block.Difficulty, block.Hash, block.Nonce, block.ParentHash, block.Size, block.UncleHash, block.IsFinal, block.Miner, block.ExtraData, block.Reward, block.UnclesReward).
|
blockRepository.NodeID, block.Number, block.GasLimit, block.GasUsed, block.Time, block.Difficulty, block.Hash, block.Nonce, block.ParentHash, block.Size, block.UncleHash, block.IsFinal, block.Miner, block.ExtraData, block.Reward, block.UnclesReward).
|
||||||
Scan(&blockId)
|
Scan(&blockId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
return ErrDBInsertFailed
|
return postgres.ErrDBInsertFailed
|
||||||
}
|
}
|
||||||
err = blockRepository.createTransactions(tx, blockId, block.Transactions)
|
err = blockRepository.createTransactions(tx, blockId, block.Transactions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
return ErrDBInsertFailed
|
return postgres.ErrDBInsertFailed
|
||||||
}
|
}
|
||||||
tx.Commit()
|
tx.Commit()
|
||||||
return nil
|
return nil
|
||||||
@ -191,7 +191,7 @@ func (blockRepository BlockRepository) getBlockHash(block core.Block) (string, b
|
|||||||
`SELECT hash
|
`SELECT hash
|
||||||
FROM blocks
|
FROM blocks
|
||||||
WHERE number = $1 AND node_id = $2`,
|
WHERE number = $1 AND node_id = $2`,
|
||||||
block.Number, blockRepository.nodeId)
|
block.Number, blockRepository.NodeID)
|
||||||
return retrievedBlockHash, blockExists(retrievedBlockHash)
|
return retrievedBlockHash, blockExists(retrievedBlockHash)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,7 +204,7 @@ func (blockRepository BlockRepository) createLogs(tx *sql.Tx, logs []core.Log, r
|
|||||||
tlog.BlockNumber, tlog.Address, tlog.TxHash, tlog.Index, tlog.Topics[0], tlog.Topics[1], tlog.Topics[2], tlog.Topics[3], tlog.Data, receiptId,
|
tlog.BlockNumber, tlog.Address, tlog.TxHash, tlog.Index, tlog.Topics[0], tlog.Topics[1], tlog.Topics[2], tlog.Topics[3], tlog.Data, receiptId,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ErrDBInsertFailed
|
return postgres.ErrDBInsertFailed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -219,9 +219,9 @@ func (blockRepository BlockRepository) removeBlock(blockNumber int64) error {
|
|||||||
`DELETE FROM
|
`DELETE FROM
|
||||||
blocks
|
blocks
|
||||||
WHERE number=$1 AND node_id=$2`,
|
WHERE number=$1 AND node_id=$2`,
|
||||||
blockNumber, blockRepository.nodeId)
|
blockNumber, blockRepository.NodeID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ErrDBDeleteFailed
|
return postgres.ErrDBDeleteFailed
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -261,7 +261,6 @@ func (blockRepository BlockRepository) LoadTransactions(transactionRows *sqlx.Ro
|
|||||||
var transaction core.Transaction
|
var transaction core.Transaction
|
||||||
err := transactionRows.StructScan(&transaction)
|
err := transactionRows.StructScan(&transaction)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
transactions = append(transactions, transaction)
|
transactions = append(transactions, transaction)
|
@ -1,4 +1,4 @@
|
|||||||
package postgres_test
|
package repositories_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/big"
|
"math/big"
|
||||||
@ -7,22 +7,24 @@ import (
|
|||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/repositories"
|
"github.com/vulcanize/vulcanizedb/pkg/datastore"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/repositories/postgres"
|
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres/repositories"
|
||||||
|
"github.com/vulcanize/vulcanizedb/test_config"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("Saving blocks", func() {
|
var _ = Describe("Saving blocks", func() {
|
||||||
var db *postgres.DB
|
var db *postgres.DB
|
||||||
var blockRepository repositories.BlockRepository
|
var blockRepository datastore.BlockRepository
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
node := core.Node{
|
node := core.Node{
|
||||||
GenesisBlock: "GENESIS",
|
GenesisBlock: "GENESIS",
|
||||||
NetworkId: 1,
|
NetworkID: 1,
|
||||||
Id: "b6f90c0fdd8ec9607aed8ee45c69322e47b7063f0bfb7a29c8ecafab24d0a22d24dd2329b5ee6ed4125a03cb14e57fd584e67f9e53e6c631055cbbd82f080845",
|
ID: "b6f90c0fdd8ec9607aed8ee45c69322e47b7063f0bfb7a29c8ecafab24d0a22d24dd2329b5ee6ed4125a03cb14e57fd584e67f9e53e6c631055cbbd82f080845",
|
||||||
ClientName: "Geth/v1.7.2-stable-1db4ecdc/darwin-amd64/go1.9",
|
ClientName: "Geth/v1.7.2-stable-1db4ecdc/darwin-amd64/go1.9",
|
||||||
}
|
}
|
||||||
db = postgres.NewTestDB(node)
|
db = test_config.NewTestDB(node)
|
||||||
blockRepository = postgres.BlockRepository{DB: db}
|
blockRepository = repositories.BlockRepository{DB: db}
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -33,12 +35,12 @@ var _ = Describe("Saving blocks", func() {
|
|||||||
blockRepository.CreateOrUpdateBlock(block)
|
blockRepository.CreateOrUpdateBlock(block)
|
||||||
nodeTwo := core.Node{
|
nodeTwo := core.Node{
|
||||||
GenesisBlock: "0x456",
|
GenesisBlock: "0x456",
|
||||||
NetworkId: 1,
|
NetworkID: 1,
|
||||||
Id: "x123456",
|
ID: "x123456",
|
||||||
ClientName: "Geth",
|
ClientName: "Geth",
|
||||||
}
|
}
|
||||||
dbTwo := postgres.NewTestDB(nodeTwo)
|
dbTwo := test_config.NewTestDB(nodeTwo)
|
||||||
repositoryTwo := postgres.BlockRepository{DB: dbTwo}
|
repositoryTwo := repositories.BlockRepository{DB: dbTwo}
|
||||||
|
|
||||||
_, err := repositoryTwo.GetBlock(123)
|
_, err := repositoryTwo.GetBlock(123)
|
||||||
Expect(err).To(HaveOccurred())
|
Expect(err).To(HaveOccurred())
|
||||||
@ -161,10 +163,10 @@ var _ = Describe("Saving blocks", func() {
|
|||||||
blockRepository.CreateOrUpdateBlock(blockOne)
|
blockRepository.CreateOrUpdateBlock(blockOne)
|
||||||
nodeTwo := core.Node{
|
nodeTwo := core.Node{
|
||||||
GenesisBlock: "0x456",
|
GenesisBlock: "0x456",
|
||||||
NetworkId: 1,
|
NetworkID: 1,
|
||||||
}
|
}
|
||||||
dbTwo := postgres.NewTestDB(nodeTwo)
|
dbTwo := test_config.NewTestDB(nodeTwo)
|
||||||
repositoryTwo := postgres.BlockRepository{DB: dbTwo}
|
repositoryTwo := repositories.BlockRepository{DB: dbTwo}
|
||||||
|
|
||||||
blockRepository.CreateOrUpdateBlock(blockOne)
|
blockRepository.CreateOrUpdateBlock(blockOne)
|
||||||
repositoryTwo.CreateOrUpdateBlock(blockTwo)
|
repositoryTwo.CreateOrUpdateBlock(blockTwo)
|
||||||
@ -283,6 +285,5 @@ var _ = Describe("Saving blocks", func() {
|
|||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(blockTwo.IsFinal).To(BeFalse())
|
Expect(blockTwo.IsFinal).To(BeFalse())
|
||||||
})
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
})
|
})
|
@ -1,14 +1,15 @@
|
|||||||
package postgres
|
package repositories
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/repositories"
|
"github.com/vulcanize/vulcanizedb/pkg/datastore"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ContractRepository struct {
|
type ContractRepository struct {
|
||||||
*DB
|
*postgres.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
func (contractRepository ContractRepository) CreateContract(contract core.Contract) error {
|
func (contractRepository ContractRepository) CreateContract(contract core.Contract) error {
|
||||||
@ -25,7 +26,7 @@ func (contractRepository ContractRepository) CreateContract(contract core.Contra
|
|||||||
SET contract_hash = $1, contract_abi = $2
|
SET contract_hash = $1, contract_abi = $2
|
||||||
`, contract.Hash, abiToInsert)
|
`, contract.Hash, abiToInsert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ErrDBInsertFailed
|
return postgres.ErrDBInsertFailed
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -47,7 +48,7 @@ func (contractRepository ContractRepository) GetContract(contractHash string) (c
|
|||||||
`SELECT contract_hash, contract_abi FROM watched_contracts WHERE contract_hash=$1`, contractHash)
|
`SELECT contract_hash, contract_abi FROM watched_contracts WHERE contract_hash=$1`, contractHash)
|
||||||
err := contract.Scan(&hash, &abi)
|
err := contract.Scan(&hash, &abi)
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
return core.Contract{}, repositories.ErrContractDoesNotExist(contractHash)
|
return core.Contract{}, datastore.ErrContractDoesNotExist(contractHash)
|
||||||
}
|
}
|
||||||
savedContract := contractRepository.addTransactions(core.Contract{Hash: hash, Abi: abi})
|
savedContract := contractRepository.addTransactions(core.Contract{Hash: hash, Abi: abi})
|
||||||
return savedContract, nil
|
return savedContract, nil
|
@ -1,4 +1,4 @@
|
|||||||
package postgres_test
|
package repositories_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sort"
|
"sort"
|
||||||
@ -6,24 +6,26 @@ import (
|
|||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/repositories"
|
"github.com/vulcanize/vulcanizedb/pkg/datastore"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/repositories/postgres"
|
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres/repositories"
|
||||||
|
"github.com/vulcanize/vulcanizedb/test_config"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("Creating contracts", func() {
|
var _ = Describe("Creating contracts", func() {
|
||||||
var db *postgres.DB
|
var db *postgres.DB
|
||||||
var contractRepository repositories.ContractRepository
|
var contractRepository datastore.ContractRepository
|
||||||
var node core.Node
|
var node core.Node
|
||||||
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
node = core.Node{
|
node = core.Node{
|
||||||
GenesisBlock: "GENESIS",
|
GenesisBlock: "GENESIS",
|
||||||
NetworkId: 1,
|
NetworkID: 1,
|
||||||
Id: "b6f90c0fdd8ec9607aed8ee45c69322e47b7063f0bfb7a29c8ecafab24d0a22d24dd2329b5ee6ed4125a03cb14e57fd584e67f9e53e6c631055cbbd82f080845",
|
ID: "b6f90c0fdd8ec9607aed8ee45c69322e47b7063f0bfb7a29c8ecafab24d0a22d24dd2329b5ee6ed4125a03cb14e57fd584e67f9e53e6c631055cbbd82f080845",
|
||||||
ClientName: "Geth/v1.7.2-stable-1db4ecdc/darwin-amd64/go1.9",
|
ClientName: "Geth/v1.7.2-stable-1db4ecdc/darwin-amd64/go1.9",
|
||||||
}
|
}
|
||||||
db = postgres.NewTestDB(node)
|
db = test_config.NewTestDB(node)
|
||||||
contractRepository = postgres.ContractRepository{DB: db}
|
contractRepository = repositories.ContractRepository{DB: db}
|
||||||
})
|
})
|
||||||
|
|
||||||
It("returns the contract when it exists", func() {
|
It("returns the contract when it exists", func() {
|
||||||
@ -50,8 +52,8 @@ var _ = Describe("Creating contracts", func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
It("returns transactions 'To' a contract", func() {
|
It("returns transactions 'To' a contract", func() {
|
||||||
var blockRepository repositories.BlockRepository
|
var blockRepository datastore.BlockRepository
|
||||||
blockRepository = postgres.BlockRepository{DB: db}
|
blockRepository = repositories.BlockRepository{DB: db}
|
||||||
block := core.Block{
|
block := core.Block{
|
||||||
Number: 123,
|
Number: 123,
|
||||||
Transactions: []core.Transaction{
|
Transactions: []core.Transaction{
|
@ -1,4 +1,4 @@
|
|||||||
package postgres
|
package repositories
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
@ -6,12 +6,13 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/datastore"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/filters"
|
"github.com/vulcanize/vulcanizedb/pkg/filters"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/repositories"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type FilterRepository struct {
|
type FilterRepository struct {
|
||||||
*DB
|
*postgres.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
func (filterRepository FilterRepository) CreateFilter(query filters.LogFilter) error {
|
func (filterRepository FilterRepository) CreateFilter(query filters.LogFilter) error {
|
||||||
@ -41,7 +42,7 @@ func (filterRepository FilterRepository) GetFilter(name string) (filters.LogFilt
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
switch err {
|
switch err {
|
||||||
case sql.ErrNoRows:
|
case sql.ErrNoRows:
|
||||||
return filters.LogFilter{}, repositories.ErrFilterDoesNotExist(name)
|
return filters.LogFilter{}, datastore.ErrFilterDoesNotExist(name)
|
||||||
default:
|
default:
|
||||||
return filters.LogFilter{}, err
|
return filters.LogFilter{}, err
|
||||||
}
|
}
|
||||||
@ -57,9 +58,7 @@ func (t *DBTopics) Scan(src interface{}) error {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return error(errors.New("scan source was not []byte"))
|
return error(errors.New("scan source was not []byte"))
|
||||||
}
|
}
|
||||||
json.Unmarshal(asBytes, &t)
|
return json.Unmarshal(asBytes, &t)
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type DBLogFilter struct {
|
type DBLogFilter struct {
|
@ -1,27 +1,29 @@
|
|||||||
package postgres_test
|
package repositories_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/datastore"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres/repositories"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/filters"
|
"github.com/vulcanize/vulcanizedb/pkg/filters"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/repositories"
|
"github.com/vulcanize/vulcanizedb/test_config"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/repositories/postgres"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("Log Filters Repository", func() {
|
var _ = Describe("Log Filters Repository", func() {
|
||||||
var db *postgres.DB
|
var db *postgres.DB
|
||||||
var filterRepository repositories.FilterRepository
|
var filterRepository datastore.FilterRepository
|
||||||
var node core.Node
|
var node core.Node
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
node = core.Node{
|
node = core.Node{
|
||||||
GenesisBlock: "GENESIS",
|
GenesisBlock: "GENESIS",
|
||||||
NetworkId: 1,
|
NetworkID: 1,
|
||||||
Id: "b6f90c0fdd8ec9607aed8ee45c69322e47b7063f0bfb7a29c8ecafab24d0a22d24dd2329b5ee6ed4125a03cb14e57fd584e67f9e53e6c631055cbbd82f080845",
|
ID: "b6f90c0fdd8ec9607aed8ee45c69322e47b7063f0bfb7a29c8ecafab24d0a22d24dd2329b5ee6ed4125a03cb14e57fd584e67f9e53e6c631055cbbd82f080845",
|
||||||
ClientName: "Geth/v1.7.2-stable-1db4ecdc/darwin-amd64/go1.9",
|
ClientName: "Geth/v1.7.2-stable-1db4ecdc/darwin-amd64/go1.9",
|
||||||
}
|
}
|
||||||
db = postgres.NewTestDB(node)
|
db = test_config.NewTestDB(node)
|
||||||
filterRepository = postgres.FilterRepository{DB: db}
|
filterRepository = repositories.FilterRepository{DB: db}
|
||||||
})
|
})
|
||||||
|
|
||||||
Describe("LogFilter", func() {
|
Describe("LogFilter", func() {
|
||||||
@ -63,7 +65,7 @@ var _ = Describe("Log Filters Repository", func() {
|
|||||||
|
|
||||||
It("gets a log filter", func() {
|
It("gets a log filter", func() {
|
||||||
|
|
||||||
logFilter1 := filters.LogFilter{
|
expectedLogFilter1 := filters.LogFilter{
|
||||||
Name: "TestFilter1",
|
Name: "TestFilter1",
|
||||||
FromBlock: 1,
|
FromBlock: 1,
|
||||||
ToBlock: 2,
|
ToBlock: 2,
|
||||||
@ -75,9 +77,9 @@ var _ = Describe("Log Filters Repository", func() {
|
|||||||
"",
|
"",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
err := filterRepository.CreateFilter(logFilter1)
|
err := filterRepository.CreateFilter(expectedLogFilter1)
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
logFilter2 := filters.LogFilter{
|
expectedLogFilter2 := filters.LogFilter{
|
||||||
Name: "TestFilter2",
|
Name: "TestFilter2",
|
||||||
FromBlock: 10,
|
FromBlock: 10,
|
||||||
ToBlock: 20,
|
ToBlock: 20,
|
||||||
@ -89,20 +91,20 @@ var _ = Describe("Log Filters Repository", func() {
|
|||||||
"",
|
"",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
err = filterRepository.CreateFilter(logFilter2)
|
err = filterRepository.CreateFilter(expectedLogFilter2)
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
logFilter1, err = filterRepository.GetFilter("TestFilter1")
|
logFilter1, err := filterRepository.GetFilter("TestFilter1")
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(logFilter1).To(Equal(logFilter1))
|
Expect(logFilter1).To(Equal(expectedLogFilter1))
|
||||||
logFilter1, err = filterRepository.GetFilter("TestFilter1")
|
logFilter2, err := filterRepository.GetFilter("TestFilter2")
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(logFilter2).To(Equal(logFilter2))
|
Expect(logFilter2).To(Equal(expectedLogFilter2))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("returns ErrFilterDoesNotExist error when log does not exist", func() {
|
It("returns ErrFilterDoesNotExist error when log does not exist", func() {
|
||||||
_, err := filterRepository.GetFilter("TestFilter1")
|
_, err := filterRepository.GetFilter("TestFilter1")
|
||||||
Expect(err).To(Equal(repositories.ErrFilterDoesNotExist("TestFilter1")))
|
Expect(err).To(Equal(datastore.ErrFilterDoesNotExist("TestFilter1")))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
@ -1,4 +1,4 @@
|
|||||||
package postgres
|
package repositories
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -6,10 +6,11 @@ import (
|
|||||||
"database/sql"
|
"database/sql"
|
||||||
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LogRepository struct {
|
type LogRepository struct {
|
||||||
*DB
|
*postgres.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
func (logRepository LogRepository) CreateLogs(lgs []core.Log) error {
|
func (logRepository LogRepository) CreateLogs(lgs []core.Log) error {
|
||||||
@ -23,7 +24,7 @@ func (logRepository LogRepository) CreateLogs(lgs []core.Log) error {
|
|||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
tx.Rollback()
|
tx.Rollback()
|
||||||
return ErrDBInsertFailed
|
return postgres.ErrDBInsertFailed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tx.Commit()
|
tx.Commit()
|
@ -1,4 +1,4 @@
|
|||||||
package postgres_test
|
package repositories_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sort"
|
"sort"
|
||||||
@ -6,23 +6,25 @@ import (
|
|||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/repositories"
|
"github.com/vulcanize/vulcanizedb/pkg/datastore"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/repositories/postgres"
|
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres/repositories"
|
||||||
|
"github.com/vulcanize/vulcanizedb/test_config"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("Logs Repository", func() {
|
var _ = Describe("Logs Repository", func() {
|
||||||
var db *postgres.DB
|
var db *postgres.DB
|
||||||
var logsRepository repositories.LogRepository
|
var logsRepository datastore.LogRepository
|
||||||
var node core.Node
|
var node core.Node
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
node = core.Node{
|
node = core.Node{
|
||||||
GenesisBlock: "GENESIS",
|
GenesisBlock: "GENESIS",
|
||||||
NetworkId: 1,
|
NetworkID: 1,
|
||||||
Id: "b6f90c0fdd8ec9607aed8ee45c69322e47b7063f0bfb7a29c8ecafab24d0a22d24dd2329b5ee6ed4125a03cb14e57fd584e67f9e53e6c631055cbbd82f080845",
|
ID: "b6f90c0fdd8ec9607aed8ee45c69322e47b7063f0bfb7a29c8ecafab24d0a22d24dd2329b5ee6ed4125a03cb14e57fd584e67f9e53e6c631055cbbd82f080845",
|
||||||
ClientName: "Geth/v1.7.2-stable-1db4ecdc/darwin-amd64/go1.9",
|
ClientName: "Geth/v1.7.2-stable-1db4ecdc/darwin-amd64/go1.9",
|
||||||
}
|
}
|
||||||
db = postgres.NewTestDB(node)
|
db = test_config.NewTestDB(node)
|
||||||
logsRepository = postgres.LogRepository{DB: db}
|
logsRepository = repositories.LogRepository{DB: db}
|
||||||
})
|
})
|
||||||
|
|
||||||
Describe("Saving logs", func() {
|
Describe("Saving logs", func() {
|
||||||
@ -115,8 +117,8 @@ var _ = Describe("Logs Repository", func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
It("saves the logs attached to a receipt", func() {
|
It("saves the logs attached to a receipt", func() {
|
||||||
var blockRepository repositories.BlockRepository
|
var blockRepository datastore.BlockRepository
|
||||||
blockRepository = postgres.BlockRepository{DB: db}
|
blockRepository = repositories.BlockRepository{DB: db}
|
||||||
|
|
||||||
logs := []core.Log{{
|
logs := []core.Log{{
|
||||||
Address: "0x8a4774fe82c63484afef97ca8d89a6ea5e21f973",
|
Address: "0x8a4774fe82c63484afef97ca8d89a6ea5e21f973",
|
||||||
@ -176,6 +178,5 @@ var _ = Describe("Logs Repository", func() {
|
|||||||
expected := logs[1:]
|
expected := logs[1:]
|
||||||
Expect(retrievedLogs).To(Equal(expected))
|
Expect(retrievedLogs).To(Equal(expected))
|
||||||
})
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
})
|
})
|
@ -1,14 +1,15 @@
|
|||||||
package postgres
|
package repositories
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/repositories"
|
"github.com/vulcanize/vulcanizedb/pkg/datastore"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ReceiptRepository struct {
|
type ReceiptRepository struct {
|
||||||
*DB
|
*postgres.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
func (receiptRepository ReceiptRepository) GetReceipt(txHash string) (core.Receipt, error) {
|
func (receiptRepository ReceiptRepository) GetReceipt(txHash string) (core.Receipt, error) {
|
||||||
@ -25,7 +26,7 @@ func (receiptRepository ReceiptRepository) GetReceipt(txHash string) (core.Recei
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
switch err {
|
switch err {
|
||||||
case sql.ErrNoRows:
|
case sql.ErrNoRows:
|
||||||
return core.Receipt{}, repositories.ErrReceiptDoesNotExist(txHash)
|
return core.Receipt{}, datastore.ErrReceiptDoesNotExist(txHash)
|
||||||
default:
|
default:
|
||||||
return core.Receipt{}, err
|
return core.Receipt{}, err
|
||||||
}
|
}
|
@ -1,33 +1,35 @@
|
|||||||
package postgres_test
|
package repositories_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/repositories"
|
"github.com/vulcanize/vulcanizedb/pkg/datastore"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/repositories/postgres"
|
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres/repositories"
|
||||||
|
"github.com/vulcanize/vulcanizedb/test_config"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ bool = Describe("Logs Repository", func() {
|
var _ bool = Describe("Logs Repository", func() {
|
||||||
var receiptRepository repositories.ReceiptRepository
|
var receiptRepository datastore.ReceiptRepository
|
||||||
var db *postgres.DB
|
var db *postgres.DB
|
||||||
var node core.Node
|
var node core.Node
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
node = core.Node{
|
node = core.Node{
|
||||||
GenesisBlock: "GENESIS",
|
GenesisBlock: "GENESIS",
|
||||||
NetworkId: 1,
|
NetworkID: 1,
|
||||||
Id: "b6f90c0fdd8ec9607aed8ee45c69322e47b7063f0bfb7a29c8ecafab24d0a22d24dd2329b5ee6ed4125a03cb14e57fd584e67f9e53e6c631055cbbd82f080845",
|
ID: "b6f90c0fdd8ec9607aed8ee45c69322e47b7063f0bfb7a29c8ecafab24d0a22d24dd2329b5ee6ed4125a03cb14e57fd584e67f9e53e6c631055cbbd82f080845",
|
||||||
ClientName: "Geth/v1.7.2-stable-1db4ecdc/darwin-amd64/go1.9",
|
ClientName: "Geth/v1.7.2-stable-1db4ecdc/darwin-amd64/go1.9",
|
||||||
}
|
}
|
||||||
db = postgres.NewTestDB(node)
|
db = test_config.NewTestDB(node)
|
||||||
receiptRepository = postgres.ReceiptRepository{DB: db}
|
receiptRepository = repositories.ReceiptRepository{DB: db}
|
||||||
})
|
})
|
||||||
|
|
||||||
Describe("Saving receipts", func() {
|
Describe("Saving receipts", func() {
|
||||||
It("returns the receipt when it exists", func() {
|
It("returns the receipt when it exists", func() {
|
||||||
var blockRepository repositories.BlockRepository
|
var blockRepository datastore.BlockRepository
|
||||||
db := postgres.NewTestDB(node)
|
db := test_config.NewTestDB(node)
|
||||||
blockRepository = postgres.BlockRepository{DB: db}
|
blockRepository = repositories.BlockRepository{DB: db}
|
||||||
expected := core.Receipt{
|
expected := core.Receipt{
|
||||||
ContractAddress: "0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae",
|
ContractAddress: "0xde0b295669a9fd93d5f28d9ec85e40f4cb697bae",
|
||||||
CumulativeGasUsed: 7996119,
|
CumulativeGasUsed: 7996119,
|
||||||
@ -64,9 +66,9 @@ var _ bool = Describe("Logs Repository", func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
It("still saves receipts without logs", func() {
|
It("still saves receipts without logs", func() {
|
||||||
var blockRepository repositories.BlockRepository
|
var blockRepository datastore.BlockRepository
|
||||||
db := postgres.NewTestDB(node)
|
db := test_config.NewTestDB(node)
|
||||||
blockRepository = postgres.BlockRepository{DB: db}
|
blockRepository = repositories.BlockRepository{DB: db}
|
||||||
receipt := core.Receipt{
|
receipt := core.Receipt{
|
||||||
TxHash: "0x002c4799161d809b23f67884eb6598c9df5894929fe1a9ead97ca175d360f547",
|
TxHash: "0x002c4799161d809b23f67884eb6598c9df5894929fe1a9ead97ca175d360f547",
|
||||||
}
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package repositories_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRepositories(t *testing.T) {
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
RunSpecs(t, "Repositories Suite")
|
||||||
|
}
|
@ -1,15 +1,16 @@
|
|||||||
package postgres
|
package repositories
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres"
|
||||||
)
|
)
|
||||||
|
|
||||||
type WatchedEventRepository struct {
|
type WatchedEventRepository struct {
|
||||||
*DB
|
*postgres.DB
|
||||||
}
|
}
|
||||||
|
|
||||||
func (watchedEventRepository WatchedEventRepository) GetWatchedEvents(name string) ([]*core.WatchedEvent, error) {
|
func (watchedEventRepository WatchedEventRepository) GetWatchedEvents(name string) ([]*core.WatchedEvent, error) {
|
||||||
rows, err := watchedEventRepository.DB.Queryx(`SELECT name, block_number, address, tx_hash, index, topic0, topic1, topic2, topic3, data FROM watched_event_logs where name=$1`, name)
|
rows, err := watchedEventRepository.DB.Queryx(`SELECT id, name, block_number, address, tx_hash, index, topic0, topic1, topic2, topic3, data FROM watched_event_logs where name=$1`, name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -18,7 +19,7 @@ func (watchedEventRepository WatchedEventRepository) GetWatchedEvents(name strin
|
|||||||
lgs := make([]*core.WatchedEvent, 0)
|
lgs := make([]*core.WatchedEvent, 0)
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
lg := new(core.WatchedEvent)
|
lg := new(core.WatchedEvent)
|
||||||
err := rows.StructScan(lg)
|
err = rows.StructScan(lg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
@ -1,24 +1,27 @@
|
|||||||
package postgres_test
|
package repositories_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/datastore"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres/repositories"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/filters"
|
"github.com/vulcanize/vulcanizedb/pkg/filters"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/repositories/postgres"
|
"github.com/vulcanize/vulcanizedb/test_config"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("Watched Events Repository", func() {
|
var _ = Describe("Watched Events Repository", func() {
|
||||||
var db *postgres.DB
|
var db *postgres.DB
|
||||||
var logRepository postgres.LogRepository
|
var logRepository datastore.LogRepository
|
||||||
var filterRepository postgres.FilterRepository
|
var filterRepository datastore.FilterRepository
|
||||||
var watchedEventRepository postgres.WatchedEventRepository
|
var watchedEventRepository datastore.WatchedEventRepository
|
||||||
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
db = postgres.NewTestDB(core.Node{})
|
db = test_config.NewTestDB(core.Node{})
|
||||||
logRepository = postgres.LogRepository{DB: db}
|
logRepository = repositories.LogRepository{DB: db}
|
||||||
filterRepository = postgres.FilterRepository{DB: db}
|
filterRepository = repositories.FilterRepository{DB: db}
|
||||||
watchedEventRepository = postgres.WatchedEventRepository{DB: db}
|
watchedEventRepository = repositories.WatchedEventRepository{DB: db}
|
||||||
})
|
})
|
||||||
|
|
||||||
It("retrieves watched event logs that match the event filter", func() {
|
It("retrieves watched event logs that match the event filter", func() {
|
||||||
@ -57,7 +60,15 @@ var _ = Describe("Watched Events Repository", func() {
|
|||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
matchingLogs, err := watchedEventRepository.GetWatchedEvents("Filter1")
|
matchingLogs, err := watchedEventRepository.GetWatchedEvents("Filter1")
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(matchingLogs).To(Equal(expectedWatchedEventLog))
|
Expect(len(matchingLogs)).To(Equal(1))
|
||||||
|
Expect(matchingLogs[0].Name).To(Equal(expectedWatchedEventLog[0].Name))
|
||||||
|
Expect(matchingLogs[0].BlockNumber).To(Equal(expectedWatchedEventLog[0].BlockNumber))
|
||||||
|
Expect(matchingLogs[0].TxHash).To(Equal(expectedWatchedEventLog[0].TxHash))
|
||||||
|
Expect(matchingLogs[0].Address).To(Equal(expectedWatchedEventLog[0].Address))
|
||||||
|
Expect(matchingLogs[0].Topic0).To(Equal(expectedWatchedEventLog[0].Topic0))
|
||||||
|
Expect(matchingLogs[0].Topic1).To(Equal(expectedWatchedEventLog[0].Topic1))
|
||||||
|
Expect(matchingLogs[0].Topic2).To(Equal(expectedWatchedEventLog[0].Topic2))
|
||||||
|
Expect(matchingLogs[0].Data).To(Equal(expectedWatchedEventLog[0].Data))
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -103,7 +114,14 @@ var _ = Describe("Watched Events Repository", func() {
|
|||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
matchingLogs, err := watchedEventRepository.GetWatchedEvents("Filter1")
|
matchingLogs, err := watchedEventRepository.GetWatchedEvents("Filter1")
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
Expect(matchingLogs).To(Equal(expectedWatchedEventLog))
|
Expect(len(matchingLogs)).To(Equal(1))
|
||||||
|
Expect(matchingLogs[0].Name).To(Equal(expectedWatchedEventLog[0].Name))
|
||||||
|
Expect(matchingLogs[0].BlockNumber).To(Equal(expectedWatchedEventLog[0].BlockNumber))
|
||||||
|
Expect(matchingLogs[0].TxHash).To(Equal(expectedWatchedEventLog[0].TxHash))
|
||||||
|
Expect(matchingLogs[0].Address).To(Equal(expectedWatchedEventLog[0].Address))
|
||||||
|
Expect(matchingLogs[0].Topic0).To(Equal(expectedWatchedEventLog[0].Topic0))
|
||||||
|
Expect(matchingLogs[0].Topic1).To(Equal(expectedWatchedEventLog[0].Topic1))
|
||||||
|
Expect(matchingLogs[0].Topic2).To(Equal(expectedWatchedEventLog[0].Topic2))
|
||||||
|
Expect(matchingLogs[0].Data).To(Equal(expectedWatchedEventLog[0].Data))
|
||||||
})
|
})
|
||||||
})
|
})
|
@ -1,7 +1,6 @@
|
|||||||
package repositories
|
package datastore
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||||
@ -9,7 +8,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var ErrBlockDoesNotExist = func(blockNumber int64) error {
|
var ErrBlockDoesNotExist = func(blockNumber int64) error {
|
||||||
return errors.New(fmt.Sprintf("Block number %d does not exist", blockNumber))
|
return fmt.Errorf("Block number %d does not exist", blockNumber)
|
||||||
}
|
}
|
||||||
|
|
||||||
type BlockRepository interface {
|
type BlockRepository interface {
|
||||||
@ -20,7 +19,7 @@ type BlockRepository interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var ErrContractDoesNotExist = func(contractHash string) error {
|
var ErrContractDoesNotExist = func(contractHash string) error {
|
||||||
return errors.New(fmt.Sprintf("Contract %v does not exist", contractHash))
|
return fmt.Errorf("Contract %v does not exist", contractHash)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ContractRepository interface {
|
type ContractRepository interface {
|
||||||
@ -30,7 +29,7 @@ type ContractRepository interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var ErrFilterDoesNotExist = func(name string) error {
|
var ErrFilterDoesNotExist = func(name string) error {
|
||||||
return errors.New(fmt.Sprintf("filter %s does not exist", name))
|
return fmt.Errorf("filter %s does not exist", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
type FilterRepository interface {
|
type FilterRepository interface {
|
||||||
@ -44,7 +43,7 @@ type LogRepository interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var ErrReceiptDoesNotExist = func(txHash string) error {
|
var ErrReceiptDoesNotExist = func(txHash string) error {
|
||||||
return errors.New(fmt.Sprintf("Receipt for tx: %v does not exist", txHash))
|
return fmt.Errorf("Receipt for tx: %v does not exist", txHash)
|
||||||
}
|
}
|
||||||
|
|
||||||
type ReceiptRepository interface {
|
type ReceiptRepository interface {
|
@ -15,6 +15,11 @@ type Blockchain struct {
|
|||||||
blocksChannel chan core.Block
|
blocksChannel chan core.Block
|
||||||
WasToldToStop bool
|
WasToldToStop bool
|
||||||
node core.Node
|
node core.Node
|
||||||
|
ContractReturnValue []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (blockchain *Blockchain) CallContract(contractHash string, input []byte, blockNumber *big.Int) ([]byte, error) {
|
||||||
|
return blockchain.ContractReturnValue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (blockchain *Blockchain) LastBlock() *big.Int {
|
func (blockchain *Blockchain) LastBlock() *big.Int {
|
||||||
@ -50,7 +55,7 @@ func NewBlockchain() *Blockchain {
|
|||||||
blocks: make(map[int64]core.Block),
|
blocks: make(map[int64]core.Block),
|
||||||
logs: make(map[string][]core.Log),
|
logs: make(map[string][]core.Log),
|
||||||
contractAttributes: make(map[string]map[string]string),
|
contractAttributes: make(map[string]map[string]string),
|
||||||
node: core.Node{GenesisBlock: "GENESIS", NetworkId: 1, Id: "x123", ClientName: "Geth"},
|
node: core.Node{GenesisBlock: "GENESIS", NetworkID: 1, ID: "x123", ClientName: "Geth"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,7 +96,7 @@ func (blockchain *Blockchain) GetAttributes(contract core.Contract) (core.Contra
|
|||||||
var contractAttributes core.ContractAttributes
|
var contractAttributes core.ContractAttributes
|
||||||
attributes, ok := blockchain.contractAttributes[contract.Hash+"-1"]
|
attributes, ok := blockchain.contractAttributes[contract.Hash+"-1"]
|
||||||
if ok {
|
if ok {
|
||||||
for key, _ := range attributes {
|
for key := range attributes {
|
||||||
contractAttributes = append(contractAttributes, core.ContractAttribute{Name: key, Type: "string"})
|
contractAttributes = append(contractAttributes, core.ContractAttribute{Name: key, Type: "string"})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,7 +31,7 @@ func (filterQuery *LogFilter) UnmarshalJSON(input []byte) error {
|
|||||||
}{
|
}{
|
||||||
Alias: (*Alias)(filterQuery),
|
Alias: (*Alias)(filterQuery),
|
||||||
}
|
}
|
||||||
if err := json.Unmarshal(input, &aux); err != nil {
|
if err = json.Unmarshal(input, &aux); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if filterQuery.Name == "" {
|
if filterQuery.Name == "" {
|
||||||
|
@ -25,20 +25,19 @@ type Response struct {
|
|||||||
Result string
|
Result string
|
||||||
}
|
}
|
||||||
|
|
||||||
type EtherScanApi struct {
|
type EtherScanAPI struct {
|
||||||
client *http.Client
|
client *http.Client
|
||||||
url string
|
url string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewEtherScanClient(url string) *EtherScanApi {
|
func NewEtherScanClient(url string) *EtherScanAPI {
|
||||||
return &EtherScanApi{
|
return &EtherScanAPI{
|
||||||
client: &http.Client{Timeout: 10 * time.Second},
|
client: &http.Client{Timeout: 10 * time.Second},
|
||||||
url: url,
|
url: url,
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GenUrl(network string) string {
|
func GenURL(network string) string {
|
||||||
switch network {
|
switch network {
|
||||||
case "ropsten":
|
case "ropsten":
|
||||||
return "https://ropsten.etherscan.io"
|
return "https://ropsten.etherscan.io"
|
||||||
@ -52,7 +51,7 @@ func GenUrl(network string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//https://api.etherscan.io/api?module=contract&action=getabi&address=%s
|
//https://api.etherscan.io/api?module=contract&action=getabi&address=%s
|
||||||
func (e *EtherScanApi) GetAbi(contractHash string) (string, error) {
|
func (e *EtherScanAPI) GetAbi(contractHash string) (string, error) {
|
||||||
target := new(Response)
|
target := new(Response)
|
||||||
request := fmt.Sprintf("%s/api?module=contract&action=getabi&address=%s", e.url, contractHash)
|
request := fmt.Sprintf("%s/api?module=contract&action=getabi&address=%s", e.url, contractHash)
|
||||||
r, err := e.client.Get(request)
|
r, err := e.client.Get(request)
|
||||||
@ -60,8 +59,8 @@ func (e *EtherScanApi) GetAbi(contractHash string) (string, error) {
|
|||||||
return "", ErrApiRequestFailed
|
return "", ErrApiRequestFailed
|
||||||
}
|
}
|
||||||
defer r.Body.Close()
|
defer r.Body.Close()
|
||||||
json.NewDecoder(r.Body).Decode(&target)
|
err = json.NewDecoder(r.Body).Decode(&target)
|
||||||
return target.Result, nil
|
return target.Result, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseAbiFile(abiFilePath string) (abi.ABI, error) {
|
func ParseAbiFile(abiFilePath string) (abi.ABI, error) {
|
||||||
|
@ -1,20 +1,16 @@
|
|||||||
package geth_test
|
package geth_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
"github.com/onsi/gomega/ghttp"
|
"github.com/onsi/gomega/ghttp"
|
||||||
cfg "github.com/vulcanize/vulcanizedb/pkg/config"
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/geth"
|
"github.com/vulcanize/vulcanizedb/pkg/geth"
|
||||||
|
"github.com/vulcanize/vulcanizedb/test_config"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("ABI files", func() {
|
var _ = Describe("ABI files", func() {
|
||||||
@ -22,7 +18,7 @@ var _ = Describe("ABI files", func() {
|
|||||||
Describe("Reading ABI files", func() {
|
Describe("Reading ABI files", func() {
|
||||||
|
|
||||||
It("loads a valid ABI file", func() {
|
It("loads a valid ABI file", func() {
|
||||||
path := filepath.Join(cfg.ProjectRoot(), "pkg", "geth", "testing", "valid_abi.json")
|
path := test_config.ABIFilePath + "valid_abi.json"
|
||||||
|
|
||||||
contractAbi, err := geth.ParseAbiFile(path)
|
contractAbi, err := geth.ParseAbiFile(path)
|
||||||
|
|
||||||
@ -31,7 +27,7 @@ var _ = Describe("ABI files", func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
It("reads the contents of a valid ABI file", func() {
|
It("reads the contents of a valid ABI file", func() {
|
||||||
path := filepath.Join(cfg.ProjectRoot(), "pkg", "geth", "testing", "valid_abi.json")
|
path := test_config.ABIFilePath + "valid_abi.json"
|
||||||
|
|
||||||
contractAbi, err := geth.ReadAbiFile(path)
|
contractAbi, err := geth.ReadAbiFile(path)
|
||||||
|
|
||||||
@ -40,7 +36,7 @@ var _ = Describe("ABI files", func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
It("returns an error when the file does not exist", func() {
|
It("returns an error when the file does not exist", func() {
|
||||||
path := filepath.Join(cfg.ProjectRoot(), "pkg", "geth", "testing", "missing_abi.json")
|
path := test_config.ABIFilePath + "missing_abi.json"
|
||||||
|
|
||||||
contractAbi, err := geth.ParseAbiFile(path)
|
contractAbi, err := geth.ParseAbiFile(path)
|
||||||
|
|
||||||
@ -49,7 +45,7 @@ var _ = Describe("ABI files", func() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
It("returns an error when the file has invalid contents", func() {
|
It("returns an error when the file has invalid contents", func() {
|
||||||
path := filepath.Join(cfg.ProjectRoot(), "pkg", "geth", "testing", "invalid_abi.json")
|
path := test_config.ABIFilePath + "invalid_abi.json"
|
||||||
|
|
||||||
contractAbi, err := geth.ParseAbiFile(path)
|
contractAbi, err := geth.ParseAbiFile(path)
|
||||||
|
|
||||||
@ -61,19 +57,20 @@ var _ = Describe("ABI files", func() {
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
server *ghttp.Server
|
server *ghttp.Server
|
||||||
client *geth.EtherScanApi
|
client *geth.EtherScanAPI
|
||||||
abiString string
|
abiString string
|
||||||
|
err error
|
||||||
)
|
)
|
||||||
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
server = ghttp.NewServer()
|
server = ghttp.NewServer()
|
||||||
client = geth.NewEtherScanClient(server.URL())
|
client = geth.NewEtherScanClient(server.URL())
|
||||||
path := filepath.Join(cfg.ProjectRoot(), "pkg", "geth", "testing", "sample_abi.json")
|
path := test_config.ABIFilePath + "sample_abi.json"
|
||||||
abiString, err := geth.ReadAbiFile(path)
|
abiString, err = geth.ReadAbiFile(path)
|
||||||
|
|
||||||
|
Expect(err).NotTo(HaveOccurred())
|
||||||
_, err = geth.ParseAbi(abiString)
|
_, err = geth.ParseAbi(abiString)
|
||||||
if err != nil {
|
Expect(err).NotTo(HaveOccurred())
|
||||||
log.Fatalln("Could not parse ABI")
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
AfterEach(func() {
|
AfterEach(func() {
|
||||||
@ -104,14 +101,14 @@ var _ = Describe("ABI files", func() {
|
|||||||
|
|
||||||
Describe("Generating etherscan endpoints based on network", func() {
|
Describe("Generating etherscan endpoints based on network", func() {
|
||||||
It("should return the main endpoint as the default", func() {
|
It("should return the main endpoint as the default", func() {
|
||||||
url := geth.GenUrl("")
|
url := geth.GenURL("")
|
||||||
Expect(url).To(Equal("https://api.etherscan.io"))
|
Expect(url).To(Equal("https://api.etherscan.io"))
|
||||||
})
|
})
|
||||||
|
|
||||||
It("generates various test network endpoint if test network is supplied", func() {
|
It("generates various test network endpoint if test network is supplied", func() {
|
||||||
ropstenUrl := geth.GenUrl("ropsten")
|
ropstenUrl := geth.GenURL("ropsten")
|
||||||
rinkebyUrl := geth.GenUrl("rinkeby")
|
rinkebyUrl := geth.GenURL("rinkeby")
|
||||||
kovanUrl := geth.GenUrl("kovan")
|
kovanUrl := geth.GenURL("kovan")
|
||||||
|
|
||||||
Expect(ropstenUrl).To(Equal("https://ropsten.etherscan.io"))
|
Expect(ropstenUrl).To(Equal("https://ropsten.etherscan.io"))
|
||||||
Expect(kovanUrl).To(Equal("https://kovan.etherscan.io"))
|
Expect(kovanUrl).To(Equal("https://kovan.etherscan.io"))
|
||||||
|
@ -12,12 +12,12 @@ import (
|
|||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
type GethClient interface {
|
type Client interface {
|
||||||
TransactionSender(ctx context.Context, tx *types.Transaction, block common.Hash, index uint) (common.Address, error)
|
TransactionSender(ctx context.Context, tx *types.Transaction, block common.Hash, index uint) (common.Address, error)
|
||||||
TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error)
|
TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func ToCoreBlock(gethBlock *types.Block, client GethClient) core.Block {
|
func ToCoreBlock(gethBlock *types.Block, client Client) core.Block {
|
||||||
transactions := convertTransactionsToCore(gethBlock, client)
|
transactions := convertTransactionsToCore(gethBlock, client)
|
||||||
coreBlock := core.Block{
|
coreBlock := core.Block{
|
||||||
Difficulty: gethBlock.Difficulty().Int64(),
|
Difficulty: gethBlock.Difficulty().Int64(),
|
||||||
@ -39,7 +39,7 @@ func ToCoreBlock(gethBlock *types.Block, client GethClient) core.Block {
|
|||||||
return coreBlock
|
return coreBlock
|
||||||
}
|
}
|
||||||
|
|
||||||
func convertTransactionsToCore(gethBlock *types.Block, client GethClient) []core.Transaction {
|
func convertTransactionsToCore(gethBlock *types.Block, client Client) []core.Transaction {
|
||||||
transactions := make([]core.Transaction, 0)
|
transactions := make([]core.Transaction, 0)
|
||||||
for i, gethTransaction := range gethBlock.Transactions() {
|
for i, gethTransaction := range gethBlock.Transactions() {
|
||||||
from, err := client.TransactionSender(context.Background(), gethTransaction, gethBlock.Hash(), uint(i))
|
from, err := client.TransactionSender(context.Background(), gethTransaction, gethBlock.Hash(), uint(i))
|
||||||
@ -56,7 +56,7 @@ func convertTransactionsToCore(gethBlock *types.Block, client GethClient) []core
|
|||||||
return transactions
|
return transactions
|
||||||
}
|
}
|
||||||
|
|
||||||
func appendReceiptToTransaction(client GethClient, transaction core.Transaction) (core.Transaction, error) {
|
func appendReceiptToTransaction(client Client, transaction core.Transaction) (core.Transaction, error) {
|
||||||
gethReceipt, err := client.TransactionReceipt(context.Background(), common.HexToHash(transaction.Hash))
|
gethReceipt, err := client.TransactionReceipt(context.Background(), common.HexToHash(transaction.Hash))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
@ -84,7 +84,6 @@ func transToCoreTrans(transaction *types.Transaction, from *common.Address) core
|
|||||||
func addressToHex(to *common.Address) string {
|
func addressToHex(to *common.Address) string {
|
||||||
if to == nil {
|
if to == nil {
|
||||||
return ""
|
return ""
|
||||||
} else {
|
|
||||||
return to.Hex()
|
|
||||||
}
|
}
|
||||||
|
return to.Hex()
|
||||||
}
|
}
|
||||||
|
@ -29,12 +29,13 @@ func NewBlockchain(ipcPath string) *Blockchain {
|
|||||||
blockchain := Blockchain{}
|
blockchain := Blockchain{}
|
||||||
rpcClient, err := rpc.Dial(ipcPath)
|
rpcClient, err := rpc.Dial(ipcPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
log.Println("Unable to connect to node")
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
client := ethclient.NewClient(rpcClient)
|
client := ethclient.NewClient(rpcClient)
|
||||||
blockchain.node = node.Info(rpcClient)
|
blockchain.node = node.Info(rpcClient)
|
||||||
if infura := isInfuraNode(ipcPath); infura {
|
if infura := isInfuraNode(ipcPath); infura {
|
||||||
blockchain.node.Id = "infura"
|
blockchain.node.ID = "infura"
|
||||||
blockchain.node.ClientName = "infura"
|
blockchain.node.ClientName = "infura"
|
||||||
}
|
}
|
||||||
blockchain.client = client
|
blockchain.client = client
|
||||||
|
@ -27,7 +27,7 @@ func (blockchain *Blockchain) GetAttribute(contract core.Contract, attributeName
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, ErrInvalidStateAttribute
|
return nil, ErrInvalidStateAttribute
|
||||||
}
|
}
|
||||||
output, err := callContract(contract.Hash, input, blockchain, blockNumber)
|
output, err := blockchain.callContract(contract.Hash, input, blockNumber)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -38,7 +38,23 @@ func (blockchain *Blockchain) GetAttribute(contract core.Contract, attributeName
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func callContract(contractHash string, input []byte, blockchain *Blockchain, blockNumber *big.Int) ([]byte, error) {
|
func (blockchain *Blockchain) FetchContractData(abiJSON string, address string, method string, methodArg interface{}, result interface{}, blockNumber int64) error {
|
||||||
|
parsed, err := ParseAbi(abiJSON)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
input, err := parsed.Pack(method, methodArg)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
output, err := blockchain.callContract(address, input, big.NewInt(blockNumber))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return parsed.Unpack(result, method, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (blockchain *Blockchain) callContract(contractHash string, input []byte, blockNumber *big.Int) ([]byte, error) {
|
||||||
to := common.HexToAddress(contractHash)
|
to := common.HexToAddress(contractHash)
|
||||||
msg := ethereum.CallMsg{To: &to, Data: input}
|
msg := ethereum.CallMsg{To: &to, Data: input}
|
||||||
return blockchain.client.CallContract(context.Background(), msg, blockNumber)
|
return blockchain.client.CallContract(context.Background(), msg, blockNumber)
|
||||||
|
@ -13,13 +13,13 @@ import (
|
|||||||
|
|
||||||
func Info(client *rpc.Client) core.Node {
|
func Info(client *rpc.Client) core.Node {
|
||||||
node := core.Node{}
|
node := core.Node{}
|
||||||
node.NetworkId = NetworkId(client)
|
node.NetworkID = NetworkID(client)
|
||||||
node.GenesisBlock = GenesisBlock(client)
|
node.GenesisBlock = GenesisBlock(client)
|
||||||
node.Id, node.ClientName = IdClientName(client)
|
node.ID, node.ClientName = IDClientName(client)
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
func IdClientName(client *rpc.Client) (string, string) {
|
func IDClientName(client *rpc.Client) (string, string) {
|
||||||
var info p2p.NodeInfo
|
var info p2p.NodeInfo
|
||||||
modules, _ := client.SupportedModules()
|
modules, _ := client.SupportedModules()
|
||||||
if _, ok := modules["admin"]; ok {
|
if _, ok := modules["admin"]; ok {
|
||||||
@ -29,7 +29,7 @@ func IdClientName(client *rpc.Client) (string, string) {
|
|||||||
return "", ""
|
return "", ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func NetworkId(client *rpc.Client) float64 {
|
func NetworkID(client *rpc.Client) float64 {
|
||||||
var version string
|
var version string
|
||||||
client.CallContext(context.Background(), &version, "net_version")
|
client.CallContext(context.Background(), &version, "net_version")
|
||||||
networkId, _ := strconv.ParseFloat(version, 64)
|
networkId, _ := strconv.ParseFloat(version, 64)
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
package testing
|
package testing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"path/filepath"
|
"log"
|
||||||
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/config"
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/geth"
|
"github.com/vulcanize/vulcanizedb/pkg/geth"
|
||||||
|
"github.com/vulcanize/vulcanizedb/test_config"
|
||||||
)
|
)
|
||||||
|
|
||||||
func FindAttribute(contractAttributes core.ContractAttributes, attributeName string) *core.ContractAttribute {
|
func FindAttribute(contractAttributes core.ContractAttributes, attributeName string) *core.ContractAttribute {
|
||||||
@ -25,7 +25,9 @@ func SampleContract() core.Contract {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func sampleAbiFileContents() string {
|
func sampleAbiFileContents() string {
|
||||||
abiFilepath := filepath.Join(config.ProjectRoot(), "pkg", "geth", "testing", "sample_abi.json")
|
abiFileContents, err := geth.ReadAbiFile(test_config.ABIFilePath + "sample_abi.json")
|
||||||
abiFileContents, _ := geth.ReadAbiFile(abiFilepath)
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
return abiFileContents
|
return abiFileContents
|
||||||
}
|
}
|
||||||
|
@ -1,169 +0,0 @@
|
|||||||
package graphql_server
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/filters"
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/repositories"
|
|
||||||
)
|
|
||||||
|
|
||||||
var Schema = `
|
|
||||||
schema {
|
|
||||||
query: Query
|
|
||||||
}
|
|
||||||
type Query {
|
|
||||||
logFilter(name: String!): LogFilter
|
|
||||||
watchedEvents(name: String!): WatchedEventList
|
|
||||||
}
|
|
||||||
|
|
||||||
type LogFilter {
|
|
||||||
name: String!
|
|
||||||
fromBlock: Int
|
|
||||||
toBlock: Int
|
|
||||||
address: String!
|
|
||||||
topics: [String]!
|
|
||||||
}
|
|
||||||
|
|
||||||
type WatchedEventList{
|
|
||||||
total: Int!
|
|
||||||
watchedEvents: [WatchedEvent]!
|
|
||||||
}
|
|
||||||
|
|
||||||
type WatchedEvent {
|
|
||||||
name: String!
|
|
||||||
blockNumber: Int!
|
|
||||||
address: String!
|
|
||||||
tx_hash: String!
|
|
||||||
topic0: String!
|
|
||||||
topic1: String!
|
|
||||||
topic2: String!
|
|
||||||
topic3: String!
|
|
||||||
data: String!
|
|
||||||
}
|
|
||||||
`
|
|
||||||
|
|
||||||
type GraphQLRepositories struct {
|
|
||||||
repositories.BlockRepository
|
|
||||||
repositories.LogRepository
|
|
||||||
repositories.WatchedEventRepository
|
|
||||||
repositories.FilterRepository
|
|
||||||
}
|
|
||||||
|
|
||||||
type Resolver struct {
|
|
||||||
graphQLRepositories GraphQLRepositories
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewResolver(repositories GraphQLRepositories) *Resolver {
|
|
||||||
return &Resolver{graphQLRepositories: repositories}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Resolver) LogFilter(args struct {
|
|
||||||
Name string
|
|
||||||
}) (*logFilterResolver, error) {
|
|
||||||
logFilter, err := r.graphQLRepositories.GetFilter(args.Name)
|
|
||||||
if err != nil {
|
|
||||||
return &logFilterResolver{}, err
|
|
||||||
}
|
|
||||||
return &logFilterResolver{&logFilter}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type logFilterResolver struct {
|
|
||||||
lf *filters.LogFilter
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lfr *logFilterResolver) Name() string {
|
|
||||||
return lfr.lf.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lfr *logFilterResolver) FromBlock() *int32 {
|
|
||||||
fromBlock := int32(lfr.lf.FromBlock)
|
|
||||||
return &fromBlock
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lfr *logFilterResolver) ToBlock() *int32 {
|
|
||||||
toBlock := int32(lfr.lf.ToBlock)
|
|
||||||
return &toBlock
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lfr *logFilterResolver) Address() string {
|
|
||||||
return lfr.lf.Address
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lfr *logFilterResolver) Topics() []*string {
|
|
||||||
var topics = make([]*string, 4)
|
|
||||||
for i := range topics {
|
|
||||||
if lfr.lf.Topics[i] != "" {
|
|
||||||
topics[i] = &lfr.lf.Topics[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return topics
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Resolver) WatchedEvents(args struct {
|
|
||||||
Name string
|
|
||||||
}) (*watchedEventsResolver, error) {
|
|
||||||
watchedEvents, err := r.graphQLRepositories.GetWatchedEvents(args.Name)
|
|
||||||
if err != nil {
|
|
||||||
return &watchedEventsResolver{}, err
|
|
||||||
}
|
|
||||||
return &watchedEventsResolver{watchedEvents: watchedEvents}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
type watchedEventsResolver struct {
|
|
||||||
watchedEvents []*core.WatchedEvent
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wesr watchedEventsResolver) WatchedEvents() []*watchedEventResolver {
|
|
||||||
return resolveWatchedEvents(wesr.watchedEvents)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wesr watchedEventsResolver) Total() int32 {
|
|
||||||
return int32(len(wesr.watchedEvents))
|
|
||||||
}
|
|
||||||
|
|
||||||
func resolveWatchedEvents(watchedEvents []*core.WatchedEvent) []*watchedEventResolver {
|
|
||||||
watchedEventResolvers := make([]*watchedEventResolver, 0)
|
|
||||||
for _, watchedEvent := range watchedEvents {
|
|
||||||
watchedEventResolvers = append(watchedEventResolvers, &watchedEventResolver{watchedEvent})
|
|
||||||
}
|
|
||||||
return watchedEventResolvers
|
|
||||||
}
|
|
||||||
|
|
||||||
type watchedEventResolver struct {
|
|
||||||
we *core.WatchedEvent
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wer watchedEventResolver) Name() string {
|
|
||||||
return wer.we.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wer watchedEventResolver) BlockNumber() int32 {
|
|
||||||
return int32(wer.we.BlockNumber)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wer watchedEventResolver) Address() string {
|
|
||||||
return wer.we.Address
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wer watchedEventResolver) TxHash() string {
|
|
||||||
return wer.we.TxHash
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wer watchedEventResolver) Topic0() string {
|
|
||||||
return wer.we.Topic0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wer watchedEventResolver) Topic1() string {
|
|
||||||
return wer.we.Topic1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wer watchedEventResolver) Topic2() string {
|
|
||||||
return wer.we.Topic2
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wer watchedEventResolver) Topic3() string {
|
|
||||||
return wer.we.Topic3
|
|
||||||
}
|
|
||||||
|
|
||||||
func (wer watchedEventResolver) Data() string {
|
|
||||||
return wer.we.Data
|
|
||||||
}
|
|
@ -1,178 +0,0 @@
|
|||||||
package graphql_server_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"context"
|
|
||||||
|
|
||||||
"github.com/neelance/graphql-go"
|
|
||||||
. "github.com/onsi/ginkgo"
|
|
||||||
. "github.com/onsi/gomega"
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/config"
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/filters"
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/graphql_server"
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/repositories/postgres"
|
|
||||||
)
|
|
||||||
|
|
||||||
func formatJSON(data []byte) []byte {
|
|
||||||
var v interface{}
|
|
||||||
if err := json.Unmarshal(data, &v); err != nil {
|
|
||||||
log.Fatalf("invalid JSON: %s", err)
|
|
||||||
}
|
|
||||||
formatted, err := json.Marshal(v)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
return formatted
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ = Describe("GraphQL", func() {
|
|
||||||
var cfg config.Config
|
|
||||||
var graphQLRepositories graphql_server.GraphQLRepositories
|
|
||||||
|
|
||||||
BeforeEach(func() {
|
|
||||||
|
|
||||||
cfg, _ = config.NewConfig("private")
|
|
||||||
node := core.Node{GenesisBlock: "GENESIS", NetworkId: 1, Id: "x123", ClientName: "geth"}
|
|
||||||
db := postgres.NewTestDB(node)
|
|
||||||
blockRepository := &postgres.BlockRepository{DB: db}
|
|
||||||
logRepository := &postgres.LogRepository{DB: db}
|
|
||||||
filterRepository := &postgres.FilterRepository{DB: db}
|
|
||||||
watchedEventRepository := &postgres.WatchedEventRepository{DB: db}
|
|
||||||
graphQLRepositories = graphql_server.GraphQLRepositories{
|
|
||||||
WatchedEventRepository: watchedEventRepository,
|
|
||||||
BlockRepository: blockRepository,
|
|
||||||
LogRepository: logRepository,
|
|
||||||
FilterRepository: filterRepository,
|
|
||||||
}
|
|
||||||
|
|
||||||
err := graphQLRepositories.CreateFilter(filters.LogFilter{
|
|
||||||
Name: "TestFilter1",
|
|
||||||
FromBlock: 1,
|
|
||||||
ToBlock: 10,
|
|
||||||
Address: "0x123456789",
|
|
||||||
Topics: core.Topics{0: "topic=1", 2: "topic=2"},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
filter, err := graphQLRepositories.GetFilter("TestFilter1")
|
|
||||||
if err != nil {
|
|
||||||
log.Println(filter)
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
matchingEvent := core.Log{
|
|
||||||
BlockNumber: 5,
|
|
||||||
TxHash: "0xTX1",
|
|
||||||
Address: "0x123456789",
|
|
||||||
Topics: core.Topics{0: "topic=1", 2: "topic=2"},
|
|
||||||
Index: 0,
|
|
||||||
Data: "0xDATADATADATA",
|
|
||||||
}
|
|
||||||
nonMatchingEvent := core.Log{
|
|
||||||
BlockNumber: 5,
|
|
||||||
TxHash: "0xTX2",
|
|
||||||
Address: "0xOTHERADDRESS",
|
|
||||||
Topics: core.Topics{0: "topic=1", 2: "topic=2"},
|
|
||||||
Index: 0,
|
|
||||||
Data: "0xDATADATADATA",
|
|
||||||
}
|
|
||||||
err = graphQLRepositories.CreateLogs([]core.Log{matchingEvent, nonMatchingEvent})
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
It("Queries example schema for specific log filter", func() {
|
|
||||||
var variables map[string]interface{}
|
|
||||||
resolver := graphql_server.NewResolver(graphQLRepositories)
|
|
||||||
var schema = graphql.MustParseSchema(graphql_server.Schema, resolver)
|
|
||||||
response := schema.Exec(context.Background(),
|
|
||||||
`{
|
|
||||||
logFilter(name: "TestFilter1") {
|
|
||||||
name
|
|
||||||
fromBlock
|
|
||||||
toBlock
|
|
||||||
address
|
|
||||||
topics
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
"",
|
|
||||||
variables)
|
|
||||||
expected := `{
|
|
||||||
"logFilter": {
|
|
||||||
"name": "TestFilter1",
|
|
||||||
"fromBlock": 1,
|
|
||||||
"toBlock": 10,
|
|
||||||
"address": "0x123456789",
|
|
||||||
"topics": ["topic=1", null, "topic=2", null]
|
|
||||||
}
|
|
||||||
}`
|
|
||||||
var v interface{}
|
|
||||||
if len(response.Errors) != 0 {
|
|
||||||
log.Fatal(response.Errors)
|
|
||||||
}
|
|
||||||
err := json.Unmarshal(response.Data, &v)
|
|
||||||
Expect(err).ToNot(HaveOccurred())
|
|
||||||
actualJSON := formatJSON(response.Data)
|
|
||||||
expectedJSON := formatJSON([]byte(expected))
|
|
||||||
Expect(actualJSON).To(Equal(expectedJSON))
|
|
||||||
})
|
|
||||||
|
|
||||||
It("Queries example schema for specific watched event log", func() {
|
|
||||||
var variables map[string]interface{}
|
|
||||||
|
|
||||||
resolver := graphql_server.NewResolver(graphQLRepositories)
|
|
||||||
var schema = graphql.MustParseSchema(graphql_server.Schema, resolver)
|
|
||||||
response := schema.Exec(context.Background(),
|
|
||||||
`{
|
|
||||||
watchedEvents(name: "TestFilter1") {
|
|
||||||
total
|
|
||||||
watchedEvents{
|
|
||||||
name
|
|
||||||
blockNumber
|
|
||||||
address
|
|
||||||
tx_hash
|
|
||||||
topic0
|
|
||||||
topic1
|
|
||||||
topic2
|
|
||||||
topic3
|
|
||||||
data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}`,
|
|
||||||
"",
|
|
||||||
variables)
|
|
||||||
expected := `{
|
|
||||||
"watchedEvents":
|
|
||||||
{
|
|
||||||
"total": 1,
|
|
||||||
"watchedEvents": [
|
|
||||||
{"name":"TestFilter1",
|
|
||||||
"blockNumber": 5,
|
|
||||||
"address": "0x123456789",
|
|
||||||
"tx_hash": "0xTX1",
|
|
||||||
"topic0": "topic=1",
|
|
||||||
"topic1": "",
|
|
||||||
"topic2": "topic=2",
|
|
||||||
"topic3": "",
|
|
||||||
"data": "0xDATADATADATA"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}`
|
|
||||||
var v interface{}
|
|
||||||
if len(response.Errors) != 0 {
|
|
||||||
log.Fatal(response.Errors)
|
|
||||||
}
|
|
||||||
err := json.Unmarshal(response.Data, &v)
|
|
||||||
Expect(err).ToNot(HaveOccurred())
|
|
||||||
actualJSON := formatJSON(response.Data)
|
|
||||||
expectedJSON := formatJSON([]byte(expected))
|
|
||||||
Expect(actualJSON).To(Equal(expectedJSON))
|
|
||||||
})
|
|
||||||
})
|
|
@ -4,10 +4,10 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/repositories"
|
"github.com/vulcanize/vulcanizedb/pkg/datastore"
|
||||||
)
|
)
|
||||||
|
|
||||||
func PopulateMissingBlocks(blockchain core.Blockchain, blockRepository repositories.BlockRepository, startingBlockNumber int64) int {
|
func PopulateMissingBlocks(blockchain core.Blockchain, blockRepository datastore.BlockRepository, startingBlockNumber int64) int {
|
||||||
lastBlock := blockchain.LastBlock().Int64()
|
lastBlock := blockchain.LastBlock().Int64()
|
||||||
blockRange := blockRepository.MissingBlockNumbers(startingBlockNumber, lastBlock-1)
|
blockRange := blockRepository.MissingBlockNumbers(startingBlockNumber, lastBlock-1)
|
||||||
log.SetPrefix("")
|
log.SetPrefix("")
|
||||||
@ -16,7 +16,7 @@ func PopulateMissingBlocks(blockchain core.Blockchain, blockRepository repositor
|
|||||||
return len(blockRange)
|
return len(blockRange)
|
||||||
}
|
}
|
||||||
|
|
||||||
func RetrieveAndUpdateBlocks(blockchain core.Blockchain, blockRepository repositories.BlockRepository, blockNumbers []int64) int {
|
func RetrieveAndUpdateBlocks(blockchain core.Blockchain, blockRepository datastore.BlockRepository, blockNumbers []int64) int {
|
||||||
for _, blockNumber := range blockNumbers {
|
for _, blockNumber := range blockNumbers {
|
||||||
block := blockchain.GetBlockByNumber(blockNumber)
|
block := blockchain.GetBlockByNumber(blockNumber)
|
||||||
blockRepository.CreateOrUpdateBlock(block)
|
blockRepository.CreateOrUpdateBlock(block)
|
||||||
|
@ -4,9 +4,9 @@ import (
|
|||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/datastore/inmemory"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/fakes"
|
"github.com/vulcanize/vulcanizedb/pkg/fakes"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/history"
|
"github.com/vulcanize/vulcanizedb/pkg/history"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/repositories/inmemory"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("Populating blocks", func() {
|
var _ = Describe("Populating blocks", func() {
|
||||||
|
@ -5,7 +5,7 @@ import (
|
|||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/repositories"
|
"github.com/vulcanize/vulcanizedb/pkg/datastore"
|
||||||
)
|
)
|
||||||
|
|
||||||
const WindowTemplate = `Validating Blocks
|
const WindowTemplate = `Validating Blocks
|
||||||
@ -17,12 +17,12 @@ var ParsedWindowTemplate = *template.Must(template.New("window").Parse(WindowTem
|
|||||||
|
|
||||||
type BlockValidator struct {
|
type BlockValidator struct {
|
||||||
blockchain core.Blockchain
|
blockchain core.Blockchain
|
||||||
blockRepository repositories.BlockRepository
|
blockRepository datastore.BlockRepository
|
||||||
windowSize int
|
windowSize int
|
||||||
parsedLoggingTemplate template.Template
|
parsedLoggingTemplate template.Template
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBlockValidator(blockchain core.Blockchain, blockRepository repositories.BlockRepository, windowSize int) *BlockValidator {
|
func NewBlockValidator(blockchain core.Blockchain, blockRepository datastore.BlockRepository, windowSize int) *BlockValidator {
|
||||||
return &BlockValidator{
|
return &BlockValidator{
|
||||||
blockchain,
|
blockchain,
|
||||||
blockRepository,
|
blockRepository,
|
||||||
|
@ -9,9 +9,9 @@ import (
|
|||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/datastore/inmemory"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/fakes"
|
"github.com/vulcanize/vulcanizedb/pkg/fakes"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/history"
|
"github.com/vulcanize/vulcanizedb/pkg/history"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/repositories/inmemory"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
package postgres
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/config"
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
|
||||||
)
|
|
||||||
|
|
||||||
func (db *DB) clearData() {
|
|
||||||
db.MustExec("DELETE FROM watched_contracts")
|
|
||||||
db.MustExec("DELETE FROM transactions")
|
|
||||||
db.MustExec("DELETE FROM blocks")
|
|
||||||
db.MustExec("DELETE FROM logs")
|
|
||||||
db.MustExec("DELETE FROM receipts")
|
|
||||||
db.MustExec("DELETE FROM log_filters")
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewTestDB(node core.Node) *DB {
|
|
||||||
cfg, _ := config.NewConfig("private")
|
|
||||||
db, _ := NewDB(cfg.Database, node)
|
|
||||||
db.clearData()
|
|
||||||
return db
|
|
||||||
}
|
|
78
test_config/test_config.go
Normal file
78
test_config/test_config.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package test_config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/config"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres"
|
||||||
|
)
|
||||||
|
|
||||||
|
var TestConfig *viper.Viper
|
||||||
|
var DBConfig config.Database
|
||||||
|
var TestClientConfig config.Client
|
||||||
|
var Infura *viper.Viper
|
||||||
|
var InfuraClient config.Client
|
||||||
|
var ABIFilePath string
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
setTestConfig()
|
||||||
|
setInfuraConfig()
|
||||||
|
setABIPath()
|
||||||
|
}
|
||||||
|
|
||||||
|
func setTestConfig() {
|
||||||
|
TestConfig = viper.New()
|
||||||
|
TestConfig.SetConfigName("private")
|
||||||
|
TestConfig.AddConfigPath("$GOPATH/src/github.com/vulcanize/vulcanizedb/environments/")
|
||||||
|
err := TestConfig.ReadInConfig()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
hn := TestConfig.GetString("database.hostname")
|
||||||
|
port := TestConfig.GetInt("database.port")
|
||||||
|
name := TestConfig.GetString("database.name")
|
||||||
|
DBConfig = config.Database{
|
||||||
|
Hostname: hn,
|
||||||
|
Name: name,
|
||||||
|
Port: port,
|
||||||
|
}
|
||||||
|
ipc := TestConfig.GetString("client.ipcpath")
|
||||||
|
gopath := os.Getenv("GOPATH")
|
||||||
|
TestClientConfig = config.Client{
|
||||||
|
IPCPath: gopath + "/src/github.com/vulcanize/vulcanizedb/" + ipc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setInfuraConfig() {
|
||||||
|
Infura = viper.New()
|
||||||
|
Infura.SetConfigName("infura")
|
||||||
|
Infura.AddConfigPath("$GOPATH/src/github.com/vulcanize/vulcanizedb/environments/")
|
||||||
|
err := Infura.ReadInConfig()
|
||||||
|
ipc := Infura.GetString("client.ipcpath")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
InfuraClient = config.Client{
|
||||||
|
IPCPath: ipc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setABIPath() {
|
||||||
|
gp := os.Getenv("GOPATH")
|
||||||
|
ABIFilePath = gp + "/src/github.com/vulcanize/vulcanizedb/pkg/geth/testing/"
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTestDB(node core.Node) *postgres.DB {
|
||||||
|
db, _ := postgres.NewDB(DBConfig, node)
|
||||||
|
db.MustExec("DELETE FROM watched_contracts")
|
||||||
|
db.MustExec("DELETE FROM transactions")
|
||||||
|
db.MustExec("DELETE FROM blocks")
|
||||||
|
db.MustExec("DELETE FROM logs")
|
||||||
|
db.MustExec("DELETE FROM receipts")
|
||||||
|
db.MustExec("DELETE FROM log_filters")
|
||||||
|
return db
|
||||||
|
}
|
@ -11,18 +11,10 @@ import (
|
|||||||
|
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/config"
|
"github.com/vulcanize/vulcanizedb/pkg/config"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/core"
|
"github.com/vulcanize/vulcanizedb/pkg/core"
|
||||||
|
"github.com/vulcanize/vulcanizedb/pkg/datastore/postgres"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/geth"
|
"github.com/vulcanize/vulcanizedb/pkg/geth"
|
||||||
"github.com/vulcanize/vulcanizedb/pkg/repositories/postgres"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func LoadConfig(environment string) config.Config {
|
|
||||||
cfg, err := config.NewConfig(environment)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error loading config\n%v", err)
|
|
||||||
}
|
|
||||||
return cfg
|
|
||||||
}
|
|
||||||
|
|
||||||
func LoadPostgres(database config.Database, node core.Node) postgres.DB {
|
func LoadPostgres(database config.Database, node core.Node) postgres.DB {
|
||||||
db, err := postgres.NewDB(database, node)
|
db, err := postgres.NewDB(database, node)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -53,7 +45,7 @@ func GetAbi(abiFilepath string, contractHash string, network string) string {
|
|||||||
if abiFilepath != "" {
|
if abiFilepath != "" {
|
||||||
contractAbiString = ReadAbiFile(abiFilepath)
|
contractAbiString = ReadAbiFile(abiFilepath)
|
||||||
} else {
|
} else {
|
||||||
url := geth.GenUrl(network)
|
url := geth.GenURL(network)
|
||||||
etherscan := geth.NewEtherScanClient(url)
|
etherscan := geth.NewEtherScanClient(url)
|
||||||
log.Printf("No ABI supplied. Retrieving ABI from Etherscan: %s", url)
|
log.Printf("No ABI supplied. Retrieving ABI from Etherscan: %s", url)
|
||||||
contractAbiString, _ = etherscan.GetAbi(contractHash)
|
contractAbiString, _ = etherscan.GetAbi(contractHash)
|
||||||
|
5
vendor/github.com/BurntSushi/toml/.gitignore
generated
vendored
5
vendor/github.com/BurntSushi/toml/.gitignore
generated
vendored
@ -1,5 +0,0 @@
|
|||||||
TAGS
|
|
||||||
tags
|
|
||||||
.*.swp
|
|
||||||
tomlcheck/tomlcheck
|
|
||||||
toml.test
|
|
15
vendor/github.com/BurntSushi/toml/.travis.yml
generated
vendored
15
vendor/github.com/BurntSushi/toml/.travis.yml
generated
vendored
@ -1,15 +0,0 @@
|
|||||||
language: go
|
|
||||||
go:
|
|
||||||
- 1.1
|
|
||||||
- 1.2
|
|
||||||
- 1.3
|
|
||||||
- 1.4
|
|
||||||
- 1.5
|
|
||||||
- 1.6
|
|
||||||
- tip
|
|
||||||
install:
|
|
||||||
- go install ./...
|
|
||||||
- go get github.com/BurntSushi/toml-test
|
|
||||||
script:
|
|
||||||
- export PATH="$PATH:$HOME/gopath/bin"
|
|
||||||
- make test
|
|
3
vendor/github.com/BurntSushi/toml/COMPATIBLE
generated
vendored
3
vendor/github.com/BurntSushi/toml/COMPATIBLE
generated
vendored
@ -1,3 +0,0 @@
|
|||||||
Compatible with TOML version
|
|
||||||
[v0.4.0](https://github.com/toml-lang/toml/blob/v0.4.0/versions/en/toml-v0.4.0.md)
|
|
||||||
|
|
14
vendor/github.com/BurntSushi/toml/COPYING
generated
vendored
14
vendor/github.com/BurntSushi/toml/COPYING
generated
vendored
@ -1,14 +0,0 @@
|
|||||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
|
||||||
Version 2, December 2004
|
|
||||||
|
|
||||||
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
|
||||||
|
|
||||||
Everyone is permitted to copy and distribute verbatim or modified
|
|
||||||
copies of this license document, and changing it is allowed as long
|
|
||||||
as the name is changed.
|
|
||||||
|
|
||||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
|
||||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
|
||||||
|
|
||||||
0. You just DO WHAT THE FUCK YOU WANT TO.
|
|
||||||
|
|
19
vendor/github.com/BurntSushi/toml/Makefile
generated
vendored
19
vendor/github.com/BurntSushi/toml/Makefile
generated
vendored
@ -1,19 +0,0 @@
|
|||||||
install:
|
|
||||||
go install ./...
|
|
||||||
|
|
||||||
test: install
|
|
||||||
go test -v
|
|
||||||
toml-test toml-test-decoder
|
|
||||||
toml-test -encoder toml-test-encoder
|
|
||||||
|
|
||||||
fmt:
|
|
||||||
gofmt -w *.go */*.go
|
|
||||||
colcheck *.go */*.go
|
|
||||||
|
|
||||||
tags:
|
|
||||||
find ./ -name '*.go' -print0 | xargs -0 gotags > TAGS
|
|
||||||
|
|
||||||
push:
|
|
||||||
git push origin master
|
|
||||||
git push github master
|
|
||||||
|
|
218
vendor/github.com/BurntSushi/toml/README.md
generated
vendored
218
vendor/github.com/BurntSushi/toml/README.md
generated
vendored
@ -1,218 +0,0 @@
|
|||||||
## TOML parser and encoder for Go with reflection
|
|
||||||
|
|
||||||
TOML stands for Tom's Obvious, Minimal Language. This Go package provides a
|
|
||||||
reflection interface similar to Go's standard library `json` and `xml`
|
|
||||||
packages. This package also supports the `encoding.TextUnmarshaler` and
|
|
||||||
`encoding.TextMarshaler` interfaces so that you can define custom data
|
|
||||||
representations. (There is an example of this below.)
|
|
||||||
|
|
||||||
Spec: https://github.com/toml-lang/toml
|
|
||||||
|
|
||||||
Compatible with TOML version
|
|
||||||
[v0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md)
|
|
||||||
|
|
||||||
Documentation: https://godoc.org/github.com/BurntSushi/toml
|
|
||||||
|
|
||||||
Installation:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go get github.com/BurntSushi/toml
|
|
||||||
```
|
|
||||||
|
|
||||||
Try the toml validator:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go get github.com/BurntSushi/toml/cmd/tomlv
|
|
||||||
tomlv some-toml-file.toml
|
|
||||||
```
|
|
||||||
|
|
||||||
[![Build Status](https://travis-ci.org/BurntSushi/toml.svg?branch=master)](https://travis-ci.org/BurntSushi/toml) [![GoDoc](https://godoc.org/github.com/BurntSushi/toml?status.svg)](https://godoc.org/github.com/BurntSushi/toml)
|
|
||||||
|
|
||||||
### Testing
|
|
||||||
|
|
||||||
This package passes all tests in
|
|
||||||
[toml-test](https://github.com/BurntSushi/toml-test) for both the decoder
|
|
||||||
and the encoder.
|
|
||||||
|
|
||||||
### Examples
|
|
||||||
|
|
||||||
This package works similarly to how the Go standard library handles `XML`
|
|
||||||
and `JSON`. Namely, data is loaded into Go values via reflection.
|
|
||||||
|
|
||||||
For the simplest example, consider some TOML file as just a list of keys
|
|
||||||
and values:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
Age = 25
|
|
||||||
Cats = [ "Cauchy", "Plato" ]
|
|
||||||
Pi = 3.14
|
|
||||||
Perfection = [ 6, 28, 496, 8128 ]
|
|
||||||
DOB = 1987-07-05T05:45:00Z
|
|
||||||
```
|
|
||||||
|
|
||||||
Which could be defined in Go as:
|
|
||||||
|
|
||||||
```go
|
|
||||||
type Config struct {
|
|
||||||
Age int
|
|
||||||
Cats []string
|
|
||||||
Pi float64
|
|
||||||
Perfection []int
|
|
||||||
DOB time.Time // requires `import time`
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
And then decoded with:
|
|
||||||
|
|
||||||
```go
|
|
||||||
var conf Config
|
|
||||||
if _, err := toml.Decode(tomlData, &conf); err != nil {
|
|
||||||
// handle error
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
You can also use struct tags if your struct field name doesn't map to a TOML
|
|
||||||
key value directly:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
some_key_NAME = "wat"
|
|
||||||
```
|
|
||||||
|
|
||||||
```go
|
|
||||||
type TOML struct {
|
|
||||||
ObscureKey string `toml:"some_key_NAME"`
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Using the `encoding.TextUnmarshaler` interface
|
|
||||||
|
|
||||||
Here's an example that automatically parses duration strings into
|
|
||||||
`time.Duration` values:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
[[song]]
|
|
||||||
name = "Thunder Road"
|
|
||||||
duration = "4m49s"
|
|
||||||
|
|
||||||
[[song]]
|
|
||||||
name = "Stairway to Heaven"
|
|
||||||
duration = "8m03s"
|
|
||||||
```
|
|
||||||
|
|
||||||
Which can be decoded with:
|
|
||||||
|
|
||||||
```go
|
|
||||||
type song struct {
|
|
||||||
Name string
|
|
||||||
Duration duration
|
|
||||||
}
|
|
||||||
type songs struct {
|
|
||||||
Song []song
|
|
||||||
}
|
|
||||||
var favorites songs
|
|
||||||
if _, err := toml.Decode(blob, &favorites); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, s := range favorites.Song {
|
|
||||||
fmt.Printf("%s (%s)\n", s.Name, s.Duration)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
And you'll also need a `duration` type that satisfies the
|
|
||||||
`encoding.TextUnmarshaler` interface:
|
|
||||||
|
|
||||||
```go
|
|
||||||
type duration struct {
|
|
||||||
time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *duration) UnmarshalText(text []byte) error {
|
|
||||||
var err error
|
|
||||||
d.Duration, err = time.ParseDuration(string(text))
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### More complex usage
|
|
||||||
|
|
||||||
Here's an example of how to load the example from the official spec page:
|
|
||||||
|
|
||||||
```toml
|
|
||||||
# This is a TOML document. Boom.
|
|
||||||
|
|
||||||
title = "TOML Example"
|
|
||||||
|
|
||||||
[owner]
|
|
||||||
name = "Tom Preston-Werner"
|
|
||||||
organization = "GitHub"
|
|
||||||
bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
|
|
||||||
dob = 1979-05-27T07:32:00Z # First class dates? Why not?
|
|
||||||
|
|
||||||
[database]
|
|
||||||
server = "192.168.1.1"
|
|
||||||
ports = [ 8001, 8001, 8002 ]
|
|
||||||
connection_max = 5000
|
|
||||||
enabled = true
|
|
||||||
|
|
||||||
[servers]
|
|
||||||
|
|
||||||
# You can indent as you please. Tabs or spaces. TOML don't care.
|
|
||||||
[servers.alpha]
|
|
||||||
ip = "10.0.0.1"
|
|
||||||
dc = "eqdc10"
|
|
||||||
|
|
||||||
[servers.beta]
|
|
||||||
ip = "10.0.0.2"
|
|
||||||
dc = "eqdc10"
|
|
||||||
|
|
||||||
[clients]
|
|
||||||
data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it
|
|
||||||
|
|
||||||
# Line breaks are OK when inside arrays
|
|
||||||
hosts = [
|
|
||||||
"alpha",
|
|
||||||
"omega"
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
And the corresponding Go types are:
|
|
||||||
|
|
||||||
```go
|
|
||||||
type tomlConfig struct {
|
|
||||||
Title string
|
|
||||||
Owner ownerInfo
|
|
||||||
DB database `toml:"database"`
|
|
||||||
Servers map[string]server
|
|
||||||
Clients clients
|
|
||||||
}
|
|
||||||
|
|
||||||
type ownerInfo struct {
|
|
||||||
Name string
|
|
||||||
Org string `toml:"organization"`
|
|
||||||
Bio string
|
|
||||||
DOB time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
type database struct {
|
|
||||||
Server string
|
|
||||||
Ports []int
|
|
||||||
ConnMax int `toml:"connection_max"`
|
|
||||||
Enabled bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type server struct {
|
|
||||||
IP string
|
|
||||||
DC string
|
|
||||||
}
|
|
||||||
|
|
||||||
type clients struct {
|
|
||||||
Data [][]interface{}
|
|
||||||
Hosts []string
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that a case insensitive match will be tried if an exact match can't be
|
|
||||||
found.
|
|
||||||
|
|
||||||
A working example of the above can be found in `_examples/example.{go,toml}`.
|
|
61
vendor/github.com/BurntSushi/toml/_examples/example.go
generated
vendored
61
vendor/github.com/BurntSushi/toml/_examples/example.go
generated
vendored
@ -1,61 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/BurntSushi/toml"
|
|
||||||
)
|
|
||||||
|
|
||||||
type tomlConfig struct {
|
|
||||||
Title string
|
|
||||||
Owner ownerInfo
|
|
||||||
DB database `toml:"database"`
|
|
||||||
Servers map[string]server
|
|
||||||
Clients clients
|
|
||||||
}
|
|
||||||
|
|
||||||
type ownerInfo struct {
|
|
||||||
Name string
|
|
||||||
Org string `toml:"organization"`
|
|
||||||
Bio string
|
|
||||||
DOB time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
type database struct {
|
|
||||||
Server string
|
|
||||||
Ports []int
|
|
||||||
ConnMax int `toml:"connection_max"`
|
|
||||||
Enabled bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type server struct {
|
|
||||||
IP string
|
|
||||||
DC string
|
|
||||||
}
|
|
||||||
|
|
||||||
type clients struct {
|
|
||||||
Data [][]interface{}
|
|
||||||
Hosts []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
var config tomlConfig
|
|
||||||
if _, err := toml.DecodeFile("example.toml", &config); err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("Title: %s\n", config.Title)
|
|
||||||
fmt.Printf("Owner: %s (%s, %s), Born: %s\n",
|
|
||||||
config.Owner.Name, config.Owner.Org, config.Owner.Bio,
|
|
||||||
config.Owner.DOB)
|
|
||||||
fmt.Printf("Database: %s %v (Max conn. %d), Enabled? %v\n",
|
|
||||||
config.DB.Server, config.DB.Ports, config.DB.ConnMax,
|
|
||||||
config.DB.Enabled)
|
|
||||||
for serverName, server := range config.Servers {
|
|
||||||
fmt.Printf("Server: %s (%s, %s)\n", serverName, server.IP, server.DC)
|
|
||||||
}
|
|
||||||
fmt.Printf("Client data: %v\n", config.Clients.Data)
|
|
||||||
fmt.Printf("Client hosts: %v\n", config.Clients.Hosts)
|
|
||||||
}
|
|
35
vendor/github.com/BurntSushi/toml/_examples/example.toml
generated
vendored
35
vendor/github.com/BurntSushi/toml/_examples/example.toml
generated
vendored
@ -1,35 +0,0 @@
|
|||||||
# This is a TOML document. Boom.
|
|
||||||
|
|
||||||
title = "TOML Example"
|
|
||||||
|
|
||||||
[owner]
|
|
||||||
name = "Tom Preston-Werner"
|
|
||||||
organization = "GitHub"
|
|
||||||
bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
|
|
||||||
dob = 1979-05-27T07:32:00Z # First class dates? Why not?
|
|
||||||
|
|
||||||
[database]
|
|
||||||
server = "192.168.1.1"
|
|
||||||
ports = [ 8001, 8001, 8002 ]
|
|
||||||
connection_max = 5000
|
|
||||||
enabled = true
|
|
||||||
|
|
||||||
[servers]
|
|
||||||
|
|
||||||
# You can indent as you please. Tabs or spaces. TOML don't care.
|
|
||||||
[servers.alpha]
|
|
||||||
ip = "10.0.0.1"
|
|
||||||
dc = "eqdc10"
|
|
||||||
|
|
||||||
[servers.beta]
|
|
||||||
ip = "10.0.0.2"
|
|
||||||
dc = "eqdc10"
|
|
||||||
|
|
||||||
[clients]
|
|
||||||
data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it
|
|
||||||
|
|
||||||
# Line breaks are OK when inside arrays
|
|
||||||
hosts = [
|
|
||||||
"alpha",
|
|
||||||
"omega"
|
|
||||||
]
|
|
22
vendor/github.com/BurntSushi/toml/_examples/hard.toml
generated
vendored
22
vendor/github.com/BurntSushi/toml/_examples/hard.toml
generated
vendored
@ -1,22 +0,0 @@
|
|||||||
# Test file for TOML
|
|
||||||
# Only this one tries to emulate a TOML file written by a user of the kind of parser writers probably hate
|
|
||||||
# This part you'll really hate
|
|
||||||
|
|
||||||
[the]
|
|
||||||
test_string = "You'll hate me after this - #" # " Annoying, isn't it?
|
|
||||||
|
|
||||||
[the.hard]
|
|
||||||
test_array = [ "] ", " # "] # ] There you go, parse this!
|
|
||||||
test_array2 = [ "Test #11 ]proved that", "Experiment #9 was a success" ]
|
|
||||||
# You didn't think it'd as easy as chucking out the last #, did you?
|
|
||||||
another_test_string = " Same thing, but with a string #"
|
|
||||||
harder_test_string = " And when \"'s are in the string, along with # \"" # "and comments are there too"
|
|
||||||
# Things will get harder
|
|
||||||
|
|
||||||
[the.hard.bit#]
|
|
||||||
what? = "You don't think some user won't do that?"
|
|
||||||
multi_line_array = [
|
|
||||||
"]",
|
|
||||||
# ] Oh yes I did
|
|
||||||
]
|
|
||||||
|
|
4
vendor/github.com/BurntSushi/toml/_examples/implicit.toml
generated
vendored
4
vendor/github.com/BurntSushi/toml/_examples/implicit.toml
generated
vendored
@ -1,4 +0,0 @@
|
|||||||
# [x] you
|
|
||||||
# [x.y] don't
|
|
||||||
# [x.y.z] need these
|
|
||||||
[x.y.z.w] # for this to work
|
|
6
vendor/github.com/BurntSushi/toml/_examples/invalid-apples.toml
generated
vendored
6
vendor/github.com/BurntSushi/toml/_examples/invalid-apples.toml
generated
vendored
@ -1,6 +0,0 @@
|
|||||||
# DO NOT WANT
|
|
||||||
[fruit]
|
|
||||||
type = "apple"
|
|
||||||
|
|
||||||
[fruit.type]
|
|
||||||
apple = "yes"
|
|
35
vendor/github.com/BurntSushi/toml/_examples/invalid.toml
generated
vendored
35
vendor/github.com/BurntSushi/toml/_examples/invalid.toml
generated
vendored
@ -1,35 +0,0 @@
|
|||||||
# This is an INVALID TOML document. Boom.
|
|
||||||
# Can you spot the error without help?
|
|
||||||
|
|
||||||
title = "TOML Example"
|
|
||||||
|
|
||||||
[owner]
|
|
||||||
name = "Tom Preston-Werner"
|
|
||||||
organization = "GitHub"
|
|
||||||
bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
|
|
||||||
dob = 1979-05-27T7:32:00Z # First class dates? Why not?
|
|
||||||
|
|
||||||
[database]
|
|
||||||
server = "192.168.1.1"
|
|
||||||
ports = [ 8001, 8001, 8002 ]
|
|
||||||
connection_max = 5000
|
|
||||||
enabled = true
|
|
||||||
|
|
||||||
[servers]
|
|
||||||
# You can indent as you please. Tabs or spaces. TOML don't care.
|
|
||||||
[servers.alpha]
|
|
||||||
ip = "10.0.0.1"
|
|
||||||
dc = "eqdc10"
|
|
||||||
|
|
||||||
[servers.beta]
|
|
||||||
ip = "10.0.0.2"
|
|
||||||
dc = "eqdc10"
|
|
||||||
|
|
||||||
[clients]
|
|
||||||
data = [ ["gamma", "delta"], [1, 2] ] # just an update to make sure parsers support it
|
|
||||||
|
|
||||||
# Line breaks are OK when inside arrays
|
|
||||||
hosts = [
|
|
||||||
"alpha",
|
|
||||||
"omega"
|
|
||||||
]
|
|
5
vendor/github.com/BurntSushi/toml/_examples/readme1.toml
generated
vendored
5
vendor/github.com/BurntSushi/toml/_examples/readme1.toml
generated
vendored
@ -1,5 +0,0 @@
|
|||||||
Age = 25
|
|
||||||
Cats = [ "Cauchy", "Plato" ]
|
|
||||||
Pi = 3.14
|
|
||||||
Perfection = [ 6, 28, 496, 8128 ]
|
|
||||||
DOB = 1987-07-05T05:45:00Z
|
|
1
vendor/github.com/BurntSushi/toml/_examples/readme2.toml
generated
vendored
1
vendor/github.com/BurntSushi/toml/_examples/readme2.toml
generated
vendored
@ -1 +0,0 @@
|
|||||||
some_key_NAME = "wat"
|
|
14
vendor/github.com/BurntSushi/toml/cmd/toml-test-decoder/COPYING
generated
vendored
14
vendor/github.com/BurntSushi/toml/cmd/toml-test-decoder/COPYING
generated
vendored
@ -1,14 +0,0 @@
|
|||||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
|
||||||
Version 2, December 2004
|
|
||||||
|
|
||||||
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
|
||||||
|
|
||||||
Everyone is permitted to copy and distribute verbatim or modified
|
|
||||||
copies of this license document, and changing it is allowed as long
|
|
||||||
as the name is changed.
|
|
||||||
|
|
||||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
|
||||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
|
||||||
|
|
||||||
0. You just DO WHAT THE FUCK YOU WANT TO.
|
|
||||||
|
|
13
vendor/github.com/BurntSushi/toml/cmd/toml-test-decoder/README.md
generated
vendored
13
vendor/github.com/BurntSushi/toml/cmd/toml-test-decoder/README.md
generated
vendored
@ -1,13 +0,0 @@
|
|||||||
# Implements the TOML test suite interface
|
|
||||||
|
|
||||||
This is an implementation of the interface expected by
|
|
||||||
[toml-test](https://github.com/BurntSushi/toml-test) for my
|
|
||||||
[toml parser written in Go](https://github.com/BurntSushi/toml).
|
|
||||||
In particular, it maps TOML data on `stdin` to a JSON format on `stdout`.
|
|
||||||
|
|
||||||
|
|
||||||
Compatible with TOML version
|
|
||||||
[v0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md)
|
|
||||||
|
|
||||||
Compatible with `toml-test` version
|
|
||||||
[v0.2.0](https://github.com/BurntSushi/toml-test/tree/v0.2.0)
|
|
90
vendor/github.com/BurntSushi/toml/cmd/toml-test-decoder/main.go
generated
vendored
90
vendor/github.com/BurntSushi/toml/cmd/toml-test-decoder/main.go
generated
vendored
@ -1,90 +0,0 @@
|
|||||||
// Command toml-test-decoder satisfies the toml-test interface for testing
|
|
||||||
// TOML decoders. Namely, it accepts TOML on stdin and outputs JSON on stdout.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/BurntSushi/toml"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
log.SetFlags(0)
|
|
||||||
|
|
||||||
flag.Usage = usage
|
|
||||||
flag.Parse()
|
|
||||||
}
|
|
||||||
|
|
||||||
func usage() {
|
|
||||||
log.Printf("Usage: %s < toml-file\n", path.Base(os.Args[0]))
|
|
||||||
flag.PrintDefaults()
|
|
||||||
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if flag.NArg() != 0 {
|
|
||||||
flag.Usage()
|
|
||||||
}
|
|
||||||
|
|
||||||
var tmp interface{}
|
|
||||||
if _, err := toml.DecodeReader(os.Stdin, &tmp); err != nil {
|
|
||||||
log.Fatalf("Error decoding TOML: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
typedTmp := translate(tmp)
|
|
||||||
if err := json.NewEncoder(os.Stdout).Encode(typedTmp); err != nil {
|
|
||||||
log.Fatalf("Error encoding JSON: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func translate(tomlData interface{}) interface{} {
|
|
||||||
switch orig := tomlData.(type) {
|
|
||||||
case map[string]interface{}:
|
|
||||||
typed := make(map[string]interface{}, len(orig))
|
|
||||||
for k, v := range orig {
|
|
||||||
typed[k] = translate(v)
|
|
||||||
}
|
|
||||||
return typed
|
|
||||||
case []map[string]interface{}:
|
|
||||||
typed := make([]map[string]interface{}, len(orig))
|
|
||||||
for i, v := range orig {
|
|
||||||
typed[i] = translate(v).(map[string]interface{})
|
|
||||||
}
|
|
||||||
return typed
|
|
||||||
case []interface{}:
|
|
||||||
typed := make([]interface{}, len(orig))
|
|
||||||
for i, v := range orig {
|
|
||||||
typed[i] = translate(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// We don't really need to tag arrays, but let's be future proof.
|
|
||||||
// (If TOML ever supports tuples, we'll need this.)
|
|
||||||
return tag("array", typed)
|
|
||||||
case time.Time:
|
|
||||||
return tag("datetime", orig.Format("2006-01-02T15:04:05Z"))
|
|
||||||
case bool:
|
|
||||||
return tag("bool", fmt.Sprintf("%v", orig))
|
|
||||||
case int64:
|
|
||||||
return tag("integer", fmt.Sprintf("%d", orig))
|
|
||||||
case float64:
|
|
||||||
return tag("float", fmt.Sprintf("%v", orig))
|
|
||||||
case string:
|
|
||||||
return tag("string", orig)
|
|
||||||
}
|
|
||||||
|
|
||||||
panic(fmt.Sprintf("Unknown type: %T", tomlData))
|
|
||||||
}
|
|
||||||
|
|
||||||
func tag(typeName string, data interface{}) map[string]interface{} {
|
|
||||||
return map[string]interface{}{
|
|
||||||
"type": typeName,
|
|
||||||
"value": data,
|
|
||||||
}
|
|
||||||
}
|
|
14
vendor/github.com/BurntSushi/toml/cmd/toml-test-encoder/COPYING
generated
vendored
14
vendor/github.com/BurntSushi/toml/cmd/toml-test-encoder/COPYING
generated
vendored
@ -1,14 +0,0 @@
|
|||||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
|
||||||
Version 2, December 2004
|
|
||||||
|
|
||||||
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
|
||||||
|
|
||||||
Everyone is permitted to copy and distribute verbatim or modified
|
|
||||||
copies of this license document, and changing it is allowed as long
|
|
||||||
as the name is changed.
|
|
||||||
|
|
||||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
|
||||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
|
||||||
|
|
||||||
0. You just DO WHAT THE FUCK YOU WANT TO.
|
|
||||||
|
|
13
vendor/github.com/BurntSushi/toml/cmd/toml-test-encoder/README.md
generated
vendored
13
vendor/github.com/BurntSushi/toml/cmd/toml-test-encoder/README.md
generated
vendored
@ -1,13 +0,0 @@
|
|||||||
# Implements the TOML test suite interface for TOML encoders
|
|
||||||
|
|
||||||
This is an implementation of the interface expected by
|
|
||||||
[toml-test](https://github.com/BurntSushi/toml-test) for the
|
|
||||||
[TOML encoder](https://github.com/BurntSushi/toml).
|
|
||||||
In particular, it maps JSON data on `stdin` to a TOML format on `stdout`.
|
|
||||||
|
|
||||||
|
|
||||||
Compatible with TOML version
|
|
||||||
[v0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md)
|
|
||||||
|
|
||||||
Compatible with `toml-test` version
|
|
||||||
[v0.2.0](https://github.com/BurntSushi/toml-test/tree/v0.2.0)
|
|
131
vendor/github.com/BurntSushi/toml/cmd/toml-test-encoder/main.go
generated
vendored
131
vendor/github.com/BurntSushi/toml/cmd/toml-test-encoder/main.go
generated
vendored
@ -1,131 +0,0 @@
|
|||||||
// Command toml-test-encoder satisfies the toml-test interface for testing
|
|
||||||
// TOML encoders. Namely, it accepts JSON on stdin and outputs TOML on stdout.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"flag"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/BurntSushi/toml"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
log.SetFlags(0)
|
|
||||||
|
|
||||||
flag.Usage = usage
|
|
||||||
flag.Parse()
|
|
||||||
}
|
|
||||||
|
|
||||||
func usage() {
|
|
||||||
log.Printf("Usage: %s < json-file\n", path.Base(os.Args[0]))
|
|
||||||
flag.PrintDefaults()
|
|
||||||
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if flag.NArg() != 0 {
|
|
||||||
flag.Usage()
|
|
||||||
}
|
|
||||||
|
|
||||||
var tmp interface{}
|
|
||||||
if err := json.NewDecoder(os.Stdin).Decode(&tmp); err != nil {
|
|
||||||
log.Fatalf("Error decoding JSON: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tomlData := translate(tmp)
|
|
||||||
if err := toml.NewEncoder(os.Stdout).Encode(tomlData); err != nil {
|
|
||||||
log.Fatalf("Error encoding TOML: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func translate(typedJson interface{}) interface{} {
|
|
||||||
switch v := typedJson.(type) {
|
|
||||||
case map[string]interface{}:
|
|
||||||
if len(v) == 2 && in("type", v) && in("value", v) {
|
|
||||||
return untag(v)
|
|
||||||
}
|
|
||||||
m := make(map[string]interface{}, len(v))
|
|
||||||
for k, v2 := range v {
|
|
||||||
m[k] = translate(v2)
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
case []interface{}:
|
|
||||||
tabArray := make([]map[string]interface{}, len(v))
|
|
||||||
for i := range v {
|
|
||||||
if m, ok := translate(v[i]).(map[string]interface{}); ok {
|
|
||||||
tabArray[i] = m
|
|
||||||
} else {
|
|
||||||
log.Fatalf("JSON arrays may only contain objects. This " +
|
|
||||||
"corresponds to only tables being allowed in " +
|
|
||||||
"TOML table arrays.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return tabArray
|
|
||||||
}
|
|
||||||
log.Fatalf("Unrecognized JSON format '%T'.", typedJson)
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
|
|
||||||
func untag(typed map[string]interface{}) interface{} {
|
|
||||||
t := typed["type"].(string)
|
|
||||||
v := typed["value"]
|
|
||||||
switch t {
|
|
||||||
case "string":
|
|
||||||
return v.(string)
|
|
||||||
case "integer":
|
|
||||||
v := v.(string)
|
|
||||||
n, err := strconv.Atoi(v)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Could not parse '%s' as integer: %s", v, err)
|
|
||||||
}
|
|
||||||
return n
|
|
||||||
case "float":
|
|
||||||
v := v.(string)
|
|
||||||
f, err := strconv.ParseFloat(v, 64)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Could not parse '%s' as float64: %s", v, err)
|
|
||||||
}
|
|
||||||
return f
|
|
||||||
case "datetime":
|
|
||||||
v := v.(string)
|
|
||||||
t, err := time.Parse("2006-01-02T15:04:05Z", v)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Could not parse '%s' as a datetime: %s", v, err)
|
|
||||||
}
|
|
||||||
return t
|
|
||||||
case "bool":
|
|
||||||
v := v.(string)
|
|
||||||
switch v {
|
|
||||||
case "true":
|
|
||||||
return true
|
|
||||||
case "false":
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
log.Fatalf("Could not parse '%s' as a boolean.", v)
|
|
||||||
case "array":
|
|
||||||
v := v.([]interface{})
|
|
||||||
array := make([]interface{}, len(v))
|
|
||||||
for i := range v {
|
|
||||||
if m, ok := v[i].(map[string]interface{}); ok {
|
|
||||||
array[i] = untag(m)
|
|
||||||
} else {
|
|
||||||
log.Fatalf("Arrays may only contain other arrays or "+
|
|
||||||
"primitive values, but found a '%T'.", m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return array
|
|
||||||
}
|
|
||||||
log.Fatalf("Unrecognized tag type '%s'.", t)
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
|
|
||||||
func in(key string, m map[string]interface{}) bool {
|
|
||||||
_, ok := m[key]
|
|
||||||
return ok
|
|
||||||
}
|
|
14
vendor/github.com/BurntSushi/toml/cmd/tomlv/COPYING
generated
vendored
14
vendor/github.com/BurntSushi/toml/cmd/tomlv/COPYING
generated
vendored
@ -1,14 +0,0 @@
|
|||||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
|
||||||
Version 2, December 2004
|
|
||||||
|
|
||||||
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
|
||||||
|
|
||||||
Everyone is permitted to copy and distribute verbatim or modified
|
|
||||||
copies of this license document, and changing it is allowed as long
|
|
||||||
as the name is changed.
|
|
||||||
|
|
||||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
|
||||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
|
||||||
|
|
||||||
0. You just DO WHAT THE FUCK YOU WANT TO.
|
|
||||||
|
|
21
vendor/github.com/BurntSushi/toml/cmd/tomlv/README.md
generated
vendored
21
vendor/github.com/BurntSushi/toml/cmd/tomlv/README.md
generated
vendored
@ -1,21 +0,0 @@
|
|||||||
# TOML Validator
|
|
||||||
|
|
||||||
If Go is installed, it's simple to try it out:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
go get github.com/BurntSushi/toml/cmd/tomlv
|
|
||||||
tomlv some-toml-file.toml
|
|
||||||
```
|
|
||||||
|
|
||||||
You can see the types of every key in a TOML file with:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
tomlv -types some-toml-file.toml
|
|
||||||
```
|
|
||||||
|
|
||||||
At the moment, only one error message is reported at a time. Error messages
|
|
||||||
include line numbers. No output means that the files given are valid TOML, or
|
|
||||||
there is a bug in `tomlv`.
|
|
||||||
|
|
||||||
Compatible with TOML version
|
|
||||||
[v0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md)
|
|
61
vendor/github.com/BurntSushi/toml/cmd/tomlv/main.go
generated
vendored
61
vendor/github.com/BurntSushi/toml/cmd/tomlv/main.go
generated
vendored
@ -1,61 +0,0 @@
|
|||||||
// Command tomlv validates TOML documents and prints each key's type.
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
"text/tabwriter"
|
|
||||||
|
|
||||||
"github.com/BurntSushi/toml"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
flagTypes = false
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
log.SetFlags(0)
|
|
||||||
|
|
||||||
flag.BoolVar(&flagTypes, "types", flagTypes,
|
|
||||||
"When set, the types of every defined key will be shown.")
|
|
||||||
|
|
||||||
flag.Usage = usage
|
|
||||||
flag.Parse()
|
|
||||||
}
|
|
||||||
|
|
||||||
func usage() {
|
|
||||||
log.Printf("Usage: %s toml-file [ toml-file ... ]\n",
|
|
||||||
path.Base(os.Args[0]))
|
|
||||||
flag.PrintDefaults()
|
|
||||||
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
if flag.NArg() < 1 {
|
|
||||||
flag.Usage()
|
|
||||||
}
|
|
||||||
for _, f := range flag.Args() {
|
|
||||||
var tmp interface{}
|
|
||||||
md, err := toml.DecodeFile(f, &tmp)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("Error in '%s': %s", f, err)
|
|
||||||
}
|
|
||||||
if flagTypes {
|
|
||||||
printTypes(md)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func printTypes(md toml.MetaData) {
|
|
||||||
tabw := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
|
|
||||||
for _, key := range md.Keys() {
|
|
||||||
fmt.Fprintf(tabw, "%s%s\t%s\n",
|
|
||||||
strings.Repeat(" ", len(key)-1), key, md.Type(key...))
|
|
||||||
}
|
|
||||||
tabw.Flush()
|
|
||||||
}
|
|
509
vendor/github.com/BurntSushi/toml/decode.go
generated
vendored
509
vendor/github.com/BurntSushi/toml/decode.go
generated
vendored
@ -1,509 +0,0 @@
|
|||||||
package toml
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/ioutil"
|
|
||||||
"math"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func e(format string, args ...interface{}) error {
|
|
||||||
return fmt.Errorf("toml: "+format, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unmarshaler is the interface implemented by objects that can unmarshal a
|
|
||||||
// TOML description of themselves.
|
|
||||||
type Unmarshaler interface {
|
|
||||||
UnmarshalTOML(interface{}) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unmarshal decodes the contents of `p` in TOML format into a pointer `v`.
|
|
||||||
func Unmarshal(p []byte, v interface{}) error {
|
|
||||||
_, err := Decode(string(p), v)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Primitive is a TOML value that hasn't been decoded into a Go value.
|
|
||||||
// When using the various `Decode*` functions, the type `Primitive` may
|
|
||||||
// be given to any value, and its decoding will be delayed.
|
|
||||||
//
|
|
||||||
// A `Primitive` value can be decoded using the `PrimitiveDecode` function.
|
|
||||||
//
|
|
||||||
// The underlying representation of a `Primitive` value is subject to change.
|
|
||||||
// Do not rely on it.
|
|
||||||
//
|
|
||||||
// N.B. Primitive values are still parsed, so using them will only avoid
|
|
||||||
// the overhead of reflection. They can be useful when you don't know the
|
|
||||||
// exact type of TOML data until run time.
|
|
||||||
type Primitive struct {
|
|
||||||
undecoded interface{}
|
|
||||||
context Key
|
|
||||||
}
|
|
||||||
|
|
||||||
// DEPRECATED!
|
|
||||||
//
|
|
||||||
// Use MetaData.PrimitiveDecode instead.
|
|
||||||
func PrimitiveDecode(primValue Primitive, v interface{}) error {
|
|
||||||
md := MetaData{decoded: make(map[string]bool)}
|
|
||||||
return md.unify(primValue.undecoded, rvalue(v))
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrimitiveDecode is just like the other `Decode*` functions, except it
|
|
||||||
// decodes a TOML value that has already been parsed. Valid primitive values
|
|
||||||
// can *only* be obtained from values filled by the decoder functions,
|
|
||||||
// including this method. (i.e., `v` may contain more `Primitive`
|
|
||||||
// values.)
|
|
||||||
//
|
|
||||||
// Meta data for primitive values is included in the meta data returned by
|
|
||||||
// the `Decode*` functions with one exception: keys returned by the Undecoded
|
|
||||||
// method will only reflect keys that were decoded. Namely, any keys hidden
|
|
||||||
// behind a Primitive will be considered undecoded. Executing this method will
|
|
||||||
// update the undecoded keys in the meta data. (See the example.)
|
|
||||||
func (md *MetaData) PrimitiveDecode(primValue Primitive, v interface{}) error {
|
|
||||||
md.context = primValue.context
|
|
||||||
defer func() { md.context = nil }()
|
|
||||||
return md.unify(primValue.undecoded, rvalue(v))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode will decode the contents of `data` in TOML format into a pointer
|
|
||||||
// `v`.
|
|
||||||
//
|
|
||||||
// TOML hashes correspond to Go structs or maps. (Dealer's choice. They can be
|
|
||||||
// used interchangeably.)
|
|
||||||
//
|
|
||||||
// TOML arrays of tables correspond to either a slice of structs or a slice
|
|
||||||
// of maps.
|
|
||||||
//
|
|
||||||
// TOML datetimes correspond to Go `time.Time` values.
|
|
||||||
//
|
|
||||||
// All other TOML types (float, string, int, bool and array) correspond
|
|
||||||
// to the obvious Go types.
|
|
||||||
//
|
|
||||||
// An exception to the above rules is if a type implements the
|
|
||||||
// encoding.TextUnmarshaler interface. In this case, any primitive TOML value
|
|
||||||
// (floats, strings, integers, booleans and datetimes) will be converted to
|
|
||||||
// a byte string and given to the value's UnmarshalText method. See the
|
|
||||||
// Unmarshaler example for a demonstration with time duration strings.
|
|
||||||
//
|
|
||||||
// Key mapping
|
|
||||||
//
|
|
||||||
// TOML keys can map to either keys in a Go map or field names in a Go
|
|
||||||
// struct. The special `toml` struct tag may be used to map TOML keys to
|
|
||||||
// struct fields that don't match the key name exactly. (See the example.)
|
|
||||||
// A case insensitive match to struct names will be tried if an exact match
|
|
||||||
// can't be found.
|
|
||||||
//
|
|
||||||
// The mapping between TOML values and Go values is loose. That is, there
|
|
||||||
// may exist TOML values that cannot be placed into your representation, and
|
|
||||||
// there may be parts of your representation that do not correspond to
|
|
||||||
// TOML values. This loose mapping can be made stricter by using the IsDefined
|
|
||||||
// and/or Undecoded methods on the MetaData returned.
|
|
||||||
//
|
|
||||||
// This decoder will not handle cyclic types. If a cyclic type is passed,
|
|
||||||
// `Decode` will not terminate.
|
|
||||||
func Decode(data string, v interface{}) (MetaData, error) {
|
|
||||||
rv := reflect.ValueOf(v)
|
|
||||||
if rv.Kind() != reflect.Ptr {
|
|
||||||
return MetaData{}, e("Decode of non-pointer %s", reflect.TypeOf(v))
|
|
||||||
}
|
|
||||||
if rv.IsNil() {
|
|
||||||
return MetaData{}, e("Decode of nil %s", reflect.TypeOf(v))
|
|
||||||
}
|
|
||||||
p, err := parse(data)
|
|
||||||
if err != nil {
|
|
||||||
return MetaData{}, err
|
|
||||||
}
|
|
||||||
md := MetaData{
|
|
||||||
p.mapping, p.types, p.ordered,
|
|
||||||
make(map[string]bool, len(p.ordered)), nil,
|
|
||||||
}
|
|
||||||
return md, md.unify(p.mapping, indirect(rv))
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodeFile is just like Decode, except it will automatically read the
|
|
||||||
// contents of the file at `fpath` and decode it for you.
|
|
||||||
func DecodeFile(fpath string, v interface{}) (MetaData, error) {
|
|
||||||
bs, err := ioutil.ReadFile(fpath)
|
|
||||||
if err != nil {
|
|
||||||
return MetaData{}, err
|
|
||||||
}
|
|
||||||
return Decode(string(bs), v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// DecodeReader is just like Decode, except it will consume all bytes
|
|
||||||
// from the reader and decode it for you.
|
|
||||||
func DecodeReader(r io.Reader, v interface{}) (MetaData, error) {
|
|
||||||
bs, err := ioutil.ReadAll(r)
|
|
||||||
if err != nil {
|
|
||||||
return MetaData{}, err
|
|
||||||
}
|
|
||||||
return Decode(string(bs), v)
|
|
||||||
}
|
|
||||||
|
|
||||||
// unify performs a sort of type unification based on the structure of `rv`,
|
|
||||||
// which is the client representation.
|
|
||||||
//
|
|
||||||
// Any type mismatch produces an error. Finding a type that we don't know
|
|
||||||
// how to handle produces an unsupported type error.
|
|
||||||
func (md *MetaData) unify(data interface{}, rv reflect.Value) error {
|
|
||||||
|
|
||||||
// Special case. Look for a `Primitive` value.
|
|
||||||
if rv.Type() == reflect.TypeOf((*Primitive)(nil)).Elem() {
|
|
||||||
// Save the undecoded data and the key context into the primitive
|
|
||||||
// value.
|
|
||||||
context := make(Key, len(md.context))
|
|
||||||
copy(context, md.context)
|
|
||||||
rv.Set(reflect.ValueOf(Primitive{
|
|
||||||
undecoded: data,
|
|
||||||
context: context,
|
|
||||||
}))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Special case. Unmarshaler Interface support.
|
|
||||||
if rv.CanAddr() {
|
|
||||||
if v, ok := rv.Addr().Interface().(Unmarshaler); ok {
|
|
||||||
return v.UnmarshalTOML(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Special case. Handle time.Time values specifically.
|
|
||||||
// TODO: Remove this code when we decide to drop support for Go 1.1.
|
|
||||||
// This isn't necessary in Go 1.2 because time.Time satisfies the encoding
|
|
||||||
// interfaces.
|
|
||||||
if rv.Type().AssignableTo(rvalue(time.Time{}).Type()) {
|
|
||||||
return md.unifyDatetime(data, rv)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Special case. Look for a value satisfying the TextUnmarshaler interface.
|
|
||||||
if v, ok := rv.Interface().(TextUnmarshaler); ok {
|
|
||||||
return md.unifyText(data, v)
|
|
||||||
}
|
|
||||||
// BUG(burntsushi)
|
|
||||||
// The behavior here is incorrect whenever a Go type satisfies the
|
|
||||||
// encoding.TextUnmarshaler interface but also corresponds to a TOML
|
|
||||||
// hash or array. In particular, the unmarshaler should only be applied
|
|
||||||
// to primitive TOML values. But at this point, it will be applied to
|
|
||||||
// all kinds of values and produce an incorrect error whenever those values
|
|
||||||
// are hashes or arrays (including arrays of tables).
|
|
||||||
|
|
||||||
k := rv.Kind()
|
|
||||||
|
|
||||||
// laziness
|
|
||||||
if k >= reflect.Int && k <= reflect.Uint64 {
|
|
||||||
return md.unifyInt(data, rv)
|
|
||||||
}
|
|
||||||
switch k {
|
|
||||||
case reflect.Ptr:
|
|
||||||
elem := reflect.New(rv.Type().Elem())
|
|
||||||
err := md.unify(data, reflect.Indirect(elem))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
rv.Set(elem)
|
|
||||||
return nil
|
|
||||||
case reflect.Struct:
|
|
||||||
return md.unifyStruct(data, rv)
|
|
||||||
case reflect.Map:
|
|
||||||
return md.unifyMap(data, rv)
|
|
||||||
case reflect.Array:
|
|
||||||
return md.unifyArray(data, rv)
|
|
||||||
case reflect.Slice:
|
|
||||||
return md.unifySlice(data, rv)
|
|
||||||
case reflect.String:
|
|
||||||
return md.unifyString(data, rv)
|
|
||||||
case reflect.Bool:
|
|
||||||
return md.unifyBool(data, rv)
|
|
||||||
case reflect.Interface:
|
|
||||||
// we only support empty interfaces.
|
|
||||||
if rv.NumMethod() > 0 {
|
|
||||||
return e("unsupported type %s", rv.Type())
|
|
||||||
}
|
|
||||||
return md.unifyAnything(data, rv)
|
|
||||||
case reflect.Float32:
|
|
||||||
fallthrough
|
|
||||||
case reflect.Float64:
|
|
||||||
return md.unifyFloat64(data, rv)
|
|
||||||
}
|
|
||||||
return e("unsupported type %s", rv.Kind())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (md *MetaData) unifyStruct(mapping interface{}, rv reflect.Value) error {
|
|
||||||
tmap, ok := mapping.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
if mapping == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return e("type mismatch for %s: expected table but found %T",
|
|
||||||
rv.Type().String(), mapping)
|
|
||||||
}
|
|
||||||
|
|
||||||
for key, datum := range tmap {
|
|
||||||
var f *field
|
|
||||||
fields := cachedTypeFields(rv.Type())
|
|
||||||
for i := range fields {
|
|
||||||
ff := &fields[i]
|
|
||||||
if ff.name == key {
|
|
||||||
f = ff
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if f == nil && strings.EqualFold(ff.name, key) {
|
|
||||||
f = ff
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if f != nil {
|
|
||||||
subv := rv
|
|
||||||
for _, i := range f.index {
|
|
||||||
subv = indirect(subv.Field(i))
|
|
||||||
}
|
|
||||||
if isUnifiable(subv) {
|
|
||||||
md.decoded[md.context.add(key).String()] = true
|
|
||||||
md.context = append(md.context, key)
|
|
||||||
if err := md.unify(datum, subv); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
md.context = md.context[0 : len(md.context)-1]
|
|
||||||
} else if f.name != "" {
|
|
||||||
// Bad user! No soup for you!
|
|
||||||
return e("cannot write unexported field %s.%s",
|
|
||||||
rv.Type().String(), f.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (md *MetaData) unifyMap(mapping interface{}, rv reflect.Value) error {
|
|
||||||
tmap, ok := mapping.(map[string]interface{})
|
|
||||||
if !ok {
|
|
||||||
if tmap == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return badtype("map", mapping)
|
|
||||||
}
|
|
||||||
if rv.IsNil() {
|
|
||||||
rv.Set(reflect.MakeMap(rv.Type()))
|
|
||||||
}
|
|
||||||
for k, v := range tmap {
|
|
||||||
md.decoded[md.context.add(k).String()] = true
|
|
||||||
md.context = append(md.context, k)
|
|
||||||
|
|
||||||
rvkey := indirect(reflect.New(rv.Type().Key()))
|
|
||||||
rvval := reflect.Indirect(reflect.New(rv.Type().Elem()))
|
|
||||||
if err := md.unify(v, rvval); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
md.context = md.context[0 : len(md.context)-1]
|
|
||||||
|
|
||||||
rvkey.SetString(k)
|
|
||||||
rv.SetMapIndex(rvkey, rvval)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (md *MetaData) unifyArray(data interface{}, rv reflect.Value) error {
|
|
||||||
datav := reflect.ValueOf(data)
|
|
||||||
if datav.Kind() != reflect.Slice {
|
|
||||||
if !datav.IsValid() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return badtype("slice", data)
|
|
||||||
}
|
|
||||||
sliceLen := datav.Len()
|
|
||||||
if sliceLen != rv.Len() {
|
|
||||||
return e("expected array length %d; got TOML array of length %d",
|
|
||||||
rv.Len(), sliceLen)
|
|
||||||
}
|
|
||||||
return md.unifySliceArray(datav, rv)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (md *MetaData) unifySlice(data interface{}, rv reflect.Value) error {
|
|
||||||
datav := reflect.ValueOf(data)
|
|
||||||
if datav.Kind() != reflect.Slice {
|
|
||||||
if !datav.IsValid() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return badtype("slice", data)
|
|
||||||
}
|
|
||||||
n := datav.Len()
|
|
||||||
if rv.IsNil() || rv.Cap() < n {
|
|
||||||
rv.Set(reflect.MakeSlice(rv.Type(), n, n))
|
|
||||||
}
|
|
||||||
rv.SetLen(n)
|
|
||||||
return md.unifySliceArray(datav, rv)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (md *MetaData) unifySliceArray(data, rv reflect.Value) error {
|
|
||||||
sliceLen := data.Len()
|
|
||||||
for i := 0; i < sliceLen; i++ {
|
|
||||||
v := data.Index(i).Interface()
|
|
||||||
sliceval := indirect(rv.Index(i))
|
|
||||||
if err := md.unify(v, sliceval); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (md *MetaData) unifyDatetime(data interface{}, rv reflect.Value) error {
|
|
||||||
if _, ok := data.(time.Time); ok {
|
|
||||||
rv.Set(reflect.ValueOf(data))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return badtype("time.Time", data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (md *MetaData) unifyString(data interface{}, rv reflect.Value) error {
|
|
||||||
if s, ok := data.(string); ok {
|
|
||||||
rv.SetString(s)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return badtype("string", data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (md *MetaData) unifyFloat64(data interface{}, rv reflect.Value) error {
|
|
||||||
if num, ok := data.(float64); ok {
|
|
||||||
switch rv.Kind() {
|
|
||||||
case reflect.Float32:
|
|
||||||
fallthrough
|
|
||||||
case reflect.Float64:
|
|
||||||
rv.SetFloat(num)
|
|
||||||
default:
|
|
||||||
panic("bug")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return badtype("float", data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (md *MetaData) unifyInt(data interface{}, rv reflect.Value) error {
|
|
||||||
if num, ok := data.(int64); ok {
|
|
||||||
if rv.Kind() >= reflect.Int && rv.Kind() <= reflect.Int64 {
|
|
||||||
switch rv.Kind() {
|
|
||||||
case reflect.Int, reflect.Int64:
|
|
||||||
// No bounds checking necessary.
|
|
||||||
case reflect.Int8:
|
|
||||||
if num < math.MinInt8 || num > math.MaxInt8 {
|
|
||||||
return e("value %d is out of range for int8", num)
|
|
||||||
}
|
|
||||||
case reflect.Int16:
|
|
||||||
if num < math.MinInt16 || num > math.MaxInt16 {
|
|
||||||
return e("value %d is out of range for int16", num)
|
|
||||||
}
|
|
||||||
case reflect.Int32:
|
|
||||||
if num < math.MinInt32 || num > math.MaxInt32 {
|
|
||||||
return e("value %d is out of range for int32", num)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rv.SetInt(num)
|
|
||||||
} else if rv.Kind() >= reflect.Uint && rv.Kind() <= reflect.Uint64 {
|
|
||||||
unum := uint64(num)
|
|
||||||
switch rv.Kind() {
|
|
||||||
case reflect.Uint, reflect.Uint64:
|
|
||||||
// No bounds checking necessary.
|
|
||||||
case reflect.Uint8:
|
|
||||||
if num < 0 || unum > math.MaxUint8 {
|
|
||||||
return e("value %d is out of range for uint8", num)
|
|
||||||
}
|
|
||||||
case reflect.Uint16:
|
|
||||||
if num < 0 || unum > math.MaxUint16 {
|
|
||||||
return e("value %d is out of range for uint16", num)
|
|
||||||
}
|
|
||||||
case reflect.Uint32:
|
|
||||||
if num < 0 || unum > math.MaxUint32 {
|
|
||||||
return e("value %d is out of range for uint32", num)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
rv.SetUint(unum)
|
|
||||||
} else {
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return badtype("integer", data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (md *MetaData) unifyBool(data interface{}, rv reflect.Value) error {
|
|
||||||
if b, ok := data.(bool); ok {
|
|
||||||
rv.SetBool(b)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return badtype("boolean", data)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (md *MetaData) unifyAnything(data interface{}, rv reflect.Value) error {
|
|
||||||
rv.Set(reflect.ValueOf(data))
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (md *MetaData) unifyText(data interface{}, v TextUnmarshaler) error {
|
|
||||||
var s string
|
|
||||||
switch sdata := data.(type) {
|
|
||||||
case TextMarshaler:
|
|
||||||
text, err := sdata.MarshalText()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
s = string(text)
|
|
||||||
case fmt.Stringer:
|
|
||||||
s = sdata.String()
|
|
||||||
case string:
|
|
||||||
s = sdata
|
|
||||||
case bool:
|
|
||||||
s = fmt.Sprintf("%v", sdata)
|
|
||||||
case int64:
|
|
||||||
s = fmt.Sprintf("%d", sdata)
|
|
||||||
case float64:
|
|
||||||
s = fmt.Sprintf("%f", sdata)
|
|
||||||
default:
|
|
||||||
return badtype("primitive (string-like)", data)
|
|
||||||
}
|
|
||||||
if err := v.UnmarshalText([]byte(s)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// rvalue returns a reflect.Value of `v`. All pointers are resolved.
|
|
||||||
func rvalue(v interface{}) reflect.Value {
|
|
||||||
return indirect(reflect.ValueOf(v))
|
|
||||||
}
|
|
||||||
|
|
||||||
// indirect returns the value pointed to by a pointer.
|
|
||||||
// Pointers are followed until the value is not a pointer.
|
|
||||||
// New values are allocated for each nil pointer.
|
|
||||||
//
|
|
||||||
// An exception to this rule is if the value satisfies an interface of
|
|
||||||
// interest to us (like encoding.TextUnmarshaler).
|
|
||||||
func indirect(v reflect.Value) reflect.Value {
|
|
||||||
if v.Kind() != reflect.Ptr {
|
|
||||||
if v.CanSet() {
|
|
||||||
pv := v.Addr()
|
|
||||||
if _, ok := pv.Interface().(TextUnmarshaler); ok {
|
|
||||||
return pv
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
if v.IsNil() {
|
|
||||||
v.Set(reflect.New(v.Type().Elem()))
|
|
||||||
}
|
|
||||||
return indirect(reflect.Indirect(v))
|
|
||||||
}
|
|
||||||
|
|
||||||
func isUnifiable(rv reflect.Value) bool {
|
|
||||||
if rv.CanSet() {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if _, ok := rv.Interface().(TextUnmarshaler); ok {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func badtype(expected string, data interface{}) error {
|
|
||||||
return e("cannot load TOML value of type %T into a Go %s", data, expected)
|
|
||||||
}
|
|
121
vendor/github.com/BurntSushi/toml/decode_meta.go
generated
vendored
121
vendor/github.com/BurntSushi/toml/decode_meta.go
generated
vendored
@ -1,121 +0,0 @@
|
|||||||
package toml
|
|
||||||
|
|
||||||
import "strings"
|
|
||||||
|
|
||||||
// MetaData allows access to meta information about TOML data that may not
|
|
||||||
// be inferrable via reflection. In particular, whether a key has been defined
|
|
||||||
// and the TOML type of a key.
|
|
||||||
type MetaData struct {
|
|
||||||
mapping map[string]interface{}
|
|
||||||
types map[string]tomlType
|
|
||||||
keys []Key
|
|
||||||
decoded map[string]bool
|
|
||||||
context Key // Used only during decoding.
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsDefined returns true if the key given exists in the TOML data. The key
|
|
||||||
// should be specified hierarchially. e.g.,
|
|
||||||
//
|
|
||||||
// // access the TOML key 'a.b.c'
|
|
||||||
// IsDefined("a", "b", "c")
|
|
||||||
//
|
|
||||||
// IsDefined will return false if an empty key given. Keys are case sensitive.
|
|
||||||
func (md *MetaData) IsDefined(key ...string) bool {
|
|
||||||
if len(key) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var hash map[string]interface{}
|
|
||||||
var ok bool
|
|
||||||
var hashOrVal interface{} = md.mapping
|
|
||||||
for _, k := range key {
|
|
||||||
if hash, ok = hashOrVal.(map[string]interface{}); !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if hashOrVal, ok = hash[k]; !ok {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Type returns a string representation of the type of the key specified.
|
|
||||||
//
|
|
||||||
// Type will return the empty string if given an empty key or a key that
|
|
||||||
// does not exist. Keys are case sensitive.
|
|
||||||
func (md *MetaData) Type(key ...string) string {
|
|
||||||
fullkey := strings.Join(key, ".")
|
|
||||||
if typ, ok := md.types[fullkey]; ok {
|
|
||||||
return typ.typeString()
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// Key is the type of any TOML key, including key groups. Use (MetaData).Keys
|
|
||||||
// to get values of this type.
|
|
||||||
type Key []string
|
|
||||||
|
|
||||||
func (k Key) String() string {
|
|
||||||
return strings.Join(k, ".")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k Key) maybeQuotedAll() string {
|
|
||||||
var ss []string
|
|
||||||
for i := range k {
|
|
||||||
ss = append(ss, k.maybeQuoted(i))
|
|
||||||
}
|
|
||||||
return strings.Join(ss, ".")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k Key) maybeQuoted(i int) string {
|
|
||||||
quote := false
|
|
||||||
for _, c := range k[i] {
|
|
||||||
if !isBareKeyChar(c) {
|
|
||||||
quote = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if quote {
|
|
||||||
return "\"" + strings.Replace(k[i], "\"", "\\\"", -1) + "\""
|
|
||||||
}
|
|
||||||
return k[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (k Key) add(piece string) Key {
|
|
||||||
newKey := make(Key, len(k)+1)
|
|
||||||
copy(newKey, k)
|
|
||||||
newKey[len(k)] = piece
|
|
||||||
return newKey
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keys returns a slice of every key in the TOML data, including key groups.
|
|
||||||
// Each key is itself a slice, where the first element is the top of the
|
|
||||||
// hierarchy and the last is the most specific.
|
|
||||||
//
|
|
||||||
// The list will have the same order as the keys appeared in the TOML data.
|
|
||||||
//
|
|
||||||
// All keys returned are non-empty.
|
|
||||||
func (md *MetaData) Keys() []Key {
|
|
||||||
return md.keys
|
|
||||||
}
|
|
||||||
|
|
||||||
// Undecoded returns all keys that have not been decoded in the order in which
|
|
||||||
// they appear in the original TOML document.
|
|
||||||
//
|
|
||||||
// This includes keys that haven't been decoded because of a Primitive value.
|
|
||||||
// Once the Primitive value is decoded, the keys will be considered decoded.
|
|
||||||
//
|
|
||||||
// Also note that decoding into an empty interface will result in no decoding,
|
|
||||||
// and so no keys will be considered decoded.
|
|
||||||
//
|
|
||||||
// In this sense, the Undecoded keys correspond to keys in the TOML document
|
|
||||||
// that do not have a concrete type in your representation.
|
|
||||||
func (md *MetaData) Undecoded() []Key {
|
|
||||||
undecoded := make([]Key, 0, len(md.keys))
|
|
||||||
for _, key := range md.keys {
|
|
||||||
if !md.decoded[key.String()] {
|
|
||||||
undecoded = append(undecoded, key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return undecoded
|
|
||||||
}
|
|
1447
vendor/github.com/BurntSushi/toml/decode_test.go
generated
vendored
1447
vendor/github.com/BurntSushi/toml/decode_test.go
generated
vendored
File diff suppressed because it is too large
Load Diff
27
vendor/github.com/BurntSushi/toml/doc.go
generated
vendored
27
vendor/github.com/BurntSushi/toml/doc.go
generated
vendored
@ -1,27 +0,0 @@
|
|||||||
/*
|
|
||||||
Package toml provides facilities for decoding and encoding TOML configuration
|
|
||||||
files via reflection. There is also support for delaying decoding with
|
|
||||||
the Primitive type, and querying the set of keys in a TOML document with the
|
|
||||||
MetaData type.
|
|
||||||
|
|
||||||
The specification implemented: https://github.com/toml-lang/toml
|
|
||||||
|
|
||||||
The sub-command github.com/BurntSushi/toml/cmd/tomlv can be used to verify
|
|
||||||
whether a file is a valid TOML document. It can also be used to print the
|
|
||||||
type of each key in a TOML document.
|
|
||||||
|
|
||||||
Testing
|
|
||||||
|
|
||||||
There are two important types of tests used for this package. The first is
|
|
||||||
contained inside '*_test.go' files and uses the standard Go unit testing
|
|
||||||
framework. These tests are primarily devoted to holistically testing the
|
|
||||||
decoder and encoder.
|
|
||||||
|
|
||||||
The second type of testing is used to verify the implementation's adherence
|
|
||||||
to the TOML specification. These tests have been factored into their own
|
|
||||||
project: https://github.com/BurntSushi/toml-test
|
|
||||||
|
|
||||||
The reason the tests are in a separate project is so that they can be used by
|
|
||||||
any implementation of TOML. Namely, it is language agnostic.
|
|
||||||
*/
|
|
||||||
package toml
|
|
568
vendor/github.com/BurntSushi/toml/encode.go
generated
vendored
568
vendor/github.com/BurntSushi/toml/encode.go
generated
vendored
@ -1,568 +0,0 @@
|
|||||||
package toml
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"reflect"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type tomlEncodeError struct{ error }
|
|
||||||
|
|
||||||
var (
|
|
||||||
errArrayMixedElementTypes = errors.New(
|
|
||||||
"toml: cannot encode array with mixed element types")
|
|
||||||
errArrayNilElement = errors.New(
|
|
||||||
"toml: cannot encode array with nil element")
|
|
||||||
errNonString = errors.New(
|
|
||||||
"toml: cannot encode a map with non-string key type")
|
|
||||||
errAnonNonStruct = errors.New(
|
|
||||||
"toml: cannot encode an anonymous field that is not a struct")
|
|
||||||
errArrayNoTable = errors.New(
|
|
||||||
"toml: TOML array element cannot contain a table")
|
|
||||||
errNoKey = errors.New(
|
|
||||||
"toml: top-level values must be Go maps or structs")
|
|
||||||
errAnything = errors.New("") // used in testing
|
|
||||||
)
|
|
||||||
|
|
||||||
var quotedReplacer = strings.NewReplacer(
|
|
||||||
"\t", "\\t",
|
|
||||||
"\n", "\\n",
|
|
||||||
"\r", "\\r",
|
|
||||||
"\"", "\\\"",
|
|
||||||
"\\", "\\\\",
|
|
||||||
)
|
|
||||||
|
|
||||||
// Encoder controls the encoding of Go values to a TOML document to some
|
|
||||||
// io.Writer.
|
|
||||||
//
|
|
||||||
// The indentation level can be controlled with the Indent field.
|
|
||||||
type Encoder struct {
|
|
||||||
// A single indentation level. By default it is two spaces.
|
|
||||||
Indent string
|
|
||||||
|
|
||||||
// hasWritten is whether we have written any output to w yet.
|
|
||||||
hasWritten bool
|
|
||||||
w *bufio.Writer
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewEncoder returns a TOML encoder that encodes Go values to the io.Writer
|
|
||||||
// given. By default, a single indentation level is 2 spaces.
|
|
||||||
func NewEncoder(w io.Writer) *Encoder {
|
|
||||||
return &Encoder{
|
|
||||||
w: bufio.NewWriter(w),
|
|
||||||
Indent: " ",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Encode writes a TOML representation of the Go value to the underlying
|
|
||||||
// io.Writer. If the value given cannot be encoded to a valid TOML document,
|
|
||||||
// then an error is returned.
|
|
||||||
//
|
|
||||||
// The mapping between Go values and TOML values should be precisely the same
|
|
||||||
// as for the Decode* functions. Similarly, the TextMarshaler interface is
|
|
||||||
// supported by encoding the resulting bytes as strings. (If you want to write
|
|
||||||
// arbitrary binary data then you will need to use something like base64 since
|
|
||||||
// TOML does not have any binary types.)
|
|
||||||
//
|
|
||||||
// When encoding TOML hashes (i.e., Go maps or structs), keys without any
|
|
||||||
// sub-hashes are encoded first.
|
|
||||||
//
|
|
||||||
// If a Go map is encoded, then its keys are sorted alphabetically for
|
|
||||||
// deterministic output. More control over this behavior may be provided if
|
|
||||||
// there is demand for it.
|
|
||||||
//
|
|
||||||
// Encoding Go values without a corresponding TOML representation---like map
|
|
||||||
// types with non-string keys---will cause an error to be returned. Similarly
|
|
||||||
// for mixed arrays/slices, arrays/slices with nil elements, embedded
|
|
||||||
// non-struct types and nested slices containing maps or structs.
|
|
||||||
// (e.g., [][]map[string]string is not allowed but []map[string]string is OK
|
|
||||||
// and so is []map[string][]string.)
|
|
||||||
func (enc *Encoder) Encode(v interface{}) error {
|
|
||||||
rv := eindirect(reflect.ValueOf(v))
|
|
||||||
if err := enc.safeEncode(Key([]string{}), rv); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return enc.w.Flush()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (enc *Encoder) safeEncode(key Key, rv reflect.Value) (err error) {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
if terr, ok := r.(tomlEncodeError); ok {
|
|
||||||
err = terr.error
|
|
||||||
return
|
|
||||||
}
|
|
||||||
panic(r)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
enc.encode(key, rv)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (enc *Encoder) encode(key Key, rv reflect.Value) {
|
|
||||||
// Special case. Time needs to be in ISO8601 format.
|
|
||||||
// Special case. If we can marshal the type to text, then we used that.
|
|
||||||
// Basically, this prevents the encoder for handling these types as
|
|
||||||
// generic structs (or whatever the underlying type of a TextMarshaler is).
|
|
||||||
switch rv.Interface().(type) {
|
|
||||||
case time.Time, TextMarshaler:
|
|
||||||
enc.keyEqElement(key, rv)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
k := rv.Kind()
|
|
||||||
switch k {
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
|
|
||||||
reflect.Int64,
|
|
||||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
|
|
||||||
reflect.Uint64,
|
|
||||||
reflect.Float32, reflect.Float64, reflect.String, reflect.Bool:
|
|
||||||
enc.keyEqElement(key, rv)
|
|
||||||
case reflect.Array, reflect.Slice:
|
|
||||||
if typeEqual(tomlArrayHash, tomlTypeOfGo(rv)) {
|
|
||||||
enc.eArrayOfTables(key, rv)
|
|
||||||
} else {
|
|
||||||
enc.keyEqElement(key, rv)
|
|
||||||
}
|
|
||||||
case reflect.Interface:
|
|
||||||
if rv.IsNil() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
enc.encode(key, rv.Elem())
|
|
||||||
case reflect.Map:
|
|
||||||
if rv.IsNil() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
enc.eTable(key, rv)
|
|
||||||
case reflect.Ptr:
|
|
||||||
if rv.IsNil() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
enc.encode(key, rv.Elem())
|
|
||||||
case reflect.Struct:
|
|
||||||
enc.eTable(key, rv)
|
|
||||||
default:
|
|
||||||
panic(e("unsupported type for key '%s': %s", key, k))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// eElement encodes any value that can be an array element (primitives and
|
|
||||||
// arrays).
|
|
||||||
func (enc *Encoder) eElement(rv reflect.Value) {
|
|
||||||
switch v := rv.Interface().(type) {
|
|
||||||
case time.Time:
|
|
||||||
// Special case time.Time as a primitive. Has to come before
|
|
||||||
// TextMarshaler below because time.Time implements
|
|
||||||
// encoding.TextMarshaler, but we need to always use UTC.
|
|
||||||
enc.wf(v.UTC().Format("2006-01-02T15:04:05Z"))
|
|
||||||
return
|
|
||||||
case TextMarshaler:
|
|
||||||
// Special case. Use text marshaler if it's available for this value.
|
|
||||||
if s, err := v.MarshalText(); err != nil {
|
|
||||||
encPanic(err)
|
|
||||||
} else {
|
|
||||||
enc.writeQuoted(string(s))
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
switch rv.Kind() {
|
|
||||||
case reflect.Bool:
|
|
||||||
enc.wf(strconv.FormatBool(rv.Bool()))
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
|
|
||||||
reflect.Int64:
|
|
||||||
enc.wf(strconv.FormatInt(rv.Int(), 10))
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16,
|
|
||||||
reflect.Uint32, reflect.Uint64:
|
|
||||||
enc.wf(strconv.FormatUint(rv.Uint(), 10))
|
|
||||||
case reflect.Float32:
|
|
||||||
enc.wf(floatAddDecimal(strconv.FormatFloat(rv.Float(), 'f', -1, 32)))
|
|
||||||
case reflect.Float64:
|
|
||||||
enc.wf(floatAddDecimal(strconv.FormatFloat(rv.Float(), 'f', -1, 64)))
|
|
||||||
case reflect.Array, reflect.Slice:
|
|
||||||
enc.eArrayOrSliceElement(rv)
|
|
||||||
case reflect.Interface:
|
|
||||||
enc.eElement(rv.Elem())
|
|
||||||
case reflect.String:
|
|
||||||
enc.writeQuoted(rv.String())
|
|
||||||
default:
|
|
||||||
panic(e("unexpected primitive type: %s", rv.Kind()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// By the TOML spec, all floats must have a decimal with at least one
|
|
||||||
// number on either side.
|
|
||||||
func floatAddDecimal(fstr string) string {
|
|
||||||
if !strings.Contains(fstr, ".") {
|
|
||||||
return fstr + ".0"
|
|
||||||
}
|
|
||||||
return fstr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (enc *Encoder) writeQuoted(s string) {
|
|
||||||
enc.wf("\"%s\"", quotedReplacer.Replace(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (enc *Encoder) eArrayOrSliceElement(rv reflect.Value) {
|
|
||||||
length := rv.Len()
|
|
||||||
enc.wf("[")
|
|
||||||
for i := 0; i < length; i++ {
|
|
||||||
elem := rv.Index(i)
|
|
||||||
enc.eElement(elem)
|
|
||||||
if i != length-1 {
|
|
||||||
enc.wf(", ")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
enc.wf("]")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (enc *Encoder) eArrayOfTables(key Key, rv reflect.Value) {
|
|
||||||
if len(key) == 0 {
|
|
||||||
encPanic(errNoKey)
|
|
||||||
}
|
|
||||||
for i := 0; i < rv.Len(); i++ {
|
|
||||||
trv := rv.Index(i)
|
|
||||||
if isNil(trv) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
panicIfInvalidKey(key)
|
|
||||||
enc.newline()
|
|
||||||
enc.wf("%s[[%s]]", enc.indentStr(key), key.maybeQuotedAll())
|
|
||||||
enc.newline()
|
|
||||||
enc.eMapOrStruct(key, trv)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (enc *Encoder) eTable(key Key, rv reflect.Value) {
|
|
||||||
panicIfInvalidKey(key)
|
|
||||||
if len(key) == 1 {
|
|
||||||
// Output an extra newline between top-level tables.
|
|
||||||
// (The newline isn't written if nothing else has been written though.)
|
|
||||||
enc.newline()
|
|
||||||
}
|
|
||||||
if len(key) > 0 {
|
|
||||||
enc.wf("%s[%s]", enc.indentStr(key), key.maybeQuotedAll())
|
|
||||||
enc.newline()
|
|
||||||
}
|
|
||||||
enc.eMapOrStruct(key, rv)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (enc *Encoder) eMapOrStruct(key Key, rv reflect.Value) {
|
|
||||||
switch rv := eindirect(rv); rv.Kind() {
|
|
||||||
case reflect.Map:
|
|
||||||
enc.eMap(key, rv)
|
|
||||||
case reflect.Struct:
|
|
||||||
enc.eStruct(key, rv)
|
|
||||||
default:
|
|
||||||
panic("eTable: unhandled reflect.Value Kind: " + rv.Kind().String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (enc *Encoder) eMap(key Key, rv reflect.Value) {
|
|
||||||
rt := rv.Type()
|
|
||||||
if rt.Key().Kind() != reflect.String {
|
|
||||||
encPanic(errNonString)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort keys so that we have deterministic output. And write keys directly
|
|
||||||
// underneath this key first, before writing sub-structs or sub-maps.
|
|
||||||
var mapKeysDirect, mapKeysSub []string
|
|
||||||
for _, mapKey := range rv.MapKeys() {
|
|
||||||
k := mapKey.String()
|
|
||||||
if typeIsHash(tomlTypeOfGo(rv.MapIndex(mapKey))) {
|
|
||||||
mapKeysSub = append(mapKeysSub, k)
|
|
||||||
} else {
|
|
||||||
mapKeysDirect = append(mapKeysDirect, k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var writeMapKeys = func(mapKeys []string) {
|
|
||||||
sort.Strings(mapKeys)
|
|
||||||
for _, mapKey := range mapKeys {
|
|
||||||
mrv := rv.MapIndex(reflect.ValueOf(mapKey))
|
|
||||||
if isNil(mrv) {
|
|
||||||
// Don't write anything for nil fields.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
enc.encode(key.add(mapKey), mrv)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
writeMapKeys(mapKeysDirect)
|
|
||||||
writeMapKeys(mapKeysSub)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (enc *Encoder) eStruct(key Key, rv reflect.Value) {
|
|
||||||
// Write keys for fields directly under this key first, because if we write
|
|
||||||
// a field that creates a new table, then all keys under it will be in that
|
|
||||||
// table (not the one we're writing here).
|
|
||||||
rt := rv.Type()
|
|
||||||
var fieldsDirect, fieldsSub [][]int
|
|
||||||
var addFields func(rt reflect.Type, rv reflect.Value, start []int)
|
|
||||||
addFields = func(rt reflect.Type, rv reflect.Value, start []int) {
|
|
||||||
for i := 0; i < rt.NumField(); i++ {
|
|
||||||
f := rt.Field(i)
|
|
||||||
// skip unexported fields
|
|
||||||
if f.PkgPath != "" && !f.Anonymous {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
frv := rv.Field(i)
|
|
||||||
if f.Anonymous {
|
|
||||||
t := f.Type
|
|
||||||
switch t.Kind() {
|
|
||||||
case reflect.Struct:
|
|
||||||
// Treat anonymous struct fields with
|
|
||||||
// tag names as though they are not
|
|
||||||
// anonymous, like encoding/json does.
|
|
||||||
if getOptions(f.Tag).name == "" {
|
|
||||||
addFields(t, frv, f.Index)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
case reflect.Ptr:
|
|
||||||
if t.Elem().Kind() == reflect.Struct &&
|
|
||||||
getOptions(f.Tag).name == "" {
|
|
||||||
if !frv.IsNil() {
|
|
||||||
addFields(t.Elem(), frv.Elem(), f.Index)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Fall through to the normal field encoding logic below
|
|
||||||
// for non-struct anonymous fields.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if typeIsHash(tomlTypeOfGo(frv)) {
|
|
||||||
fieldsSub = append(fieldsSub, append(start, f.Index...))
|
|
||||||
} else {
|
|
||||||
fieldsDirect = append(fieldsDirect, append(start, f.Index...))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addFields(rt, rv, nil)
|
|
||||||
|
|
||||||
var writeFields = func(fields [][]int) {
|
|
||||||
for _, fieldIndex := range fields {
|
|
||||||
sft := rt.FieldByIndex(fieldIndex)
|
|
||||||
sf := rv.FieldByIndex(fieldIndex)
|
|
||||||
if isNil(sf) {
|
|
||||||
// Don't write anything for nil fields.
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := getOptions(sft.Tag)
|
|
||||||
if opts.skip {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
keyName := sft.Name
|
|
||||||
if opts.name != "" {
|
|
||||||
keyName = opts.name
|
|
||||||
}
|
|
||||||
if opts.omitempty && isEmpty(sf) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if opts.omitzero && isZero(sf) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
enc.encode(key.add(keyName), sf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
writeFields(fieldsDirect)
|
|
||||||
writeFields(fieldsSub)
|
|
||||||
}
|
|
||||||
|
|
||||||
// tomlTypeName returns the TOML type name of the Go value's type. It is
|
|
||||||
// used to determine whether the types of array elements are mixed (which is
|
|
||||||
// forbidden). If the Go value is nil, then it is illegal for it to be an array
|
|
||||||
// element, and valueIsNil is returned as true.
|
|
||||||
|
|
||||||
// Returns the TOML type of a Go value. The type may be `nil`, which means
|
|
||||||
// no concrete TOML type could be found.
|
|
||||||
func tomlTypeOfGo(rv reflect.Value) tomlType {
|
|
||||||
if isNil(rv) || !rv.IsValid() {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
switch rv.Kind() {
|
|
||||||
case reflect.Bool:
|
|
||||||
return tomlBool
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32,
|
|
||||||
reflect.Int64,
|
|
||||||
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32,
|
|
||||||
reflect.Uint64:
|
|
||||||
return tomlInteger
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
return tomlFloat
|
|
||||||
case reflect.Array, reflect.Slice:
|
|
||||||
if typeEqual(tomlHash, tomlArrayType(rv)) {
|
|
||||||
return tomlArrayHash
|
|
||||||
}
|
|
||||||
return tomlArray
|
|
||||||
case reflect.Ptr, reflect.Interface:
|
|
||||||
return tomlTypeOfGo(rv.Elem())
|
|
||||||
case reflect.String:
|
|
||||||
return tomlString
|
|
||||||
case reflect.Map:
|
|
||||||
return tomlHash
|
|
||||||
case reflect.Struct:
|
|
||||||
switch rv.Interface().(type) {
|
|
||||||
case time.Time:
|
|
||||||
return tomlDatetime
|
|
||||||
case TextMarshaler:
|
|
||||||
return tomlString
|
|
||||||
default:
|
|
||||||
return tomlHash
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
panic("unexpected reflect.Kind: " + rv.Kind().String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// tomlArrayType returns the element type of a TOML array. The type returned
|
|
||||||
// may be nil if it cannot be determined (e.g., a nil slice or a zero length
|
|
||||||
// slize). This function may also panic if it finds a type that cannot be
|
|
||||||
// expressed in TOML (such as nil elements, heterogeneous arrays or directly
|
|
||||||
// nested arrays of tables).
|
|
||||||
func tomlArrayType(rv reflect.Value) tomlType {
|
|
||||||
if isNil(rv) || !rv.IsValid() || rv.Len() == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
firstType := tomlTypeOfGo(rv.Index(0))
|
|
||||||
if firstType == nil {
|
|
||||||
encPanic(errArrayNilElement)
|
|
||||||
}
|
|
||||||
|
|
||||||
rvlen := rv.Len()
|
|
||||||
for i := 1; i < rvlen; i++ {
|
|
||||||
elem := rv.Index(i)
|
|
||||||
switch elemType := tomlTypeOfGo(elem); {
|
|
||||||
case elemType == nil:
|
|
||||||
encPanic(errArrayNilElement)
|
|
||||||
case !typeEqual(firstType, elemType):
|
|
||||||
encPanic(errArrayMixedElementTypes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If we have a nested array, then we must make sure that the nested
|
|
||||||
// array contains ONLY primitives.
|
|
||||||
// This checks arbitrarily nested arrays.
|
|
||||||
if typeEqual(firstType, tomlArray) || typeEqual(firstType, tomlArrayHash) {
|
|
||||||
nest := tomlArrayType(eindirect(rv.Index(0)))
|
|
||||||
if typeEqual(nest, tomlHash) || typeEqual(nest, tomlArrayHash) {
|
|
||||||
encPanic(errArrayNoTable)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return firstType
|
|
||||||
}
|
|
||||||
|
|
||||||
type tagOptions struct {
|
|
||||||
skip bool // "-"
|
|
||||||
name string
|
|
||||||
omitempty bool
|
|
||||||
omitzero bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func getOptions(tag reflect.StructTag) tagOptions {
|
|
||||||
t := tag.Get("toml")
|
|
||||||
if t == "-" {
|
|
||||||
return tagOptions{skip: true}
|
|
||||||
}
|
|
||||||
var opts tagOptions
|
|
||||||
parts := strings.Split(t, ",")
|
|
||||||
opts.name = parts[0]
|
|
||||||
for _, s := range parts[1:] {
|
|
||||||
switch s {
|
|
||||||
case "omitempty":
|
|
||||||
opts.omitempty = true
|
|
||||||
case "omitzero":
|
|
||||||
opts.omitzero = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return opts
|
|
||||||
}
|
|
||||||
|
|
||||||
func isZero(rv reflect.Value) bool {
|
|
||||||
switch rv.Kind() {
|
|
||||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
|
||||||
return rv.Int() == 0
|
|
||||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
|
||||||
return rv.Uint() == 0
|
|
||||||
case reflect.Float32, reflect.Float64:
|
|
||||||
return rv.Float() == 0.0
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func isEmpty(rv reflect.Value) bool {
|
|
||||||
switch rv.Kind() {
|
|
||||||
case reflect.Array, reflect.Slice, reflect.Map, reflect.String:
|
|
||||||
return rv.Len() == 0
|
|
||||||
case reflect.Bool:
|
|
||||||
return !rv.Bool()
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (enc *Encoder) newline() {
|
|
||||||
if enc.hasWritten {
|
|
||||||
enc.wf("\n")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (enc *Encoder) keyEqElement(key Key, val reflect.Value) {
|
|
||||||
if len(key) == 0 {
|
|
||||||
encPanic(errNoKey)
|
|
||||||
}
|
|
||||||
panicIfInvalidKey(key)
|
|
||||||
enc.wf("%s%s = ", enc.indentStr(key), key.maybeQuoted(len(key)-1))
|
|
||||||
enc.eElement(val)
|
|
||||||
enc.newline()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (enc *Encoder) wf(format string, v ...interface{}) {
|
|
||||||
if _, err := fmt.Fprintf(enc.w, format, v...); err != nil {
|
|
||||||
encPanic(err)
|
|
||||||
}
|
|
||||||
enc.hasWritten = true
|
|
||||||
}
|
|
||||||
|
|
||||||
func (enc *Encoder) indentStr(key Key) string {
|
|
||||||
return strings.Repeat(enc.Indent, len(key)-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func encPanic(err error) {
|
|
||||||
panic(tomlEncodeError{err})
|
|
||||||
}
|
|
||||||
|
|
||||||
func eindirect(v reflect.Value) reflect.Value {
|
|
||||||
switch v.Kind() {
|
|
||||||
case reflect.Ptr, reflect.Interface:
|
|
||||||
return eindirect(v.Elem())
|
|
||||||
default:
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func isNil(rv reflect.Value) bool {
|
|
||||||
switch rv.Kind() {
|
|
||||||
case reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice:
|
|
||||||
return rv.IsNil()
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func panicIfInvalidKey(key Key) {
|
|
||||||
for _, k := range key {
|
|
||||||
if len(k) == 0 {
|
|
||||||
encPanic(e("Key '%s' is not a valid table name. Key names "+
|
|
||||||
"cannot be empty.", key.maybeQuotedAll()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func isValidKeyName(s string) bool {
|
|
||||||
return len(s) != 0
|
|
||||||
}
|
|
615
vendor/github.com/BurntSushi/toml/encode_test.go
generated
vendored
615
vendor/github.com/BurntSushi/toml/encode_test.go
generated
vendored
@ -1,615 +0,0 @@
|
|||||||
package toml
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"net"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestEncodeRoundTrip(t *testing.T) {
|
|
||||||
type Config struct {
|
|
||||||
Age int
|
|
||||||
Cats []string
|
|
||||||
Pi float64
|
|
||||||
Perfection []int
|
|
||||||
DOB time.Time
|
|
||||||
Ipaddress net.IP
|
|
||||||
}
|
|
||||||
|
|
||||||
var inputs = Config{
|
|
||||||
13,
|
|
||||||
[]string{"one", "two", "three"},
|
|
||||||
3.145,
|
|
||||||
[]int{11, 2, 3, 4},
|
|
||||||
time.Now(),
|
|
||||||
net.ParseIP("192.168.59.254"),
|
|
||||||
}
|
|
||||||
|
|
||||||
var firstBuffer bytes.Buffer
|
|
||||||
e := NewEncoder(&firstBuffer)
|
|
||||||
err := e.Encode(inputs)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
var outputs Config
|
|
||||||
if _, err := Decode(firstBuffer.String(), &outputs); err != nil {
|
|
||||||
t.Logf("Could not decode:\n-----\n%s\n-----\n",
|
|
||||||
firstBuffer.String())
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// could test each value individually, but I'm lazy
|
|
||||||
var secondBuffer bytes.Buffer
|
|
||||||
e2 := NewEncoder(&secondBuffer)
|
|
||||||
err = e2.Encode(outputs)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if firstBuffer.String() != secondBuffer.String() {
|
|
||||||
t.Error(
|
|
||||||
firstBuffer.String(),
|
|
||||||
"\n\n is not identical to\n\n",
|
|
||||||
secondBuffer.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// XXX(burntsushi)
|
|
||||||
// I think these tests probably should be removed. They are good, but they
|
|
||||||
// ought to be obsolete by toml-test.
|
|
||||||
func TestEncode(t *testing.T) {
|
|
||||||
type Embedded struct {
|
|
||||||
Int int `toml:"_int"`
|
|
||||||
}
|
|
||||||
type NonStruct int
|
|
||||||
|
|
||||||
date := time.Date(2014, 5, 11, 20, 30, 40, 0, time.FixedZone("IST", 3600))
|
|
||||||
dateStr := "2014-05-11T19:30:40Z"
|
|
||||||
|
|
||||||
tests := map[string]struct {
|
|
||||||
input interface{}
|
|
||||||
wantOutput string
|
|
||||||
wantError error
|
|
||||||
}{
|
|
||||||
"bool field": {
|
|
||||||
input: struct {
|
|
||||||
BoolTrue bool
|
|
||||||
BoolFalse bool
|
|
||||||
}{true, false},
|
|
||||||
wantOutput: "BoolTrue = true\nBoolFalse = false\n",
|
|
||||||
},
|
|
||||||
"int fields": {
|
|
||||||
input: struct {
|
|
||||||
Int int
|
|
||||||
Int8 int8
|
|
||||||
Int16 int16
|
|
||||||
Int32 int32
|
|
||||||
Int64 int64
|
|
||||||
}{1, 2, 3, 4, 5},
|
|
||||||
wantOutput: "Int = 1\nInt8 = 2\nInt16 = 3\nInt32 = 4\nInt64 = 5\n",
|
|
||||||
},
|
|
||||||
"uint fields": {
|
|
||||||
input: struct {
|
|
||||||
Uint uint
|
|
||||||
Uint8 uint8
|
|
||||||
Uint16 uint16
|
|
||||||
Uint32 uint32
|
|
||||||
Uint64 uint64
|
|
||||||
}{1, 2, 3, 4, 5},
|
|
||||||
wantOutput: "Uint = 1\nUint8 = 2\nUint16 = 3\nUint32 = 4" +
|
|
||||||
"\nUint64 = 5\n",
|
|
||||||
},
|
|
||||||
"float fields": {
|
|
||||||
input: struct {
|
|
||||||
Float32 float32
|
|
||||||
Float64 float64
|
|
||||||
}{1.5, 2.5},
|
|
||||||
wantOutput: "Float32 = 1.5\nFloat64 = 2.5\n",
|
|
||||||
},
|
|
||||||
"string field": {
|
|
||||||
input: struct{ String string }{"foo"},
|
|
||||||
wantOutput: "String = \"foo\"\n",
|
|
||||||
},
|
|
||||||
"string field and unexported field": {
|
|
||||||
input: struct {
|
|
||||||
String string
|
|
||||||
unexported int
|
|
||||||
}{"foo", 0},
|
|
||||||
wantOutput: "String = \"foo\"\n",
|
|
||||||
},
|
|
||||||
"datetime field in UTC": {
|
|
||||||
input: struct{ Date time.Time }{date},
|
|
||||||
wantOutput: fmt.Sprintf("Date = %s\n", dateStr),
|
|
||||||
},
|
|
||||||
"datetime field as primitive": {
|
|
||||||
// Using a map here to fail if isStructOrMap() returns true for
|
|
||||||
// time.Time.
|
|
||||||
input: map[string]interface{}{
|
|
||||||
"Date": date,
|
|
||||||
"Int": 1,
|
|
||||||
},
|
|
||||||
wantOutput: fmt.Sprintf("Date = %s\nInt = 1\n", dateStr),
|
|
||||||
},
|
|
||||||
"array fields": {
|
|
||||||
input: struct {
|
|
||||||
IntArray0 [0]int
|
|
||||||
IntArray3 [3]int
|
|
||||||
}{[0]int{}, [3]int{1, 2, 3}},
|
|
||||||
wantOutput: "IntArray0 = []\nIntArray3 = [1, 2, 3]\n",
|
|
||||||
},
|
|
||||||
"slice fields": {
|
|
||||||
input: struct{ IntSliceNil, IntSlice0, IntSlice3 []int }{
|
|
||||||
nil, []int{}, []int{1, 2, 3},
|
|
||||||
},
|
|
||||||
wantOutput: "IntSlice0 = []\nIntSlice3 = [1, 2, 3]\n",
|
|
||||||
},
|
|
||||||
"datetime slices": {
|
|
||||||
input: struct{ DatetimeSlice []time.Time }{
|
|
||||||
[]time.Time{date, date},
|
|
||||||
},
|
|
||||||
wantOutput: fmt.Sprintf("DatetimeSlice = [%s, %s]\n",
|
|
||||||
dateStr, dateStr),
|
|
||||||
},
|
|
||||||
"nested arrays and slices": {
|
|
||||||
input: struct {
|
|
||||||
SliceOfArrays [][2]int
|
|
||||||
ArrayOfSlices [2][]int
|
|
||||||
SliceOfArraysOfSlices [][2][]int
|
|
||||||
ArrayOfSlicesOfArrays [2][][2]int
|
|
||||||
SliceOfMixedArrays [][2]interface{}
|
|
||||||
ArrayOfMixedSlices [2][]interface{}
|
|
||||||
}{
|
|
||||||
[][2]int{{1, 2}, {3, 4}},
|
|
||||||
[2][]int{{1, 2}, {3, 4}},
|
|
||||||
[][2][]int{
|
|
||||||
{
|
|
||||||
{1, 2}, {3, 4},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
{5, 6}, {7, 8},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
[2][][2]int{
|
|
||||||
{
|
|
||||||
{1, 2}, {3, 4},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
{5, 6}, {7, 8},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
[][2]interface{}{
|
|
||||||
{1, 2}, {"a", "b"},
|
|
||||||
},
|
|
||||||
[2][]interface{}{
|
|
||||||
{1, 2}, {"a", "b"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wantOutput: `SliceOfArrays = [[1, 2], [3, 4]]
|
|
||||||
ArrayOfSlices = [[1, 2], [3, 4]]
|
|
||||||
SliceOfArraysOfSlices = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
|
|
||||||
ArrayOfSlicesOfArrays = [[[1, 2], [3, 4]], [[5, 6], [7, 8]]]
|
|
||||||
SliceOfMixedArrays = [[1, 2], ["a", "b"]]
|
|
||||||
ArrayOfMixedSlices = [[1, 2], ["a", "b"]]
|
|
||||||
`,
|
|
||||||
},
|
|
||||||
"empty slice": {
|
|
||||||
input: struct{ Empty []interface{} }{[]interface{}{}},
|
|
||||||
wantOutput: "Empty = []\n",
|
|
||||||
},
|
|
||||||
"(error) slice with element type mismatch (string and integer)": {
|
|
||||||
input: struct{ Mixed []interface{} }{[]interface{}{1, "a"}},
|
|
||||||
wantError: errArrayMixedElementTypes,
|
|
||||||
},
|
|
||||||
"(error) slice with element type mismatch (integer and float)": {
|
|
||||||
input: struct{ Mixed []interface{} }{[]interface{}{1, 2.5}},
|
|
||||||
wantError: errArrayMixedElementTypes,
|
|
||||||
},
|
|
||||||
"slice with elems of differing Go types, same TOML types": {
|
|
||||||
input: struct {
|
|
||||||
MixedInts []interface{}
|
|
||||||
MixedFloats []interface{}
|
|
||||||
}{
|
|
||||||
[]interface{}{
|
|
||||||
int(1), int8(2), int16(3), int32(4), int64(5),
|
|
||||||
uint(1), uint8(2), uint16(3), uint32(4), uint64(5),
|
|
||||||
},
|
|
||||||
[]interface{}{float32(1.5), float64(2.5)},
|
|
||||||
},
|
|
||||||
wantOutput: "MixedInts = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5]\n" +
|
|
||||||
"MixedFloats = [1.5, 2.5]\n",
|
|
||||||
},
|
|
||||||
"(error) slice w/ element type mismatch (one is nested array)": {
|
|
||||||
input: struct{ Mixed []interface{} }{
|
|
||||||
[]interface{}{1, []interface{}{2}},
|
|
||||||
},
|
|
||||||
wantError: errArrayMixedElementTypes,
|
|
||||||
},
|
|
||||||
"(error) slice with 1 nil element": {
|
|
||||||
input: struct{ NilElement1 []interface{} }{[]interface{}{nil}},
|
|
||||||
wantError: errArrayNilElement,
|
|
||||||
},
|
|
||||||
"(error) slice with 1 nil element (and other non-nil elements)": {
|
|
||||||
input: struct{ NilElement []interface{} }{
|
|
||||||
[]interface{}{1, nil},
|
|
||||||
},
|
|
||||||
wantError: errArrayNilElement,
|
|
||||||
},
|
|
||||||
"simple map": {
|
|
||||||
input: map[string]int{"a": 1, "b": 2},
|
|
||||||
wantOutput: "a = 1\nb = 2\n",
|
|
||||||
},
|
|
||||||
"map with interface{} value type": {
|
|
||||||
input: map[string]interface{}{"a": 1, "b": "c"},
|
|
||||||
wantOutput: "a = 1\nb = \"c\"\n",
|
|
||||||
},
|
|
||||||
"map with interface{} value type, some of which are structs": {
|
|
||||||
input: map[string]interface{}{
|
|
||||||
"a": struct{ Int int }{2},
|
|
||||||
"b": 1,
|
|
||||||
},
|
|
||||||
wantOutput: "b = 1\n\n[a]\n Int = 2\n",
|
|
||||||
},
|
|
||||||
"nested map": {
|
|
||||||
input: map[string]map[string]int{
|
|
||||||
"a": {"b": 1},
|
|
||||||
"c": {"d": 2},
|
|
||||||
},
|
|
||||||
wantOutput: "[a]\n b = 1\n\n[c]\n d = 2\n",
|
|
||||||
},
|
|
||||||
"nested struct": {
|
|
||||||
input: struct{ Struct struct{ Int int } }{
|
|
||||||
struct{ Int int }{1},
|
|
||||||
},
|
|
||||||
wantOutput: "[Struct]\n Int = 1\n",
|
|
||||||
},
|
|
||||||
"nested struct and non-struct field": {
|
|
||||||
input: struct {
|
|
||||||
Struct struct{ Int int }
|
|
||||||
Bool bool
|
|
||||||
}{struct{ Int int }{1}, true},
|
|
||||||
wantOutput: "Bool = true\n\n[Struct]\n Int = 1\n",
|
|
||||||
},
|
|
||||||
"2 nested structs": {
|
|
||||||
input: struct{ Struct1, Struct2 struct{ Int int } }{
|
|
||||||
struct{ Int int }{1}, struct{ Int int }{2},
|
|
||||||
},
|
|
||||||
wantOutput: "[Struct1]\n Int = 1\n\n[Struct2]\n Int = 2\n",
|
|
||||||
},
|
|
||||||
"deeply nested structs": {
|
|
||||||
input: struct {
|
|
||||||
Struct1, Struct2 struct{ Struct3 *struct{ Int int } }
|
|
||||||
}{
|
|
||||||
struct{ Struct3 *struct{ Int int } }{&struct{ Int int }{1}},
|
|
||||||
struct{ Struct3 *struct{ Int int } }{nil},
|
|
||||||
},
|
|
||||||
wantOutput: "[Struct1]\n [Struct1.Struct3]\n Int = 1" +
|
|
||||||
"\n\n[Struct2]\n",
|
|
||||||
},
|
|
||||||
"nested struct with nil struct elem": {
|
|
||||||
input: struct {
|
|
||||||
Struct struct{ Inner *struct{ Int int } }
|
|
||||||
}{
|
|
||||||
struct{ Inner *struct{ Int int } }{nil},
|
|
||||||
},
|
|
||||||
wantOutput: "[Struct]\n",
|
|
||||||
},
|
|
||||||
"nested struct with no fields": {
|
|
||||||
input: struct {
|
|
||||||
Struct struct{ Inner struct{} }
|
|
||||||
}{
|
|
||||||
struct{ Inner struct{} }{struct{}{}},
|
|
||||||
},
|
|
||||||
wantOutput: "[Struct]\n [Struct.Inner]\n",
|
|
||||||
},
|
|
||||||
"struct with tags": {
|
|
||||||
input: struct {
|
|
||||||
Struct struct {
|
|
||||||
Int int `toml:"_int"`
|
|
||||||
} `toml:"_struct"`
|
|
||||||
Bool bool `toml:"_bool"`
|
|
||||||
}{
|
|
||||||
struct {
|
|
||||||
Int int `toml:"_int"`
|
|
||||||
}{1}, true,
|
|
||||||
},
|
|
||||||
wantOutput: "_bool = true\n\n[_struct]\n _int = 1\n",
|
|
||||||
},
|
|
||||||
"embedded struct": {
|
|
||||||
input: struct{ Embedded }{Embedded{1}},
|
|
||||||
wantOutput: "_int = 1\n",
|
|
||||||
},
|
|
||||||
"embedded *struct": {
|
|
||||||
input: struct{ *Embedded }{&Embedded{1}},
|
|
||||||
wantOutput: "_int = 1\n",
|
|
||||||
},
|
|
||||||
"nested embedded struct": {
|
|
||||||
input: struct {
|
|
||||||
Struct struct{ Embedded } `toml:"_struct"`
|
|
||||||
}{struct{ Embedded }{Embedded{1}}},
|
|
||||||
wantOutput: "[_struct]\n _int = 1\n",
|
|
||||||
},
|
|
||||||
"nested embedded *struct": {
|
|
||||||
input: struct {
|
|
||||||
Struct struct{ *Embedded } `toml:"_struct"`
|
|
||||||
}{struct{ *Embedded }{&Embedded{1}}},
|
|
||||||
wantOutput: "[_struct]\n _int = 1\n",
|
|
||||||
},
|
|
||||||
"embedded non-struct": {
|
|
||||||
input: struct{ NonStruct }{5},
|
|
||||||
wantOutput: "NonStruct = 5\n",
|
|
||||||
},
|
|
||||||
"array of tables": {
|
|
||||||
input: struct {
|
|
||||||
Structs []*struct{ Int int } `toml:"struct"`
|
|
||||||
}{
|
|
||||||
[]*struct{ Int int }{{1}, {3}},
|
|
||||||
},
|
|
||||||
wantOutput: "[[struct]]\n Int = 1\n\n[[struct]]\n Int = 3\n",
|
|
||||||
},
|
|
||||||
"array of tables order": {
|
|
||||||
input: map[string]interface{}{
|
|
||||||
"map": map[string]interface{}{
|
|
||||||
"zero": 5,
|
|
||||||
"arr": []map[string]int{
|
|
||||||
{
|
|
||||||
"friend": 5,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
wantOutput: "[map]\n zero = 5\n\n [[map.arr]]\n friend = 5\n",
|
|
||||||
},
|
|
||||||
"(error) top-level slice": {
|
|
||||||
input: []struct{ Int int }{{1}, {2}, {3}},
|
|
||||||
wantError: errNoKey,
|
|
||||||
},
|
|
||||||
"(error) slice of slice": {
|
|
||||||
input: struct {
|
|
||||||
Slices [][]struct{ Int int }
|
|
||||||
}{
|
|
||||||
[][]struct{ Int int }{{{1}}, {{2}}, {{3}}},
|
|
||||||
},
|
|
||||||
wantError: errArrayNoTable,
|
|
||||||
},
|
|
||||||
"(error) map no string key": {
|
|
||||||
input: map[int]string{1: ""},
|
|
||||||
wantError: errNonString,
|
|
||||||
},
|
|
||||||
"(error) empty key name": {
|
|
||||||
input: map[string]int{"": 1},
|
|
||||||
wantError: errAnything,
|
|
||||||
},
|
|
||||||
"(error) empty map name": {
|
|
||||||
input: map[string]interface{}{
|
|
||||||
"": map[string]int{"v": 1},
|
|
||||||
},
|
|
||||||
wantError: errAnything,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
for label, test := range tests {
|
|
||||||
encodeExpected(t, label, test.input, test.wantOutput, test.wantError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEncodeNestedTableArrays(t *testing.T) {
|
|
||||||
type song struct {
|
|
||||||
Name string `toml:"name"`
|
|
||||||
}
|
|
||||||
type album struct {
|
|
||||||
Name string `toml:"name"`
|
|
||||||
Songs []song `toml:"songs"`
|
|
||||||
}
|
|
||||||
type springsteen struct {
|
|
||||||
Albums []album `toml:"albums"`
|
|
||||||
}
|
|
||||||
value := springsteen{
|
|
||||||
[]album{
|
|
||||||
{"Born to Run",
|
|
||||||
[]song{{"Jungleland"}, {"Meeting Across the River"}}},
|
|
||||||
{"Born in the USA",
|
|
||||||
[]song{{"Glory Days"}, {"Dancing in the Dark"}}},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
expected := `[[albums]]
|
|
||||||
name = "Born to Run"
|
|
||||||
|
|
||||||
[[albums.songs]]
|
|
||||||
name = "Jungleland"
|
|
||||||
|
|
||||||
[[albums.songs]]
|
|
||||||
name = "Meeting Across the River"
|
|
||||||
|
|
||||||
[[albums]]
|
|
||||||
name = "Born in the USA"
|
|
||||||
|
|
||||||
[[albums.songs]]
|
|
||||||
name = "Glory Days"
|
|
||||||
|
|
||||||
[[albums.songs]]
|
|
||||||
name = "Dancing in the Dark"
|
|
||||||
`
|
|
||||||
encodeExpected(t, "nested table arrays", value, expected, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEncodeArrayHashWithNormalHashOrder(t *testing.T) {
|
|
||||||
type Alpha struct {
|
|
||||||
V int
|
|
||||||
}
|
|
||||||
type Beta struct {
|
|
||||||
V int
|
|
||||||
}
|
|
||||||
type Conf struct {
|
|
||||||
V int
|
|
||||||
A Alpha
|
|
||||||
B []Beta
|
|
||||||
}
|
|
||||||
|
|
||||||
val := Conf{
|
|
||||||
V: 1,
|
|
||||||
A: Alpha{2},
|
|
||||||
B: []Beta{{3}},
|
|
||||||
}
|
|
||||||
expected := "V = 1\n\n[A]\n V = 2\n\n[[B]]\n V = 3\n"
|
|
||||||
encodeExpected(t, "array hash with normal hash order", val, expected, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEncodeWithOmitEmpty(t *testing.T) {
|
|
||||||
type simple struct {
|
|
||||||
Bool bool `toml:"bool,omitempty"`
|
|
||||||
String string `toml:"string,omitempty"`
|
|
||||||
Array [0]byte `toml:"array,omitempty"`
|
|
||||||
Slice []int `toml:"slice,omitempty"`
|
|
||||||
Map map[string]string `toml:"map,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
var v simple
|
|
||||||
encodeExpected(t, "fields with omitempty are omitted when empty", v, "", nil)
|
|
||||||
v = simple{
|
|
||||||
Bool: true,
|
|
||||||
String: " ",
|
|
||||||
Slice: []int{2, 3, 4},
|
|
||||||
Map: map[string]string{"foo": "bar"},
|
|
||||||
}
|
|
||||||
expected := `bool = true
|
|
||||||
string = " "
|
|
||||||
slice = [2, 3, 4]
|
|
||||||
|
|
||||||
[map]
|
|
||||||
foo = "bar"
|
|
||||||
`
|
|
||||||
encodeExpected(t, "fields with omitempty are not omitted when non-empty",
|
|
||||||
v, expected, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEncodeWithOmitZero(t *testing.T) {
|
|
||||||
type simple struct {
|
|
||||||
Number int `toml:"number,omitzero"`
|
|
||||||
Real float64 `toml:"real,omitzero"`
|
|
||||||
Unsigned uint `toml:"unsigned,omitzero"`
|
|
||||||
}
|
|
||||||
|
|
||||||
value := simple{0, 0.0, uint(0)}
|
|
||||||
expected := ""
|
|
||||||
|
|
||||||
encodeExpected(t, "simple with omitzero, all zero", value, expected, nil)
|
|
||||||
|
|
||||||
value.Number = 10
|
|
||||||
value.Real = 20
|
|
||||||
value.Unsigned = 5
|
|
||||||
expected = `number = 10
|
|
||||||
real = 20.0
|
|
||||||
unsigned = 5
|
|
||||||
`
|
|
||||||
encodeExpected(t, "simple with omitzero, non-zero", value, expected, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEncodeOmitemptyWithEmptyName(t *testing.T) {
|
|
||||||
type simple struct {
|
|
||||||
S []int `toml:",omitempty"`
|
|
||||||
}
|
|
||||||
v := simple{[]int{1, 2, 3}}
|
|
||||||
expected := "S = [1, 2, 3]\n"
|
|
||||||
encodeExpected(t, "simple with omitempty, no name, non-empty field",
|
|
||||||
v, expected, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEncodeAnonymousStruct(t *testing.T) {
|
|
||||||
type Inner struct{ N int }
|
|
||||||
type Outer0 struct{ Inner }
|
|
||||||
type Outer1 struct {
|
|
||||||
Inner `toml:"inner"`
|
|
||||||
}
|
|
||||||
|
|
||||||
v0 := Outer0{Inner{3}}
|
|
||||||
expected := "N = 3\n"
|
|
||||||
encodeExpected(t, "embedded anonymous untagged struct", v0, expected, nil)
|
|
||||||
|
|
||||||
v1 := Outer1{Inner{3}}
|
|
||||||
expected = "[inner]\n N = 3\n"
|
|
||||||
encodeExpected(t, "embedded anonymous tagged struct", v1, expected, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEncodeAnonymousStructPointerField(t *testing.T) {
|
|
||||||
type Inner struct{ N int }
|
|
||||||
type Outer0 struct{ *Inner }
|
|
||||||
type Outer1 struct {
|
|
||||||
*Inner `toml:"inner"`
|
|
||||||
}
|
|
||||||
|
|
||||||
v0 := Outer0{}
|
|
||||||
expected := ""
|
|
||||||
encodeExpected(t, "nil anonymous untagged struct pointer field", v0, expected, nil)
|
|
||||||
|
|
||||||
v0 = Outer0{&Inner{3}}
|
|
||||||
expected = "N = 3\n"
|
|
||||||
encodeExpected(t, "non-nil anonymous untagged struct pointer field", v0, expected, nil)
|
|
||||||
|
|
||||||
v1 := Outer1{}
|
|
||||||
expected = ""
|
|
||||||
encodeExpected(t, "nil anonymous tagged struct pointer field", v1, expected, nil)
|
|
||||||
|
|
||||||
v1 = Outer1{&Inner{3}}
|
|
||||||
expected = "[inner]\n N = 3\n"
|
|
||||||
encodeExpected(t, "non-nil anonymous tagged struct pointer field", v1, expected, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEncodeIgnoredFields(t *testing.T) {
|
|
||||||
type simple struct {
|
|
||||||
Number int `toml:"-"`
|
|
||||||
}
|
|
||||||
value := simple{}
|
|
||||||
expected := ""
|
|
||||||
encodeExpected(t, "ignored field", value, expected, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func encodeExpected(
|
|
||||||
t *testing.T, label string, val interface{}, wantStr string, wantErr error,
|
|
||||||
) {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
enc := NewEncoder(&buf)
|
|
||||||
err := enc.Encode(val)
|
|
||||||
if err != wantErr {
|
|
||||||
if wantErr != nil {
|
|
||||||
if wantErr == errAnything && err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
t.Errorf("%s: want Encode error %v, got %v", label, wantErr, err)
|
|
||||||
} else {
|
|
||||||
t.Errorf("%s: Encode failed: %s", label, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if got := buf.String(); wantStr != got {
|
|
||||||
t.Errorf("%s: want\n-----\n%q\n-----\nbut got\n-----\n%q\n-----\n",
|
|
||||||
label, wantStr, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleEncoder_Encode() {
|
|
||||||
date, _ := time.Parse(time.RFC822, "14 Mar 10 18:00 UTC")
|
|
||||||
var config = map[string]interface{}{
|
|
||||||
"date": date,
|
|
||||||
"counts": []int{1, 1, 2, 3, 5, 8},
|
|
||||||
"hash": map[string]string{
|
|
||||||
"key1": "val1",
|
|
||||||
"key2": "val2",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
if err := NewEncoder(buf).Encode(config); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
fmt.Println(buf.String())
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// counts = [1, 1, 2, 3, 5, 8]
|
|
||||||
// date = 2010-03-14T18:00:00Z
|
|
||||||
//
|
|
||||||
// [hash]
|
|
||||||
// key1 = "val1"
|
|
||||||
// key2 = "val2"
|
|
||||||
}
|
|
19
vendor/github.com/BurntSushi/toml/encoding_types.go
generated
vendored
19
vendor/github.com/BurntSushi/toml/encoding_types.go
generated
vendored
@ -1,19 +0,0 @@
|
|||||||
// +build go1.2
|
|
||||||
|
|
||||||
package toml
|
|
||||||
|
|
||||||
// In order to support Go 1.1, we define our own TextMarshaler and
|
|
||||||
// TextUnmarshaler types. For Go 1.2+, we just alias them with the
|
|
||||||
// standard library interfaces.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TextMarshaler is a synonym for encoding.TextMarshaler. It is defined here
|
|
||||||
// so that Go 1.1 can be supported.
|
|
||||||
type TextMarshaler encoding.TextMarshaler
|
|
||||||
|
|
||||||
// TextUnmarshaler is a synonym for encoding.TextUnmarshaler. It is defined
|
|
||||||
// here so that Go 1.1 can be supported.
|
|
||||||
type TextUnmarshaler encoding.TextUnmarshaler
|
|
18
vendor/github.com/BurntSushi/toml/encoding_types_1.1.go
generated
vendored
18
vendor/github.com/BurntSushi/toml/encoding_types_1.1.go
generated
vendored
@ -1,18 +0,0 @@
|
|||||||
// +build !go1.2
|
|
||||||
|
|
||||||
package toml
|
|
||||||
|
|
||||||
// These interfaces were introduced in Go 1.2, so we add them manually when
|
|
||||||
// compiling for Go 1.1.
|
|
||||||
|
|
||||||
// TextMarshaler is a synonym for encoding.TextMarshaler. It is defined here
|
|
||||||
// so that Go 1.1 can be supported.
|
|
||||||
type TextMarshaler interface {
|
|
||||||
MarshalText() (text []byte, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TextUnmarshaler is a synonym for encoding.TextUnmarshaler. It is defined
|
|
||||||
// here so that Go 1.1 can be supported.
|
|
||||||
type TextUnmarshaler interface {
|
|
||||||
UnmarshalText(text []byte) error
|
|
||||||
}
|
|
953
vendor/github.com/BurntSushi/toml/lex.go
generated
vendored
953
vendor/github.com/BurntSushi/toml/lex.go
generated
vendored
@ -1,953 +0,0 @@
|
|||||||
package toml
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"unicode"
|
|
||||||
"unicode/utf8"
|
|
||||||
)
|
|
||||||
|
|
||||||
type itemType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
itemError itemType = iota
|
|
||||||
itemNIL // used in the parser to indicate no type
|
|
||||||
itemEOF
|
|
||||||
itemText
|
|
||||||
itemString
|
|
||||||
itemRawString
|
|
||||||
itemMultilineString
|
|
||||||
itemRawMultilineString
|
|
||||||
itemBool
|
|
||||||
itemInteger
|
|
||||||
itemFloat
|
|
||||||
itemDatetime
|
|
||||||
itemArray // the start of an array
|
|
||||||
itemArrayEnd
|
|
||||||
itemTableStart
|
|
||||||
itemTableEnd
|
|
||||||
itemArrayTableStart
|
|
||||||
itemArrayTableEnd
|
|
||||||
itemKeyStart
|
|
||||||
itemCommentStart
|
|
||||||
itemInlineTableStart
|
|
||||||
itemInlineTableEnd
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
eof = 0
|
|
||||||
comma = ','
|
|
||||||
tableStart = '['
|
|
||||||
tableEnd = ']'
|
|
||||||
arrayTableStart = '['
|
|
||||||
arrayTableEnd = ']'
|
|
||||||
tableSep = '.'
|
|
||||||
keySep = '='
|
|
||||||
arrayStart = '['
|
|
||||||
arrayEnd = ']'
|
|
||||||
commentStart = '#'
|
|
||||||
stringStart = '"'
|
|
||||||
stringEnd = '"'
|
|
||||||
rawStringStart = '\''
|
|
||||||
rawStringEnd = '\''
|
|
||||||
inlineTableStart = '{'
|
|
||||||
inlineTableEnd = '}'
|
|
||||||
)
|
|
||||||
|
|
||||||
type stateFn func(lx *lexer) stateFn
|
|
||||||
|
|
||||||
type lexer struct {
|
|
||||||
input string
|
|
||||||
start int
|
|
||||||
pos int
|
|
||||||
line int
|
|
||||||
state stateFn
|
|
||||||
items chan item
|
|
||||||
|
|
||||||
// Allow for backing up up to three runes.
|
|
||||||
// This is necessary because TOML contains 3-rune tokens (""" and ''').
|
|
||||||
prevWidths [3]int
|
|
||||||
nprev int // how many of prevWidths are in use
|
|
||||||
// If we emit an eof, we can still back up, but it is not OK to call
|
|
||||||
// next again.
|
|
||||||
atEOF bool
|
|
||||||
|
|
||||||
// A stack of state functions used to maintain context.
|
|
||||||
// The idea is to reuse parts of the state machine in various places.
|
|
||||||
// For example, values can appear at the top level or within arbitrarily
|
|
||||||
// nested arrays. The last state on the stack is used after a value has
|
|
||||||
// been lexed. Similarly for comments.
|
|
||||||
stack []stateFn
|
|
||||||
}
|
|
||||||
|
|
||||||
type item struct {
|
|
||||||
typ itemType
|
|
||||||
val string
|
|
||||||
line int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lx *lexer) nextItem() item {
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case item := <-lx.items:
|
|
||||||
return item
|
|
||||||
default:
|
|
||||||
lx.state = lx.state(lx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func lex(input string) *lexer {
|
|
||||||
lx := &lexer{
|
|
||||||
input: input,
|
|
||||||
state: lexTop,
|
|
||||||
line: 1,
|
|
||||||
items: make(chan item, 10),
|
|
||||||
stack: make([]stateFn, 0, 10),
|
|
||||||
}
|
|
||||||
return lx
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lx *lexer) push(state stateFn) {
|
|
||||||
lx.stack = append(lx.stack, state)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lx *lexer) pop() stateFn {
|
|
||||||
if len(lx.stack) == 0 {
|
|
||||||
return lx.errorf("BUG in lexer: no states to pop")
|
|
||||||
}
|
|
||||||
last := lx.stack[len(lx.stack)-1]
|
|
||||||
lx.stack = lx.stack[0 : len(lx.stack)-1]
|
|
||||||
return last
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lx *lexer) current() string {
|
|
||||||
return lx.input[lx.start:lx.pos]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lx *lexer) emit(typ itemType) {
|
|
||||||
lx.items <- item{typ, lx.current(), lx.line}
|
|
||||||
lx.start = lx.pos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lx *lexer) emitTrim(typ itemType) {
|
|
||||||
lx.items <- item{typ, strings.TrimSpace(lx.current()), lx.line}
|
|
||||||
lx.start = lx.pos
|
|
||||||
}
|
|
||||||
|
|
||||||
func (lx *lexer) next() (r rune) {
|
|
||||||
if lx.atEOF {
|
|
||||||
panic("next called after EOF")
|
|
||||||
}
|
|
||||||
if lx.pos >= len(lx.input) {
|
|
||||||
lx.atEOF = true
|
|
||||||
return eof
|
|
||||||
}
|
|
||||||
|
|
||||||
if lx.input[lx.pos] == '\n' {
|
|
||||||
lx.line++
|
|
||||||
}
|
|
||||||
lx.prevWidths[2] = lx.prevWidths[1]
|
|
||||||
lx.prevWidths[1] = lx.prevWidths[0]
|
|
||||||
if lx.nprev < 3 {
|
|
||||||
lx.nprev++
|
|
||||||
}
|
|
||||||
r, w := utf8.DecodeRuneInString(lx.input[lx.pos:])
|
|
||||||
lx.prevWidths[0] = w
|
|
||||||
lx.pos += w
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// ignore skips over the pending input before this point.
|
|
||||||
func (lx *lexer) ignore() {
|
|
||||||
lx.start = lx.pos
|
|
||||||
}
|
|
||||||
|
|
||||||
// backup steps back one rune. Can be called only twice between calls to next.
|
|
||||||
func (lx *lexer) backup() {
|
|
||||||
if lx.atEOF {
|
|
||||||
lx.atEOF = false
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if lx.nprev < 1 {
|
|
||||||
panic("backed up too far")
|
|
||||||
}
|
|
||||||
w := lx.prevWidths[0]
|
|
||||||
lx.prevWidths[0] = lx.prevWidths[1]
|
|
||||||
lx.prevWidths[1] = lx.prevWidths[2]
|
|
||||||
lx.nprev--
|
|
||||||
lx.pos -= w
|
|
||||||
if lx.pos < len(lx.input) && lx.input[lx.pos] == '\n' {
|
|
||||||
lx.line--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// accept consumes the next rune if it's equal to `valid`.
|
|
||||||
func (lx *lexer) accept(valid rune) bool {
|
|
||||||
if lx.next() == valid {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
lx.backup()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// peek returns but does not consume the next rune in the input.
|
|
||||||
func (lx *lexer) peek() rune {
|
|
||||||
r := lx.next()
|
|
||||||
lx.backup()
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
|
|
||||||
// skip ignores all input that matches the given predicate.
|
|
||||||
func (lx *lexer) skip(pred func(rune) bool) {
|
|
||||||
for {
|
|
||||||
r := lx.next()
|
|
||||||
if pred(r) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
lx.backup()
|
|
||||||
lx.ignore()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// errorf stops all lexing by emitting an error and returning `nil`.
|
|
||||||
// Note that any value that is a character is escaped if it's a special
|
|
||||||
// character (newlines, tabs, etc.).
|
|
||||||
func (lx *lexer) errorf(format string, values ...interface{}) stateFn {
|
|
||||||
lx.items <- item{
|
|
||||||
itemError,
|
|
||||||
fmt.Sprintf(format, values...),
|
|
||||||
lx.line,
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexTop consumes elements at the top level of TOML data.
|
|
||||||
func lexTop(lx *lexer) stateFn {
|
|
||||||
r := lx.next()
|
|
||||||
if isWhitespace(r) || isNL(r) {
|
|
||||||
return lexSkip(lx, lexTop)
|
|
||||||
}
|
|
||||||
switch r {
|
|
||||||
case commentStart:
|
|
||||||
lx.push(lexTop)
|
|
||||||
return lexCommentStart
|
|
||||||
case tableStart:
|
|
||||||
return lexTableStart
|
|
||||||
case eof:
|
|
||||||
if lx.pos > lx.start {
|
|
||||||
return lx.errorf("unexpected EOF")
|
|
||||||
}
|
|
||||||
lx.emit(itemEOF)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// At this point, the only valid item can be a key, so we back up
|
|
||||||
// and let the key lexer do the rest.
|
|
||||||
lx.backup()
|
|
||||||
lx.push(lexTopEnd)
|
|
||||||
return lexKeyStart
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexTopEnd is entered whenever a top-level item has been consumed. (A value
|
|
||||||
// or a table.) It must see only whitespace, and will turn back to lexTop
|
|
||||||
// upon a newline. If it sees EOF, it will quit the lexer successfully.
|
|
||||||
func lexTopEnd(lx *lexer) stateFn {
|
|
||||||
r := lx.next()
|
|
||||||
switch {
|
|
||||||
case r == commentStart:
|
|
||||||
// a comment will read to a newline for us.
|
|
||||||
lx.push(lexTop)
|
|
||||||
return lexCommentStart
|
|
||||||
case isWhitespace(r):
|
|
||||||
return lexTopEnd
|
|
||||||
case isNL(r):
|
|
||||||
lx.ignore()
|
|
||||||
return lexTop
|
|
||||||
case r == eof:
|
|
||||||
lx.emit(itemEOF)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return lx.errorf("expected a top-level item to end with a newline, "+
|
|
||||||
"comment, or EOF, but got %q instead", r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexTable lexes the beginning of a table. Namely, it makes sure that
|
|
||||||
// it starts with a character other than '.' and ']'.
|
|
||||||
// It assumes that '[' has already been consumed.
|
|
||||||
// It also handles the case that this is an item in an array of tables.
|
|
||||||
// e.g., '[[name]]'.
|
|
||||||
func lexTableStart(lx *lexer) stateFn {
|
|
||||||
if lx.peek() == arrayTableStart {
|
|
||||||
lx.next()
|
|
||||||
lx.emit(itemArrayTableStart)
|
|
||||||
lx.push(lexArrayTableEnd)
|
|
||||||
} else {
|
|
||||||
lx.emit(itemTableStart)
|
|
||||||
lx.push(lexTableEnd)
|
|
||||||
}
|
|
||||||
return lexTableNameStart
|
|
||||||
}
|
|
||||||
|
|
||||||
func lexTableEnd(lx *lexer) stateFn {
|
|
||||||
lx.emit(itemTableEnd)
|
|
||||||
return lexTopEnd
|
|
||||||
}
|
|
||||||
|
|
||||||
func lexArrayTableEnd(lx *lexer) stateFn {
|
|
||||||
if r := lx.next(); r != arrayTableEnd {
|
|
||||||
return lx.errorf("expected end of table array name delimiter %q, "+
|
|
||||||
"but got %q instead", arrayTableEnd, r)
|
|
||||||
}
|
|
||||||
lx.emit(itemArrayTableEnd)
|
|
||||||
return lexTopEnd
|
|
||||||
}
|
|
||||||
|
|
||||||
func lexTableNameStart(lx *lexer) stateFn {
|
|
||||||
lx.skip(isWhitespace)
|
|
||||||
switch r := lx.peek(); {
|
|
||||||
case r == tableEnd || r == eof:
|
|
||||||
return lx.errorf("unexpected end of table name " +
|
|
||||||
"(table names cannot be empty)")
|
|
||||||
case r == tableSep:
|
|
||||||
return lx.errorf("unexpected table separator " +
|
|
||||||
"(table names cannot be empty)")
|
|
||||||
case r == stringStart || r == rawStringStart:
|
|
||||||
lx.ignore()
|
|
||||||
lx.push(lexTableNameEnd)
|
|
||||||
return lexValue // reuse string lexing
|
|
||||||
default:
|
|
||||||
return lexBareTableName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexBareTableName lexes the name of a table. It assumes that at least one
|
|
||||||
// valid character for the table has already been read.
|
|
||||||
func lexBareTableName(lx *lexer) stateFn {
|
|
||||||
r := lx.next()
|
|
||||||
if isBareKeyChar(r) {
|
|
||||||
return lexBareTableName
|
|
||||||
}
|
|
||||||
lx.backup()
|
|
||||||
lx.emit(itemText)
|
|
||||||
return lexTableNameEnd
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexTableNameEnd reads the end of a piece of a table name, optionally
|
|
||||||
// consuming whitespace.
|
|
||||||
func lexTableNameEnd(lx *lexer) stateFn {
|
|
||||||
lx.skip(isWhitespace)
|
|
||||||
switch r := lx.next(); {
|
|
||||||
case isWhitespace(r):
|
|
||||||
return lexTableNameEnd
|
|
||||||
case r == tableSep:
|
|
||||||
lx.ignore()
|
|
||||||
return lexTableNameStart
|
|
||||||
case r == tableEnd:
|
|
||||||
return lx.pop()
|
|
||||||
default:
|
|
||||||
return lx.errorf("expected '.' or ']' to end table name, "+
|
|
||||||
"but got %q instead", r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexKeyStart consumes a key name up until the first non-whitespace character.
|
|
||||||
// lexKeyStart will ignore whitespace.
|
|
||||||
func lexKeyStart(lx *lexer) stateFn {
|
|
||||||
r := lx.peek()
|
|
||||||
switch {
|
|
||||||
case r == keySep:
|
|
||||||
return lx.errorf("unexpected key separator %q", keySep)
|
|
||||||
case isWhitespace(r) || isNL(r):
|
|
||||||
lx.next()
|
|
||||||
return lexSkip(lx, lexKeyStart)
|
|
||||||
case r == stringStart || r == rawStringStart:
|
|
||||||
lx.ignore()
|
|
||||||
lx.emit(itemKeyStart)
|
|
||||||
lx.push(lexKeyEnd)
|
|
||||||
return lexValue // reuse string lexing
|
|
||||||
default:
|
|
||||||
lx.ignore()
|
|
||||||
lx.emit(itemKeyStart)
|
|
||||||
return lexBareKey
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexBareKey consumes the text of a bare key. Assumes that the first character
|
|
||||||
// (which is not whitespace) has not yet been consumed.
|
|
||||||
func lexBareKey(lx *lexer) stateFn {
|
|
||||||
switch r := lx.next(); {
|
|
||||||
case isBareKeyChar(r):
|
|
||||||
return lexBareKey
|
|
||||||
case isWhitespace(r):
|
|
||||||
lx.backup()
|
|
||||||
lx.emit(itemText)
|
|
||||||
return lexKeyEnd
|
|
||||||
case r == keySep:
|
|
||||||
lx.backup()
|
|
||||||
lx.emit(itemText)
|
|
||||||
return lexKeyEnd
|
|
||||||
default:
|
|
||||||
return lx.errorf("bare keys cannot contain %q", r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexKeyEnd consumes the end of a key and trims whitespace (up to the key
|
|
||||||
// separator).
|
|
||||||
func lexKeyEnd(lx *lexer) stateFn {
|
|
||||||
switch r := lx.next(); {
|
|
||||||
case r == keySep:
|
|
||||||
return lexSkip(lx, lexValue)
|
|
||||||
case isWhitespace(r):
|
|
||||||
return lexSkip(lx, lexKeyEnd)
|
|
||||||
default:
|
|
||||||
return lx.errorf("expected key separator %q, but got %q instead",
|
|
||||||
keySep, r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexValue starts the consumption of a value anywhere a value is expected.
|
|
||||||
// lexValue will ignore whitespace.
|
|
||||||
// After a value is lexed, the last state on the next is popped and returned.
|
|
||||||
func lexValue(lx *lexer) stateFn {
|
|
||||||
// We allow whitespace to precede a value, but NOT newlines.
|
|
||||||
// In array syntax, the array states are responsible for ignoring newlines.
|
|
||||||
r := lx.next()
|
|
||||||
switch {
|
|
||||||
case isWhitespace(r):
|
|
||||||
return lexSkip(lx, lexValue)
|
|
||||||
case isDigit(r):
|
|
||||||
lx.backup() // avoid an extra state and use the same as above
|
|
||||||
return lexNumberOrDateStart
|
|
||||||
}
|
|
||||||
switch r {
|
|
||||||
case arrayStart:
|
|
||||||
lx.ignore()
|
|
||||||
lx.emit(itemArray)
|
|
||||||
return lexArrayValue
|
|
||||||
case inlineTableStart:
|
|
||||||
lx.ignore()
|
|
||||||
lx.emit(itemInlineTableStart)
|
|
||||||
return lexInlineTableValue
|
|
||||||
case stringStart:
|
|
||||||
if lx.accept(stringStart) {
|
|
||||||
if lx.accept(stringStart) {
|
|
||||||
lx.ignore() // Ignore """
|
|
||||||
return lexMultilineString
|
|
||||||
}
|
|
||||||
lx.backup()
|
|
||||||
}
|
|
||||||
lx.ignore() // ignore the '"'
|
|
||||||
return lexString
|
|
||||||
case rawStringStart:
|
|
||||||
if lx.accept(rawStringStart) {
|
|
||||||
if lx.accept(rawStringStart) {
|
|
||||||
lx.ignore() // Ignore """
|
|
||||||
return lexMultilineRawString
|
|
||||||
}
|
|
||||||
lx.backup()
|
|
||||||
}
|
|
||||||
lx.ignore() // ignore the "'"
|
|
||||||
return lexRawString
|
|
||||||
case '+', '-':
|
|
||||||
return lexNumberStart
|
|
||||||
case '.': // special error case, be kind to users
|
|
||||||
return lx.errorf("floats must start with a digit, not '.'")
|
|
||||||
}
|
|
||||||
if unicode.IsLetter(r) {
|
|
||||||
// Be permissive here; lexBool will give a nice error if the
|
|
||||||
// user wrote something like
|
|
||||||
// x = foo
|
|
||||||
// (i.e. not 'true' or 'false' but is something else word-like.)
|
|
||||||
lx.backup()
|
|
||||||
return lexBool
|
|
||||||
}
|
|
||||||
return lx.errorf("expected value but found %q instead", r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexArrayValue consumes one value in an array. It assumes that '[' or ','
|
|
||||||
// have already been consumed. All whitespace and newlines are ignored.
|
|
||||||
func lexArrayValue(lx *lexer) stateFn {
|
|
||||||
r := lx.next()
|
|
||||||
switch {
|
|
||||||
case isWhitespace(r) || isNL(r):
|
|
||||||
return lexSkip(lx, lexArrayValue)
|
|
||||||
case r == commentStart:
|
|
||||||
lx.push(lexArrayValue)
|
|
||||||
return lexCommentStart
|
|
||||||
case r == comma:
|
|
||||||
return lx.errorf("unexpected comma")
|
|
||||||
case r == arrayEnd:
|
|
||||||
// NOTE(caleb): The spec isn't clear about whether you can have
|
|
||||||
// a trailing comma or not, so we'll allow it.
|
|
||||||
return lexArrayEnd
|
|
||||||
}
|
|
||||||
|
|
||||||
lx.backup()
|
|
||||||
lx.push(lexArrayValueEnd)
|
|
||||||
return lexValue
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexArrayValueEnd consumes everything between the end of an array value and
|
|
||||||
// the next value (or the end of the array): it ignores whitespace and newlines
|
|
||||||
// and expects either a ',' or a ']'.
|
|
||||||
func lexArrayValueEnd(lx *lexer) stateFn {
|
|
||||||
r := lx.next()
|
|
||||||
switch {
|
|
||||||
case isWhitespace(r) || isNL(r):
|
|
||||||
return lexSkip(lx, lexArrayValueEnd)
|
|
||||||
case r == commentStart:
|
|
||||||
lx.push(lexArrayValueEnd)
|
|
||||||
return lexCommentStart
|
|
||||||
case r == comma:
|
|
||||||
lx.ignore()
|
|
||||||
return lexArrayValue // move on to the next value
|
|
||||||
case r == arrayEnd:
|
|
||||||
return lexArrayEnd
|
|
||||||
}
|
|
||||||
return lx.errorf(
|
|
||||||
"expected a comma or array terminator %q, but got %q instead",
|
|
||||||
arrayEnd, r,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexArrayEnd finishes the lexing of an array.
|
|
||||||
// It assumes that a ']' has just been consumed.
|
|
||||||
func lexArrayEnd(lx *lexer) stateFn {
|
|
||||||
lx.ignore()
|
|
||||||
lx.emit(itemArrayEnd)
|
|
||||||
return lx.pop()
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexInlineTableValue consumes one key/value pair in an inline table.
|
|
||||||
// It assumes that '{' or ',' have already been consumed. Whitespace is ignored.
|
|
||||||
func lexInlineTableValue(lx *lexer) stateFn {
|
|
||||||
r := lx.next()
|
|
||||||
switch {
|
|
||||||
case isWhitespace(r):
|
|
||||||
return lexSkip(lx, lexInlineTableValue)
|
|
||||||
case isNL(r):
|
|
||||||
return lx.errorf("newlines not allowed within inline tables")
|
|
||||||
case r == commentStart:
|
|
||||||
lx.push(lexInlineTableValue)
|
|
||||||
return lexCommentStart
|
|
||||||
case r == comma:
|
|
||||||
return lx.errorf("unexpected comma")
|
|
||||||
case r == inlineTableEnd:
|
|
||||||
return lexInlineTableEnd
|
|
||||||
}
|
|
||||||
lx.backup()
|
|
||||||
lx.push(lexInlineTableValueEnd)
|
|
||||||
return lexKeyStart
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexInlineTableValueEnd consumes everything between the end of an inline table
|
|
||||||
// key/value pair and the next pair (or the end of the table):
|
|
||||||
// it ignores whitespace and expects either a ',' or a '}'.
|
|
||||||
func lexInlineTableValueEnd(lx *lexer) stateFn {
|
|
||||||
r := lx.next()
|
|
||||||
switch {
|
|
||||||
case isWhitespace(r):
|
|
||||||
return lexSkip(lx, lexInlineTableValueEnd)
|
|
||||||
case isNL(r):
|
|
||||||
return lx.errorf("newlines not allowed within inline tables")
|
|
||||||
case r == commentStart:
|
|
||||||
lx.push(lexInlineTableValueEnd)
|
|
||||||
return lexCommentStart
|
|
||||||
case r == comma:
|
|
||||||
lx.ignore()
|
|
||||||
return lexInlineTableValue
|
|
||||||
case r == inlineTableEnd:
|
|
||||||
return lexInlineTableEnd
|
|
||||||
}
|
|
||||||
return lx.errorf("expected a comma or an inline table terminator %q, "+
|
|
||||||
"but got %q instead", inlineTableEnd, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexInlineTableEnd finishes the lexing of an inline table.
|
|
||||||
// It assumes that a '}' has just been consumed.
|
|
||||||
func lexInlineTableEnd(lx *lexer) stateFn {
|
|
||||||
lx.ignore()
|
|
||||||
lx.emit(itemInlineTableEnd)
|
|
||||||
return lx.pop()
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexString consumes the inner contents of a string. It assumes that the
|
|
||||||
// beginning '"' has already been consumed and ignored.
|
|
||||||
func lexString(lx *lexer) stateFn {
|
|
||||||
r := lx.next()
|
|
||||||
switch {
|
|
||||||
case r == eof:
|
|
||||||
return lx.errorf("unexpected EOF")
|
|
||||||
case isNL(r):
|
|
||||||
return lx.errorf("strings cannot contain newlines")
|
|
||||||
case r == '\\':
|
|
||||||
lx.push(lexString)
|
|
||||||
return lexStringEscape
|
|
||||||
case r == stringEnd:
|
|
||||||
lx.backup()
|
|
||||||
lx.emit(itemString)
|
|
||||||
lx.next()
|
|
||||||
lx.ignore()
|
|
||||||
return lx.pop()
|
|
||||||
}
|
|
||||||
return lexString
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexMultilineString consumes the inner contents of a string. It assumes that
|
|
||||||
// the beginning '"""' has already been consumed and ignored.
|
|
||||||
func lexMultilineString(lx *lexer) stateFn {
|
|
||||||
switch lx.next() {
|
|
||||||
case eof:
|
|
||||||
return lx.errorf("unexpected EOF")
|
|
||||||
case '\\':
|
|
||||||
return lexMultilineStringEscape
|
|
||||||
case stringEnd:
|
|
||||||
if lx.accept(stringEnd) {
|
|
||||||
if lx.accept(stringEnd) {
|
|
||||||
lx.backup()
|
|
||||||
lx.backup()
|
|
||||||
lx.backup()
|
|
||||||
lx.emit(itemMultilineString)
|
|
||||||
lx.next()
|
|
||||||
lx.next()
|
|
||||||
lx.next()
|
|
||||||
lx.ignore()
|
|
||||||
return lx.pop()
|
|
||||||
}
|
|
||||||
lx.backup()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return lexMultilineString
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexRawString consumes a raw string. Nothing can be escaped in such a string.
|
|
||||||
// It assumes that the beginning "'" has already been consumed and ignored.
|
|
||||||
func lexRawString(lx *lexer) stateFn {
|
|
||||||
r := lx.next()
|
|
||||||
switch {
|
|
||||||
case r == eof:
|
|
||||||
return lx.errorf("unexpected EOF")
|
|
||||||
case isNL(r):
|
|
||||||
return lx.errorf("strings cannot contain newlines")
|
|
||||||
case r == rawStringEnd:
|
|
||||||
lx.backup()
|
|
||||||
lx.emit(itemRawString)
|
|
||||||
lx.next()
|
|
||||||
lx.ignore()
|
|
||||||
return lx.pop()
|
|
||||||
}
|
|
||||||
return lexRawString
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexMultilineRawString consumes a raw string. Nothing can be escaped in such
|
|
||||||
// a string. It assumes that the beginning "'''" has already been consumed and
|
|
||||||
// ignored.
|
|
||||||
func lexMultilineRawString(lx *lexer) stateFn {
|
|
||||||
switch lx.next() {
|
|
||||||
case eof:
|
|
||||||
return lx.errorf("unexpected EOF")
|
|
||||||
case rawStringEnd:
|
|
||||||
if lx.accept(rawStringEnd) {
|
|
||||||
if lx.accept(rawStringEnd) {
|
|
||||||
lx.backup()
|
|
||||||
lx.backup()
|
|
||||||
lx.backup()
|
|
||||||
lx.emit(itemRawMultilineString)
|
|
||||||
lx.next()
|
|
||||||
lx.next()
|
|
||||||
lx.next()
|
|
||||||
lx.ignore()
|
|
||||||
return lx.pop()
|
|
||||||
}
|
|
||||||
lx.backup()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return lexMultilineRawString
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexMultilineStringEscape consumes an escaped character. It assumes that the
|
|
||||||
// preceding '\\' has already been consumed.
|
|
||||||
func lexMultilineStringEscape(lx *lexer) stateFn {
|
|
||||||
// Handle the special case first:
|
|
||||||
if isNL(lx.next()) {
|
|
||||||
return lexMultilineString
|
|
||||||
}
|
|
||||||
lx.backup()
|
|
||||||
lx.push(lexMultilineString)
|
|
||||||
return lexStringEscape(lx)
|
|
||||||
}
|
|
||||||
|
|
||||||
func lexStringEscape(lx *lexer) stateFn {
|
|
||||||
r := lx.next()
|
|
||||||
switch r {
|
|
||||||
case 'b':
|
|
||||||
fallthrough
|
|
||||||
case 't':
|
|
||||||
fallthrough
|
|
||||||
case 'n':
|
|
||||||
fallthrough
|
|
||||||
case 'f':
|
|
||||||
fallthrough
|
|
||||||
case 'r':
|
|
||||||
fallthrough
|
|
||||||
case '"':
|
|
||||||
fallthrough
|
|
||||||
case '\\':
|
|
||||||
return lx.pop()
|
|
||||||
case 'u':
|
|
||||||
return lexShortUnicodeEscape
|
|
||||||
case 'U':
|
|
||||||
return lexLongUnicodeEscape
|
|
||||||
}
|
|
||||||
return lx.errorf("invalid escape character %q; only the following "+
|
|
||||||
"escape characters are allowed: "+
|
|
||||||
`\b, \t, \n, \f, \r, \", \\, \uXXXX, and \UXXXXXXXX`, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
func lexShortUnicodeEscape(lx *lexer) stateFn {
|
|
||||||
var r rune
|
|
||||||
for i := 0; i < 4; i++ {
|
|
||||||
r = lx.next()
|
|
||||||
if !isHexadecimal(r) {
|
|
||||||
return lx.errorf(`expected four hexadecimal digits after '\u', `+
|
|
||||||
"but got %q instead", lx.current())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return lx.pop()
|
|
||||||
}
|
|
||||||
|
|
||||||
func lexLongUnicodeEscape(lx *lexer) stateFn {
|
|
||||||
var r rune
|
|
||||||
for i := 0; i < 8; i++ {
|
|
||||||
r = lx.next()
|
|
||||||
if !isHexadecimal(r) {
|
|
||||||
return lx.errorf(`expected eight hexadecimal digits after '\U', `+
|
|
||||||
"but got %q instead", lx.current())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return lx.pop()
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexNumberOrDateStart consumes either an integer, a float, or datetime.
|
|
||||||
func lexNumberOrDateStart(lx *lexer) stateFn {
|
|
||||||
r := lx.next()
|
|
||||||
if isDigit(r) {
|
|
||||||
return lexNumberOrDate
|
|
||||||
}
|
|
||||||
switch r {
|
|
||||||
case '_':
|
|
||||||
return lexNumber
|
|
||||||
case 'e', 'E':
|
|
||||||
return lexFloat
|
|
||||||
case '.':
|
|
||||||
return lx.errorf("floats must start with a digit, not '.'")
|
|
||||||
}
|
|
||||||
return lx.errorf("expected a digit but got %q", r)
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexNumberOrDate consumes either an integer, float or datetime.
|
|
||||||
func lexNumberOrDate(lx *lexer) stateFn {
|
|
||||||
r := lx.next()
|
|
||||||
if isDigit(r) {
|
|
||||||
return lexNumberOrDate
|
|
||||||
}
|
|
||||||
switch r {
|
|
||||||
case '-':
|
|
||||||
return lexDatetime
|
|
||||||
case '_':
|
|
||||||
return lexNumber
|
|
||||||
case '.', 'e', 'E':
|
|
||||||
return lexFloat
|
|
||||||
}
|
|
||||||
|
|
||||||
lx.backup()
|
|
||||||
lx.emit(itemInteger)
|
|
||||||
return lx.pop()
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexDatetime consumes a Datetime, to a first approximation.
|
|
||||||
// The parser validates that it matches one of the accepted formats.
|
|
||||||
func lexDatetime(lx *lexer) stateFn {
|
|
||||||
r := lx.next()
|
|
||||||
if isDigit(r) {
|
|
||||||
return lexDatetime
|
|
||||||
}
|
|
||||||
switch r {
|
|
||||||
case '-', 'T', ':', '.', 'Z':
|
|
||||||
return lexDatetime
|
|
||||||
}
|
|
||||||
|
|
||||||
lx.backup()
|
|
||||||
lx.emit(itemDatetime)
|
|
||||||
return lx.pop()
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexNumberStart consumes either an integer or a float. It assumes that a sign
|
|
||||||
// has already been read, but that *no* digits have been consumed.
|
|
||||||
// lexNumberStart will move to the appropriate integer or float states.
|
|
||||||
func lexNumberStart(lx *lexer) stateFn {
|
|
||||||
// We MUST see a digit. Even floats have to start with a digit.
|
|
||||||
r := lx.next()
|
|
||||||
if !isDigit(r) {
|
|
||||||
if r == '.' {
|
|
||||||
return lx.errorf("floats must start with a digit, not '.'")
|
|
||||||
}
|
|
||||||
return lx.errorf("expected a digit but got %q", r)
|
|
||||||
}
|
|
||||||
return lexNumber
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexNumber consumes an integer or a float after seeing the first digit.
|
|
||||||
func lexNumber(lx *lexer) stateFn {
|
|
||||||
r := lx.next()
|
|
||||||
if isDigit(r) {
|
|
||||||
return lexNumber
|
|
||||||
}
|
|
||||||
switch r {
|
|
||||||
case '_':
|
|
||||||
return lexNumber
|
|
||||||
case '.', 'e', 'E':
|
|
||||||
return lexFloat
|
|
||||||
}
|
|
||||||
|
|
||||||
lx.backup()
|
|
||||||
lx.emit(itemInteger)
|
|
||||||
return lx.pop()
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexFloat consumes the elements of a float. It allows any sequence of
|
|
||||||
// float-like characters, so floats emitted by the lexer are only a first
|
|
||||||
// approximation and must be validated by the parser.
|
|
||||||
func lexFloat(lx *lexer) stateFn {
|
|
||||||
r := lx.next()
|
|
||||||
if isDigit(r) {
|
|
||||||
return lexFloat
|
|
||||||
}
|
|
||||||
switch r {
|
|
||||||
case '_', '.', '-', '+', 'e', 'E':
|
|
||||||
return lexFloat
|
|
||||||
}
|
|
||||||
|
|
||||||
lx.backup()
|
|
||||||
lx.emit(itemFloat)
|
|
||||||
return lx.pop()
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexBool consumes a bool string: 'true' or 'false.
|
|
||||||
func lexBool(lx *lexer) stateFn {
|
|
||||||
var rs []rune
|
|
||||||
for {
|
|
||||||
r := lx.next()
|
|
||||||
if !unicode.IsLetter(r) {
|
|
||||||
lx.backup()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
rs = append(rs, r)
|
|
||||||
}
|
|
||||||
s := string(rs)
|
|
||||||
switch s {
|
|
||||||
case "true", "false":
|
|
||||||
lx.emit(itemBool)
|
|
||||||
return lx.pop()
|
|
||||||
}
|
|
||||||
return lx.errorf("expected value but found %q instead", s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexCommentStart begins the lexing of a comment. It will emit
|
|
||||||
// itemCommentStart and consume no characters, passing control to lexComment.
|
|
||||||
func lexCommentStart(lx *lexer) stateFn {
|
|
||||||
lx.ignore()
|
|
||||||
lx.emit(itemCommentStart)
|
|
||||||
return lexComment
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexComment lexes an entire comment. It assumes that '#' has been consumed.
|
|
||||||
// It will consume *up to* the first newline character, and pass control
|
|
||||||
// back to the last state on the stack.
|
|
||||||
func lexComment(lx *lexer) stateFn {
|
|
||||||
r := lx.peek()
|
|
||||||
if isNL(r) || r == eof {
|
|
||||||
lx.emit(itemText)
|
|
||||||
return lx.pop()
|
|
||||||
}
|
|
||||||
lx.next()
|
|
||||||
return lexComment
|
|
||||||
}
|
|
||||||
|
|
||||||
// lexSkip ignores all slurped input and moves on to the next state.
|
|
||||||
func lexSkip(lx *lexer, nextState stateFn) stateFn {
|
|
||||||
return func(lx *lexer) stateFn {
|
|
||||||
lx.ignore()
|
|
||||||
return nextState
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// isWhitespace returns true if `r` is a whitespace character according
|
|
||||||
// to the spec.
|
|
||||||
func isWhitespace(r rune) bool {
|
|
||||||
return r == '\t' || r == ' '
|
|
||||||
}
|
|
||||||
|
|
||||||
func isNL(r rune) bool {
|
|
||||||
return r == '\n' || r == '\r'
|
|
||||||
}
|
|
||||||
|
|
||||||
func isDigit(r rune) bool {
|
|
||||||
return r >= '0' && r <= '9'
|
|
||||||
}
|
|
||||||
|
|
||||||
func isHexadecimal(r rune) bool {
|
|
||||||
return (r >= '0' && r <= '9') ||
|
|
||||||
(r >= 'a' && r <= 'f') ||
|
|
||||||
(r >= 'A' && r <= 'F')
|
|
||||||
}
|
|
||||||
|
|
||||||
func isBareKeyChar(r rune) bool {
|
|
||||||
return (r >= 'A' && r <= 'Z') ||
|
|
||||||
(r >= 'a' && r <= 'z') ||
|
|
||||||
(r >= '0' && r <= '9') ||
|
|
||||||
r == '_' ||
|
|
||||||
r == '-'
|
|
||||||
}
|
|
||||||
|
|
||||||
func (itype itemType) String() string {
|
|
||||||
switch itype {
|
|
||||||
case itemError:
|
|
||||||
return "Error"
|
|
||||||
case itemNIL:
|
|
||||||
return "NIL"
|
|
||||||
case itemEOF:
|
|
||||||
return "EOF"
|
|
||||||
case itemText:
|
|
||||||
return "Text"
|
|
||||||
case itemString, itemRawString, itemMultilineString, itemRawMultilineString:
|
|
||||||
return "String"
|
|
||||||
case itemBool:
|
|
||||||
return "Bool"
|
|
||||||
case itemInteger:
|
|
||||||
return "Integer"
|
|
||||||
case itemFloat:
|
|
||||||
return "Float"
|
|
||||||
case itemDatetime:
|
|
||||||
return "DateTime"
|
|
||||||
case itemTableStart:
|
|
||||||
return "TableStart"
|
|
||||||
case itemTableEnd:
|
|
||||||
return "TableEnd"
|
|
||||||
case itemKeyStart:
|
|
||||||
return "KeyStart"
|
|
||||||
case itemArray:
|
|
||||||
return "Array"
|
|
||||||
case itemArrayEnd:
|
|
||||||
return "ArrayEnd"
|
|
||||||
case itemCommentStart:
|
|
||||||
return "CommentStart"
|
|
||||||
}
|
|
||||||
panic(fmt.Sprintf("BUG: Unknown type '%d'.", int(itype)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (item item) String() string {
|
|
||||||
return fmt.Sprintf("(%s, %s)", item.typ.String(), item.val)
|
|
||||||
}
|
|
592
vendor/github.com/BurntSushi/toml/parse.go
generated
vendored
592
vendor/github.com/BurntSushi/toml/parse.go
generated
vendored
@ -1,592 +0,0 @@
|
|||||||
package toml
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
"unicode"
|
|
||||||
"unicode/utf8"
|
|
||||||
)
|
|
||||||
|
|
||||||
type parser struct {
|
|
||||||
mapping map[string]interface{}
|
|
||||||
types map[string]tomlType
|
|
||||||
lx *lexer
|
|
||||||
|
|
||||||
// A list of keys in the order that they appear in the TOML data.
|
|
||||||
ordered []Key
|
|
||||||
|
|
||||||
// the full key for the current hash in scope
|
|
||||||
context Key
|
|
||||||
|
|
||||||
// the base key name for everything except hashes
|
|
||||||
currentKey string
|
|
||||||
|
|
||||||
// rough approximation of line number
|
|
||||||
approxLine int
|
|
||||||
|
|
||||||
// A map of 'key.group.names' to whether they were created implicitly.
|
|
||||||
implicits map[string]bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type parseError string
|
|
||||||
|
|
||||||
func (pe parseError) Error() string {
|
|
||||||
return string(pe)
|
|
||||||
}
|
|
||||||
|
|
||||||
func parse(data string) (p *parser, err error) {
|
|
||||||
defer func() {
|
|
||||||
if r := recover(); r != nil {
|
|
||||||
var ok bool
|
|
||||||
if err, ok = r.(parseError); ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
panic(r)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
p = &parser{
|
|
||||||
mapping: make(map[string]interface{}),
|
|
||||||
types: make(map[string]tomlType),
|
|
||||||
lx: lex(data),
|
|
||||||
ordered: make([]Key, 0),
|
|
||||||
implicits: make(map[string]bool),
|
|
||||||
}
|
|
||||||
for {
|
|
||||||
item := p.next()
|
|
||||||
if item.typ == itemEOF {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
p.topLevel(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
return p, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parser) panicf(format string, v ...interface{}) {
|
|
||||||
msg := fmt.Sprintf("Near line %d (last key parsed '%s'): %s",
|
|
||||||
p.approxLine, p.current(), fmt.Sprintf(format, v...))
|
|
||||||
panic(parseError(msg))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parser) next() item {
|
|
||||||
it := p.lx.nextItem()
|
|
||||||
if it.typ == itemError {
|
|
||||||
p.panicf("%s", it.val)
|
|
||||||
}
|
|
||||||
return it
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parser) bug(format string, v ...interface{}) {
|
|
||||||
panic(fmt.Sprintf("BUG: "+format+"\n\n", v...))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parser) expect(typ itemType) item {
|
|
||||||
it := p.next()
|
|
||||||
p.assertEqual(typ, it.typ)
|
|
||||||
return it
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parser) assertEqual(expected, got itemType) {
|
|
||||||
if expected != got {
|
|
||||||
p.bug("Expected '%s' but got '%s'.", expected, got)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parser) topLevel(item item) {
|
|
||||||
switch item.typ {
|
|
||||||
case itemCommentStart:
|
|
||||||
p.approxLine = item.line
|
|
||||||
p.expect(itemText)
|
|
||||||
case itemTableStart:
|
|
||||||
kg := p.next()
|
|
||||||
p.approxLine = kg.line
|
|
||||||
|
|
||||||
var key Key
|
|
||||||
for ; kg.typ != itemTableEnd && kg.typ != itemEOF; kg = p.next() {
|
|
||||||
key = append(key, p.keyString(kg))
|
|
||||||
}
|
|
||||||
p.assertEqual(itemTableEnd, kg.typ)
|
|
||||||
|
|
||||||
p.establishContext(key, false)
|
|
||||||
p.setType("", tomlHash)
|
|
||||||
p.ordered = append(p.ordered, key)
|
|
||||||
case itemArrayTableStart:
|
|
||||||
kg := p.next()
|
|
||||||
p.approxLine = kg.line
|
|
||||||
|
|
||||||
var key Key
|
|
||||||
for ; kg.typ != itemArrayTableEnd && kg.typ != itemEOF; kg = p.next() {
|
|
||||||
key = append(key, p.keyString(kg))
|
|
||||||
}
|
|
||||||
p.assertEqual(itemArrayTableEnd, kg.typ)
|
|
||||||
|
|
||||||
p.establishContext(key, true)
|
|
||||||
p.setType("", tomlArrayHash)
|
|
||||||
p.ordered = append(p.ordered, key)
|
|
||||||
case itemKeyStart:
|
|
||||||
kname := p.next()
|
|
||||||
p.approxLine = kname.line
|
|
||||||
p.currentKey = p.keyString(kname)
|
|
||||||
|
|
||||||
val, typ := p.value(p.next())
|
|
||||||
p.setValue(p.currentKey, val)
|
|
||||||
p.setType(p.currentKey, typ)
|
|
||||||
p.ordered = append(p.ordered, p.context.add(p.currentKey))
|
|
||||||
p.currentKey = ""
|
|
||||||
default:
|
|
||||||
p.bug("Unexpected type at top level: %s", item.typ)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gets a string for a key (or part of a key in a table name).
|
|
||||||
func (p *parser) keyString(it item) string {
|
|
||||||
switch it.typ {
|
|
||||||
case itemText:
|
|
||||||
return it.val
|
|
||||||
case itemString, itemMultilineString,
|
|
||||||
itemRawString, itemRawMultilineString:
|
|
||||||
s, _ := p.value(it)
|
|
||||||
return s.(string)
|
|
||||||
default:
|
|
||||||
p.bug("Unexpected key type: %s", it.typ)
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// value translates an expected value from the lexer into a Go value wrapped
|
|
||||||
// as an empty interface.
|
|
||||||
func (p *parser) value(it item) (interface{}, tomlType) {
|
|
||||||
switch it.typ {
|
|
||||||
case itemString:
|
|
||||||
return p.replaceEscapes(it.val), p.typeOfPrimitive(it)
|
|
||||||
case itemMultilineString:
|
|
||||||
trimmed := stripFirstNewline(stripEscapedWhitespace(it.val))
|
|
||||||
return p.replaceEscapes(trimmed), p.typeOfPrimitive(it)
|
|
||||||
case itemRawString:
|
|
||||||
return it.val, p.typeOfPrimitive(it)
|
|
||||||
case itemRawMultilineString:
|
|
||||||
return stripFirstNewline(it.val), p.typeOfPrimitive(it)
|
|
||||||
case itemBool:
|
|
||||||
switch it.val {
|
|
||||||
case "true":
|
|
||||||
return true, p.typeOfPrimitive(it)
|
|
||||||
case "false":
|
|
||||||
return false, p.typeOfPrimitive(it)
|
|
||||||
}
|
|
||||||
p.bug("Expected boolean value, but got '%s'.", it.val)
|
|
||||||
case itemInteger:
|
|
||||||
if !numUnderscoresOK(it.val) {
|
|
||||||
p.panicf("Invalid integer %q: underscores must be surrounded by digits",
|
|
||||||
it.val)
|
|
||||||
}
|
|
||||||
val := strings.Replace(it.val, "_", "", -1)
|
|
||||||
num, err := strconv.ParseInt(val, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
// Distinguish integer values. Normally, it'd be a bug if the lexer
|
|
||||||
// provides an invalid integer, but it's possible that the number is
|
|
||||||
// out of range of valid values (which the lexer cannot determine).
|
|
||||||
// So mark the former as a bug but the latter as a legitimate user
|
|
||||||
// error.
|
|
||||||
if e, ok := err.(*strconv.NumError); ok &&
|
|
||||||
e.Err == strconv.ErrRange {
|
|
||||||
|
|
||||||
p.panicf("Integer '%s' is out of the range of 64-bit "+
|
|
||||||
"signed integers.", it.val)
|
|
||||||
} else {
|
|
||||||
p.bug("Expected integer value, but got '%s'.", it.val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return num, p.typeOfPrimitive(it)
|
|
||||||
case itemFloat:
|
|
||||||
parts := strings.FieldsFunc(it.val, func(r rune) bool {
|
|
||||||
switch r {
|
|
||||||
case '.', 'e', 'E':
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
for _, part := range parts {
|
|
||||||
if !numUnderscoresOK(part) {
|
|
||||||
p.panicf("Invalid float %q: underscores must be "+
|
|
||||||
"surrounded by digits", it.val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !numPeriodsOK(it.val) {
|
|
||||||
// As a special case, numbers like '123.' or '1.e2',
|
|
||||||
// which are valid as far as Go/strconv are concerned,
|
|
||||||
// must be rejected because TOML says that a fractional
|
|
||||||
// part consists of '.' followed by 1+ digits.
|
|
||||||
p.panicf("Invalid float %q: '.' must be followed "+
|
|
||||||
"by one or more digits", it.val)
|
|
||||||
}
|
|
||||||
val := strings.Replace(it.val, "_", "", -1)
|
|
||||||
num, err := strconv.ParseFloat(val, 64)
|
|
||||||
if err != nil {
|
|
||||||
if e, ok := err.(*strconv.NumError); ok &&
|
|
||||||
e.Err == strconv.ErrRange {
|
|
||||||
|
|
||||||
p.panicf("Float '%s' is out of the range of 64-bit "+
|
|
||||||
"IEEE-754 floating-point numbers.", it.val)
|
|
||||||
} else {
|
|
||||||
p.panicf("Invalid float value: %q", it.val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return num, p.typeOfPrimitive(it)
|
|
||||||
case itemDatetime:
|
|
||||||
var t time.Time
|
|
||||||
var ok bool
|
|
||||||
var err error
|
|
||||||
for _, format := range []string{
|
|
||||||
"2006-01-02T15:04:05Z07:00",
|
|
||||||
"2006-01-02T15:04:05",
|
|
||||||
"2006-01-02",
|
|
||||||
} {
|
|
||||||
t, err = time.ParseInLocation(format, it.val, time.Local)
|
|
||||||
if err == nil {
|
|
||||||
ok = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !ok {
|
|
||||||
p.panicf("Invalid TOML Datetime: %q.", it.val)
|
|
||||||
}
|
|
||||||
return t, p.typeOfPrimitive(it)
|
|
||||||
case itemArray:
|
|
||||||
array := make([]interface{}, 0)
|
|
||||||
types := make([]tomlType, 0)
|
|
||||||
|
|
||||||
for it = p.next(); it.typ != itemArrayEnd; it = p.next() {
|
|
||||||
if it.typ == itemCommentStart {
|
|
||||||
p.expect(itemText)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
val, typ := p.value(it)
|
|
||||||
array = append(array, val)
|
|
||||||
types = append(types, typ)
|
|
||||||
}
|
|
||||||
return array, p.typeOfArray(types)
|
|
||||||
case itemInlineTableStart:
|
|
||||||
var (
|
|
||||||
hash = make(map[string]interface{})
|
|
||||||
outerContext = p.context
|
|
||||||
outerKey = p.currentKey
|
|
||||||
)
|
|
||||||
|
|
||||||
p.context = append(p.context, p.currentKey)
|
|
||||||
p.currentKey = ""
|
|
||||||
for it := p.next(); it.typ != itemInlineTableEnd; it = p.next() {
|
|
||||||
if it.typ != itemKeyStart {
|
|
||||||
p.bug("Expected key start but instead found %q, around line %d",
|
|
||||||
it.val, p.approxLine)
|
|
||||||
}
|
|
||||||
if it.typ == itemCommentStart {
|
|
||||||
p.expect(itemText)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// retrieve key
|
|
||||||
k := p.next()
|
|
||||||
p.approxLine = k.line
|
|
||||||
kname := p.keyString(k)
|
|
||||||
|
|
||||||
// retrieve value
|
|
||||||
p.currentKey = kname
|
|
||||||
val, typ := p.value(p.next())
|
|
||||||
// make sure we keep metadata up to date
|
|
||||||
p.setType(kname, typ)
|
|
||||||
p.ordered = append(p.ordered, p.context.add(p.currentKey))
|
|
||||||
hash[kname] = val
|
|
||||||
}
|
|
||||||
p.context = outerContext
|
|
||||||
p.currentKey = outerKey
|
|
||||||
return hash, tomlHash
|
|
||||||
}
|
|
||||||
p.bug("Unexpected value type: %s", it.typ)
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
|
|
||||||
// numUnderscoresOK checks whether each underscore in s is surrounded by
|
|
||||||
// characters that are not underscores.
|
|
||||||
func numUnderscoresOK(s string) bool {
|
|
||||||
accept := false
|
|
||||||
for _, r := range s {
|
|
||||||
if r == '_' {
|
|
||||||
if !accept {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
accept = false
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
accept = true
|
|
||||||
}
|
|
||||||
return accept
|
|
||||||
}
|
|
||||||
|
|
||||||
// numPeriodsOK checks whether every period in s is followed by a digit.
|
|
||||||
func numPeriodsOK(s string) bool {
|
|
||||||
period := false
|
|
||||||
for _, r := range s {
|
|
||||||
if period && !isDigit(r) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
period = r == '.'
|
|
||||||
}
|
|
||||||
return !period
|
|
||||||
}
|
|
||||||
|
|
||||||
// establishContext sets the current context of the parser,
|
|
||||||
// where the context is either a hash or an array of hashes. Which one is
|
|
||||||
// set depends on the value of the `array` parameter.
|
|
||||||
//
|
|
||||||
// Establishing the context also makes sure that the key isn't a duplicate, and
|
|
||||||
// will create implicit hashes automatically.
|
|
||||||
func (p *parser) establishContext(key Key, array bool) {
|
|
||||||
var ok bool
|
|
||||||
|
|
||||||
// Always start at the top level and drill down for our context.
|
|
||||||
hashContext := p.mapping
|
|
||||||
keyContext := make(Key, 0)
|
|
||||||
|
|
||||||
// We only need implicit hashes for key[0:-1]
|
|
||||||
for _, k := range key[0 : len(key)-1] {
|
|
||||||
_, ok = hashContext[k]
|
|
||||||
keyContext = append(keyContext, k)
|
|
||||||
|
|
||||||
// No key? Make an implicit hash and move on.
|
|
||||||
if !ok {
|
|
||||||
p.addImplicit(keyContext)
|
|
||||||
hashContext[k] = make(map[string]interface{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the hash context is actually an array of tables, then set
|
|
||||||
// the hash context to the last element in that array.
|
|
||||||
//
|
|
||||||
// Otherwise, it better be a table, since this MUST be a key group (by
|
|
||||||
// virtue of it not being the last element in a key).
|
|
||||||
switch t := hashContext[k].(type) {
|
|
||||||
case []map[string]interface{}:
|
|
||||||
hashContext = t[len(t)-1]
|
|
||||||
case map[string]interface{}:
|
|
||||||
hashContext = t
|
|
||||||
default:
|
|
||||||
p.panicf("Key '%s' was already created as a hash.", keyContext)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p.context = keyContext
|
|
||||||
if array {
|
|
||||||
// If this is the first element for this array, then allocate a new
|
|
||||||
// list of tables for it.
|
|
||||||
k := key[len(key)-1]
|
|
||||||
if _, ok := hashContext[k]; !ok {
|
|
||||||
hashContext[k] = make([]map[string]interface{}, 0, 5)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add a new table. But make sure the key hasn't already been used
|
|
||||||
// for something else.
|
|
||||||
if hash, ok := hashContext[k].([]map[string]interface{}); ok {
|
|
||||||
hashContext[k] = append(hash, make(map[string]interface{}))
|
|
||||||
} else {
|
|
||||||
p.panicf("Key '%s' was already created and cannot be used as "+
|
|
||||||
"an array.", keyContext)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
p.setValue(key[len(key)-1], make(map[string]interface{}))
|
|
||||||
}
|
|
||||||
p.context = append(p.context, key[len(key)-1])
|
|
||||||
}
|
|
||||||
|
|
||||||
// setValue sets the given key to the given value in the current context.
|
|
||||||
// It will make sure that the key hasn't already been defined, account for
|
|
||||||
// implicit key groups.
|
|
||||||
func (p *parser) setValue(key string, value interface{}) {
|
|
||||||
var tmpHash interface{}
|
|
||||||
var ok bool
|
|
||||||
|
|
||||||
hash := p.mapping
|
|
||||||
keyContext := make(Key, 0)
|
|
||||||
for _, k := range p.context {
|
|
||||||
keyContext = append(keyContext, k)
|
|
||||||
if tmpHash, ok = hash[k]; !ok {
|
|
||||||
p.bug("Context for key '%s' has not been established.", keyContext)
|
|
||||||
}
|
|
||||||
switch t := tmpHash.(type) {
|
|
||||||
case []map[string]interface{}:
|
|
||||||
// The context is a table of hashes. Pick the most recent table
|
|
||||||
// defined as the current hash.
|
|
||||||
hash = t[len(t)-1]
|
|
||||||
case map[string]interface{}:
|
|
||||||
hash = t
|
|
||||||
default:
|
|
||||||
p.bug("Expected hash to have type 'map[string]interface{}', but "+
|
|
||||||
"it has '%T' instead.", tmpHash)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
keyContext = append(keyContext, key)
|
|
||||||
|
|
||||||
if _, ok := hash[key]; ok {
|
|
||||||
// Typically, if the given key has already been set, then we have
|
|
||||||
// to raise an error since duplicate keys are disallowed. However,
|
|
||||||
// it's possible that a key was previously defined implicitly. In this
|
|
||||||
// case, it is allowed to be redefined concretely. (See the
|
|
||||||
// `tests/valid/implicit-and-explicit-after.toml` test in `toml-test`.)
|
|
||||||
//
|
|
||||||
// But we have to make sure to stop marking it as an implicit. (So that
|
|
||||||
// another redefinition provokes an error.)
|
|
||||||
//
|
|
||||||
// Note that since it has already been defined (as a hash), we don't
|
|
||||||
// want to overwrite it. So our business is done.
|
|
||||||
if p.isImplicit(keyContext) {
|
|
||||||
p.removeImplicit(keyContext)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, we have a concrete key trying to override a previous
|
|
||||||
// key, which is *always* wrong.
|
|
||||||
p.panicf("Key '%s' has already been defined.", keyContext)
|
|
||||||
}
|
|
||||||
hash[key] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
// setType sets the type of a particular value at a given key.
|
|
||||||
// It should be called immediately AFTER setValue.
|
|
||||||
//
|
|
||||||
// Note that if `key` is empty, then the type given will be applied to the
|
|
||||||
// current context (which is either a table or an array of tables).
|
|
||||||
func (p *parser) setType(key string, typ tomlType) {
|
|
||||||
keyContext := make(Key, 0, len(p.context)+1)
|
|
||||||
for _, k := range p.context {
|
|
||||||
keyContext = append(keyContext, k)
|
|
||||||
}
|
|
||||||
if len(key) > 0 { // allow type setting for hashes
|
|
||||||
keyContext = append(keyContext, key)
|
|
||||||
}
|
|
||||||
p.types[keyContext.String()] = typ
|
|
||||||
}
|
|
||||||
|
|
||||||
// addImplicit sets the given Key as having been created implicitly.
|
|
||||||
func (p *parser) addImplicit(key Key) {
|
|
||||||
p.implicits[key.String()] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// removeImplicit stops tagging the given key as having been implicitly
|
|
||||||
// created.
|
|
||||||
func (p *parser) removeImplicit(key Key) {
|
|
||||||
p.implicits[key.String()] = false
|
|
||||||
}
|
|
||||||
|
|
||||||
// isImplicit returns true if the key group pointed to by the key was created
|
|
||||||
// implicitly.
|
|
||||||
func (p *parser) isImplicit(key Key) bool {
|
|
||||||
return p.implicits[key.String()]
|
|
||||||
}
|
|
||||||
|
|
||||||
// current returns the full key name of the current context.
|
|
||||||
func (p *parser) current() string {
|
|
||||||
if len(p.currentKey) == 0 {
|
|
||||||
return p.context.String()
|
|
||||||
}
|
|
||||||
if len(p.context) == 0 {
|
|
||||||
return p.currentKey
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s.%s", p.context, p.currentKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
func stripFirstNewline(s string) string {
|
|
||||||
if len(s) == 0 || s[0] != '\n' {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
return s[1:]
|
|
||||||
}
|
|
||||||
|
|
||||||
func stripEscapedWhitespace(s string) string {
|
|
||||||
esc := strings.Split(s, "\\\n")
|
|
||||||
if len(esc) > 1 {
|
|
||||||
for i := 1; i < len(esc); i++ {
|
|
||||||
esc[i] = strings.TrimLeftFunc(esc[i], unicode.IsSpace)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return strings.Join(esc, "")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parser) replaceEscapes(str string) string {
|
|
||||||
var replaced []rune
|
|
||||||
s := []byte(str)
|
|
||||||
r := 0
|
|
||||||
for r < len(s) {
|
|
||||||
if s[r] != '\\' {
|
|
||||||
c, size := utf8.DecodeRune(s[r:])
|
|
||||||
r += size
|
|
||||||
replaced = append(replaced, c)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
r += 1
|
|
||||||
if r >= len(s) {
|
|
||||||
p.bug("Escape sequence at end of string.")
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
switch s[r] {
|
|
||||||
default:
|
|
||||||
p.bug("Expected valid escape code after \\, but got %q.", s[r])
|
|
||||||
return ""
|
|
||||||
case 'b':
|
|
||||||
replaced = append(replaced, rune(0x0008))
|
|
||||||
r += 1
|
|
||||||
case 't':
|
|
||||||
replaced = append(replaced, rune(0x0009))
|
|
||||||
r += 1
|
|
||||||
case 'n':
|
|
||||||
replaced = append(replaced, rune(0x000A))
|
|
||||||
r += 1
|
|
||||||
case 'f':
|
|
||||||
replaced = append(replaced, rune(0x000C))
|
|
||||||
r += 1
|
|
||||||
case 'r':
|
|
||||||
replaced = append(replaced, rune(0x000D))
|
|
||||||
r += 1
|
|
||||||
case '"':
|
|
||||||
replaced = append(replaced, rune(0x0022))
|
|
||||||
r += 1
|
|
||||||
case '\\':
|
|
||||||
replaced = append(replaced, rune(0x005C))
|
|
||||||
r += 1
|
|
||||||
case 'u':
|
|
||||||
// At this point, we know we have a Unicode escape of the form
|
|
||||||
// `uXXXX` at [r, r+5). (Because the lexer guarantees this
|
|
||||||
// for us.)
|
|
||||||
escaped := p.asciiEscapeToUnicode(s[r+1 : r+5])
|
|
||||||
replaced = append(replaced, escaped)
|
|
||||||
r += 5
|
|
||||||
case 'U':
|
|
||||||
// At this point, we know we have a Unicode escape of the form
|
|
||||||
// `uXXXX` at [r, r+9). (Because the lexer guarantees this
|
|
||||||
// for us.)
|
|
||||||
escaped := p.asciiEscapeToUnicode(s[r+1 : r+9])
|
|
||||||
replaced = append(replaced, escaped)
|
|
||||||
r += 9
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return string(replaced)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parser) asciiEscapeToUnicode(bs []byte) rune {
|
|
||||||
s := string(bs)
|
|
||||||
hex, err := strconv.ParseUint(strings.ToLower(s), 16, 32)
|
|
||||||
if err != nil {
|
|
||||||
p.bug("Could not parse '%s' as a hexadecimal number, but the "+
|
|
||||||
"lexer claims it's OK: %s", s, err)
|
|
||||||
}
|
|
||||||
if !utf8.ValidRune(rune(hex)) {
|
|
||||||
p.panicf("Escaped character '\\u%s' is not valid UTF-8.", s)
|
|
||||||
}
|
|
||||||
return rune(hex)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isStringType(ty itemType) bool {
|
|
||||||
return ty == itemString || ty == itemMultilineString ||
|
|
||||||
ty == itemRawString || ty == itemRawMultilineString
|
|
||||||
}
|
|
1
vendor/github.com/BurntSushi/toml/session.vim
generated
vendored
1
vendor/github.com/BurntSushi/toml/session.vim
generated
vendored
@ -1 +0,0 @@
|
|||||||
au BufWritePost *.go silent!make tags > /dev/null 2>&1
|
|
91
vendor/github.com/BurntSushi/toml/type_check.go
generated
vendored
91
vendor/github.com/BurntSushi/toml/type_check.go
generated
vendored
@ -1,91 +0,0 @@
|
|||||||
package toml
|
|
||||||
|
|
||||||
// tomlType represents any Go type that corresponds to a TOML type.
|
|
||||||
// While the first draft of the TOML spec has a simplistic type system that
|
|
||||||
// probably doesn't need this level of sophistication, we seem to be militating
|
|
||||||
// toward adding real composite types.
|
|
||||||
type tomlType interface {
|
|
||||||
typeString() string
|
|
||||||
}
|
|
||||||
|
|
||||||
// typeEqual accepts any two types and returns true if they are equal.
|
|
||||||
func typeEqual(t1, t2 tomlType) bool {
|
|
||||||
if t1 == nil || t2 == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return t1.typeString() == t2.typeString()
|
|
||||||
}
|
|
||||||
|
|
||||||
func typeIsHash(t tomlType) bool {
|
|
||||||
return typeEqual(t, tomlHash) || typeEqual(t, tomlArrayHash)
|
|
||||||
}
|
|
||||||
|
|
||||||
type tomlBaseType string
|
|
||||||
|
|
||||||
func (btype tomlBaseType) typeString() string {
|
|
||||||
return string(btype)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (btype tomlBaseType) String() string {
|
|
||||||
return btype.typeString()
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
tomlInteger tomlBaseType = "Integer"
|
|
||||||
tomlFloat tomlBaseType = "Float"
|
|
||||||
tomlDatetime tomlBaseType = "Datetime"
|
|
||||||
tomlString tomlBaseType = "String"
|
|
||||||
tomlBool tomlBaseType = "Bool"
|
|
||||||
tomlArray tomlBaseType = "Array"
|
|
||||||
tomlHash tomlBaseType = "Hash"
|
|
||||||
tomlArrayHash tomlBaseType = "ArrayHash"
|
|
||||||
)
|
|
||||||
|
|
||||||
// typeOfPrimitive returns a tomlType of any primitive value in TOML.
|
|
||||||
// Primitive values are: Integer, Float, Datetime, String and Bool.
|
|
||||||
//
|
|
||||||
// Passing a lexer item other than the following will cause a BUG message
|
|
||||||
// to occur: itemString, itemBool, itemInteger, itemFloat, itemDatetime.
|
|
||||||
func (p *parser) typeOfPrimitive(lexItem item) tomlType {
|
|
||||||
switch lexItem.typ {
|
|
||||||
case itemInteger:
|
|
||||||
return tomlInteger
|
|
||||||
case itemFloat:
|
|
||||||
return tomlFloat
|
|
||||||
case itemDatetime:
|
|
||||||
return tomlDatetime
|
|
||||||
case itemString:
|
|
||||||
return tomlString
|
|
||||||
case itemMultilineString:
|
|
||||||
return tomlString
|
|
||||||
case itemRawString:
|
|
||||||
return tomlString
|
|
||||||
case itemRawMultilineString:
|
|
||||||
return tomlString
|
|
||||||
case itemBool:
|
|
||||||
return tomlBool
|
|
||||||
}
|
|
||||||
p.bug("Cannot infer primitive type of lex item '%s'.", lexItem)
|
|
||||||
panic("unreachable")
|
|
||||||
}
|
|
||||||
|
|
||||||
// typeOfArray returns a tomlType for an array given a list of types of its
|
|
||||||
// values.
|
|
||||||
//
|
|
||||||
// In the current spec, if an array is homogeneous, then its type is always
|
|
||||||
// "Array". If the array is not homogeneous, an error is generated.
|
|
||||||
func (p *parser) typeOfArray(types []tomlType) tomlType {
|
|
||||||
// Empty arrays are cool.
|
|
||||||
if len(types) == 0 {
|
|
||||||
return tomlArray
|
|
||||||
}
|
|
||||||
|
|
||||||
theType := types[0]
|
|
||||||
for _, t := range types[1:] {
|
|
||||||
if !typeEqual(theType, t) {
|
|
||||||
p.panicf("Array contains values of type '%s' and '%s', but "+
|
|
||||||
"arrays must be homogeneous.", theType, t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return tomlArray
|
|
||||||
}
|
|
242
vendor/github.com/BurntSushi/toml/type_fields.go
generated
vendored
242
vendor/github.com/BurntSushi/toml/type_fields.go
generated
vendored
@ -1,242 +0,0 @@
|
|||||||
package toml
|
|
||||||
|
|
||||||
// Struct field handling is adapted from code in encoding/json:
|
|
||||||
//
|
|
||||||
// Copyright 2010 The Go Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style
|
|
||||||
// license that can be found in the Go distribution.
|
|
||||||
|
|
||||||
import (
|
|
||||||
"reflect"
|
|
||||||
"sort"
|
|
||||||
"sync"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A field represents a single field found in a struct.
|
|
||||||
type field struct {
|
|
||||||
name string // the name of the field (`toml` tag included)
|
|
||||||
tag bool // whether field has a `toml` tag
|
|
||||||
index []int // represents the depth of an anonymous field
|
|
||||||
typ reflect.Type // the type of the field
|
|
||||||
}
|
|
||||||
|
|
||||||
// byName sorts field by name, breaking ties with depth,
|
|
||||||
// then breaking ties with "name came from toml tag", then
|
|
||||||
// breaking ties with index sequence.
|
|
||||||
type byName []field
|
|
||||||
|
|
||||||
func (x byName) Len() int { return len(x) }
|
|
||||||
|
|
||||||
func (x byName) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
|
||||||
|
|
||||||
func (x byName) Less(i, j int) bool {
|
|
||||||
if x[i].name != x[j].name {
|
|
||||||
return x[i].name < x[j].name
|
|
||||||
}
|
|
||||||
if len(x[i].index) != len(x[j].index) {
|
|
||||||
return len(x[i].index) < len(x[j].index)
|
|
||||||
}
|
|
||||||
if x[i].tag != x[j].tag {
|
|
||||||
return x[i].tag
|
|
||||||
}
|
|
||||||
return byIndex(x).Less(i, j)
|
|
||||||
}
|
|
||||||
|
|
||||||
// byIndex sorts field by index sequence.
|
|
||||||
type byIndex []field
|
|
||||||
|
|
||||||
func (x byIndex) Len() int { return len(x) }
|
|
||||||
|
|
||||||
func (x byIndex) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
|
||||||
|
|
||||||
func (x byIndex) Less(i, j int) bool {
|
|
||||||
for k, xik := range x[i].index {
|
|
||||||
if k >= len(x[j].index) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if xik != x[j].index[k] {
|
|
||||||
return xik < x[j].index[k]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return len(x[i].index) < len(x[j].index)
|
|
||||||
}
|
|
||||||
|
|
||||||
// typeFields returns a list of fields that TOML should recognize for the given
|
|
||||||
// type. The algorithm is breadth-first search over the set of structs to
|
|
||||||
// include - the top struct and then any reachable anonymous structs.
|
|
||||||
func typeFields(t reflect.Type) []field {
|
|
||||||
// Anonymous fields to explore at the current level and the next.
|
|
||||||
current := []field{}
|
|
||||||
next := []field{{typ: t}}
|
|
||||||
|
|
||||||
// Count of queued names for current level and the next.
|
|
||||||
count := map[reflect.Type]int{}
|
|
||||||
nextCount := map[reflect.Type]int{}
|
|
||||||
|
|
||||||
// Types already visited at an earlier level.
|
|
||||||
visited := map[reflect.Type]bool{}
|
|
||||||
|
|
||||||
// Fields found.
|
|
||||||
var fields []field
|
|
||||||
|
|
||||||
for len(next) > 0 {
|
|
||||||
current, next = next, current[:0]
|
|
||||||
count, nextCount = nextCount, map[reflect.Type]int{}
|
|
||||||
|
|
||||||
for _, f := range current {
|
|
||||||
if visited[f.typ] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
visited[f.typ] = true
|
|
||||||
|
|
||||||
// Scan f.typ for fields to include.
|
|
||||||
for i := 0; i < f.typ.NumField(); i++ {
|
|
||||||
sf := f.typ.Field(i)
|
|
||||||
if sf.PkgPath != "" && !sf.Anonymous { // unexported
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
opts := getOptions(sf.Tag)
|
|
||||||
if opts.skip {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
index := make([]int, len(f.index)+1)
|
|
||||||
copy(index, f.index)
|
|
||||||
index[len(f.index)] = i
|
|
||||||
|
|
||||||
ft := sf.Type
|
|
||||||
if ft.Name() == "" && ft.Kind() == reflect.Ptr {
|
|
||||||
// Follow pointer.
|
|
||||||
ft = ft.Elem()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Record found field and index sequence.
|
|
||||||
if opts.name != "" || !sf.Anonymous || ft.Kind() != reflect.Struct {
|
|
||||||
tagged := opts.name != ""
|
|
||||||
name := opts.name
|
|
||||||
if name == "" {
|
|
||||||
name = sf.Name
|
|
||||||
}
|
|
||||||
fields = append(fields, field{name, tagged, index, ft})
|
|
||||||
if count[f.typ] > 1 {
|
|
||||||
// If there were multiple instances, add a second,
|
|
||||||
// so that the annihilation code will see a duplicate.
|
|
||||||
// It only cares about the distinction between 1 or 2,
|
|
||||||
// so don't bother generating any more copies.
|
|
||||||
fields = append(fields, fields[len(fields)-1])
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Record new anonymous struct to explore in next round.
|
|
||||||
nextCount[ft]++
|
|
||||||
if nextCount[ft] == 1 {
|
|
||||||
f := field{name: ft.Name(), index: index, typ: ft}
|
|
||||||
next = append(next, f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Sort(byName(fields))
|
|
||||||
|
|
||||||
// Delete all fields that are hidden by the Go rules for embedded fields,
|
|
||||||
// except that fields with TOML tags are promoted.
|
|
||||||
|
|
||||||
// The fields are sorted in primary order of name, secondary order
|
|
||||||
// of field index length. Loop over names; for each name, delete
|
|
||||||
// hidden fields by choosing the one dominant field that survives.
|
|
||||||
out := fields[:0]
|
|
||||||
for advance, i := 0, 0; i < len(fields); i += advance {
|
|
||||||
// One iteration per name.
|
|
||||||
// Find the sequence of fields with the name of this first field.
|
|
||||||
fi := fields[i]
|
|
||||||
name := fi.name
|
|
||||||
for advance = 1; i+advance < len(fields); advance++ {
|
|
||||||
fj := fields[i+advance]
|
|
||||||
if fj.name != name {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if advance == 1 { // Only one field with this name
|
|
||||||
out = append(out, fi)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
dominant, ok := dominantField(fields[i : i+advance])
|
|
||||||
if ok {
|
|
||||||
out = append(out, dominant)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fields = out
|
|
||||||
sort.Sort(byIndex(fields))
|
|
||||||
|
|
||||||
return fields
|
|
||||||
}
|
|
||||||
|
|
||||||
// dominantField looks through the fields, all of which are known to
|
|
||||||
// have the same name, to find the single field that dominates the
|
|
||||||
// others using Go's embedding rules, modified by the presence of
|
|
||||||
// TOML tags. If there are multiple top-level fields, the boolean
|
|
||||||
// will be false: This condition is an error in Go and we skip all
|
|
||||||
// the fields.
|
|
||||||
func dominantField(fields []field) (field, bool) {
|
|
||||||
// The fields are sorted in increasing index-length order. The winner
|
|
||||||
// must therefore be one with the shortest index length. Drop all
|
|
||||||
// longer entries, which is easy: just truncate the slice.
|
|
||||||
length := len(fields[0].index)
|
|
||||||
tagged := -1 // Index of first tagged field.
|
|
||||||
for i, f := range fields {
|
|
||||||
if len(f.index) > length {
|
|
||||||
fields = fields[:i]
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if f.tag {
|
|
||||||
if tagged >= 0 {
|
|
||||||
// Multiple tagged fields at the same level: conflict.
|
|
||||||
// Return no field.
|
|
||||||
return field{}, false
|
|
||||||
}
|
|
||||||
tagged = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if tagged >= 0 {
|
|
||||||
return fields[tagged], true
|
|
||||||
}
|
|
||||||
// All remaining fields have the same length. If there's more than one,
|
|
||||||
// we have a conflict (two fields named "X" at the same level) and we
|
|
||||||
// return no field.
|
|
||||||
if len(fields) > 1 {
|
|
||||||
return field{}, false
|
|
||||||
}
|
|
||||||
return fields[0], true
|
|
||||||
}
|
|
||||||
|
|
||||||
var fieldCache struct {
|
|
||||||
sync.RWMutex
|
|
||||||
m map[reflect.Type][]field
|
|
||||||
}
|
|
||||||
|
|
||||||
// cachedTypeFields is like typeFields but uses a cache to avoid repeated work.
|
|
||||||
func cachedTypeFields(t reflect.Type) []field {
|
|
||||||
fieldCache.RLock()
|
|
||||||
f := fieldCache.m[t]
|
|
||||||
fieldCache.RUnlock()
|
|
||||||
if f != nil {
|
|
||||||
return f
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute fields without lock.
|
|
||||||
// Might duplicate effort but won't hold other computations back.
|
|
||||||
f = typeFields(t)
|
|
||||||
if f == nil {
|
|
||||||
f = []field{}
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldCache.Lock()
|
|
||||||
if fieldCache.m == nil {
|
|
||||||
fieldCache.m = map[reflect.Type][]field{}
|
|
||||||
}
|
|
||||||
fieldCache.m[t] = f
|
|
||||||
fieldCache.Unlock()
|
|
||||||
return f
|
|
||||||
}
|
|
1
vendor/github.com/neelance/graphql-go/.gitignore
generated
vendored
1
vendor/github.com/neelance/graphql-go/.gitignore
generated
vendored
@ -1 +0,0 @@
|
|||||||
/internal/tests/testdata/graphql-js
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user