mirror of https://github.com/ethereum/go-ethereum
cmd/geth: add --config file flag (#13875)
* p2p/discover, p2p/discv5: add marshaling methods to Node * p2p/netutil: make Netlist decodable from TOML * common/math: encode nil HexOrDecimal256 as 0x0 * cmd/geth: add --config file flag * cmd/geth: add missing license header * eth: prettify Config again, fix tests * eth: use gasprice.Config instead of duplicating its fields * eth/gasprice: hide nil default from dumpconfig output * cmd/geth: hide genesis block in dumpconfig output * node: make tests compile * console: fix tests * cmd/geth: make TOML keys look exactly like Go struct fields * p2p: use discovery by default This makes the zero Config slightly more useful. It also fixes package node tests because Node detects reuse of the datadir through the NodeDatabase. * cmd/geth: make ethstats URL settable through config file * cmd/faucet: fix configuration * cmd/geth: dedup attach tests * eth: add comment for DefaultConfig * eth: pass downloader.SyncMode in Config This removes the FastSync, LightSync flags in favour of a more general SyncMode flag. * cmd/utils: remove jitvm flags * cmd/utils: make mutually exclusive flag error prettier It now reads: Fatal: flags --dev, --testnet can't be used at the same time * p2p: fix typo * node: add DefaultConfig, use it for geth * mobile: add missing NoDiscovery option * cmd/utils: drop MakeNode This exposed a couple of places that needed to be updated to use node.DefaultConfig. * node: fix typo * eth: make fast sync the default mode * cmd/utils: remove IPCApiFlag (unused) * node: remove default IPC path Set it in the frontends instead. * cmd/geth: add --syncmode * cmd/utils: make --ipcdisable and --ipcpath mutually exclusive * cmd/utils: don't enable WS, HTTP when setting addr * cmd/utils: fix --identitypull/14323/head
parent
b57680b0b2
commit
30d706c35e
@ -0,0 +1,186 @@ |
|||||||
|
// Copyright 2015 The go-ethereum Authors
|
||||||
|
// This file is part of go-ethereum.
|
||||||
|
//
|
||||||
|
// go-ethereum is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// go-ethereum 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 General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package main |
||||||
|
|
||||||
|
import ( |
||||||
|
"bufio" |
||||||
|
"encoding/hex" |
||||||
|
"errors" |
||||||
|
"fmt" |
||||||
|
"io" |
||||||
|
"os" |
||||||
|
"reflect" |
||||||
|
"unicode" |
||||||
|
|
||||||
|
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/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", |
||||||
|
} |
||||||
|
) |
||||||
|
|
||||||
|
// These settings ensure that TOML keys use the same names as Go struct fields.
|
||||||
|
var tomlSettings = toml.Config{ |
||||||
|
NormFieldName: func(rt reflect.Type, key string) string { |
||||||
|
return key |
||||||
|
}, |
||||||
|
FieldToKey: func(rt reflect.Type, field string) string { |
||||||
|
return field |
||||||
|
}, |
||||||
|
MissingField: func(rt reflect.Type, field string) error { |
||||||
|
link := "" |
||||||
|
if unicode.IsUpper(rune(rt.Name()[0])) && rt.PkgPath() != "main" { |
||||||
|
link = fmt.Sprintf(", see https://godoc.org/%s#%s for available fields", rt.PkgPath(), rt.Name()) |
||||||
|
} |
||||||
|
return fmt.Errorf("field '%s' is not defined in %s%s", field, rt.String(), link) |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
type ethstatsConfig struct { |
||||||
|
URL string `toml:",omitempty"` |
||||||
|
} |
||||||
|
|
||||||
|
type gethConfig struct { |
||||||
|
Eth eth.Config |
||||||
|
Node node.Config |
||||||
|
Ethstats ethstatsConfig |
||||||
|
} |
||||||
|
|
||||||
|
func loadConfig(file string, cfg *gethConfig) error { |
||||||
|
f, err := os.Open(file) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
defer f.Close() |
||||||
|
|
||||||
|
err = tomlSettings.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 defaultNodeConfig() node.Config { |
||||||
|
cfg := node.DefaultConfig |
||||||
|
cfg.Name = clientIdentifier |
||||||
|
cfg.Version = params.VersionWithCommit(gitCommit) |
||||||
|
cfg.HTTPModules = append(cfg.HTTPModules, "eth") |
||||||
|
cfg.WSModules = append(cfg.WSModules, "eth") |
||||||
|
cfg.IPCPath = "geth.ipc" |
||||||
|
return cfg |
||||||
|
} |
||||||
|
|
||||||
|
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, &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) |
||||||
|
if ctx.GlobalIsSet(utils.EthStatsURLFlag.Name) { |
||||||
|
cfg.Ethstats.URL = ctx.GlobalString(utils.EthStatsURLFlag.Name) |
||||||
|
} |
||||||
|
|
||||||
|
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 cfg.Ethstats.URL != "" { |
||||||
|
utils.RegisterEthStatsService(stack, cfg.Ethstats.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) |
||||||
|
comment := "" |
||||||
|
|
||||||
|
if cfg.Eth.Genesis != nil { |
||||||
|
cfg.Eth.Genesis = nil |
||||||
|
comment += "# Note: this config doesn't contain the genesis block.\n\n" |
||||||
|
} |
||||||
|
|
||||||
|
out, err := tomlSettings.Marshal(&cfg) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
io.WriteString(os.Stdout, comment) |
||||||
|
os.Stdout.Write(out) |
||||||
|
return nil |
||||||
|
} |
@ -0,0 +1,117 @@ |
|||||||
|
// 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/eth/downloader" |
||||||
|
"github.com/ethereum/go-ethereum/eth/gasprice" |
||||||
|
"github.com/ethereum/go-ethereum/params" |
||||||
|
) |
||||||
|
|
||||||
|
// DefaultConfig contains default settings for use on the Ethereum main net.
|
||||||
|
var DefaultConfig = Config{ |
||||||
|
SyncMode: downloader.FastSync, |
||||||
|
EthashCachesInMem: 2, |
||||||
|
EthashCachesOnDisk: 3, |
||||||
|
EthashDatasetsInMem: 1, |
||||||
|
EthashDatasetsOnDisk: 2, |
||||||
|
NetworkId: 1, |
||||||
|
LightPeers: 20, |
||||||
|
DatabaseCache: 128, |
||||||
|
GasPrice: big.NewInt(20 * params.Shannon), |
||||||
|
|
||||||
|
GPO: gasprice.Config{ |
||||||
|
Blocks: 10, |
||||||
|
Percentile: 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"` |
||||||
|
|
||||||
|
// Protocol options
|
||||||
|
NetworkId int // Network ID to use for selecting peers to connect to
|
||||||
|
SyncMode downloader.SyncMode |
||||||
|
|
||||||
|
// Light client options
|
||||||
|
LightServ int `toml:",omitempty"` // Maximum percentage of time allowed for serving LES requests
|
||||||
|
LightPeers int `toml:",omitempty"` // Maximum number of LES client peers
|
||||||
|
MaxPeers int `toml:"-"` // Maximum number of global peers
|
||||||
|
|
||||||
|
// Database options
|
||||||
|
SkipBcVersionCheck bool `toml:"-"` |
||||||
|
DatabaseHandles int `toml:"-"` |
||||||
|
DatabaseCache int |
||||||
|
|
||||||
|
// Mining-related options
|
||||||
|
Etherbase common.Address `toml:",omitempty"` |
||||||
|
MinerThreads int `toml:",omitempty"` |
||||||
|
ExtraData []byte `toml:",omitempty"` |
||||||
|
GasPrice *big.Int |
||||||
|
|
||||||
|
// Ethash options
|
||||||
|
EthashCacheDir string |
||||||
|
EthashCachesInMem int |
||||||
|
EthashCachesOnDisk int |
||||||
|
EthashDatasetDir string |
||||||
|
EthashDatasetsInMem int |
||||||
|
EthashDatasetsOnDisk int |
||||||
|
|
||||||
|
// Gas Price Oracle options
|
||||||
|
GPO gasprice.Config |
||||||
|
|
||||||
|
// Enables tracking of SHA3 preimages in the VM
|
||||||
|
EnablePreimageRecording bool |
||||||
|
|
||||||
|
// Miscellaneous options
|
||||||
|
SolcPath string |
||||||
|
DocRoot string `toml:"-"` |
||||||
|
PowFake bool `toml:"-"` |
||||||
|
PowTest bool `toml:"-"` |
||||||
|
PowShared bool `toml:"-"` |
||||||
|
} |
||||||
|
|
||||||
|
type configMarshaling struct { |
||||||
|
ExtraData hexutil.Bytes |
||||||
|
} |
@ -0,0 +1,186 @@ |
|||||||
|
// 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" |
||||||
|
"github.com/ethereum/go-ethereum/eth/downloader" |
||||||
|
"github.com/ethereum/go-ethereum/eth/gasprice" |
||||||
|
) |
||||||
|
|
||||||
|
func (c Config) MarshalTOML() (interface{}, error) { |
||||||
|
type Config struct { |
||||||
|
Genesis *core.Genesis `toml:",omitempty"` |
||||||
|
NetworkId int |
||||||
|
SyncMode downloader.SyncMode |
||||||
|
LightServ int `toml:",omitempty"` |
||||||
|
LightPeers int `toml:",omitempty"` |
||||||
|
MaxPeers int `toml:"-"` |
||||||
|
SkipBcVersionCheck bool `toml:"-"` |
||||||
|
DatabaseHandles int `toml:"-"` |
||||||
|
DatabaseCache int |
||||||
|
Etherbase common.Address `toml:",omitempty"` |
||||||
|
MinerThreads int `toml:",omitempty"` |
||||||
|
ExtraData hexutil.Bytes `toml:",omitempty"` |
||||||
|
GasPrice *big.Int |
||||||
|
EthashCacheDir string |
||||||
|
EthashCachesInMem int |
||||||
|
EthashCachesOnDisk int |
||||||
|
EthashDatasetDir string |
||||||
|
EthashDatasetsInMem int |
||||||
|
EthashDatasetsOnDisk int |
||||||
|
GPO gasprice.Config |
||||||
|
EnablePreimageRecording bool |
||||||
|
SolcPath string |
||||||
|
DocRoot string `toml:"-"` |
||||||
|
PowFake bool `toml:"-"` |
||||||
|
PowTest bool `toml:"-"` |
||||||
|
PowShared bool `toml:"-"` |
||||||
|
} |
||||||
|
var enc Config |
||||||
|
enc.Genesis = c.Genesis |
||||||
|
enc.NetworkId = c.NetworkId |
||||||
|
enc.SyncMode = c.SyncMode |
||||||
|
enc.LightServ = c.LightServ |
||||||
|
enc.LightPeers = c.LightPeers |
||||||
|
enc.MaxPeers = c.MaxPeers |
||||||
|
enc.SkipBcVersionCheck = c.SkipBcVersionCheck |
||||||
|
enc.DatabaseHandles = c.DatabaseHandles |
||||||
|
enc.DatabaseCache = c.DatabaseCache |
||||||
|
enc.Etherbase = c.Etherbase |
||||||
|
enc.MinerThreads = c.MinerThreads |
||||||
|
enc.ExtraData = c.ExtraData |
||||||
|
enc.GasPrice = c.GasPrice |
||||||
|
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.GPO = c.GPO |
||||||
|
enc.EnablePreimageRecording = c.EnablePreimageRecording |
||||||
|
enc.SolcPath = c.SolcPath |
||||||
|
enc.DocRoot = c.DocRoot |
||||||
|
enc.PowFake = c.PowFake |
||||||
|
enc.PowTest = c.PowTest |
||||||
|
enc.PowShared = c.PowShared |
||||||
|
return &enc, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error { |
||||||
|
type Config struct { |
||||||
|
Genesis *core.Genesis `toml:",omitempty"` |
||||||
|
NetworkId *int |
||||||
|
SyncMode *downloader.SyncMode |
||||||
|
LightServ *int `toml:",omitempty"` |
||||||
|
LightPeers *int `toml:",omitempty"` |
||||||
|
MaxPeers *int `toml:"-"` |
||||||
|
SkipBcVersionCheck *bool `toml:"-"` |
||||||
|
DatabaseHandles *int `toml:"-"` |
||||||
|
DatabaseCache *int |
||||||
|
Etherbase *common.Address `toml:",omitempty"` |
||||||
|
MinerThreads *int `toml:",omitempty"` |
||||||
|
ExtraData hexutil.Bytes `toml:",omitempty"` |
||||||
|
GasPrice *big.Int |
||||||
|
EthashCacheDir *string |
||||||
|
EthashCachesInMem *int |
||||||
|
EthashCachesOnDisk *int |
||||||
|
EthashDatasetDir *string |
||||||
|
EthashDatasetsInMem *int |
||||||
|
EthashDatasetsOnDisk *int |
||||||
|
GPO *gasprice.Config |
||||||
|
EnablePreimageRecording *bool |
||||||
|
SolcPath *string |
||||||
|
DocRoot *string `toml:"-"` |
||||||
|
PowFake *bool `toml:"-"` |
||||||
|
PowTest *bool `toml:"-"` |
||||||
|
PowShared *bool `toml:"-"` |
||||||
|
} |
||||||
|
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.SyncMode != nil { |
||||||
|
c.SyncMode = *dec.SyncMode |
||||||
|
} |
||||||
|
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.DatabaseHandles != nil { |
||||||
|
c.DatabaseHandles = *dec.DatabaseHandles |
||||||
|
} |
||||||
|
if dec.DatabaseCache != nil { |
||||||
|
c.DatabaseCache = *dec.DatabaseCache |
||||||
|
} |
||||||
|
if dec.Etherbase != nil { |
||||||
|
c.Etherbase = *dec.Etherbase |
||||||
|
} |
||||||
|
if dec.MinerThreads != nil { |
||||||
|
c.MinerThreads = *dec.MinerThreads |
||||||
|
} |
||||||
|
if dec.ExtraData != nil { |
||||||
|
c.ExtraData = dec.ExtraData |
||||||
|
} |
||||||
|
if dec.GasPrice != nil { |
||||||
|
c.GasPrice = dec.GasPrice |
||||||
|
} |
||||||
|
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.GPO != nil { |
||||||
|
c.GPO = *dec.GPO |
||||||
|
} |
||||||
|
if dec.EnablePreimageRecording != nil { |
||||||
|
c.EnablePreimageRecording = *dec.EnablePreimageRecording |
||||||
|
} |
||||||
|
if dec.SolcPath != nil { |
||||||
|
c.SolcPath = *dec.SolcPath |
||||||
|
} |
||||||
|
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 |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
@ -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. |
@ -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 |
@ -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] |
||||||
|
} |
@ -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 |
||||||
|
} |
@ -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. |
@ -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 |
@ -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 |
||||||
|
} |
@ -0,0 +1,86 @@ |
|||||||
|
package toml |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"io" |
||||||
|
"reflect" |
||||||
|
"strings" |
||||||
|
|
||||||
|
stringutil "github.com/naoina/go-stringutil" |
||||||
|
"github.com/naoina/toml/ast" |
||||||
|
) |
||||||
|
|
||||||
|
// Config contains options for encoding and decoding.
|
||||||
|
type Config struct { |
||||||
|
// NormFieldName is used to match TOML keys to struct fields. The function runs for
|
||||||
|
// both input keys and struct field names and should return a string that makes the
|
||||||
|
// two match. You must set this field to use the decoder.
|
||||||
|
//
|
||||||
|
// Example: The function in the default config removes _ and lowercases all keys. This
|
||||||
|
// allows a key called 'api_key' to match the struct field 'APIKey' because both are
|
||||||
|
// normalized to 'apikey'.
|
||||||
|
//
|
||||||
|
// Note that NormFieldName is not used for fields which define a TOML
|
||||||
|
// key through the struct tag.
|
||||||
|
NormFieldName func(typ reflect.Type, keyOrField string) string |
||||||
|
|
||||||
|
// FieldToKey determines the TOML key of a struct field when encoding.
|
||||||
|
// You must set this field to use the encoder.
|
||||||
|
//
|
||||||
|
// Note that FieldToKey is not used for fields which define a TOML
|
||||||
|
// key through the struct tag.
|
||||||
|
FieldToKey func(typ reflect.Type, field string) string |
||||||
|
|
||||||
|
// MissingField, if non-nil, is called when the decoder encounters a key for which no
|
||||||
|
// matching struct field exists. The default behavior is to return an error.
|
||||||
|
MissingField func(typ reflect.Type, key string) error |
||||||
|
} |
||||||
|
|
||||||
|
// DefaultConfig contains the default options for encoding and decoding.
|
||||||
|
// Snake case (i.e. 'foo_bar') is used for key names.
|
||||||
|
var DefaultConfig = Config{ |
||||||
|
NormFieldName: defaultNormFieldName, |
||||||
|
FieldToKey: snakeCase, |
||||||
|
} |
||||||
|
|
||||||
|
func defaultNormFieldName(typ reflect.Type, s string) string { |
||||||
|
return strings.Replace(strings.ToLower(s), "_", "", -1) |
||||||
|
} |
||||||
|
|
||||||
|
func snakeCase(typ reflect.Type, s string) string { |
||||||
|
return stringutil.ToSnakeCase(s) |
||||||
|
} |
||||||
|
|
||||||
|
func defaultMissingField(typ reflect.Type, key string) error { |
||||||
|
return fmt.Errorf("field corresponding to `%s' is not defined in %v", key, typ) |
||||||
|
} |
||||||
|
|
||||||
|
// NewEncoder returns a new Encoder that writes to w.
|
||||||
|
// It is shorthand for DefaultConfig.NewEncoder(w).
|
||||||
|
func NewEncoder(w io.Writer) *Encoder { |
||||||
|
return DefaultConfig.NewEncoder(w) |
||||||
|
} |
||||||
|
|
||||||
|
// Marshal returns the TOML encoding of v.
|
||||||
|
// It is shorthand for DefaultConfig.Marshal(v).
|
||||||
|
func Marshal(v interface{}) ([]byte, error) { |
||||||
|
return DefaultConfig.Marshal(v) |
||||||
|
} |
||||||
|
|
||||||
|
// Unmarshal parses the TOML data and stores the result in the value pointed to by v.
|
||||||
|
// It is shorthand for DefaultConfig.Unmarshal(data, v).
|
||||||
|
func Unmarshal(data []byte, v interface{}) error { |
||||||
|
return DefaultConfig.Unmarshal(data, v) |
||||||
|
} |
||||||
|
|
||||||
|
// UnmarshalTable applies the contents of an ast.Table to the value pointed at by v.
|
||||||
|
// It is shorthand for DefaultConfig.UnmarshalTable(t, v).
|
||||||
|
func UnmarshalTable(t *ast.Table, v interface{}) error { |
||||||
|
return DefaultConfig.UnmarshalTable(t, v) |
||||||
|
} |
||||||
|
|
||||||
|
// NewDecoder returns a new Decoder that reads from r.
|
||||||
|
// It is shorthand for DefaultConfig.NewDecoder(r).
|
||||||
|
func NewDecoder(r io.Reader) *Decoder { |
||||||
|
return DefaultConfig.NewDecoder(r) |
||||||
|
} |
@ -0,0 +1,478 @@ |
|||||||
|
// 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 (cfg *Config) Unmarshal(data []byte, v interface{}) error { |
||||||
|
table, err := Parse(data) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
if err := cfg.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 |
||||||
|
cfg *Config |
||||||
|
} |
||||||
|
|
||||||
|
// NewDecoder returns a new Decoder that reads from r.
|
||||||
|
// Note that it reads all from r before parsing it.
|
||||||
|
func (cfg *Config) NewDecoder(r io.Reader) *Decoder { |
||||||
|
return &Decoder{r, cfg} |
||||||
|
} |
||||||
|
|
||||||
|
// 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 d.cfg.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 (cfg *Config) 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(cfg, rv, t, toplevelMap) |
||||||
|
} |
||||||
|
|
||||||
|
// used for UnmarshalerRec.
|
||||||
|
func unmarshalTableOrValue(cfg *Config, 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(cfg, rv, av); err != nil { |
||||||
|
return lineError(fieldLineNumber(av), err) |
||||||
|
} |
||||||
|
return nil |
||||||
|
case ast.Value: |
||||||
|
return setValue(cfg, 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(cfg *Config, rv reflect.Value, t *ast.Table, toplevelMap bool) error { |
||||||
|
rv = indirect(rv) |
||||||
|
if err, ok := setUnmarshaler(cfg, rv, t); ok { |
||||||
|
return lineError(t.Line, err) |
||||||
|
} |
||||||
|
switch { |
||||||
|
case rv.Kind() == reflect.Struct: |
||||||
|
fc := makeFieldCache(cfg, rv.Type()) |
||||||
|
for key, fieldAst := range t.Fields { |
||||||
|
fv, fieldName, err := fc.findField(cfg, rv, key) |
||||||
|
if err != nil { |
||||||
|
return lineError(fieldLineNumber(fieldAst), err) |
||||||
|
} |
||||||
|
if fv.IsValid() { |
||||||
|
if err := unmarshalField(cfg, 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(cfg, 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(cfg *Config, rv reflect.Value, fieldAst interface{}) error { |
||||||
|
switch av := fieldAst.(type) { |
||||||
|
case *ast.KeyValue: |
||||||
|
return setValue(cfg, rv, av.Value) |
||||||
|
case *ast.Table: |
||||||
|
return unmarshalTable(cfg, rv, av, false) |
||||||
|
case []*ast.Table: |
||||||
|
rv = indirect(rv) |
||||||
|
if err, ok := setUnmarshaler(cfg, 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(cfg, 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(cfg *Config, lhs reflect.Value, val ast.Value) error { |
||||||
|
lhs = indirect(lhs) |
||||||
|
if err, ok := setUnmarshaler(cfg, 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(cfg, 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(cfg *Config, 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(cfg, 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(cfg *Config, 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(cfg, 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 |
||||||
|
} |
@ -0,0 +1,398 @@ |
|||||||
|
package toml |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
"encoding" |
||||||
|
"fmt" |
||||||
|
"io" |
||||||
|
"reflect" |
||||||
|
"sort" |
||||||
|
"strconv" |
||||||
|
"time" |
||||||
|
|
||||||
|
"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 (cfg *Config) Marshal(v interface{}) ([]byte, error) { |
||||||
|
buf := new(bytes.Buffer) |
||||||
|
err := cfg.NewEncoder(buf).Encode(v) |
||||||
|
return buf.Bytes(), err |
||||||
|
} |
||||||
|
|
||||||
|
// A Encoder writes TOML to an output stream.
|
||||||
|
type Encoder struct { |
||||||
|
w io.Writer |
||||||
|
cfg *Config |
||||||
|
} |
||||||
|
|
||||||
|
// NewEncoder returns a new Encoder that writes to w.
|
||||||
|
func (cfg *Config) NewEncoder(w io.Writer) *Encoder { |
||||||
|
return &Encoder{w, cfg} |
||||||
|
} |
||||||
|
|
||||||
|
// 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(e.cfg, rv) |
||||||
|
case reflect.Map: |
||||||
|
err = buf.mapFields(e.cfg, 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(cfg *Config, 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 = cfg.FieldToKey(rt, ft.Name) |
||||||
|
} |
||||||
|
if err := b.field(cfg, 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(cfg *Config, 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(cfg, kv.key, kv.value); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (b *tableBuf) field(cfg *Config, 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(cfg, 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(cfg *Config, rv reflect.Value, name string) (bool, error) { |
||||||
|
isMarshaler, isTable, err := b.marshaler(cfg, 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(cfg, 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(cfg, 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(cfg, rv) |
||||||
|
b.addChild(child) |
||||||
|
return true, err |
||||||
|
case reflect.Map: |
||||||
|
child := b.newChild(name) |
||||||
|
err := child.mapFields(cfg, 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(cfg *Config, 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(cfg, 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 |
||||||
|
} |
@ -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) |
||||||
|
} |
@ -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 |
||||||
|
} |
@ -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 <- ',' |
File diff suppressed because it is too large
Load Diff
@ -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(cfg *Config, 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[cfg.NormFieldName(rt, ft.Name)] = info |
||||||
|
} else { |
||||||
|
named[col] = info |
||||||
|
} |
||||||
|
} |
||||||
|
return fieldCache{named, auto} |
||||||
|
} |
||||||
|
|
||||||
|
func (fc fieldCache) findField(cfg *Config, rv reflect.Value, name string) (reflect.Value, string, error) { |
||||||
|
info, found := fc.named[name] |
||||||
|
if !found { |
||||||
|
info, found = fc.auto[cfg.NormFieldName(rv.Type(), name)] |
||||||
|
} |
||||||
|
if !found { |
||||||
|
if cfg.MissingField == nil { |
||||||
|
return reflect.Value{}, "", fmt.Errorf("field corresponding to `%s' is not defined in %v", name, rv.Type()) |
||||||
|
} else { |
||||||
|
return reflect.Value{}, "", cfg.MissingField(rv.Type(), name) |
||||||
|
} |
||||||
|
} 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 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]), "" |
||||||
|
} |
Loading…
Reference in new issue