cmd/geth: add --config file flag

This commit is contained in:
Felix Lange 2017-04-04 02:09:10 +02:00
parent 5077f024d5
commit 5185203854
30 changed files with 6171 additions and 487 deletions

130
cmd/geth/config.go Normal file
View file

@ -0,0 +1,130 @@
package main
import (
"bufio"
"encoding/hex"
"errors"
"os"
cli "gopkg.in/urfave/cli.v1"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/contracts/release"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/params"
"github.com/naoina/toml"
)
var (
dumpConfigCommand = cli.Command{
Action: dumpConfig,
Name: "dumpconfig",
Usage: "Show configuration values",
ArgsUsage: "",
Category: "MISCELLANEOUS COMMANDS",
Description: `The dumpconfig command shows configuration values.`,
}
configFileFlag = cli.StringFlag{
Name: "config",
Usage: "TOML configuration file",
}
)
var defaultNodeConfig = node.Config{
Name: clientIdentifier,
Version: params.VersionWithCommit(gitCommit),
DataDir: node.DefaultDataDir(),
P2P: p2p.Config{
MaxPeers: 25,
Discovery: true,
},
HTTPModules: []string{"eth", "net", "web3"},
WSModules: []string{"eth", "net", "web3"},
}
type gethConfig struct {
Eth eth.Config
Node node.Config
}
func loadConfig(file string, cfg *gethConfig) error {
f, err := os.Open(file)
if err != nil {
return err
}
defer f.Close()
err = toml.NewDecoder(bufio.NewReader(f)).Decode(cfg)
// Add file name to errors that have a line number.
if _, ok := err.(*toml.LineError); ok {
err = errors.New(file + ", " + err.Error())
}
return err
}
func makeConfigNode(ctx *cli.Context) (*node.Node, gethConfig) {
// Load defaults.
cfg := gethConfig{
Eth: eth.DefaultConfig,
Node: defaultNodeConfig,
}
// Load config file.
if file := ctx.GlobalString(configFileFlag.Name); file != "" {
if err := loadConfig(file, &cfg); err != nil {
utils.Fatalf("%v", err)
}
}
// Apply flags.
utils.SetNodeConfig(ctx, clientIdentifier, gitCommit, &cfg.Node)
stack, err := node.New(&cfg.Node)
if err != nil {
utils.Fatalf("Failed to create the protocol stack: %v", err)
}
utils.SetEthConfig(ctx, stack, &cfg.Eth)
return stack, cfg
}
func makeFullNode(ctx *cli.Context) *node.Node {
stack, cfg := makeConfigNode(ctx)
utils.RegisterEthService(stack, &cfg.Eth)
// Whisper must be explicitly enabled, but is auto-enabled in --dev mode.
shhEnabled := ctx.GlobalBool(utils.WhisperEnabledFlag.Name)
shhAutoEnabled := !ctx.GlobalIsSet(utils.WhisperEnabledFlag.Name) && ctx.GlobalIsSet(utils.DevModeFlag.Name)
if shhEnabled || shhAutoEnabled {
utils.RegisterShhService(stack)
}
// Add the Ethereum Stats daemon if requested
if url := ctx.GlobalString(utils.EthStatsURLFlag.Name); url != "" {
utils.RegisterEthStatsService(stack, url)
}
// Add the release oracle service so it boots along with node.
if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
config := release.Config{
Oracle: relOracle,
Major: uint32(params.VersionMajor),
Minor: uint32(params.VersionMinor),
Patch: uint32(params.VersionPatch),
}
commit, _ := hex.DecodeString(gitCommit)
copy(config.Commit[:], commit)
return release.NewReleaseService(ctx, config)
}); err != nil {
utils.Fatalf("Failed to register the Geth release oracle service: %v", err)
}
return stack
}
// dumpConfig is the dumpconfig command.
func dumpConfig(ctx *cli.Context) error {
_, cfg := makeConfigNode(ctx)
out, err := toml.Marshal(&cfg)
if err != nil {
return err
}
os.Stdout.Write(out)
return nil
}

View file

@ -22,14 +22,12 @@ import (
"os"
"path/filepath"
"runtime"
"sort"
"strconv"
"strings"
"testing"
"time"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
)
// Tests that a node embedded within a console can be started up properly and
@ -49,11 +47,6 @@ func TestConsoleWelcome(t *testing.T) {
geth.setTemplateFunc("gover", runtime.Version)
geth.setTemplateFunc("gethver", func() string { return params.Version })
geth.setTemplateFunc("niltime", func() string { return time.Unix(0, 0).Format(time.RFC1123) })
geth.setTemplateFunc("apis", func() []string {
apis := append(strings.Split(rpc.DefaultIPCApis, ","), rpc.MetadataApi)
sort.Strings(apis)
return apis
})
// Verify the actual welcome message to the required template
geth.expect(`
@ -63,7 +56,7 @@ instance: Geth/v{{gethver}}/{{goos}}-{{goarch}}/{{gover}}
coinbase: {{.Etherbase}}
at block: 0 ({{niltime}})
datadir: {{.Datadir}}
modules:{{range apis}} {{.}}:1.0{{end}}
modules: admin:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 shh:1.0 txpool:1.0 web3:1.0
> {{.InputLine "exit"}}
`)
@ -89,7 +82,7 @@ func TestIPCAttachWelcome(t *testing.T) {
"--etherbase", coinbase, "--shh", "--ipcpath", ipc)
time.Sleep(2 * time.Second) // Simple way to wait for the RPC endpoint to open
testAttachWelcome(t, geth, "ipc:"+ipc)
testAttachWelcome(t, geth, "ipc:"+ipc, "admin:1.0 debug:1.0 eth:1.0 miner:1.0 net:1.0 personal:1.0 rpc:1.0 shh:1.0 txpool:1.0 web3:1.0")
geth.interrupt()
geth.expectExit()
@ -103,7 +96,7 @@ func TestHTTPAttachWelcome(t *testing.T) {
"--etherbase", coinbase, "--rpc", "--rpcport", port)
time.Sleep(2 * time.Second) // Simple way to wait for the RPC endpoint to open
testAttachWelcome(t, geth, "http://localhost:"+port)
testAttachWelcome(t, geth, "http://localhost:"+port, "eth:1.0 net:1.0 rpc:1.0 web3:1.0")
geth.interrupt()
geth.expectExit()
@ -118,13 +111,13 @@ func TestWSAttachWelcome(t *testing.T) {
"--etherbase", coinbase, "--ws", "--wsport", port)
time.Sleep(2 * time.Second) // Simple way to wait for the RPC endpoint to open
testAttachWelcome(t, geth, "ws://localhost:"+port)
testAttachWelcome(t, geth, "ws://localhost:"+port, "eth:1.0 net:1.0 rpc:1.0 web3:1.0")
geth.interrupt()
geth.expectExit()
}
func testAttachWelcome(t *testing.T, geth *testgeth, endpoint string) {
func testAttachWelcome(t *testing.T, geth *testgeth, endpoint, apis string) {
// Attach to a running geth note and terminate immediately
attach := runGeth(t, "attach", endpoint)
defer attach.expectExit()
@ -139,16 +132,7 @@ func testAttachWelcome(t *testing.T, geth *testgeth, endpoint string) {
attach.setTemplateFunc("niltime", func() string { return time.Unix(0, 0).Format(time.RFC1123) })
attach.setTemplateFunc("ipc", func() bool { return strings.HasPrefix(endpoint, "ipc") })
attach.setTemplateFunc("datadir", func() string { return geth.Datadir })
attach.setTemplateFunc("apis", func() []string {
var apis []string
if strings.HasPrefix(endpoint, "ipc") {
apis = append(strings.Split(rpc.DefaultIPCApis, ","), rpc.MetadataApi)
} else {
apis = append(strings.Split(rpc.DefaultHTTPApis, ","), rpc.MetadataApi)
}
sort.Strings(apis)
return apis
})
attach.setTemplateFunc("apis", func() string { return apis })
// Verify the actual welcome message to the required template
attach.expect(`
@ -158,7 +142,7 @@ instance: Geth/v{{gethver}}/{{goos}}-{{goarch}}/{{gover}}
coinbase: {{etherbase}}
at block: 0 ({{niltime}}){{if ipc}}
datadir: {{datadir}}{{end}}
modules:{{range apis}} {{.}}:1.0{{end}}
modules: {{apis}}
> {{.InputLine "exit" }}
`)

View file

@ -84,27 +84,24 @@ var daoGenesisForkBlock = big.NewInt(314)
// set in the database after various initialization procedures and invocations.
func TestDAOForkBlockNewChain(t *testing.T) {
for i, arg := range []struct {
testnet bool
genesis string
expectBlock *big.Int
expectVote bool
}{
// Test DAO Default Mainnet
{false, "", params.MainNetDAOForkBlock, true},
// test DAO Default Testnet
{true, "", params.TestNetDAOForkBlock, true},
{"", params.MainNetDAOForkBlock, true},
// test DAO Init Old Privnet
{false, daoOldGenesis, nil, false},
{daoOldGenesis, nil, false},
// test DAO Default No Fork Privnet
{false, daoNoForkGenesis, daoGenesisForkBlock, false},
{daoNoForkGenesis, daoGenesisForkBlock, false},
// test DAO Default Pro Fork Privnet
{false, daoProForkGenesis, daoGenesisForkBlock, true},
{daoProForkGenesis, daoGenesisForkBlock, true},
} {
testDAOForkBlockNewChain(t, i, arg.testnet, arg.genesis, arg.expectBlock, arg.expectVote)
testDAOForkBlockNewChain(t, i, arg.genesis, arg.expectBlock, arg.expectVote)
}
}
func testDAOForkBlockNewChain(t *testing.T, test int, testnet bool, genesis string, expectBlock *big.Int, expectVote bool) {
func testDAOForkBlockNewChain(t *testing.T, test int, genesis string, expectBlock *big.Int, expectVote bool) {
// Create a temporary data directory to use and inspect later
datadir := tmpdir(t)
defer os.RemoveAll(datadir)
@ -119,17 +116,11 @@ func testDAOForkBlockNewChain(t *testing.T, test int, testnet bool, genesis stri
} else {
// Force chain initialization
args := []string{"--port", "0", "--maxpeers", "0", "--nodiscover", "--nat", "none", "--ipcdisable", "--datadir", datadir}
if testnet {
args = append(args, "--testnet")
}
geth := runGeth(t, append(args, []string{"--exec", "2+2", "console"}...)...)
geth.cmd.Wait()
}
// Retrieve the DAO config flag from the database
path := filepath.Join(datadir, "geth", "chaindata")
if testnet && genesis == "" {
path = filepath.Join(datadir, "testnet", "geth", "chaindata")
}
db, err := ethdb.NewLDBDatabase(path, 0, 0)
if err != nil {
t.Fatalf("test %d: failed to open test database: %v", test, err)
@ -137,9 +128,6 @@ func testDAOForkBlockNewChain(t *testing.T, test int, testnet bool, genesis stri
defer db.Close()
genesisHash := common.HexToHash("0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3")
if testnet {
genesisHash = common.HexToHash("0x41941023680923e0fe4d74a34bdac8141f2540e3ae90623718e47d66d1ca4a2d")
}
if genesis != "" {
genesisHash = daoGenesisHash
}

View file

@ -18,7 +18,6 @@
package main
import (
"encoding/hex"
"fmt"
"os"
"runtime"
@ -29,17 +28,13 @@ import (
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/cmd/utils"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/console"
"github.com/ethereum/go-ethereum/contracts/release"
"github.com/ethereum/go-ethereum/eth"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/internal/debug"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"gopkg.in/urfave/cli.v1"
)
@ -82,6 +77,8 @@ func init() {
versionCommand,
bugCommand,
licenseCommand,
// See config.go
dumpConfigCommand,
}
app.Flags = []cli.Flag{
@ -150,6 +147,7 @@ func init() {
utils.GpoBlocksFlag,
utils.GpoPercentileFlag,
utils.ExtraDataFlag,
configFileFlag,
}
app.Flags = append(app.Flags, debug.Flags...)
@ -189,52 +187,6 @@ func geth(ctx *cli.Context) error {
return nil
}
func makeFullNode(ctx *cli.Context) *node.Node {
// Create the default extradata and construct the base node
var clientInfo = struct {
Version uint
Name string
GoVersion string
Os string
}{uint(params.VersionMajor<<16 | params.VersionMinor<<8 | params.VersionPatch), clientIdentifier, runtime.Version(), runtime.GOOS}
extra, err := rlp.EncodeToBytes(clientInfo)
if err != nil {
log.Warn("Failed to set canonical miner information", "err", err)
}
if uint64(len(extra)) > params.MaximumExtraDataSize {
log.Warn("Miner extra data exceed limit", "extra", hexutil.Bytes(extra), "limit", params.MaximumExtraDataSize)
extra = nil
}
stack := utils.MakeNode(ctx, clientIdentifier, gitCommit)
utils.RegisterEthService(ctx, stack, extra)
// Whisper must be explicitly enabled, but is auto-enabled in --dev mode.
shhEnabled := ctx.GlobalBool(utils.WhisperEnabledFlag.Name)
shhAutoEnabled := !ctx.GlobalIsSet(utils.WhisperEnabledFlag.Name) && ctx.GlobalIsSet(utils.DevModeFlag.Name)
if shhEnabled || shhAutoEnabled {
utils.RegisterShhService(stack)
}
// Add the Ethereum Stats daemon if requested
if url := ctx.GlobalString(utils.EthStatsURLFlag.Name); url != "" {
utils.RegisterEthStatsService(stack, url)
}
// Add the release oracle service so it boots along with node.
if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
config := release.Config{
Oracle: relOracle,
Major: uint32(params.VersionMajor),
Minor: uint32(params.VersionMinor),
Patch: uint32(params.VersionPatch),
}
commit, _ := hex.DecodeString(gitCommit)
copy(config.Commit[:], commit)
return release.NewReleaseService(ctx, config)
}); err != nil {
utils.Fatalf("Failed to register the Geth release oracle service: %v", err)
}
return stack
}
// startNode boots up the system node and all registered protocols, after which
// it unlocks any requested accounts, and starts the RPC/IPC interfaces and the
// miner.

View file

@ -23,7 +23,6 @@ import (
"io/ioutil"
"math/big"
"os"
"os/user"
"path/filepath"
"runtime"
"strconv"
@ -45,12 +44,12 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/metrics"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/p2p/discv5"
"github.com/ethereum/go-ethereum/p2p/nat"
"github.com/ethereum/go-ethereum/p2p/netutil"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
whisper "github.com/ethereum/go-ethereum/whisper/whisperv2"
"gopkg.in/urfave/cli.v1"
)
@ -121,31 +120,32 @@ var (
EthashCachesInMemoryFlag = cli.IntFlag{
Name: "ethash.cachesinmem",
Usage: "Number of recent ethash caches to keep in memory (16MB each)",
Value: 2,
Value: eth.DefaultConfig.EthashCachesInMem,
}
EthashCachesOnDiskFlag = cli.IntFlag{
Name: "ethash.cachesondisk",
Usage: "Number of recent ethash caches to keep on disk (16MB each)",
Value: 3,
Value: eth.DefaultConfig.EthashCachesOnDisk,
}
EthashDatasetDirFlag = DirectoryFlag{
Name: "ethash.dagdir",
Usage: "Directory to store the ethash mining DAGs (default = inside home folder)",
Value: DirectoryString{eth.DefaultConfig.EthashDatasetDir},
}
EthashDatasetsInMemoryFlag = cli.IntFlag{
Name: "ethash.dagsinmem",
Usage: "Number of recent ethash mining DAGs to keep in memory (1+GB each)",
Value: 1,
Value: eth.DefaultConfig.EthashDatasetsInMem,
}
EthashDatasetsOnDiskFlag = cli.IntFlag{
Name: "ethash.dagsondisk",
Usage: "Number of recent ethash mining DAGs to keep on disk (1+GB each)",
Value: 2,
Value: eth.DefaultConfig.EthashDatasetsOnDisk,
}
NetworkIdFlag = cli.IntFlag{
Name: "networkid",
Usage: "Network identifier (integer, 1=Frontier, 2=Morden (disused), 3=Ropsten)",
Value: eth.NetworkId,
Value: eth.DefaultConfig.NetworkId,
}
TestNetFlag = cli.BoolFlag{
Name: "testnet",
@ -295,7 +295,7 @@ var (
RPCApiFlag = cli.StringFlag{
Name: "rpcapi",
Usage: "API's offered over the HTTP-RPC interface",
Value: rpc.DefaultHTTPApis,
Value: "",
}
IPCDisabledFlag = cli.BoolFlag{
Name: "ipcdisable",
@ -304,7 +304,6 @@ var (
IPCApiFlag = cli.StringFlag{
Name: "ipcapi",
Usage: "APIs offered over the IPC-RPC interface",
Value: rpc.DefaultIPCApis,
}
IPCPathFlag = DirectoryFlag{
Name: "ipcpath",
@ -328,7 +327,7 @@ var (
WSApiFlag = cli.StringFlag{
Name: "wsapi",
Usage: "API's offered over the WS-RPC interface",
Value: rpc.DefaultHTTPApis,
Value: "",
}
WSAllowedOriginsFlag = cli.StringFlag{
Name: "wsorigins",
@ -412,12 +411,12 @@ var (
GpoBlocksFlag = cli.IntFlag{
Name: "gpoblocks",
Usage: "Number of recent blocks to check for gas prices",
Value: 10,
Value: eth.DefaultConfig.GpoBlocks,
}
GpoPercentileFlag = cli.IntFlag{
Name: "gpopercentile",
Usage: "Suggested gas price is the given percentile of a set of recent transaction gas prices",
Value: 50,
Value: eth.DefaultConfig.GpoPercentile,
}
)
@ -436,88 +435,47 @@ func MakeDataDir(ctx *cli.Context) string {
return ""
}
// MakeEthashCacheDir returns the directory to use for storing the ethash cache
// dumps.
func MakeEthashCacheDir(ctx *cli.Context) string {
if ctx.GlobalIsSet(EthashCacheDirFlag.Name) && ctx.GlobalString(EthashCacheDirFlag.Name) == "" {
return ""
}
if !ctx.GlobalIsSet(EthashCacheDirFlag.Name) {
return "ethash"
}
return ctx.GlobalString(EthashCacheDirFlag.Name)
}
// MakeEthashDatasetDir returns the directory to use for storing the full ethash
// dataset dumps.
func MakeEthashDatasetDir(ctx *cli.Context) string {
if !ctx.GlobalIsSet(EthashDatasetDirFlag.Name) {
home := os.Getenv("HOME")
if home == "" {
if user, err := user.Current(); err == nil {
home = user.HomeDir
}
}
if runtime.GOOS == "windows" {
return filepath.Join(home, "AppData", "Ethash")
}
return filepath.Join(home, ".ethash")
}
return ctx.GlobalString(EthashDatasetDirFlag.Name)
}
// MakeIPCPath creates an IPC path configuration from the set command line flags,
// returning an empty string if IPC was explicitly disabled, or the set path.
func MakeIPCPath(ctx *cli.Context) string {
if ctx.GlobalBool(IPCDisabledFlag.Name) {
return ""
}
return ctx.GlobalString(IPCPathFlag.Name)
}
// MakeNodeKey creates a node key from set command line flags, either loading it
// setNodeKey creates a node key from set command line flags, either loading it
// from a file or as a specified hex value. If neither flags were provided, this
// method returns nil and an emphemeral key is to be generated.
func MakeNodeKey(ctx *cli.Context) *ecdsa.PrivateKey {
func setNodeKey(ctx *cli.Context, cfg *p2p.Config) {
var (
hex = ctx.GlobalString(NodeKeyHexFlag.Name)
file = ctx.GlobalString(NodeKeyFileFlag.Name)
key *ecdsa.PrivateKey
err error
key *ecdsa.PrivateKey
err error
)
switch {
case file != "" && hex != "":
Fatalf("Options %q and %q are mutually exclusive", NodeKeyFileFlag.Name, NodeKeyHexFlag.Name)
case file != "":
if key, err = crypto.LoadECDSA(file); err != nil {
Fatalf("Option %q: %v", NodeKeyFileFlag.Name, err)
}
cfg.PrivateKey = key
case hex != "":
if key, err = crypto.HexToECDSA(hex); err != nil {
Fatalf("Option %q: %v", NodeKeyHexFlag.Name, err)
}
cfg.PrivateKey = key
}
return key
}
// makeNodeUserIdent creates the user identifier from CLI flags.
func makeNodeUserIdent(ctx *cli.Context) string {
var comps []string
// setNodeUserIdent creates the user identifier from CLI flags.
func setNodeUserIdent(ctx *cli.Context, cfg *node.Config) {
comps := strings.Split(cfg.UserIdent, "/")
if identity := ctx.GlobalString(IdentityFlag.Name); len(identity) > 0 {
comps = append(comps, identity)
}
if ctx.GlobalBool(VMEnableJitFlag.Name) {
comps = append(comps, "JIT")
}
return strings.Join(comps, "/")
cfg.UserIdent = strings.Join(comps, "/")
}
// MakeBootstrapNodes creates a list of bootstrap nodes from the command line
// setBootstrapNodes creates a list of bootstrap nodes from the command line
// flags, reverting to pre-configured ones if none have been specified.
func MakeBootstrapNodes(ctx *cli.Context) []*discover.Node {
func setBootstrapNodes(ctx *cli.Context, cfg *p2p.Config) {
urls := params.MainnetBootnodes
if ctx.GlobalIsSet(BootnodesFlag.Name) {
urls = strings.Split(ctx.GlobalString(BootnodesFlag.Name), ",")
@ -525,62 +483,68 @@ func MakeBootstrapNodes(ctx *cli.Context) []*discover.Node {
urls = params.TestnetBootnodes
}
bootnodes := make([]*discover.Node, 0, len(urls))
cfg.BootstrapNodes = make([]*discover.Node, 0, len(urls))
for _, url := range urls {
node, err := discover.ParseNode(url)
if err != nil {
log.Error("Bootstrap URL invalid", "enode", url, "err", err)
continue
}
bootnodes = append(bootnodes, node)
cfg.BootstrapNodes = append(cfg.BootstrapNodes, node)
}
return bootnodes
}
// MakeBootstrapNodesV5 creates a list of bootstrap nodes from the command line
// setBootstrapNodesV5 creates a list of bootstrap nodes from the command line
// flags, reverting to pre-configured ones if none have been specified.
func MakeBootstrapNodesV5(ctx *cli.Context) []*discv5.Node {
func setBootstrapNodesV5(ctx *cli.Context, cfg *p2p.Config) {
urls := params.DiscoveryV5Bootnodes
if ctx.GlobalIsSet(BootnodesFlag.Name) {
urls = strings.Split(ctx.GlobalString(BootnodesFlag.Name), ",")
} else if cfg.BootstrapNodesV5 == nil {
return // already set, don't apply defaults.
}
bootnodes := make([]*discv5.Node, 0, len(urls))
cfg.BootstrapNodesV5 = make([]*discv5.Node, 0, len(urls))
for _, url := range urls {
node, err := discv5.ParseNode(url)
if err != nil {
log.Error("Bootstrap URL invalid", "enode", url, "err", err)
continue
}
bootnodes = append(bootnodes, node)
cfg.BootstrapNodesV5 = append(cfg.BootstrapNodesV5, node)
}
return bootnodes
}
// MakeListenAddress creates a TCP listening address string from set command
// setListenAddress creates a TCP listening address string from set command
// line flags.
func MakeListenAddress(ctx *cli.Context) string {
return fmt.Sprintf(":%d", ctx.GlobalInt(ListenPortFlag.Name))
}
// MakeDiscoveryV5Address creates a UDP listening address string from set command
// line flags for the V5 discovery protocol.
func MakeDiscoveryV5Address(ctx *cli.Context) string {
return fmt.Sprintf(":%d", ctx.GlobalInt(ListenPortFlag.Name)+1)
}
// MakeNAT creates a port mapper from set command line flags.
func MakeNAT(ctx *cli.Context) nat.Interface {
natif, err := nat.Parse(ctx.GlobalString(NATFlag.Name))
if err != nil {
Fatalf("Option %s: %v", NATFlag.Name, err)
func setListenAddress(ctx *cli.Context, cfg *p2p.Config) {
if ctx.GlobalIsSet(ListenPortFlag.Name) {
cfg.ListenAddr = fmt.Sprintf(":%d", ctx.GlobalInt(ListenPortFlag.Name))
}
return natif
}
// MakeRPCModules splits input separated by a comma and trims excessive white
// setDiscoveryV5Address creates a UDP listening address string from set command
// line flags for the V5 discovery protocol.
func setDiscoveryV5Address(ctx *cli.Context, cfg *p2p.Config) {
if ctx.GlobalIsSet(ListenPortFlag.Name) {
cfg.DiscoveryV5Addr = fmt.Sprintf(":%d", ctx.GlobalInt(ListenPortFlag.Name)+1)
}
}
// setNAT creates a port mapper from command line flags.
func setNAT(ctx *cli.Context, cfg *p2p.Config) {
if ctx.GlobalIsSet(NATFlag.Name) {
natif, err := nat.Parse(ctx.GlobalString(NATFlag.Name))
if err != nil {
Fatalf("Option %s: %v", NATFlag.Name, err)
}
cfg.NAT = natif
}
}
// makeRPCModules splits input separated by a comma and trims excessive white
// space from the substrings.
func MakeRPCModules(input string) []string {
func makeRPCModules(input string) []string {
result := strings.Split(input, ",")
for i, r := range result {
result[i] = strings.TrimSpace(r)
@ -588,27 +552,62 @@ func MakeRPCModules(input string) []string {
return result
}
// MakeHTTPRpcHost creates the HTTP RPC listener interface string from the set
// setHTTP creates the HTTP RPC listener interface string from the set
// command line flags, returning empty if the HTTP endpoint is disabled.
func MakeHTTPRpcHost(ctx *cli.Context) string {
if !ctx.GlobalBool(RPCEnabledFlag.Name) {
return ""
func setHTTP(ctx *cli.Context, cfg *node.Config) {
if ctx.GlobalBool(RPCEnabledFlag.Name) && cfg.HTTPHost == "" {
cfg.HTTPHost = "127.0.0.1"
}
if ctx.GlobalIsSet(RPCListenAddrFlag.Name) {
cfg.HTTPHost = ctx.GlobalString(RPCListenAddrFlag.Name)
}
if ctx.GlobalIsSet(RPCPortFlag.Name) {
cfg.HTTPPort = ctx.GlobalInt(RPCPortFlag.Name)
}
if ctx.GlobalIsSet(RPCCORSDomainFlag.Name) {
cfg.HTTPCors = ctx.GlobalString(RPCCORSDomainFlag.Name)
}
if ctx.GlobalIsSet(RPCApiFlag.Name) {
cfg.HTTPModules = makeRPCModules(ctx.GlobalString(RPCApiFlag.Name))
}
return ctx.GlobalString(RPCListenAddrFlag.Name)
}
// MakeWSRpcHost creates the WebSocket RPC listener interface string from the set
// setWS creates the WebSocket RPC listener interface string from the set
// command line flags, returning empty if the HTTP endpoint is disabled.
func MakeWSRpcHost(ctx *cli.Context) string {
if !ctx.GlobalBool(WSEnabledFlag.Name) {
return ""
func setWS(ctx *cli.Context, cfg *node.Config) {
if ctx.GlobalBool(WSEnabledFlag.Name) && cfg.WSHost == "" {
cfg.WSHost = "127.0.0.1"
}
if ctx.GlobalIsSet(WSListenAddrFlag.Name) {
cfg.WSHost = ctx.GlobalString(WSListenAddrFlag.Name)
}
if ctx.GlobalIsSet(WSPortFlag.Name) {
cfg.WSPort = ctx.GlobalInt(WSPortFlag.Name)
}
if ctx.GlobalIsSet(WSAllowedOriginsFlag.Name) {
cfg.WSOrigins = ctx.GlobalString(WSAllowedOriginsFlag.Name)
}
if ctx.GlobalIsSet(WSApiFlag.Name) {
cfg.WSModules = makeRPCModules(ctx.GlobalString(WSApiFlag.Name))
}
return ctx.GlobalString(WSListenAddrFlag.Name)
}
// MakeDatabaseHandles raises out the number of allowed file handles per process
// setIPC creates an IPC path configuration from the set command line flags,
// returning an empty string if IPC was explicitly disabled, or the set path.
func setIPC(ctx *cli.Context, cfg *node.Config) {
switch {
case ctx.GlobalBool(IPCDisabledFlag.Name):
cfg.IPCPath = ""
case ctx.GlobalIsSet(IPCPathFlag.Name):
cfg.IPCPath = ctx.GlobalString(IPCPathFlag.Name)
}
}
// makeDatabaseHandles raises out the number of allowed file handles per process
// for Geth and returns half of the allowance to assign to the database.
func MakeDatabaseHandles() int {
func makeDatabaseHandles() int {
if err := raiseFdLimit(2048); err != nil {
Fatalf("Failed to raise file descriptor allowance: %v", err)
}
@ -641,33 +640,25 @@ func MakeAddress(ks *keystore.KeyStore, account string) (accounts.Account, error
return accs[index], nil
}
// MakeEtherbase retrieves the etherbase either from the directly specified
// setEtherbase retrieves the etherbase either from the directly specified
// command line flags or from the keystore if CLI indexed.
func MakeEtherbase(ks *keystore.KeyStore, ctx *cli.Context) common.Address {
func setEtherbase(ctx *cli.Context, ks *keystore.KeyStore, cfg *eth.Config) {
if ctx.GlobalIsSet(EtherbaseFlag.Name) {
account, err := MakeAddress(ks, ctx.GlobalString(EtherbaseFlag.Name))
if err != nil {
Fatalf("Option %q: %v", EtherbaseFlag.Name, err)
}
cfg.Etherbase = account.Address
return
}
accounts := ks.Accounts()
if !ctx.GlobalIsSet(EtherbaseFlag.Name) && len(accounts) == 0 {
log.Warn("No etherbase set and no accounts found as default")
return common.Address{}
if (cfg.Etherbase == common.Address{}) {
if len(accounts) > 0 {
cfg.Etherbase = accounts[0].Address
} else {
log.Warn("No etherbase set and no accounts found as default")
}
}
etherbase := ctx.GlobalString(EtherbaseFlag.Name)
if etherbase == "" {
return common.Address{}
}
// If the specified etherbase is a valid address, return it
account, err := MakeAddress(ks, etherbase)
if err != nil {
Fatalf("Option %q: %v", EtherbaseFlag.Name, err)
}
return account.Address
}
// MakeMinerExtra resolves extradata for the miner from the set command line flags
// or returns a default one composed on the client, runtime and OS metadata.
func MakeMinerExtra(extra []byte, ctx *cli.Context) []byte {
if ctx.GlobalIsSet(ExtraDataFlag.Name) {
return []byte(ctx.GlobalString(ExtraDataFlag.Name))
}
return extra
}
// MakePasswordList reads password lines from the file specified by --password.
@ -688,70 +679,120 @@ func MakePasswordList(ctx *cli.Context) []string {
return lines
}
// MakeNode configures a node with no services from command line flags.
func MakeNode(ctx *cli.Context, name, gitCommit string) *node.Node {
vsn := params.Version
if gitCommit != "" {
vsn += "-" + gitCommit[:8]
func SetP2PConfig(ctx *cli.Context, cfg *p2p.Config) {
setNodeKey(ctx, cfg)
setNAT(ctx, cfg)
setListenAddress(ctx, cfg)
setDiscoveryV5Address(ctx, cfg)
setBootstrapNodes(ctx, cfg)
setBootstrapNodesV5(ctx, cfg)
if ctx.GlobalIsSet(MaxPeersFlag.Name) {
cfg.MaxPeers = ctx.GlobalInt(MaxPeersFlag.Name)
}
if ctx.GlobalIsSet(MaxPendingPeersFlag.Name) {
cfg.MaxPendingPeers = ctx.GlobalInt(MaxPendingPeersFlag.Name)
}
if ctx.GlobalIsSet(NoDiscoverFlag.Name) || ctx.GlobalBool(LightModeFlag.Name) {
cfg.Discovery = false
}
// if we're running a light client or server, force enable the v5 peer discovery unless it is explicitly disabled with --nodiscover
// note that explicitly specifying --v5disc overrides --nodiscover, in which case the later only disables v4 discovery
// if we're running a light client or server, force enable the v5 peer discovery
// unless it is explicitly disabled with --nodiscover note that explicitly specifying
// --v5disc overrides --nodiscover, in which case the later only disables v4 discovery
forceV5Discovery := (ctx.GlobalBool(LightModeFlag.Name) || ctx.GlobalInt(LightServFlag.Name) > 0) && !ctx.GlobalBool(NoDiscoverFlag.Name)
if ctx.GlobalIsSet(DiscoveryV5Flag.Name) {
cfg.DiscoveryV5 = ctx.GlobalBool(DiscoveryV5Flag.Name)
} else if forceV5Discovery {
cfg.DiscoveryV5 = true
}
config := &node.Config{
DataDir: MakeDataDir(ctx),
KeyStoreDir: ctx.GlobalString(KeyStoreDirFlag.Name),
UseLightweightKDF: ctx.GlobalBool(LightKDFFlag.Name),
PrivateKey: MakeNodeKey(ctx),
Name: name,
Version: vsn,
UserIdent: makeNodeUserIdent(ctx),
NoDiscovery: ctx.GlobalBool(NoDiscoverFlag.Name) || ctx.GlobalBool(LightModeFlag.Name), // always disable v4 discovery in light client mode
DiscoveryV5: ctx.GlobalBool(DiscoveryV5Flag.Name) || forceV5Discovery,
DiscoveryV5Addr: MakeDiscoveryV5Address(ctx),
BootstrapNodes: MakeBootstrapNodes(ctx),
BootstrapNodesV5: MakeBootstrapNodesV5(ctx),
ListenAddr: MakeListenAddress(ctx),
NAT: MakeNAT(ctx),
MaxPeers: ctx.GlobalInt(MaxPeersFlag.Name),
MaxPendingPeers: ctx.GlobalInt(MaxPendingPeersFlag.Name),
IPCPath: MakeIPCPath(ctx),
HTTPHost: MakeHTTPRpcHost(ctx),
HTTPPort: ctx.GlobalInt(RPCPortFlag.Name),
HTTPCors: ctx.GlobalString(RPCCORSDomainFlag.Name),
HTTPModules: MakeRPCModules(ctx.GlobalString(RPCApiFlag.Name)),
WSHost: MakeWSRpcHost(ctx),
WSPort: ctx.GlobalInt(WSPortFlag.Name),
WSOrigins: ctx.GlobalString(WSAllowedOriginsFlag.Name),
WSModules: MakeRPCModules(ctx.GlobalString(WSApiFlag.Name)),
}
if ctx.GlobalBool(DevModeFlag.Name) {
if !ctx.GlobalIsSet(DataDirFlag.Name) {
config.DataDir = filepath.Join(os.TempDir(), "/ethereum_dev_mode")
}
// --dev mode does not need p2p networking.
config.MaxPeers = 0
config.ListenAddr = ":0"
}
if netrestrict := ctx.GlobalString(NetrestrictFlag.Name); netrestrict != "" {
list, err := netutil.ParseNetlist(netrestrict)
if err != nil {
Fatalf("Option %q: %v", NetrestrictFlag.Name, err)
}
config.NetRestrict = list
cfg.NetRestrict = list
}
stack, err := node.New(config)
if ctx.GlobalBool(DevModeFlag.Name) {
// --dev mode can't use p2p networking.
cfg.MaxPeers = 0
cfg.ListenAddr = ":0"
cfg.Discovery = false
cfg.DiscoveryV5 = false
}
}
// SetNodeConfig applies node-related command line flags to the config.
func SetNodeConfig(ctx *cli.Context, name, gitCommit string, cfg *node.Config) {
SetP2PConfig(ctx, &cfg.P2P)
setIPC(ctx, cfg)
setHTTP(ctx, cfg)
setWS(ctx, cfg)
setNodeUserIdent(ctx, cfg)
switch {
case ctx.GlobalIsSet(DataDirFlag.Name):
cfg.DataDir = ctx.GlobalString(DataDirFlag.Name)
case ctx.GlobalBool(DevModeFlag.Name):
cfg.DataDir = filepath.Join(os.TempDir(), "ethereum_dev_mode")
case ctx.GlobalBool(TestNetFlag.Name):
cfg.DataDir = filepath.Join(node.DefaultDataDir(), "testnet")
}
if ctx.GlobalIsSet(KeyStoreDirFlag.Name) {
cfg.KeyStoreDir = ctx.GlobalString(KeyStoreDirFlag.Name)
}
if ctx.GlobalIsSet(LightKDFFlag.Name) {
cfg.UseLightweightKDF = ctx.GlobalBool(LightKDFFlag.Name)
}
}
// MakeNode configures a node with no services from command line flags.
// Deprecated: use SetNodeConfig
func MakeNode(ctx *cli.Context, name, gitCommit string) *node.Node {
cfg := new(node.Config)
SetNodeConfig(ctx, name, gitCommit, cfg)
stack, err := node.New(cfg)
if err != nil {
Fatalf("Failed to create the protocol stack: %v", err)
}
return stack
}
// RegisterEthService configures eth.Ethereum from command line flags and adds it to the
// given node.
func RegisterEthService(ctx *cli.Context, stack *node.Node, extra []byte) {
func setGPO(ctx *cli.Context, cfg *eth.Config) {
if ctx.GlobalIsSet(GpoBlocksFlag.Name) {
cfg.GpoBlocks = ctx.GlobalInt(GpoBlocksFlag.Name)
}
if ctx.GlobalIsSet(GpoPercentileFlag.Name) {
cfg.GpoPercentile = ctx.GlobalInt(GpoPercentileFlag.Name)
}
}
func setEthash(ctx *cli.Context, cfg *eth.Config) {
if ctx.GlobalIsSet(EthashCacheDirFlag.Name) {
cfg.EthashCacheDir = ctx.GlobalString(EthashCacheDirFlag.Name)
}
if ctx.GlobalIsSet(EthashDatasetDirFlag.Name) {
cfg.EthashDatasetDir = ctx.GlobalString(EthashDatasetDirFlag.Name)
}
if ctx.GlobalIsSet(EthashCachesInMemoryFlag.Name) {
cfg.EthashCachesInMem = ctx.GlobalInt(EthashCachesInMemoryFlag.Name)
}
if ctx.GlobalIsSet(EthashCachesOnDiskFlag.Name) {
cfg.EthashCachesOnDisk = ctx.GlobalInt(EthashCachesOnDiskFlag.Name)
}
if ctx.GlobalIsSet(EthashDatasetsInMemoryFlag.Name) {
cfg.EthashDatasetsInMem = ctx.GlobalInt(EthashDatasetsInMemoryFlag.Name)
}
if ctx.GlobalIsSet(EthashDatasetsOnDiskFlag.Name) {
cfg.EthashDatasetsOnDisk = ctx.GlobalInt(EthashDatasetsOnDiskFlag.Name)
}
}
// SetEthConfig applies eth-related command line flags to the config.
func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *eth.Config) {
// Avoid conflicting network flags
networks, netFlags := 0, []cli.BoolFlag{DevModeFlag, TestNetFlag}
for _, flag := range netFlags {
@ -762,70 +803,98 @@ func RegisterEthService(ctx *cli.Context, stack *node.Node, extra []byte) {
if networks > 1 {
Fatalf("The %v flags are mutually exclusive", netFlags)
}
ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
ethConf := &eth.Config{
Etherbase: MakeEtherbase(ks, ctx),
FastSync: ctx.GlobalBool(FastSyncFlag.Name),
LightMode: ctx.GlobalBool(LightModeFlag.Name),
LightServ: ctx.GlobalInt(LightServFlag.Name),
LightPeers: ctx.GlobalInt(LightPeersFlag.Name),
MaxPeers: ctx.GlobalInt(MaxPeersFlag.Name),
DatabaseCache: ctx.GlobalInt(CacheFlag.Name),
DatabaseHandles: MakeDatabaseHandles(),
NetworkId: ctx.GlobalInt(NetworkIdFlag.Name),
MinerThreads: ctx.GlobalInt(MinerThreadsFlag.Name),
ExtraData: MakeMinerExtra(extra, ctx),
DocRoot: ctx.GlobalString(DocRootFlag.Name),
GasPrice: GlobalBig(ctx, GasPriceFlag.Name),
GpoBlocks: ctx.GlobalInt(GpoBlocksFlag.Name),
GpoPercentile: ctx.GlobalInt(GpoPercentileFlag.Name),
SolcPath: ctx.GlobalString(SolcPathFlag.Name),
EthashCacheDir: MakeEthashCacheDir(ctx),
EthashCachesInMem: ctx.GlobalInt(EthashCachesInMemoryFlag.Name),
EthashCachesOnDisk: ctx.GlobalInt(EthashCachesOnDiskFlag.Name),
EthashDatasetDir: MakeEthashDatasetDir(ctx),
EthashDatasetsInMem: ctx.GlobalInt(EthashDatasetsInMemoryFlag.Name),
EthashDatasetsOnDisk: ctx.GlobalInt(EthashDatasetsOnDiskFlag.Name),
EnablePreimageRecording: ctx.GlobalBool(VMEnableDebugFlag.Name),
ks := stack.AccountManager().Backends(keystore.KeyStoreType)[0].(*keystore.KeyStore)
setEtherbase(ctx, ks, cfg)
setGPO(ctx, cfg)
setEthash(ctx, cfg)
if ctx.GlobalIsSet(FastSyncFlag.Name) {
cfg.FastSync = ctx.GlobalBool(FastSyncFlag.Name)
}
if ctx.GlobalIsSet(LightModeFlag.Name) {
cfg.LightMode = ctx.GlobalBool(LightModeFlag.Name)
}
if ctx.GlobalIsSet(LightServFlag.Name) {
cfg.LightServ = ctx.GlobalInt(LightServFlag.Name)
}
if ctx.GlobalIsSet(LightPeersFlag.Name) {
cfg.LightPeers = ctx.GlobalInt(LightPeersFlag.Name)
}
if ctx.GlobalIsSet(NetworkIdFlag.Name) {
cfg.NetworkId = ctx.GlobalInt(NetworkIdFlag.Name)
}
// Override any default configs in dev mode or the test net
// Ethereum needs to know maxPeers to calculate the light server peer ratio.
// TODO(fjl): ensure Ethereum can get MaxPeers from node.
cfg.MaxPeers = ctx.GlobalInt(MaxPeersFlag.Name)
if ctx.GlobalIsSet(CacheFlag.Name) {
cfg.DatabaseCache = ctx.GlobalInt(CacheFlag.Name)
}
cfg.DatabaseHandles = makeDatabaseHandles()
if ctx.GlobalIsSet(MinerThreadsFlag.Name) {
cfg.MinerThreads = ctx.GlobalInt(MinerThreadsFlag.Name)
}
if ctx.GlobalIsSet(DocRootFlag.Name) {
cfg.DocRoot = ctx.GlobalString(DocRootFlag.Name)
}
if ctx.GlobalIsSet(ExtraDataFlag.Name) {
cfg.ExtraData = []byte(ctx.GlobalString(ExtraDataFlag.Name))
}
if ctx.GlobalIsSet(GasPriceFlag.Name) {
cfg.GasPrice = GlobalBig(ctx, GasPriceFlag.Name)
}
if ctx.GlobalIsSet(SolcPathFlag.Name) {
cfg.SolcPath = ctx.GlobalString(SolcPathFlag.Name)
}
if ctx.GlobalIsSet(VMEnableDebugFlag.Name) {
// TODO(fjl): force-enable this in --dev mode
cfg.EnablePreimageRecording = ctx.GlobalBool(VMEnableDebugFlag.Name)
}
// Override any default configs for --dev and --testnet.
switch {
case ctx.GlobalBool(TestNetFlag.Name):
if !ctx.GlobalIsSet(NetworkIdFlag.Name) {
ethConf.NetworkId = 3
cfg.NetworkId = 3
}
ethConf.Genesis = core.DefaultTestnetGenesisBlock()
cfg.Genesis = core.DefaultTestnetGenesisBlock()
case ctx.GlobalBool(DevModeFlag.Name):
ethConf.Genesis = core.DevGenesisBlock()
cfg.Genesis = core.DevGenesisBlock()
if !ctx.GlobalIsSet(GasPriceFlag.Name) {
ethConf.GasPrice = new(big.Int)
cfg.GasPrice = new(big.Int)
}
ethConf.PowTest = true
cfg.PowTest = true
}
// Override any global options pertaining to the Ethereum protocol
// TODO(fjl): move trie cache generations into config
if gen := ctx.GlobalInt(TrieCacheGenFlag.Name); gen > 0 {
state.MaxTrieCacheGen = uint16(gen)
}
}
if ethConf.LightMode {
if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
return les.New(ctx, ethConf)
}); err != nil {
Fatalf("Failed to register the Ethereum light node service: %v", err)
}
// RegisterEthService adds an Ethereum client to the stack.
func RegisterEthService(stack *node.Node, cfg *eth.Config) {
var err error
if cfg.LightMode {
err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
return les.New(ctx, cfg)
})
} else {
if err := stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
fullNode, err := eth.New(ctx, ethConf)
if fullNode != nil && ethConf.LightServ > 0 {
ls, _ := les.NewLesServer(fullNode, ethConf)
err = stack.Register(func(ctx *node.ServiceContext) (node.Service, error) {
fullNode, err := eth.New(ctx, cfg)
if fullNode != nil && cfg.LightServ > 0 {
ls, _ := les.NewLesServer(fullNode, cfg)
fullNode.AddLesServer(ls)
}
return fullNode, err
}); err != nil {
Fatalf("Failed to register the Ethereum full node service: %v", err)
}
})
}
if err != nil {
Fatalf("Failed to register the Ethereum service: %v", err)
}
}
@ -855,6 +924,7 @@ func RegisterEthStatsService(stack *node.Node, url string) {
// SetupNetwork configures the system for either the main net or some test network.
func SetupNetwork(ctx *cli.Context) {
// TODO(fjl): move target gas limit into config
params.TargetGasLimit = new(big.Int).SetUint64(ctx.GlobalUint64(TargetGasLimitFlag.Name))
}
@ -870,7 +940,7 @@ func ChainDbName(ctx *cli.Context) string {
func MakeChainDatabase(ctx *cli.Context, stack *node.Node) ethdb.Database {
var (
cache = ctx.GlobalInt(CacheFlag.Name)
handles = MakeDatabaseHandles()
handles = makeDatabaseHandles()
name = ChainDbName(ctx)
)

View file

@ -19,13 +19,13 @@ package eth
import (
"fmt"
"math/big"
"regexp"
"runtime"
"sync"
"sync/atomic"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/clique"
"github.com/ethereum/go-ethereum/consensus/ethash"
@ -43,55 +43,10 @@ import (
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/rpc"
)
var (
datadirInUseErrnos = map[uint]bool{11: true, 32: true, 35: true}
portInUseErrRE = regexp.MustCompile("address already in use")
)
type Config struct {
// The genesis block, which is inserted if the database is empty.
// If nil, the Ethereum main net block is used.
Genesis *core.Genesis
NetworkId int // Network ID to use for selecting peers to connect to
FastSync bool // Enables the state download based fast synchronisation algorithm
LightMode bool // Running in light client mode
LightServ int // Maximum percentage of time allowed for serving LES requests
LightPeers int // Maximum number of LES client peers
MaxPeers int // Maximum number of global peers
SkipBcVersionCheck bool // e.g. blockchain export
DatabaseCache int
DatabaseHandles int
DocRoot string
PowFake bool
PowTest bool
PowShared bool
ExtraData []byte
EthashCacheDir string
EthashCachesInMem int
EthashCachesOnDisk int
EthashDatasetDir string
EthashDatasetsInMem int
EthashDatasetsOnDisk int
Etherbase common.Address
GasPrice *big.Int
MinerThreads int
SolcPath string
GpoBlocks int
GpoPercentile int
EnablePreimageRecording bool
}
type LesServer interface {
Start(srvr *p2p.Server)
Stop()
@ -207,7 +162,7 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
eth.miner = miner.New(eth, eth.chainConfig, eth.EventMux(), eth.engine)
eth.miner.SetGasPrice(config.GasPrice)
eth.miner.SetExtra(config.ExtraData)
eth.miner.SetExtra(makeExtraData(config.ExtraData))
eth.ApiBackend = &EthApiBackend{eth, nil}
gpoParams := gasprice.Config{
@ -220,6 +175,23 @@ func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error) {
return eth, nil
}
func makeExtraData(extra []byte) []byte {
if len(extra) == 0 {
// create default extradata
extra, _ = rlp.EncodeToBytes([]interface{}{
uint(params.VersionMajor<<16 | params.VersionMinor<<8 | params.VersionPatch),
"geth",
runtime.Version(),
runtime.GOOS,
})
}
if uint64(len(extra)) > params.MaximumExtraDataSize {
log.Warn("Miner extra data exceed limit", "extra", hexutil.Bytes(extra), "limit", params.MaximumExtraDataSize)
extra = nil
}
return extra
}
// CreateDB creates the chain database.
func CreateDB(ctx *node.ServiceContext, config *Config, name string) (ethdb.Database, error) {
db, err := ctx.OpenDatabase(name, config.DatabaseCache, config.DatabaseHandles)
@ -415,8 +387,3 @@ func (s *Ethereum) Stop() error {
return nil
}
// This function will wait for a shutdown and resumes main thread execution
func (s *Ethereum) WaitForShutdown() {
<-s.shutdownChan
}

98
eth/config.go Normal file
View file

@ -0,0 +1,98 @@
// Copyright 2014 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package eth
import (
"math/big"
"os"
"os/user"
"path/filepath"
"runtime"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/params"
)
var DefaultConfig = Config{
EthashCachesInMem: 2,
EthashCachesOnDisk: 3,
EthashDatasetsInMem: 1,
EthashDatasetsOnDisk: 2,
NetworkId: 1,
LightPeers: 20,
DatabaseCache: 128,
GasPrice: big.NewInt(20 * params.Shannon),
GpoBlocks: 10,
GpoPercentile: 50,
}
func init() {
home := os.Getenv("HOME")
if home == "" {
if user, err := user.Current(); err == nil {
home = user.HomeDir
}
}
if runtime.GOOS == "windows" {
DefaultConfig.EthashDatasetDir = filepath.Join(home, "AppData", "Ethash")
} else {
DefaultConfig.EthashDatasetDir = filepath.Join(home, ".ethash")
}
}
//go:generate gencodec -type Config -field-override configMarshaling -formats toml -out gen_config.go
type Config struct {
// The genesis block, which is inserted if the database is empty.
// If nil, the Ethereum main net block is used.
Genesis *core.Genesis `toml:",omitempty"`
NetworkId int // Network ID to use for selecting peers to connect to
FastSync bool // Enables the state download based fast synchronisation algorithm
LightMode bool // Running in light client mode
LightServ int // Maximum percentage of time allowed for serving LES requests
LightPeers int // Maximum number of LES client peers
MaxPeers int // Maximum number of global peers
SkipBcVersionCheck bool `toml:",omitempty"` // e.g. blockchain export
DatabaseCache int
DatabaseHandles int `toml:"-"`
DocRoot string `toml:",omitempty"`
PowFake bool `toml:",omitempty"`
PowTest bool `toml:",omitempty"`
PowShared bool `toml:",omitempty"`
ExtraData []byte
EthashCacheDir string `toml:",omitempty"`
EthashCachesInMem int
EthashCachesOnDisk int
EthashDatasetDir string `toml:",omitempty"`
EthashDatasetsInMem int
EthashDatasetsOnDisk int
Etherbase common.Address `toml:",omitempty"`
GasPrice *big.Int
MinerThreads int `toml:",omitempty"`
SolcPath string `toml:",omitempty"`
GpoBlocks int
GpoPercentile int
EnablePreimageRecording bool
}
type configMarshaling struct {
ExtraData hexutil.Bytes
}

196
eth/gen_config.go Normal file
View file

@ -0,0 +1,196 @@
// Code generated by github.com/fjl/gencodec. DO NOT EDIT.
package eth
import (
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core"
)
func (c Config) MarshalTOML() (interface{}, error) {
type Config struct {
Genesis *core.Genesis `toml:",omitempty"`
NetworkId int
FastSync bool
LightMode bool
LightServ int
LightPeers int
MaxPeers int
SkipBcVersionCheck bool `toml:",omitempty"`
DatabaseCache int
DatabaseHandles int `toml:"-"`
DocRoot string `toml:",omitempty"`
PowFake bool `toml:",omitempty"`
PowTest bool `toml:",omitempty"`
PowShared bool `toml:",omitempty"`
ExtraData hexutil.Bytes
EthashCacheDir string `toml:",omitempty"`
EthashCachesInMem int
EthashCachesOnDisk int
EthashDatasetDir string `toml:",omitempty"`
EthashDatasetsInMem int
EthashDatasetsOnDisk int
Etherbase common.Address `toml:",omitempty"`
GasPrice *big.Int
MinerThreads int `toml:",omitempty"`
SolcPath string `toml:",omitempty"`
GpoBlocks int
GpoPercentile int
EnablePreimageRecording bool
}
var enc Config
enc.Genesis = c.Genesis
enc.NetworkId = c.NetworkId
enc.FastSync = c.FastSync
enc.LightMode = c.LightMode
enc.LightServ = c.LightServ
enc.LightPeers = c.LightPeers
enc.MaxPeers = c.MaxPeers
enc.SkipBcVersionCheck = c.SkipBcVersionCheck
enc.DatabaseCache = c.DatabaseCache
enc.DatabaseHandles = c.DatabaseHandles
enc.DocRoot = c.DocRoot
enc.PowFake = c.PowFake
enc.PowTest = c.PowTest
enc.PowShared = c.PowShared
enc.ExtraData = c.ExtraData
enc.EthashCacheDir = c.EthashCacheDir
enc.EthashCachesInMem = c.EthashCachesInMem
enc.EthashCachesOnDisk = c.EthashCachesOnDisk
enc.EthashDatasetDir = c.EthashDatasetDir
enc.EthashDatasetsInMem = c.EthashDatasetsInMem
enc.EthashDatasetsOnDisk = c.EthashDatasetsOnDisk
enc.Etherbase = c.Etherbase
enc.GasPrice = c.GasPrice
enc.MinerThreads = c.MinerThreads
enc.SolcPath = c.SolcPath
enc.GpoBlocks = c.GpoBlocks
enc.GpoPercentile = c.GpoPercentile
enc.EnablePreimageRecording = c.EnablePreimageRecording
return &enc, nil
}
func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
type Config struct {
Genesis *core.Genesis `toml:",omitempty"`
NetworkId *int
FastSync *bool
LightMode *bool
LightServ *int
LightPeers *int
MaxPeers *int
SkipBcVersionCheck *bool `toml:",omitempty"`
DatabaseCache *int
DatabaseHandles *int `toml:"-"`
DocRoot *string `toml:",omitempty"`
PowFake *bool `toml:",omitempty"`
PowTest *bool `toml:",omitempty"`
PowShared *bool `toml:",omitempty"`
ExtraData hexutil.Bytes
EthashCacheDir *string `toml:",omitempty"`
EthashCachesInMem *int
EthashCachesOnDisk *int
EthashDatasetDir *string `toml:",omitempty"`
EthashDatasetsInMem *int
EthashDatasetsOnDisk *int
Etherbase *common.Address `toml:",omitempty"`
GasPrice *big.Int
MinerThreads *int `toml:",omitempty"`
SolcPath *string `toml:",omitempty"`
GpoBlocks *int
GpoPercentile *int
EnablePreimageRecording *bool
}
var dec Config
if err := unmarshal(&dec); err != nil {
return err
}
if dec.Genesis != nil {
c.Genesis = dec.Genesis
}
if dec.NetworkId != nil {
c.NetworkId = *dec.NetworkId
}
if dec.FastSync != nil {
c.FastSync = *dec.FastSync
}
if dec.LightMode != nil {
c.LightMode = *dec.LightMode
}
if dec.LightServ != nil {
c.LightServ = *dec.LightServ
}
if dec.LightPeers != nil {
c.LightPeers = *dec.LightPeers
}
if dec.MaxPeers != nil {
c.MaxPeers = *dec.MaxPeers
}
if dec.SkipBcVersionCheck != nil {
c.SkipBcVersionCheck = *dec.SkipBcVersionCheck
}
if dec.DatabaseCache != nil {
c.DatabaseCache = *dec.DatabaseCache
}
if dec.DatabaseHandles != nil {
c.DatabaseHandles = *dec.DatabaseHandles
}
if dec.DocRoot != nil {
c.DocRoot = *dec.DocRoot
}
if dec.PowFake != nil {
c.PowFake = *dec.PowFake
}
if dec.PowTest != nil {
c.PowTest = *dec.PowTest
}
if dec.PowShared != nil {
c.PowShared = *dec.PowShared
}
if dec.ExtraData != nil {
c.ExtraData = dec.ExtraData
}
if dec.EthashCacheDir != nil {
c.EthashCacheDir = *dec.EthashCacheDir
}
if dec.EthashCachesInMem != nil {
c.EthashCachesInMem = *dec.EthashCachesInMem
}
if dec.EthashCachesOnDisk != nil {
c.EthashCachesOnDisk = *dec.EthashCachesOnDisk
}
if dec.EthashDatasetDir != nil {
c.EthashDatasetDir = *dec.EthashDatasetDir
}
if dec.EthashDatasetsInMem != nil {
c.EthashDatasetsInMem = *dec.EthashDatasetsInMem
}
if dec.EthashDatasetsOnDisk != nil {
c.EthashDatasetsOnDisk = *dec.EthashDatasetsOnDisk
}
if dec.Etherbase != nil {
c.Etherbase = *dec.Etherbase
}
if dec.GasPrice != nil {
c.GasPrice = dec.GasPrice
}
if dec.MinerThreads != nil {
c.MinerThreads = *dec.MinerThreads
}
if dec.SolcPath != nil {
c.SolcPath = *dec.SolcPath
}
if dec.GpoBlocks != nil {
c.GpoBlocks = *dec.GpoBlocks
}
if dec.GpoPercentile != nil {
c.GpoPercentile = *dec.GpoPercentile
}
if dec.EnablePreimageRecording != nil {
c.EnablePreimageRecording = *dec.EnablePreimageRecording
}
return nil
}

View file

@ -41,10 +41,7 @@ var ProtocolVersions = []uint{eth63, eth62}
// Number of implemented message corresponding to different protocol versions.
var ProtocolLengths = []uint64{17, 8}
const (
NetworkId = 1
ProtocolMaxMsgSize = 10 * 1024 * 1024 // Maximum cap on the size of a protocol message
)
const ProtocolMaxMsgSize = 10 * 1024 * 1024 // Maximum cap on the size of a protocol message
// eth protocol message codes
const (

View file

@ -31,6 +31,7 @@ import (
"github.com/ethereum/go-ethereum/ethstats"
"github.com/ethereum/go-ethereum/les"
"github.com/ethereum/go-ethereum/node"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/nat"
"github.com/ethereum/go-ethereum/params"
whisper "github.com/ethereum/go-ethereum/whisper/whisperv2"
@ -108,17 +109,19 @@ func NewNode(datadir string, config *NodeConfig) (stack *Node, _ error) {
}
// Create the empty networking stack
nodeConf := &node.Config{
Name: clientIdentifier,
Version: params.Version,
DataDir: datadir,
KeyStoreDir: filepath.Join(datadir, "keystore"), // Mobile should never use internal keystores!
NoDiscovery: true,
DiscoveryV5: true,
DiscoveryV5Addr: ":0",
BootstrapNodesV5: config.BootstrapNodes.nodes,
ListenAddr: ":0",
NAT: nat.Any(),
MaxPeers: config.MaxPeers,
Name: clientIdentifier,
Version: params.Version,
DataDir: datadir,
KeyStoreDir: filepath.Join(datadir, "keystore"), // Mobile should never use internal keystores!
P2P: p2p.Config{
Discovery: true,
DiscoveryV5: true,
DiscoveryV5Addr: ":0",
BootstrapNodesV5: config.BootstrapNodes.nodes,
ListenAddr: ":0",
NAT: nat.Any(),
MaxPeers: config.MaxPeers,
},
}
rawStack, err := node.New(nodeConf)
if err != nil {

View file

@ -20,7 +20,6 @@ import (
"crypto/ecdsa"
"fmt"
"io/ioutil"
"net"
"os"
"path/filepath"
"runtime"
@ -32,10 +31,8 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/p2p"
"github.com/ethereum/go-ethereum/p2p/discover"
"github.com/ethereum/go-ethereum/p2p/discv5"
"github.com/ethereum/go-ethereum/p2p/nat"
"github.com/ethereum/go-ethereum/p2p/netutil"
)
var (
@ -53,14 +50,14 @@ type Config struct {
// Name sets the instance name of the node. It must not contain the / character and is
// used in the devp2p node identifier. The instance name of geth is "geth". If no
// value is specified, the basename of the current executable is used.
Name string
Name string `toml:"-"`
// UserIdent, if set, is used as an additional component in the devp2p node identifier.
UserIdent string
UserIdent string `toml:",omitempty"`
// Version should be set to the version number of the program. It is used
// in the devp2p node identifier.
Version string
Version string `toml:"-"`
// DataDir is the file system folder the node should use for any data storage
// requirements. The configured data directory will not be directly shared with
@ -69,6 +66,9 @@ type Config struct {
// in memory.
DataDir string
// Configuration of peer-to-peer networking.
P2P p2p.Config `toml:"p2p"`
// KeyStoreDir is the file system folder that contains private keys. The directory can
// be specified as a relative path, in which case it is resolved relative to the
// current directory.
@ -76,106 +76,55 @@ type Config struct {
// If KeyStoreDir is empty, the default location is the "keystore" subdirectory of
// DataDir. If DataDir is unspecified and KeyStoreDir is empty, an ephemeral directory
// is created by New and destroyed when the node is stopped.
KeyStoreDir string
KeyStoreDir string `toml:",omitempty"`
// UseLightweightKDF lowers the memory and CPU requirements of the key store
// scrypt KDF at the expense of security.
UseLightweightKDF bool
UseLightweightKDF bool `toml:"use_lightweight_kdf,omitempty"`
// IPCPath is the requested location to place the IPC endpoint. If the path is
// a simple file name, it is placed inside the data directory (or on the root
// pipe path on Windows), whereas if it's a resolvable path name (absolute or
// relative), then that specific path is enforced. An empty path disables IPC.
IPCPath string
// This field should be a valid secp256k1 private key that will be used for both
// remote peer identification as well as network traffic encryption. If no key
// is configured, the preset one is loaded from the data dir, generating it if
// needed.
PrivateKey *ecdsa.PrivateKey
// NoDiscovery specifies whether the peer discovery mechanism should be started
// or not. Disabling is usually useful for protocol debugging (manual topology).
NoDiscovery bool
// DiscoveryV5 specifies whether the the new topic-discovery based V5 discovery
// protocol should be started or not.
DiscoveryV5 bool
// Listener address for the V5 discovery protocol UDP traffic.
DiscoveryV5Addr string
// Restrict communication to white listed IP networks.
// The whitelist only applies when non-nil.
NetRestrict *netutil.Netlist
// BootstrapNodes used to establish connectivity with the rest of the network.
BootstrapNodes []*discover.Node
// BootstrapNodesV5 used to establish connectivity with the rest of the network
// using the V5 discovery protocol.
BootstrapNodesV5 []*discv5.Node
// Network interface address on which the node should listen for inbound peers.
ListenAddr string
// If set to a non-nil value, the given NAT port mapper is used to make the
// listening port available to the Internet.
NAT nat.Interface
// If Dialer is set to a non-nil value, the given Dialer is used to dial outbound
// peer connections.
Dialer *net.Dialer
// If NoDial is true, the node will not dial any peers.
NoDial bool
// MaxPeers is the maximum number of peers that can be connected. If this is
// set to zero, then only the configured static and trusted peers can connect.
MaxPeers int
// MaxPendingPeers is the maximum number of peers that can be pending in the
// handshake phase, counted separately for inbound and outbound connections.
// Zero defaults to preset values.
MaxPendingPeers int
IPCPath string `toml:"ipc_path,omitempty"`
// HTTPHost is the host interface on which to start the HTTP RPC server. If this
// field is empty, no HTTP API endpoint will be started.
HTTPHost string
HTTPHost string `toml:"http_host,omitempty"`
// HTTPPort is the TCP port number on which to start the HTTP RPC server. The
// default zero value is/ valid and will pick a port number randomly (useful
// for ephemeral nodes).
HTTPPort int
HTTPPort int `toml:"http_port,omitempty"`
// HTTPCors is the Cross-Origin Resource Sharing header to send to requesting
// clients. Please be aware that CORS is a browser enforced security, it's fully
// useless for custom HTTP clients.
HTTPCors string
HTTPCors string `toml:"http_cors,omitempty"`
// HTTPModules is a list of API modules to expose via the HTTP RPC interface.
// If the module list is empty, all RPC API endpoints designated public will be
// exposed.
HTTPModules []string
HTTPModules []string `toml:"http_modules,omitempty"`
// WSHost is the host interface on which to start the websocket RPC server. If
// this field is empty, no websocket API endpoint will be started.
WSHost string
WSHost string `toml:"ws_host,omitempty"`
// WSPort is the TCP port number on which to start the websocket RPC server. The
// default zero value is/ valid and will pick a port number randomly (useful for
// ephemeral nodes).
WSPort int
WSPort int `toml:"ws_port,omitempty"`
// WSOrigins is the list of domain to accept websocket requests from. Please be
// aware that the server can only act upon the HTTP request the client sends and
// cannot verify the validity of the request header.
WSOrigins string
WSOrigins string `toml:"ws_origins,omitempty"`
// WSModules is a list of API modules to expose via the websocket RPC interface.
// If the module list is empty, all RPC API endpoints designated public will be
// exposed.
WSModules []string
WSModules []string `toml:"ws_modules,omitempty"`
}
// IPCEndpoint resolves an IPC endpoint based on a configured value, taking into
@ -326,8 +275,8 @@ func (c *Config) instanceDir() string {
// data folder. If no key can be found, a new one is generated.
func (c *Config) NodeKey() *ecdsa.PrivateKey {
// Use any specifically configured key.
if c.PrivateKey != nil {
return c.PrivateKey
if c.P2P.PrivateKey != nil {
return c.P2P.PrivateKey
}
// Generate ephemeral key if no datadir is being used.
if c.DataDir == "" {

View file

@ -153,24 +153,17 @@ func (n *Node) Start() error {
// Initialize the p2p server. This creates the node key and
// discovery databases.
n.serverConfig = p2p.Config{
PrivateKey: n.config.NodeKey(),
Name: n.config.NodeName(),
Discovery: !n.config.NoDiscovery,
DiscoveryV5: n.config.DiscoveryV5,
DiscoveryV5Addr: n.config.DiscoveryV5Addr,
BootstrapNodes: n.config.BootstrapNodes,
BootstrapNodesV5: n.config.BootstrapNodesV5,
StaticNodes: n.config.StaticNodes(),
TrustedNodes: n.config.TrusterNodes(),
NodeDatabase: n.config.NodeDB(),
ListenAddr: n.config.ListenAddr,
NetRestrict: n.config.NetRestrict,
NAT: n.config.NAT,
Dialer: n.config.Dialer,
NoDial: n.config.NoDial,
MaxPeers: n.config.MaxPeers,
MaxPendingPeers: n.config.MaxPendingPeers,
n.serverConfig = n.config.P2P
n.serverConfig.PrivateKey = n.config.NodeKey()
n.serverConfig.Name = n.config.NodeName()
if n.serverConfig.StaticNodes == nil {
n.serverConfig.StaticNodes = n.config.StaticNodes()
}
if n.serverConfig.StaticNodes == nil {
n.serverConfig.TrustedNodes = n.config.TrusterNodes()
}
if n.serverConfig.NodeDatabase == "" {
n.serverConfig.NodeDatabase = n.config.NodeDB()
}
running := &p2p.Server{Config: n.serverConfig}
log.Info("Starting peer-to-peer node", "instance", n.serverConfig.Name)

View file

@ -58,7 +58,7 @@ var errServerStopped = errors.New("server stopped")
// Config holds Server options.
type Config struct {
// This field must be set to a valid secp256k1 private key.
PrivateKey *ecdsa.PrivateKey
PrivateKey *ecdsa.PrivateKey `toml:"-"`
// MaxPeers is the maximum number of peers that can be
// connected. It must be greater than zero.
@ -67,7 +67,7 @@ type Config struct {
// MaxPendingPeers is the maximum number of peers that can be pending in the
// handshake phase, counted separately for inbound and outbound connections.
// Zero defaults to preset values.
MaxPendingPeers int
MaxPendingPeers int `toml:",omitempty"`
// Discovery specifies whether the peer discovery mechanism should be started
// or not. Disabling is usually useful for protocol debugging (manual topology).
@ -75,14 +75,14 @@ type Config struct {
// DiscoveryV5 specifies whether the the new topic-discovery based V5 discovery
// protocol should be started or not.
DiscoveryV5 bool
DiscoveryV5 bool `toml:",omitempty"`
// Listener address for the V5 discovery protocol UDP traffic.
DiscoveryV5Addr string
DiscoveryV5Addr string `toml:",omitempty"`
// Name sets the node name of this server.
// Use common.MakeName to create a name that follows existing conventions.
Name string
Name string `toml:"-"`
// BootstrapNodes are used to establish connectivity
// with the rest of the network.
@ -91,7 +91,7 @@ type Config struct {
// BootstrapNodesV5 are used to establish connectivity
// with the rest of the network using the V5 discovery
// protocol.
BootstrapNodesV5 []*discv5.Node
BootstrapNodesV5 []*discv5.Node `toml:",omitempty"`
// Static nodes are used as pre-configured connections which are always
// maintained and re-connected on disconnects.
@ -104,16 +104,16 @@ type Config struct {
// Connectivity can be restricted to certain IP networks.
// If this option is set to a non-nil value, only hosts which match one of the
// IP networks contained in the list are considered.
NetRestrict *netutil.Netlist
NetRestrict *netutil.Netlist `toml:",omitempty"`
// NodeDatabase is the path to the database containing the previously seen
// live nodes in the network.
NodeDatabase string
NodeDatabase string `toml:",omitempty"`
// Protocols should contain the protocols supported
// by the server. Matching protocols are launched for
// each peer.
Protocols []Protocol
Protocols []Protocol `toml:"-"`
// If ListenAddr is set to a non-nil address, the server
// will listen for incoming connections.
@ -126,14 +126,14 @@ type Config struct {
// If set to a non-nil value, the given NAT port mapper
// is used to make the listening port available to the
// Internet.
NAT nat.Interface
NAT nat.Interface `toml:",omitempty"`
// If Dialer is set to a non-nil value, the given Dialer
// is used to dial outbound peer connections.
Dialer *net.Dialer
Dialer *net.Dialer `toml:"-"`
// If NoDial is true, the server will not dial any peers.
NoDial bool
NoDial bool `toml:",omitempty"`
}
// Server manages all peer connections.

View file

@ -16,7 +16,9 @@
package params
import "fmt"
import (
"fmt"
)
const (
VersionMajor = 1 // Major version component of the current release
@ -33,3 +35,11 @@ var Version = func() string {
}
return v
}()
func VersionWithCommit(gitCommit string) string {
vsn := Version
if len(gitCommit) >= 8 {
vsn += "-" + gitCommit[:8]
}
return vsn
}

View file

@ -31,9 +31,7 @@ import (
const (
notificationBufferSize = 10000 // max buffered notifications before codec is closed
MetadataApi = "rpc"
DefaultIPCApis = "admin,debug,eth,miner,net,personal,shh,txpool,web3"
DefaultHTTPApis = "eth,net,web3"
MetadataApi = "rpc"
)
// CodecOption specifies which type of messages this codec supports

19
vendor/github.com/naoina/go-stringutil/LICENSE generated vendored Normal file
View file

@ -0,0 +1,19 @@
Copyright (c) 2015 Naoya Inada <naoina@kuune.org>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

13
vendor/github.com/naoina/go-stringutil/README.md generated vendored Normal file
View file

@ -0,0 +1,13 @@
# stringutil [![Build Status](https://travis-ci.org/naoina/go-stringutil.svg?branch=master)](https://travis-ci.org/naoina/go-stringutil)
## Installation
go get -u github.com/naoina/go-stringutil
## Documentation
See https://godoc.org/github.com/naoina/go-stringutil
## License
MIT

253
vendor/github.com/naoina/go-stringutil/da.go generated vendored Normal file
View file

@ -0,0 +1,253 @@
package stringutil
import (
"fmt"
"sort"
"unicode/utf8"
)
const (
terminationCharacter = '#'
)
func mustDoubleArray(da *doubleArray, err error) *doubleArray {
if err != nil {
panic(err)
}
return da
}
func (da *doubleArray) Build(keys []string) error {
records := makeRecords(keys)
if err := da.build(records, 1, 0, make(map[int]struct{})); err != nil {
return err
}
return nil
}
type doubleArray struct {
bc []baseCheck
node []int
}
func newDoubleArray(keys []string) (*doubleArray, error) {
da := &doubleArray{
bc: []baseCheck{0},
node: []int{-1}, // A start index is adjusting to 1 because 0 will be used as a mark of non-existent node.
}
if err := da.Build(keys); err != nil {
return nil, err
}
return da, nil
}
// baseCheck contains BASE, CHECK and Extra flags.
// From the top, 22bits of BASE, 2bits of Extra flags and 8bits of CHECK.
//
// BASE (22bit) | Extra flags (2bit) | CHECK (8bit)
// |----------------------|--|--------|
// 32 10 8 0
type baseCheck uint32
func (bc baseCheck) Base() int {
return int(bc >> 10)
}
func (bc *baseCheck) SetBase(base int) {
*bc |= baseCheck(base) << 10
}
func (bc baseCheck) Check() byte {
return byte(bc)
}
func (bc *baseCheck) SetCheck(check byte) {
*bc |= baseCheck(check)
}
func (bc baseCheck) IsEmpty() bool {
return bc&0xfffffcff == 0
}
func (da *doubleArray) Lookup(path string) (length int) {
idx := 1
tmpIdx := idx
for i := 0; i < len(path); i++ {
c := path[i]
tmpIdx = da.nextIndex(da.bc[tmpIdx].Base(), c)
if tmpIdx >= len(da.bc) || da.bc[tmpIdx].Check() != c {
break
}
idx = tmpIdx
}
if next := da.nextIndex(da.bc[idx].Base(), terminationCharacter); next < len(da.bc) && da.bc[next].Check() == terminationCharacter {
return da.node[da.bc[next].Base()]
}
return -1
}
func (da *doubleArray) LookupByBytes(path []byte) (length int) {
idx := 1
tmpIdx := idx
for i := 0; i < len(path); i++ {
c := path[i]
tmpIdx = da.nextIndex(da.bc[tmpIdx].Base(), c)
if tmpIdx >= len(da.bc) || da.bc[tmpIdx].Check() != c {
break
}
idx = tmpIdx
}
if next := da.nextIndex(da.bc[idx].Base(), terminationCharacter); next < len(da.bc) && da.bc[next].Check() == terminationCharacter {
return da.node[da.bc[next].Base()]
}
return -1
}
func (da *doubleArray) build(srcs []record, idx, depth int, usedBase map[int]struct{}) error {
sort.Stable(recordSlice(srcs))
base, siblings, leaf, err := da.arrange(srcs, idx, depth, usedBase)
if err != nil {
return err
}
if leaf != nil {
da.bc[idx].SetBase(len(da.node))
da.node = append(da.node, leaf.value)
}
for _, sib := range siblings {
da.setCheck(da.nextIndex(base, sib.c), sib.c)
}
for _, sib := range siblings {
if err := da.build(srcs[sib.start:sib.end], da.nextIndex(base, sib.c), depth+1, usedBase); err != nil {
return err
}
}
return nil
}
func (da *doubleArray) setBase(i, base int) {
da.bc[i].SetBase(base)
}
func (da *doubleArray) setCheck(i int, check byte) {
da.bc[i].SetCheck(check)
}
func (da *doubleArray) findEmptyIndex(start int) int {
i := start
for ; i < len(da.bc); i++ {
if da.bc[i].IsEmpty() {
break
}
}
return i
}
// findBase returns good BASE.
func (da *doubleArray) findBase(siblings []sibling, start int, usedBase map[int]struct{}) (base int) {
for idx, firstChar := start+1, siblings[0].c; ; idx = da.findEmptyIndex(idx + 1) {
base = da.nextIndex(idx, firstChar)
if _, used := usedBase[base]; used {
continue
}
i := 0
for ; i < len(siblings); i++ {
next := da.nextIndex(base, siblings[i].c)
if len(da.bc) <= next {
da.bc = append(da.bc, make([]baseCheck, next-len(da.bc)+1)...)
}
if !da.bc[next].IsEmpty() {
break
}
}
if i == len(siblings) {
break
}
}
usedBase[base] = struct{}{}
return base
}
func (da *doubleArray) arrange(records []record, idx, depth int, usedBase map[int]struct{}) (base int, siblings []sibling, leaf *record, err error) {
siblings, leaf, err = makeSiblings(records, depth)
if err != nil {
return -1, nil, nil, err
}
if len(siblings) < 1 {
return -1, nil, leaf, nil
}
base = da.findBase(siblings, idx, usedBase)
da.setBase(idx, base)
return base, siblings, leaf, err
}
type sibling struct {
start int
end int
c byte
}
func (da *doubleArray) nextIndex(base int, c byte) int {
return base ^ int(c)
}
func makeSiblings(records []record, depth int) (sib []sibling, leaf *record, err error) {
var (
pc byte
n int
)
for i, r := range records {
if len(r.key) <= depth {
leaf = &r
continue
}
c := r.key[depth]
switch {
case pc < c:
sib = append(sib, sibling{start: i, c: c})
case pc == c:
continue
default:
return nil, nil, fmt.Errorf("stringutil: BUG: records hasn't been sorted")
}
if n > 0 {
sib[n-1].end = i
}
pc = c
n++
}
if n == 0 {
return nil, leaf, nil
}
sib[n-1].end = len(records)
return sib, leaf, nil
}
type record struct {
key string
value int
}
func makeRecords(srcs []string) (records []record) {
termChar := string(terminationCharacter)
for _, s := range srcs {
records = append(records, record{
key: string(s + termChar),
value: utf8.RuneCountInString(s),
})
}
return records
}
type recordSlice []record
func (rs recordSlice) Len() int {
return len(rs)
}
func (rs recordSlice) Less(i, j int) bool {
return rs[i].key < rs[j].key
}
func (rs recordSlice) Swap(i, j int) {
rs[i], rs[j] = rs[j], rs[i]
}

320
vendor/github.com/naoina/go-stringutil/strings.go generated vendored Normal file
View file

@ -0,0 +1,320 @@
package stringutil
import (
"sync"
"unicode"
"unicode/utf8"
)
var (
mu sync.Mutex
// Based on https://github.com/golang/lint/blob/32a87160691b3c96046c0c678fe57c5bef761456/lint.go#L702
commonInitialismMap = map[string]struct{}{
"API": struct{}{},
"ASCII": struct{}{},
"CPU": struct{}{},
"CSRF": struct{}{},
"CSS": struct{}{},
"DNS": struct{}{},
"EOF": struct{}{},
"GUID": struct{}{},
"HTML": struct{}{},
"HTTP": struct{}{},
"HTTPS": struct{}{},
"ID": struct{}{},
"IP": struct{}{},
"JSON": struct{}{},
"LHS": struct{}{},
"QPS": struct{}{},
"RAM": struct{}{},
"RHS": struct{}{},
"RPC": struct{}{},
"SLA": struct{}{},
"SMTP": struct{}{},
"SQL": struct{}{},
"SSH": struct{}{},
"TCP": struct{}{},
"TLS": struct{}{},
"TTL": struct{}{},
"UDP": struct{}{},
"UI": struct{}{},
"UID": struct{}{},
"UUID": struct{}{},
"URI": struct{}{},
"URL": struct{}{},
"UTF8": struct{}{},
"VM": struct{}{},
"XML": struct{}{},
"XSRF": struct{}{},
"XSS": struct{}{},
}
commonInitialisms = keys(commonInitialismMap)
commonInitialism = mustDoubleArray(newDoubleArray(commonInitialisms))
longestLen = longestLength(commonInitialisms)
shortestLen = shortestLength(commonInitialisms, longestLen)
)
// ToUpperCamelCase returns a copy of the string s with all Unicode letters mapped to their camel case.
// It will convert to upper case previous letter of '_' and first letter, and remove letter of '_'.
func ToUpperCamelCase(s string) string {
if s == "" {
return ""
}
upper := true
start := 0
result := make([]byte, 0, len(s))
var runeBuf [utf8.UTFMax]byte
var initialism []byte
for _, c := range s {
if c == '_' {
upper = true
candidate := string(result[start:])
initialism = initialism[:0]
for _, r := range candidate {
if r < utf8.RuneSelf {
initialism = append(initialism, toUpperASCII(byte(r)))
} else {
n := utf8.EncodeRune(runeBuf[:], unicode.ToUpper(r))
initialism = append(initialism, runeBuf[:n]...)
}
}
if length := commonInitialism.LookupByBytes(initialism); length > 0 {
result = append(result[:start], initialism...)
}
start = len(result)
continue
}
if upper {
if c < utf8.RuneSelf {
result = append(result, toUpperASCII(byte(c)))
} else {
n := utf8.EncodeRune(runeBuf[:], unicode.ToUpper(c))
result = append(result, runeBuf[:n]...)
}
upper = false
continue
}
if c < utf8.RuneSelf {
result = append(result, byte(c))
} else {
n := utf8.EncodeRune(runeBuf[:], c)
result = append(result, runeBuf[:n]...)
}
}
candidate := string(result[start:])
initialism = initialism[:0]
for _, r := range candidate {
if r < utf8.RuneSelf {
initialism = append(initialism, toUpperASCII(byte(r)))
} else {
n := utf8.EncodeRune(runeBuf[:], unicode.ToUpper(r))
initialism = append(initialism, runeBuf[:n]...)
}
}
if length := commonInitialism.LookupByBytes(initialism); length > 0 {
result = append(result[:start], initialism...)
}
return string(result)
}
// ToUpperCamelCaseASCII is similar to ToUpperCamelCase, but optimized for
// only the ASCII characters.
// ToUpperCamelCaseASCII is faster than ToUpperCamelCase, but doesn't work if
// contains non-ASCII characters.
func ToUpperCamelCaseASCII(s string) string {
if s == "" {
return ""
}
upper := true
start := 0
result := make([]byte, 0, len(s))
var initialism []byte
for i := 0; i < len(s); i++ {
c := s[i]
if c == '_' {
upper = true
candidate := result[start:]
initialism = initialism[:0]
for _, b := range candidate {
initialism = append(initialism, toUpperASCII(b))
}
if length := commonInitialism.LookupByBytes(initialism); length > 0 {
result = append(result[:start], initialism...)
}
start = len(result)
continue
}
if upper {
result = append(result, toUpperASCII(c))
upper = false
continue
}
result = append(result, c)
}
candidate := result[start:]
initialism = initialism[:0]
for _, b := range candidate {
initialism = append(initialism, toUpperASCII(b))
}
if length := commonInitialism.LookupByBytes(initialism); length > 0 {
result = append(result[:start], initialism...)
}
return string(result)
}
// ToSnakeCase returns a copy of the string s with all Unicode letters mapped to their snake case.
// It will insert letter of '_' at position of previous letter of uppercase and all
// letters convert to lower case.
// ToSnakeCase does not insert '_' letter into a common initialism word like ID, URL and so on.
func ToSnakeCase(s string) string {
if s == "" {
return ""
}
result := make([]byte, 0, len(s))
var runeBuf [utf8.UTFMax]byte
var j, skipCount int
for i, c := range s {
if i < skipCount {
continue
}
if unicode.IsUpper(c) {
if i != 0 {
result = append(result, '_')
}
next := nextIndex(j, len(s))
if length := commonInitialism.Lookup(s[j:next]); length > 0 {
for _, r := range s[j : j+length] {
if r < utf8.RuneSelf {
result = append(result, toLowerASCII(byte(r)))
} else {
n := utf8.EncodeRune(runeBuf[:], unicode.ToLower(r))
result = append(result, runeBuf[:n]...)
}
}
j += length - 1
skipCount = i + length
continue
}
}
if c < utf8.RuneSelf {
result = append(result, toLowerASCII(byte(c)))
} else {
n := utf8.EncodeRune(runeBuf[:], unicode.ToLower(c))
result = append(result, runeBuf[:n]...)
}
j++
}
return string(result)
}
// ToSnakeCaseASCII is similar to ToSnakeCase, but optimized for only the ASCII
// characters.
// ToSnakeCaseASCII is faster than ToSnakeCase, but doesn't work correctly if
// contains non-ASCII characters.
func ToSnakeCaseASCII(s string) string {
if s == "" {
return ""
}
result := make([]byte, 0, len(s))
for i := 0; i < len(s); i++ {
c := s[i]
if isUpperASCII(c) {
if i != 0 {
result = append(result, '_')
}
if k := i + shortestLen - 1; k < len(s) && isUpperASCII(s[k]) {
if length := commonInitialism.Lookup(s[i:nextIndex(i, len(s))]); length > 0 {
for j, buf := 0, s[i:i+length]; j < len(buf); j++ {
result = append(result, toLowerASCII(buf[j]))
}
i += length - 1
continue
}
}
}
result = append(result, toLowerASCII(c))
}
return string(result)
}
// AddCommonInitialism adds ss to list of common initialisms.
func AddCommonInitialism(ss ...string) {
mu.Lock()
defer mu.Unlock()
for _, s := range ss {
commonInitialismMap[s] = struct{}{}
}
commonInitialisms = keys(commonInitialismMap)
commonInitialism = mustDoubleArray(newDoubleArray(commonInitialisms))
longestLen = longestLength(commonInitialisms)
shortestLen = shortestLength(commonInitialisms, longestLen)
}
// DelCommonInitialism deletes ss from list of common initialisms.
func DelCommonInitialism(ss ...string) {
mu.Lock()
defer mu.Unlock()
for _, s := range ss {
delete(commonInitialismMap, s)
}
commonInitialisms = keys(commonInitialismMap)
commonInitialism = mustDoubleArray(newDoubleArray(commonInitialisms))
longestLen = longestLength(commonInitialisms)
shortestLen = shortestLength(commonInitialisms, longestLen)
}
func isUpperASCII(c byte) bool {
return 'A' <= c && c <= 'Z'
}
func isLowerASCII(c byte) bool {
return 'a' <= c && c <= 'z'
}
func toUpperASCII(c byte) byte {
if isLowerASCII(c) {
return c - ('a' - 'A')
}
return c
}
func toLowerASCII(c byte) byte {
if isUpperASCII(c) {
return c + 'a' - 'A'
}
return c
}
func nextIndex(i, maxlen int) int {
if n := i + longestLen; n < maxlen {
return n
}
return maxlen
}
func keys(m map[string]struct{}) []string {
result := make([]string, 0, len(m))
for k := range m {
result = append(result, k)
}
return result
}
func shortestLength(strs []string, shortest int) int {
for _, s := range strs {
if candidate := utf8.RuneCountInString(s); candidate < shortest {
shortest = candidate
}
}
return shortest
}
func longestLength(strs []string) (longest int) {
for _, s := range strs {
if candidate := utf8.RuneCountInString(s); candidate > longest {
longest = candidate
}
}
return longest
}

19
vendor/github.com/naoina/toml/LICENSE generated vendored Normal file
View file

@ -0,0 +1,19 @@
Copyright (c) 2014 Naoya Inada <naoina@kuune.org>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

392
vendor/github.com/naoina/toml/README.md generated vendored Normal file
View file

@ -0,0 +1,392 @@
# TOML parser and encoder library for Golang [![Build Status](https://travis-ci.org/naoina/toml.png?branch=master)](https://travis-ci.org/naoina/toml)
[TOML](https://github.com/toml-lang/toml) parser and encoder library for [Golang](http://golang.org/).
This library is compatible with TOML version [v0.4.0](https://github.com/toml-lang/toml/blob/master/versions/en/toml-v0.4.0.md).
## Installation
go get -u github.com/naoina/toml
## Usage
The following TOML save as `example.toml`.
```toml
# This is a TOML document. Boom.
title = "TOML Example"
[owner]
name = "Lance Uppercut"
dob = 1979-05-27T07:32:00-08:00 # 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] ]
# Line breaks are OK when inside arrays
hosts = [
"alpha",
"omega"
]
```
Then above TOML will mapping to `tomlConfig` struct using `toml.Unmarshal`.
```go
package main
import (
"io/ioutil"
"os"
"time"
"github.com/naoina/toml"
)
type tomlConfig struct {
Title string
Owner struct {
Name string
Dob time.Time
}
Database struct {
Server string
Ports []int
ConnectionMax uint
Enabled bool
}
Servers map[string]ServerInfo
Clients struct {
Data [][]interface{}
Hosts []string
}
}
type ServerInfo struct {
IP net.IP
DC string
}
func main() {
f, err := os.Open("example.toml")
if err != nil {
panic(err)
}
defer f.Close()
var config Config
if err := toml.NewDecoder(f).Decode(&config); err != nil {
panic(err)
}
// then to use the unmarshaled config...
fmt.Println("IP of server 'alpha':", config.Servers["alpha"].IP)
}
```
## Mappings
A key and value of TOML will map to the corresponding field.
The fields of struct for mapping must be exported.
The rules of the mapping of key are following:
#### Exact matching
```toml
timeout_seconds = 256
```
```go
type Config struct {
Timeout_seconds int
}
```
#### Camelcase matching
```toml
server_name = "srv1"
```
```go
type Config struct {
ServerName string
}
```
#### Uppercase matching
```toml
ip = "10.0.0.1"
```
```go
type Config struct {
IP string
}
```
See the following examples for the value mappings.
### String
```toml
val = "string"
```
```go
type Config struct {
Val string
}
```
### Integer
```toml
val = 100
```
```go
type Config struct {
Val int
}
```
All types that can be used are following:
* int8 (from `-128` to `127`)
* int16 (from `-32768` to `32767`)
* int32 (from `-2147483648` to `2147483647`)
* int64 (from `-9223372036854775808` to `9223372036854775807`)
* int (same as `int32` on 32bit environment, or `int64` on 64bit environment)
* uint8 (from `0` to `255`)
* uint16 (from `0` to `65535`)
* uint32 (from `0` to `4294967295`)
* uint64 (from `0` to `18446744073709551615`)
* uint (same as `uint` on 32bit environment, or `uint64` on 64bit environment)
### Float
```toml
val = 3.1415
```
```go
type Config struct {
Val float32
}
```
All types that can be used are following:
* float32
* float64
### Boolean
```toml
val = true
```
```go
type Config struct {
Val bool
}
```
### Datetime
```toml
val = 2014-09-28T21:27:39Z
```
```go
type Config struct {
Val time.Time
}
```
### Array
```toml
val = ["a", "b", "c"]
```
```go
type Config struct {
Val []string
}
```
Also following examples all can be mapped:
```toml
val1 = [1, 2, 3]
val2 = [["a", "b"], ["c", "d"]]
val3 = [[1, 2, 3], ["a", "b", "c"]]
val4 = [[1, 2, 3], [["a", "b"], [true, false]]]
```
```go
type Config struct {
Val1 []int
Val2 [][]string
Val3 [][]interface{}
Val4 [][]interface{}
}
```
### Table
```toml
[server]
type = "app"
[server.development]
ip = "10.0.0.1"
[server.production]
ip = "10.0.0.2"
```
```go
type Config struct {
Server map[string]Server
}
type Server struct {
IP string
}
```
You can also use the following struct instead of map of struct.
```go
type Config struct {
Server struct {
Development Server
Production Server
}
}
type Server struct {
IP string
}
```
### Array of Tables
```toml
[[fruit]]
name = "apple"
[fruit.physical]
color = "red"
shape = "round"
[[fruit.variety]]
name = "red delicious"
[[fruit.variety]]
name = "granny smith"
[[fruit]]
name = "banana"
[[fruit.variety]]
name = "plantain"
```
```go
type Config struct {
Fruit []struct {
Name string
Physical struct {
Color string
Shape string
}
Variety []struct {
Name string
}
}
}
```
### Using the `encoding.TextUnmarshaler` interface
Package toml supports `encoding.TextUnmarshaler` (and `encoding.TextMarshaler`). You can
use it to apply custom marshaling rules for certain types. The `UnmarshalText` method is
called with the value text found in the TOML input. TOML strings are passed unquoted.
```toml
duration = "10s"
```
```go
import time
type Duration time.Duration
// UnmarshalText implements encoding.TextUnmarshaler
func (d *Duration) UnmarshalText(data []byte) error {
duration, err := time.ParseDuration(string(data))
if err == nil {
*d = Duration(duration)
}
return err
}
// MarshalText implements encoding.TextMarshaler
func (d Duration) MarshalText() ([]byte, error) {
return []byte(time.Duration(d).String()), nil
}
type ConfigWithDuration struct {
Duration Duration
}
```
### Using the `toml.UnmarshalerRec` interface
You can also override marshaling rules specifically for TOML using the `UnmarshalerRec`
and `MarshalerRec` interfaces. These are useful if you want to control how structs or
arrays are handled. You can apply additional validation or set unexported struct fields.
Note: `encoding.TextUnmarshaler` and `encoding.TextMarshaler` should be preferred for
simple (scalar) values because they're also compatible with other formats like JSON or
YAML.
[See the UnmarshalerRec example](https://godoc.org/github.com/naoina/toml/#example_UnmarshalerRec).
### Using the `toml.Unmarshaler` interface
If you want to deal with raw TOML syntax, use the `Unmarshaler` and `Marshaler`
interfaces. Their input and output is raw TOML syntax. As such, these interfaces are
useful if you want to handle TOML at the syntax level.
[See the Unmarshaler example](https://godoc.org/github.com/naoina/toml/#example_Unmarshaler).
## API documentation
See [Godoc](http://godoc.org/github.com/naoina/toml).
## License
MIT

192
vendor/github.com/naoina/toml/ast/ast.go generated vendored Normal file
View file

@ -0,0 +1,192 @@
package ast
import (
"strconv"
"strings"
"time"
)
type Position struct {
Begin int
End int
}
type Value interface {
Pos() int
End() int
Source() string
}
type String struct {
Position Position
Value string
Data []rune
}
func (s *String) Pos() int {
return s.Position.Begin
}
func (s *String) End() int {
return s.Position.End
}
func (s *String) Source() string {
return string(s.Data)
}
type Integer struct {
Position Position
Value string
Data []rune
}
func (i *Integer) Pos() int {
return i.Position.Begin
}
func (i *Integer) End() int {
return i.Position.End
}
func (i *Integer) Source() string {
return string(i.Data)
}
func (i *Integer) Int() (int64, error) {
return strconv.ParseInt(i.Value, 10, 64)
}
type Float struct {
Position Position
Value string
Data []rune
}
func (f *Float) Pos() int {
return f.Position.Begin
}
func (f *Float) End() int {
return f.Position.End
}
func (f *Float) Source() string {
return string(f.Data)
}
func (f *Float) Float() (float64, error) {
return strconv.ParseFloat(f.Value, 64)
}
type Boolean struct {
Position Position
Value string
Data []rune
}
func (b *Boolean) Pos() int {
return b.Position.Begin
}
func (b *Boolean) End() int {
return b.Position.End
}
func (b *Boolean) Source() string {
return string(b.Data)
}
func (b *Boolean) Boolean() (bool, error) {
return strconv.ParseBool(b.Value)
}
type Datetime struct {
Position Position
Value string
Data []rune
}
func (d *Datetime) Pos() int {
return d.Position.Begin
}
func (d *Datetime) End() int {
return d.Position.End
}
func (d *Datetime) Source() string {
return string(d.Data)
}
func (d *Datetime) Time() (time.Time, error) {
switch {
case !strings.Contains(d.Value, ":"):
return time.Parse("2006-01-02", d.Value)
case !strings.Contains(d.Value, "-"):
return time.Parse("15:04:05.999999999", d.Value)
default:
return time.Parse(time.RFC3339Nano, d.Value)
}
}
type Array struct {
Position Position
Value []Value
Data []rune
}
func (a *Array) Pos() int {
return a.Position.Begin
}
func (a *Array) End() int {
return a.Position.End
}
func (a *Array) Source() string {
return string(a.Data)
}
type TableType uint8
const (
TableTypeNormal TableType = iota
TableTypeArray
)
var tableTypes = [...]string{
"normal",
"array",
}
func (t TableType) String() string {
return tableTypes[t]
}
type Table struct {
Position Position
Line int
Name string
Fields map[string]interface{}
Type TableType
Data []rune
}
func (t *Table) Pos() int {
return t.Position.Begin
}
func (t *Table) End() int {
return t.Position.End
}
func (t *Table) Source() string {
return string(t.Data)
}
type KeyValue struct {
Key string
Value Value
Line int
}

477
vendor/github.com/naoina/toml/decode.go generated vendored Normal file
View file

@ -0,0 +1,477 @@
// Package toml encodes and decodes the TOML configuration format using reflection.
//
// This library is compatible with TOML version v0.4.0.
package toml
import (
"encoding"
"fmt"
"io"
"io/ioutil"
"reflect"
"strconv"
"strings"
"time"
"github.com/naoina/toml/ast"
)
const (
tableSeparator = '.'
)
var (
escapeReplacer = strings.NewReplacer(
"\b", "\\n",
"\f", "\\f",
"\n", "\\n",
"\r", "\\r",
"\t", "\\t",
)
underscoreReplacer = strings.NewReplacer(
"_", "",
)
)
var timeType = reflect.TypeOf(time.Time{})
// Unmarshal parses the TOML data and stores the result in the value pointed to by v.
//
// Unmarshal will mapped to v that according to following rules:
//
// TOML strings to string
// TOML integers to any int type
// TOML floats to float32 or float64
// TOML booleans to bool
// TOML datetimes to time.Time
// TOML arrays to any type of slice
// TOML tables to struct or map
// TOML array tables to slice of struct or map
func Unmarshal(data []byte, v interface{}) error {
table, err := Parse(data)
if err != nil {
return err
}
if err := UnmarshalTable(table, v); err != nil {
return err
}
return nil
}
// A Decoder reads and decodes TOML from an input stream.
type Decoder struct {
r io.Reader
}
// NewDecoder returns a new Decoder that reads from r.
// Note that it reads all from r before parsing it.
func NewDecoder(r io.Reader) *Decoder {
return &Decoder{
r: r,
}
}
// Decode parses the TOML data from its input and stores it in the value pointed to by v.
// See the documentation for Unmarshal for details about the conversion of TOML into a Go value.
func (d *Decoder) Decode(v interface{}) error {
b, err := ioutil.ReadAll(d.r)
if err != nil {
return err
}
return Unmarshal(b, v)
}
// UnmarshalerRec may be implemented by types to customize their behavior when being
// unmarshaled from TOML. You can use it to implement custom validation or to set
// unexported fields.
//
// UnmarshalTOML receives a function that can be called to unmarshal the original TOML
// value into a field or variable. It is safe to call the function more than once if
// necessary.
type UnmarshalerRec interface {
UnmarshalTOML(fn func(interface{}) error) error
}
// Unmarshaler can be used to capture and process raw TOML source of a table or value.
// UnmarshalTOML must copy the input if it wishes to retain it after returning.
//
// Note: this interface is retained for backwards compatibility. You probably want
// to implement encoding.TextUnmarshaler or UnmarshalerRec instead.
type Unmarshaler interface {
UnmarshalTOML(input []byte) error
}
// UnmarshalTable applies the contents of an ast.Table to the value pointed at by v.
//
// UnmarshalTable will mapped to v that according to following rules:
//
// TOML strings to string
// TOML integers to any int type
// TOML floats to float32 or float64
// TOML booleans to bool
// TOML datetimes to time.Time
// TOML arrays to any type of slice
// TOML tables to struct or map
// TOML array tables to slice of struct or map
func UnmarshalTable(t *ast.Table, v interface{}) error {
rv := reflect.ValueOf(v)
toplevelMap := rv.Kind() == reflect.Map
if (!toplevelMap && rv.Kind() != reflect.Ptr) || rv.IsNil() {
return &invalidUnmarshalError{reflect.TypeOf(v)}
}
return unmarshalTable(rv, t, toplevelMap)
}
// used for UnmarshalerRec.
func unmarshalTableOrValue(rv reflect.Value, av interface{}) error {
if (rv.Kind() != reflect.Ptr && rv.Kind() != reflect.Map) || rv.IsNil() {
return &invalidUnmarshalError{rv.Type()}
}
rv = indirect(rv)
switch av.(type) {
case *ast.KeyValue, *ast.Table, []*ast.Table:
if err := unmarshalField(rv, av); err != nil {
return lineError(fieldLineNumber(av), err)
}
return nil
case ast.Value:
return setValue(rv, av.(ast.Value))
default:
panic(fmt.Sprintf("BUG: unhandled AST node type %T", av))
}
}
// unmarshalTable unmarshals the fields of a table into a struct or map.
//
// toplevelMap is true when rv is an (unadressable) map given to UnmarshalTable. In this
// (special) case, the map is used as-is instead of creating a new map.
func unmarshalTable(rv reflect.Value, t *ast.Table, toplevelMap bool) error {
rv = indirect(rv)
if err, ok := setUnmarshaler(rv, t); ok {
return lineError(t.Line, err)
}
switch {
case rv.Kind() == reflect.Struct:
fc := makeFieldCache(rv.Type())
for key, fieldAst := range t.Fields {
fv, fieldName, err := fc.findField(rv, key)
if err != nil {
return lineError(fieldLineNumber(fieldAst), err)
}
if err := unmarshalField(fv, fieldAst); err != nil {
return lineErrorField(fieldLineNumber(fieldAst), rv.Type().String()+"."+fieldName, err)
}
}
case rv.Kind() == reflect.Map || isEface(rv):
m := rv
if !toplevelMap {
if rv.Kind() == reflect.Interface {
m = reflect.ValueOf(make(map[string]interface{}))
} else {
m = reflect.MakeMap(rv.Type())
}
}
elemtyp := m.Type().Elem()
for key, fieldAst := range t.Fields {
kv, err := unmarshalMapKey(m.Type().Key(), key)
if err != nil {
return lineError(fieldLineNumber(fieldAst), err)
}
fv := reflect.New(elemtyp).Elem()
if err := unmarshalField(fv, fieldAst); err != nil {
return lineError(fieldLineNumber(fieldAst), err)
}
m.SetMapIndex(kv, fv)
}
if !toplevelMap {
rv.Set(m)
}
default:
return lineError(t.Line, &unmarshalTypeError{"table", "struct or map", rv.Type()})
}
return nil
}
func fieldLineNumber(fieldAst interface{}) int {
switch av := fieldAst.(type) {
case *ast.KeyValue:
return av.Line
case *ast.Table:
return av.Line
case []*ast.Table:
return av[0].Line
default:
panic(fmt.Sprintf("BUG: unhandled node type %T", fieldAst))
}
}
func unmarshalField(rv reflect.Value, fieldAst interface{}) error {
switch av := fieldAst.(type) {
case *ast.KeyValue:
return setValue(rv, av.Value)
case *ast.Table:
return unmarshalTable(rv, av, false)
case []*ast.Table:
rv = indirect(rv)
if err, ok := setUnmarshaler(rv, fieldAst); ok {
return err
}
var slice reflect.Value
switch {
case rv.Kind() == reflect.Slice:
slice = reflect.MakeSlice(rv.Type(), len(av), len(av))
case isEface(rv):
slice = reflect.ValueOf(make([]interface{}, len(av)))
default:
return &unmarshalTypeError{"array table", "slice", rv.Type()}
}
for i, tbl := range av {
vv := reflect.New(slice.Type().Elem()).Elem()
if err := unmarshalTable(vv, tbl, false); err != nil {
return err
}
slice.Index(i).Set(vv)
}
rv.Set(slice)
default:
panic(fmt.Sprintf("BUG: unhandled AST node type %T", av))
}
return nil
}
func unmarshalMapKey(typ reflect.Type, key string) (reflect.Value, error) {
rv := reflect.New(typ).Elem()
if u, ok := rv.Addr().Interface().(encoding.TextUnmarshaler); ok {
return rv, u.UnmarshalText([]byte(key))
}
switch typ.Kind() {
case reflect.String:
rv.SetString(key)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
i, err := strconv.ParseInt(key, 10, int(typ.Size()*8))
if err != nil {
return rv, convertNumError(typ.Kind(), err)
}
rv.SetInt(i)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
i, err := strconv.ParseUint(key, 10, int(typ.Size()*8))
if err != nil {
return rv, convertNumError(typ.Kind(), err)
}
rv.SetUint(i)
default:
return rv, fmt.Errorf("invalid map key type %s", typ)
}
return rv, nil
}
func setValue(lhs reflect.Value, val ast.Value) error {
lhs = indirect(lhs)
if err, ok := setUnmarshaler(lhs, val); ok {
return err
}
if err, ok := setTextUnmarshaler(lhs, val); ok {
return err
}
switch v := val.(type) {
case *ast.Integer:
return setInt(lhs, v)
case *ast.Float:
return setFloat(lhs, v)
case *ast.String:
return setString(lhs, v)
case *ast.Boolean:
return setBoolean(lhs, v)
case *ast.Datetime:
return setDatetime(lhs, v)
case *ast.Array:
return setArray(lhs, v)
default:
panic(fmt.Sprintf("BUG: unhandled node type %T", v))
}
}
func indirect(rv reflect.Value) reflect.Value {
for rv.Kind() == reflect.Ptr {
if rv.IsNil() {
rv.Set(reflect.New(rv.Type().Elem()))
}
rv = rv.Elem()
}
return rv
}
func setUnmarshaler(lhs reflect.Value, av interface{}) (error, bool) {
if lhs.CanAddr() {
if u, ok := lhs.Addr().Interface().(UnmarshalerRec); ok {
err := u.UnmarshalTOML(func(v interface{}) error {
return unmarshalTableOrValue(reflect.ValueOf(v), av)
})
return err, true
}
if u, ok := lhs.Addr().Interface().(Unmarshaler); ok {
return u.UnmarshalTOML(unmarshalerSource(av)), true
}
}
return nil, false
}
func unmarshalerSource(av interface{}) []byte {
var source []byte
switch av := av.(type) {
case []*ast.Table:
for i, tab := range av {
source = append(source, tab.Source()...)
if i != len(av)-1 {
source = append(source, '\n')
}
}
case ast.Value:
source = []byte(av.Source())
default:
panic(fmt.Sprintf("BUG: unhandled node type %T", av))
}
return source
}
func setTextUnmarshaler(lhs reflect.Value, val ast.Value) (error, bool) {
if !lhs.CanAddr() {
return nil, false
}
u, ok := lhs.Addr().Interface().(encoding.TextUnmarshaler)
if !ok || lhs.Type() == timeType {
return nil, false
}
var data string
switch val := val.(type) {
case *ast.Array:
return &unmarshalTypeError{"array", "", lhs.Type()}, true
case *ast.String:
data = val.Value
default:
data = val.Source()
}
return u.UnmarshalText([]byte(data)), true
}
func setInt(fv reflect.Value, v *ast.Integer) error {
k := fv.Kind()
switch {
case k >= reflect.Int && k <= reflect.Int64:
i, err := strconv.ParseInt(v.Value, 10, int(fv.Type().Size()*8))
if err != nil {
return convertNumError(fv.Kind(), err)
}
fv.SetInt(i)
case k >= reflect.Uint && k <= reflect.Uintptr:
i, err := strconv.ParseUint(v.Value, 10, int(fv.Type().Size()*8))
if err != nil {
return convertNumError(fv.Kind(), err)
}
fv.SetUint(i)
case isEface(fv):
i, err := strconv.ParseInt(v.Value, 10, 64)
if err != nil {
return convertNumError(reflect.Int64, err)
}
fv.Set(reflect.ValueOf(i))
default:
return &unmarshalTypeError{"integer", "", fv.Type()}
}
return nil
}
func setFloat(fv reflect.Value, v *ast.Float) error {
f, err := v.Float()
if err != nil {
return err
}
switch {
case fv.Kind() == reflect.Float32 || fv.Kind() == reflect.Float64:
if fv.OverflowFloat(f) {
return &overflowError{fv.Kind(), v.Value}
}
fv.SetFloat(f)
case isEface(fv):
fv.Set(reflect.ValueOf(f))
default:
return &unmarshalTypeError{"float", "", fv.Type()}
}
return nil
}
func setString(fv reflect.Value, v *ast.String) error {
switch {
case fv.Kind() == reflect.String:
fv.SetString(v.Value)
case isEface(fv):
fv.Set(reflect.ValueOf(v.Value))
default:
return &unmarshalTypeError{"string", "", fv.Type()}
}
return nil
}
func setBoolean(fv reflect.Value, v *ast.Boolean) error {
b, _ := v.Boolean()
switch {
case fv.Kind() == reflect.Bool:
fv.SetBool(b)
case isEface(fv):
fv.Set(reflect.ValueOf(b))
default:
return &unmarshalTypeError{"boolean", "", fv.Type()}
}
return nil
}
func setDatetime(rv reflect.Value, v *ast.Datetime) error {
t, err := v.Time()
if err != nil {
return err
}
if !timeType.AssignableTo(rv.Type()) {
return &unmarshalTypeError{"datetime", "", rv.Type()}
}
rv.Set(reflect.ValueOf(t))
return nil
}
func setArray(rv reflect.Value, v *ast.Array) error {
var slicetyp reflect.Type
switch {
case rv.Kind() == reflect.Slice:
slicetyp = rv.Type()
case isEface(rv):
slicetyp = reflect.SliceOf(rv.Type())
default:
return &unmarshalTypeError{"array", "slice", rv.Type()}
}
if len(v.Value) == 0 {
// Ensure defined slices are always set to a non-nil value.
rv.Set(reflect.MakeSlice(slicetyp, 0, 0))
return nil
}
tomltyp := reflect.TypeOf(v.Value[0])
slice := reflect.MakeSlice(slicetyp, len(v.Value), len(v.Value))
typ := slicetyp.Elem()
for i, vv := range v.Value {
if i > 0 && tomltyp != reflect.TypeOf(vv) {
return errArrayMultiType
}
tmp := reflect.New(typ).Elem()
if err := setValue(tmp, vv); err != nil {
return err
}
slice.Index(i).Set(tmp)
}
rv.Set(slice)
return nil
}
func isEface(rv reflect.Value) bool {
return rv.Kind() == reflect.Interface && rv.Type().NumMethod() == 0
}

397
vendor/github.com/naoina/toml/encode.go generated vendored Normal file
View file

@ -0,0 +1,397 @@
package toml
import (
"bytes"
"encoding"
"fmt"
"io"
"reflect"
"sort"
"strconv"
"time"
"github.com/naoina/go-stringutil"
"github.com/naoina/toml/ast"
)
const (
tagOmitempty = "omitempty"
tagSkip = "-"
)
// Marshal returns the TOML encoding of v.
//
// Struct values encode as TOML. Each exported struct field becomes a field of
// the TOML structure unless
// - the field's tag is "-", or
// - the field is empty and its tag specifies the "omitempty" option.
// The "toml" key in the struct field's tag value is the key name, followed by
// an optional comma and options. Examples:
//
// // Field is ignored by this package.
// Field int `toml:"-"`
//
// // Field appears in TOML as key "myName".
// Field int `toml:"myName"`
//
// // Field appears in TOML as key "myName" and the field is omitted from the
// // result of encoding if its value is empty.
// Field int `toml:"myName,omitempty"`
//
// // Field appears in TOML as key "field", but the field is skipped if
// // empty. Note the leading comma.
// Field int `toml:",omitempty"`
func Marshal(v interface{}) ([]byte, error) {
buf := new(bytes.Buffer)
err := NewEncoder(buf).Encode(v)
return buf.Bytes(), err
}
// A Encoder writes TOML to an output stream.
type Encoder struct {
w io.Writer
}
// NewEncoder returns a new Encoder that writes to w.
func NewEncoder(w io.Writer) *Encoder {
return &Encoder{w: w}
}
// Encode writes the TOML of v to the stream.
// See the documentation for Marshal for details about the conversion of Go values to TOML.
func (e *Encoder) Encode(v interface{}) error {
rv := reflect.ValueOf(v)
for rv.Kind() == reflect.Ptr {
if rv.IsNil() {
return &marshalNilError{rv.Type()}
}
rv = rv.Elem()
}
buf := &tableBuf{typ: ast.TableTypeNormal}
var err error
switch rv.Kind() {
case reflect.Struct:
err = buf.structFields(rv)
case reflect.Map:
err = buf.mapFields(rv)
default:
err = &marshalTableError{rv.Type()}
}
if err != nil {
return err
}
return buf.writeTo(e.w, "")
}
// Marshaler can be implemented to override the encoding of TOML values. The returned text
// must be a simple TOML value (i.e. not a table) and is inserted into marshaler output.
//
// This interface exists for backwards-compatibility reasons. You probably want to
// implement encoding.TextMarshaler or MarshalerRec instead.
type Marshaler interface {
MarshalTOML() ([]byte, error)
}
// MarshalerRec can be implemented to override the TOML encoding of a type.
// The returned value is marshaled in place of the receiver.
type MarshalerRec interface {
MarshalTOML() (interface{}, error)
}
type tableBuf struct {
name string // already escaped / quoted
body []byte
children []*tableBuf
typ ast.TableType
arrayDepth int
}
func (b *tableBuf) writeTo(w io.Writer, prefix string) error {
key := b.name // TODO: escape dots
if prefix != "" {
key = prefix + "." + key
}
if b.name != "" {
head := "[" + key + "]"
if b.typ == ast.TableTypeArray {
head = "[" + head + "]"
}
head += "\n"
if _, err := io.WriteString(w, head); err != nil {
return err
}
}
if _, err := w.Write(b.body); err != nil {
return err
}
for i, child := range b.children {
if len(b.body) > 0 || i > 0 {
if _, err := w.Write([]byte("\n")); err != nil {
return err
}
}
if err := child.writeTo(w, key); err != nil {
return err
}
}
return nil
}
func (b *tableBuf) newChild(name string) *tableBuf {
child := &tableBuf{name: quoteName(name), typ: ast.TableTypeNormal}
if b.arrayDepth > 0 {
child.typ = ast.TableTypeArray
}
return child
}
func (b *tableBuf) addChild(child *tableBuf) {
// Empty table elision: we can avoid writing a table that doesn't have any keys on its
// own. Array tables can't be elided because they define array elements (which would
// be missing if elided).
if len(child.body) == 0 && child.typ == ast.TableTypeNormal {
for _, gchild := range child.children {
gchild.name = child.name + "." + gchild.name
b.addChild(gchild)
}
return
}
b.children = append(b.children, child)
}
func (b *tableBuf) structFields(rv reflect.Value) error {
rt := rv.Type()
for i := 0; i < rv.NumField(); i++ {
ft := rt.Field(i)
if ft.PkgPath != "" && !ft.Anonymous { // not exported
continue
}
name, rest := extractTag(ft.Tag.Get(fieldTagName))
if name == tagSkip {
continue
}
fv := rv.Field(i)
if rest == tagOmitempty && isEmptyValue(fv) {
continue
}
if name == "" {
name = stringutil.ToSnakeCase(ft.Name)
}
if err := b.field(name, fv); err != nil {
return err
}
}
return nil
}
type mapKeyList []struct {
key string
value reflect.Value
}
func (l mapKeyList) Len() int { return len(l) }
func (l mapKeyList) Swap(i, j int) { l[i], l[j] = l[j], l[i] }
func (l mapKeyList) Less(i, j int) bool { return l[i].key < l[j].key }
func (b *tableBuf) mapFields(rv reflect.Value) error {
keys := rv.MapKeys()
keylist := make(mapKeyList, len(keys))
for i, key := range keys {
var err error
keylist[i].key, err = encodeMapKey(key)
if err != nil {
return err
}
keylist[i].value = rv.MapIndex(key)
}
sort.Sort(keylist)
for _, kv := range keylist {
if err := b.field(kv.key, kv.value); err != nil {
return err
}
}
return nil
}
func (b *tableBuf) field(name string, rv reflect.Value) error {
off := len(b.body)
b.body = append(b.body, quoteName(name)...)
b.body = append(b.body, " = "...)
isTable, err := b.value(rv, name)
if isTable {
b.body = b.body[:off] // rub out "key ="
} else {
b.body = append(b.body, '\n')
}
return err
}
func (b *tableBuf) value(rv reflect.Value, name string) (bool, error) {
isMarshaler, isTable, err := b.marshaler(rv, name)
if isMarshaler {
return isTable, err
}
switch rv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
b.body = strconv.AppendInt(b.body, rv.Int(), 10)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
b.body = strconv.AppendUint(b.body, rv.Uint(), 10)
case reflect.Float32, reflect.Float64:
b.body = strconv.AppendFloat(b.body, rv.Float(), 'e', -1, 64)
case reflect.Bool:
b.body = strconv.AppendBool(b.body, rv.Bool())
case reflect.String:
b.body = strconv.AppendQuote(b.body, rv.String())
case reflect.Ptr, reflect.Interface:
if rv.IsNil() {
return false, &marshalNilError{rv.Type()}
}
return b.value(rv.Elem(), name)
case reflect.Slice, reflect.Array:
rvlen := rv.Len()
if rvlen == 0 {
b.body = append(b.body, '[', ']')
return false, nil
}
b.arrayDepth++
wroteElem := false
b.body = append(b.body, '[')
for i := 0; i < rvlen; i++ {
isTable, err := b.value(rv.Index(i), name)
if err != nil {
return isTable, err
}
wroteElem = wroteElem || !isTable
if wroteElem {
if i < rvlen-1 {
b.body = append(b.body, ',', ' ')
} else {
b.body = append(b.body, ']')
}
}
}
if !wroteElem {
b.body = b.body[:len(b.body)-1] // rub out '['
}
b.arrayDepth--
return !wroteElem, nil
case reflect.Struct:
child := b.newChild(name)
err := child.structFields(rv)
b.addChild(child)
return true, err
case reflect.Map:
child := b.newChild(name)
err := child.mapFields(rv)
b.addChild(child)
return true, err
default:
return false, fmt.Errorf("toml: marshal: unsupported type %v", rv.Kind())
}
return false, nil
}
func (b *tableBuf) marshaler(rv reflect.Value, name string) (handled, isTable bool, err error) {
switch t := rv.Interface().(type) {
case encoding.TextMarshaler:
enc, err := t.MarshalText()
if err != nil {
return true, false, err
}
b.body = encodeTextMarshaler(b.body, string(enc))
return true, false, nil
case MarshalerRec:
newval, err := t.MarshalTOML()
if err != nil {
return true, false, err
}
isTable, err = b.value(reflect.ValueOf(newval), name)
return true, isTable, err
case Marshaler:
enc, err := t.MarshalTOML()
if err != nil {
return true, false, err
}
b.body = append(b.body, enc...)
return true, false, nil
}
return false, false, nil
}
func encodeTextMarshaler(buf []byte, v string) []byte {
// Emit the value without quotes if possible.
if v == "true" || v == "false" {
return append(buf, v...)
} else if _, err := time.Parse(time.RFC3339Nano, v); err == nil {
return append(buf, v...)
} else if _, err := strconv.ParseInt(v, 10, 64); err == nil {
return append(buf, v...)
} else if _, err := strconv.ParseUint(v, 10, 64); err == nil {
return append(buf, v...)
} else if _, err := strconv.ParseFloat(v, 64); err == nil {
return append(buf, v...)
}
return strconv.AppendQuote(buf, v)
}
func encodeMapKey(rv reflect.Value) (string, error) {
if rv.Kind() == reflect.String {
return rv.String(), nil
}
if tm, ok := rv.Interface().(encoding.TextMarshaler); ok {
b, err := tm.MarshalText()
return string(b), err
}
switch rv.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return strconv.FormatInt(rv.Int(), 10), nil
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return strconv.FormatUint(rv.Uint(), 10), nil
}
return "", fmt.Errorf("toml: invalid map key type %v", rv.Type())
}
func isEmptyValue(v reflect.Value) bool {
switch v.Kind() {
case reflect.Array:
// encoding/json treats all arrays with non-zero length as non-empty. We check the
// array content here because zero-length arrays are almost never used.
len := v.Len()
for i := 0; i < len; i++ {
if !isEmptyValue(v.Index(i)) {
return false
}
}
return true
case reflect.Map, reflect.Slice, reflect.String:
return v.Len() == 0
case reflect.Bool:
return !v.Bool()
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return v.Int() == 0
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return v.Uint() == 0
case reflect.Float32, reflect.Float64:
return v.Float() == 0
case reflect.Interface, reflect.Ptr:
return v.IsNil()
}
return false
}
func quoteName(s string) string {
if len(s) == 0 {
return strconv.Quote(s)
}
for _, r := range s {
if r >= '0' && r <= '9' || r >= 'A' && r <= 'Z' || r >= 'a' && r <= 'z' || r == '-' || r == '_' {
continue
}
return strconv.Quote(s)
}
return s
}

107
vendor/github.com/naoina/toml/error.go generated vendored Normal file
View file

@ -0,0 +1,107 @@
package toml
import (
"errors"
"fmt"
"reflect"
"strconv"
)
var (
errArrayMultiType = errors.New("array can't contain multiple types")
)
// LineError is returned by Unmarshal, UnmarshalTable and Parse
// if the error is local to a line.
type LineError struct {
Line int
StructField string
Err error
}
func (err *LineError) Error() string {
field := ""
if err.StructField != "" {
field = "(" + err.StructField + ") "
}
return fmt.Sprintf("line %d: %s%v", err.Line, field, err.Err)
}
func lineError(line int, err error) error {
if err == nil {
return nil
}
if _, ok := err.(*LineError); ok {
return err
}
return &LineError{Line: line, Err: err}
}
func lineErrorField(line int, field string, err error) error {
if lerr, ok := err.(*LineError); ok {
return lerr
} else if err != nil {
err = &LineError{Line: line, StructField: field, Err: err}
}
return err
}
type overflowError struct {
kind reflect.Kind
v string
}
func (err *overflowError) Error() string {
return fmt.Sprintf("value %s is out of range for %v", err.v, err.kind)
}
func convertNumError(kind reflect.Kind, err error) error {
if numerr, ok := err.(*strconv.NumError); ok && numerr.Err == strconv.ErrRange {
return &overflowError{kind, numerr.Num}
}
return err
}
type invalidUnmarshalError struct {
typ reflect.Type
}
func (err *invalidUnmarshalError) Error() string {
if err.typ == nil {
return "toml: Unmarshal(nil)"
}
if err.typ.Kind() != reflect.Ptr {
return "toml: Unmarshal(non-pointer " + err.typ.String() + ")"
}
return "toml: Unmarshal(nil " + err.typ.String() + ")"
}
type unmarshalTypeError struct {
what string
want string
typ reflect.Type
}
func (err *unmarshalTypeError) Error() string {
msg := fmt.Sprintf("cannot unmarshal TOML %s into %s", err.what, err.typ)
if err.want != "" {
msg += " (need " + err.want + ")"
}
return msg
}
type marshalNilError struct {
typ reflect.Type
}
func (err *marshalNilError) Error() string {
return fmt.Sprintf("toml: cannot marshal nil %s", err.typ)
}
type marshalTableError struct {
typ reflect.Type
}
func (err *marshalTableError) Error() string {
return fmt.Sprintf("toml: cannot marshal %s as table, want struct or map type", err.typ)
}

376
vendor/github.com/naoina/toml/parse.go generated vendored Normal file
View file

@ -0,0 +1,376 @@
package toml
import (
"errors"
"fmt"
"strconv"
"strings"
"github.com/naoina/toml/ast"
)
// The parser is generated by github.com/pointlander/peg. To regenerate it, do:
//
// go get -u github.com/pointlander/peg
// go generate .
//go:generate peg -switch -inline parse.peg
var errParse = errors.New("invalid TOML syntax")
// Parse returns an AST representation of TOML.
// The toplevel is represented by a table.
func Parse(data []byte) (*ast.Table, error) {
d := &parseState{p: &tomlParser{Buffer: string(data)}}
d.init()
if err := d.parse(); err != nil {
return nil, err
}
return d.p.toml.table, nil
}
type parseState struct {
p *tomlParser
}
func (d *parseState) init() {
d.p.Init()
d.p.toml.init(d.p.buffer)
}
func (d *parseState) parse() error {
if err := d.p.Parse(); err != nil {
if err, ok := err.(*parseError); ok {
return lineError(err.Line(), errParse)
}
return err
}
return d.execute()
}
func (d *parseState) execute() (err error) {
defer func() {
if e := recover(); e != nil {
lerr, ok := e.(*LineError)
if !ok {
panic(e)
}
err = lerr
}
}()
d.p.Execute()
return nil
}
func (e *parseError) Line() int {
tokens := []token32{e.max}
positions, p := make([]int, 2*len(tokens)), 0
for _, token := range tokens {
positions[p], p = int(token.begin), p+1
positions[p], p = int(token.end), p+1
}
for _, t := range translatePositions(e.p.buffer, positions) {
if e.p.line < t.line {
e.p.line = t.line
}
}
return e.p.line
}
type stack struct {
key string
table *ast.Table
}
type array struct {
parent *array
child *array
current *ast.Array
line int
}
type toml struct {
table *ast.Table
line int
currentTable *ast.Table
s string
key string
val ast.Value
arr *array
stack []*stack
skip bool
}
func (p *toml) init(data []rune) {
p.line = 1
p.table = p.newTable(ast.TableTypeNormal, "")
p.table.Position.End = len(data) - 1
p.table.Data = data[:len(data)-1] // truncate the end_symbol added by PEG parse generator.
p.currentTable = p.table
}
func (p *toml) Error(err error) {
panic(lineError(p.line, err))
}
func (p *tomlParser) SetTime(begin, end int) {
p.val = &ast.Datetime{
Position: ast.Position{Begin: begin, End: end},
Data: p.buffer[begin:end],
Value: string(p.buffer[begin:end]),
}
}
func (p *tomlParser) SetFloat64(begin, end int) {
p.val = &ast.Float{
Position: ast.Position{Begin: begin, End: end},
Data: p.buffer[begin:end],
Value: underscoreReplacer.Replace(string(p.buffer[begin:end])),
}
}
func (p *tomlParser) SetInt64(begin, end int) {
p.val = &ast.Integer{
Position: ast.Position{Begin: begin, End: end},
Data: p.buffer[begin:end],
Value: underscoreReplacer.Replace(string(p.buffer[begin:end])),
}
}
func (p *tomlParser) SetString(begin, end int) {
p.val = &ast.String{
Position: ast.Position{Begin: begin, End: end},
Data: p.buffer[begin:end],
Value: p.s,
}
p.s = ""
}
func (p *tomlParser) SetBool(begin, end int) {
p.val = &ast.Boolean{
Position: ast.Position{Begin: begin, End: end},
Data: p.buffer[begin:end],
Value: string(p.buffer[begin:end]),
}
}
func (p *tomlParser) StartArray() {
if p.arr == nil {
p.arr = &array{line: p.line, current: &ast.Array{}}
return
}
p.arr.child = &array{parent: p.arr, line: p.line, current: &ast.Array{}}
p.arr = p.arr.child
}
func (p *tomlParser) AddArrayVal() {
if p.arr.current == nil {
p.arr.current = &ast.Array{}
}
p.arr.current.Value = append(p.arr.current.Value, p.val)
}
func (p *tomlParser) SetArray(begin, end int) {
p.arr.current.Position = ast.Position{Begin: begin, End: end}
p.arr.current.Data = p.buffer[begin:end]
p.val = p.arr.current
p.arr = p.arr.parent
}
func (p *toml) SetTable(buf []rune, begin, end int) {
p.setTable(p.table, buf, begin, end)
}
func (p *toml) setTable(parent *ast.Table, buf []rune, begin, end int) {
name := string(buf[begin:end])
names := splitTableKey(name)
parent, err := p.lookupTable(parent, names[:len(names)-1])
if err != nil {
p.Error(err)
}
last := names[len(names)-1]
tbl := p.newTable(ast.TableTypeNormal, last)
switch v := parent.Fields[last].(type) {
case nil:
parent.Fields[last] = tbl
case []*ast.Table:
p.Error(fmt.Errorf("table `%s' is in conflict with array table in line %d", name, v[0].Line))
case *ast.Table:
if (v.Position == ast.Position{}) {
// This table was created as an implicit parent.
// Replace it with the real defined table.
tbl.Fields = v.Fields
parent.Fields[last] = tbl
} else {
p.Error(fmt.Errorf("table `%s' is in conflict with table in line %d", name, v.Line))
}
case *ast.KeyValue:
p.Error(fmt.Errorf("table `%s' is in conflict with line %d", name, v.Line))
default:
p.Error(fmt.Errorf("BUG: table `%s' is in conflict but it's unknown type `%T'", last, v))
}
p.currentTable = tbl
}
func (p *toml) newTable(typ ast.TableType, name string) *ast.Table {
return &ast.Table{
Line: p.line,
Name: name,
Type: typ,
Fields: make(map[string]interface{}),
}
}
func (p *tomlParser) SetTableString(begin, end int) {
p.currentTable.Data = p.buffer[begin:end]
p.currentTable.Position.Begin = begin
p.currentTable.Position.End = end
}
func (p *toml) SetArrayTable(buf []rune, begin, end int) {
p.setArrayTable(p.table, buf, begin, end)
}
func (p *toml) setArrayTable(parent *ast.Table, buf []rune, begin, end int) {
name := string(buf[begin:end])
names := splitTableKey(name)
parent, err := p.lookupTable(parent, names[:len(names)-1])
if err != nil {
p.Error(err)
}
last := names[len(names)-1]
tbl := p.newTable(ast.TableTypeArray, last)
switch v := parent.Fields[last].(type) {
case nil:
parent.Fields[last] = []*ast.Table{tbl}
case []*ast.Table:
parent.Fields[last] = append(v, tbl)
case *ast.Table:
p.Error(fmt.Errorf("array table `%s' is in conflict with table in line %d", name, v.Line))
case *ast.KeyValue:
p.Error(fmt.Errorf("array table `%s' is in conflict with line %d", name, v.Line))
default:
p.Error(fmt.Errorf("BUG: array table `%s' is in conflict but it's unknown type `%T'", name, v))
}
p.currentTable = tbl
}
func (p *toml) StartInlineTable() {
p.skip = false
p.stack = append(p.stack, &stack{p.key, p.currentTable})
buf := []rune(p.key)
if p.arr == nil {
p.setTable(p.currentTable, buf, 0, len(buf))
} else {
p.setArrayTable(p.currentTable, buf, 0, len(buf))
}
}
func (p *toml) EndInlineTable() {
st := p.stack[len(p.stack)-1]
p.key, p.currentTable = st.key, st.table
p.stack[len(p.stack)-1] = nil
p.stack = p.stack[:len(p.stack)-1]
p.skip = true
}
func (p *toml) AddLineCount(i int) {
p.line += i
}
func (p *toml) SetKey(buf []rune, begin, end int) {
p.key = string(buf[begin:end])
}
func (p *toml) AddKeyValue() {
if p.skip {
p.skip = false
return
}
if val, exists := p.currentTable.Fields[p.key]; exists {
switch v := val.(type) {
case *ast.Table:
p.Error(fmt.Errorf("key `%s' is in conflict with table in line %d", p.key, v.Line))
case *ast.KeyValue:
p.Error(fmt.Errorf("key `%s' is in conflict with line %xd", p.key, v.Line))
default:
p.Error(fmt.Errorf("BUG: key `%s' is in conflict but it's unknown type `%T'", p.key, v))
}
}
p.currentTable.Fields[p.key] = &ast.KeyValue{Key: p.key, Value: p.val, Line: p.line}
}
func (p *toml) SetBasicString(buf []rune, begin, end int) {
p.s = p.unquote(string(buf[begin:end]))
}
func (p *toml) SetMultilineString() {
p.s = p.unquote(`"` + escapeReplacer.Replace(strings.TrimLeft(p.s, "\r\n")) + `"`)
}
func (p *toml) AddMultilineBasicBody(buf []rune, begin, end int) {
p.s += string(buf[begin:end])
}
func (p *toml) SetLiteralString(buf []rune, begin, end int) {
p.s = string(buf[begin:end])
}
func (p *toml) SetMultilineLiteralString(buf []rune, begin, end int) {
p.s = strings.TrimLeft(string(buf[begin:end]), "\r\n")
}
func (p *toml) unquote(s string) string {
s, err := strconv.Unquote(s)
if err != nil {
p.Error(err)
}
return s
}
func (p *toml) lookupTable(t *ast.Table, keys []string) (*ast.Table, error) {
for _, s := range keys {
val, exists := t.Fields[s]
if !exists {
tbl := p.newTable(ast.TableTypeNormal, s)
t.Fields[s] = tbl
t = tbl
continue
}
switch v := val.(type) {
case *ast.Table:
t = v
case []*ast.Table:
t = v[len(v)-1]
case *ast.KeyValue:
return nil, fmt.Errorf("key `%s' is in conflict with line %d", s, v.Line)
default:
return nil, fmt.Errorf("BUG: key `%s' is in conflict but it's unknown type `%T'", s, v)
}
}
return t, nil
}
func splitTableKey(tk string) []string {
key := make([]byte, 0, 1)
keys := make([]string, 0, 1)
inQuote := false
for i := 0; i < len(tk); i++ {
k := tk[i]
switch {
case k == tableSeparator && !inQuote:
keys = append(keys, string(key))
key = key[:0] // reuse buffer.
case k == '"':
inQuote = !inQuote
case (k == ' ' || k == '\t') && !inQuote:
// skip.
default:
key = append(key, k)
}
}
keys = append(keys, string(key))
return keys
}

145
vendor/github.com/naoina/toml/parse.peg generated vendored Normal file
View file

@ -0,0 +1,145 @@
package toml
type tomlParser Peg {
toml
}
TOML <- Expression (newline Expression)* newline? !. { _ = buffer }
Expression <- (
<ws table ws comment? (wsnl keyval ws comment?)*> { p.SetTableString(begin, end) }
/ ws keyval ws comment?
/ ws comment?
/ ws
)
newline <- <[\r\n]+> { p.AddLineCount(end - begin) }
ws <- [ \t]*
wsnl <- (
[ \t]
/ <[\r\n]> { p.AddLineCount(end - begin) }
)*
comment <- '#' <[\t -\0x10FFFF]*>
keyval <- key ws '=' ws val { p.AddKeyValue() }
key <- bareKey / quotedKey
bareKey <- <[0-9A-Za-z\-_]+> { p.SetKey(p.buffer, begin, end) }
quotedKey <- '"' <basicChar+> '"' { p.SetKey(p.buffer, begin-1, end+1) }
val <- (
<datetime> { p.SetTime(begin, end) }
/ <float> { p.SetFloat64(begin, end) }
/ <integer> { p.SetInt64(begin, end) }
/ <string> { p.SetString(begin, end) }
/ <boolean> { p.SetBool(begin, end) }
/ <array> { p.SetArray(begin, end) }
/ inlineTable
)
table <- stdTable / arrayTable
stdTable <- '[' ws <tableKey> ws ']' { p.SetTable(p.buffer, begin, end) }
arrayTable <- '[[' ws <tableKey> ws ']]' { p.SetArrayTable(p.buffer, begin, end) }
inlineTable <- (
'{' { p.StartInlineTable() }
ws inlineTableKeyValues ws
'}' { p.EndInlineTable() }
)
inlineTableKeyValues <- (keyval inlineTableValSep?)*
tableKey <- key (tableKeySep key)*
tableKeySep <- ws '.' ws
inlineTableValSep <- ws ',' ws
integer <- [\-+]? int
int <- [1-9] (digit / '_' digit)+ / digit
float <- integer (frac exp? / frac? exp)
frac <- '.' digit (digit / '_' digit)*
exp <- [eE] [\-+]? digit (digit / '_' digit)*
string <- (
mlLiteralString
/ literalString
/ mlBasicString
/ basicString
)
basicString <- <'"' basicChar* '"'> { p.SetBasicString(p.buffer, begin, end) }
basicChar <- basicUnescaped / escaped
escaped <- escape ([btnfr"/\\] / 'u' hexQuad / 'U' hexQuad hexQuad)
basicUnescaped <- [ -!#-\[\]-\0x10FFFF]
escape <- '\\'
mlBasicString <- '"""' mlBasicBody '"""' { p.SetMultilineString() }
mlBasicBody <- (
<basicChar / newline> { p.AddMultilineBasicBody(p.buffer, begin, end) }
/ escape newline wsnl
)*
literalString <- "'" <literalChar*> "'" { p.SetLiteralString(p.buffer, begin, end) }
literalChar <- [\t -&(-\0x10FFFF]
mlLiteralString <- "'''" <mlLiteralBody> "'''" { p.SetMultilineLiteralString(p.buffer, begin, end) }
mlLiteralBody <- (!"'''" (mlLiteralChar / newline))*
mlLiteralChar <- [\t -\0x10FFFF]
hexdigit <- [0-9A-Fa-f]
hexQuad <- hexdigit hexdigit hexdigit hexdigit
boolean <- 'true' / 'false'
dateFullYear <- digitQuad
dateMonth <- digitDual
dateMDay <- digitDual
timeHour <- digitDual
timeMinute <- digitDual
timeSecond <- digitDual
timeSecfrac <- '.' digit+
timeNumoffset <- [\-+] timeHour ':' timeMinute
timeOffset <- 'Z' / timeNumoffset
partialTime <- timeHour ':' timeMinute ':' timeSecond timeSecfrac?
fullDate <- dateFullYear '-' dateMonth '-' dateMDay
fullTime <- partialTime timeOffset
datetime <- (fullDate ('T' fullTime)?) / partialTime
digit <- [0-9]
digitDual <- digit digit
digitQuad <- digitDual digitDual
array <- (
'[' { p.StartArray() }
wsnl arrayValues? wsnl
']'
)
arrayValues <- (
val { p.AddArrayVal() }
(
wsnl comment?
wsnl arraySep
wsnl comment?
wsnl val { p.AddArrayVal() }
)*
wsnl arraySep?
wsnl comment?
)
arraySep <- ','

2556
vendor/github.com/naoina/toml/parse.peg.go generated vendored Normal file

File diff suppressed because it is too large Load diff

65
vendor/github.com/naoina/toml/util.go generated vendored Normal file
View file

@ -0,0 +1,65 @@
package toml
import (
"fmt"
"reflect"
"strings"
)
const fieldTagName = "toml"
// fieldCache maps normalized field names to their position in a struct.
type fieldCache struct {
named map[string]fieldInfo // fields with an explicit name in tag
auto map[string]fieldInfo // fields with auto-assigned normalized names
}
type fieldInfo struct {
index []int
name string
ignored bool
}
func makeFieldCache(rt reflect.Type) fieldCache {
named, auto := make(map[string]fieldInfo), make(map[string]fieldInfo)
for i := 0; i < rt.NumField(); i++ {
ft := rt.Field(i)
// skip unexported fields
if ft.PkgPath != "" && !ft.Anonymous {
continue
}
col, _ := extractTag(ft.Tag.Get(fieldTagName))
info := fieldInfo{index: ft.Index, name: ft.Name, ignored: col == "-"}
if col == "" || col == "-" {
auto[normFieldName(ft.Name)] = info
} else {
named[col] = info
}
}
return fieldCache{named, auto}
}
func (fc fieldCache) findField(rv reflect.Value, name string) (reflect.Value, string, error) {
info, found := fc.named[name]
if !found {
info, found = fc.auto[normFieldName(name)]
}
if !found {
return reflect.Value{}, "", fmt.Errorf("field corresponding to `%s' is not defined in %v", name, rv.Type())
} else if info.ignored {
return reflect.Value{}, "", fmt.Errorf("field corresponding to `%s' in %v cannot be set through TOML", name, rv.Type())
}
return rv.FieldByIndex(info.index), info.name, nil
}
func normFieldName(s string) string {
return strings.Replace(strings.ToLower(s), "_", "", -1)
}
func extractTag(tag string) (col, rest string) {
tags := strings.SplitN(tag, ",", 2)
if len(tags) == 2 {
return strings.TrimSpace(tags[0]), strings.TrimSpace(tags[1])
}
return strings.TrimSpace(tags[0]), ""
}

18
vendor/vendor.json vendored
View file

@ -201,6 +201,24 @@
"revision": "ad45545899c7b13c020ea92b2072220eefad42b8",
"revisionTime": "2015-03-14T17:03:34Z"
},
{
"checksumSHA1": "A6nZdZ1/lTOVINhIndyw2ZWN9JU=",
"path": "github.com/naoina/go-stringutil",
"revision": "6b638e95a32d0c1131db0e7fe83775cbea4a0d0b",
"revisionTime": "2015-11-18T23:44:43Z"
},
{
"checksumSHA1": "qzMyvEe802B17Amr94Lq2KvFif4=",
"path": "github.com/naoina/toml",
"revision": "5fabdde119404993567f090673d9fb7b05b57833",
"revisionTime": "2017-04-05T10:49:32Z"
},
{
"checksumSHA1": "xZBlSMT5o/A+EDOro6KbfHZwSNc=",
"path": "github.com/naoina/toml/ast",
"revision": "eb52202f758b98ac5b1a8eb26f36455205d688f0",
"revisionTime": "2017-04-03T15:03:10Z"
},
{
"checksumSHA1": "R1h9XHH3dTmLq7yKL9/uW0xFwfs=",
"path": "github.com/nsf/termbox-go",