package planr import ( // "fmt" "log" "github.com/BurntSushi/toml" ) /* TODO: Every property defined within the defaults currently has to implement the "inherit" method to conditionally inherit a property in relation to a parent. (Ostensibly so that test cases can override default configuration.) This is pedantic because most properties will end up writing boilerplate amounting to: parent.inherit() ... if config.property == nil { config.property = config.parent.property } This library provides copying behavior between structs with common properties using reflection. It seems like a slight abuse of reflection... But, it could be retro-fitted to implement this behavior if a "onlyCopyZeroFields" option was provided. > "github.com/jinzhu/copier" */ // Inheritable configuration can inherit properties defined in a // defaults file. This happens on a per-directory basis so multiple // tests can share common configuration. // // The parent will always be of the same type as the child and an // assertion is required to define the proper behavior. type InheritableConfig interface { Inherit(parent interface{}) } // A parser function takes a blob of TOML and decodes it into // configuration relevant to an adapter type TomlParser func (toml.Primitive) InheritableConfig // The name under which an adapter registers corresponds // to a table under the super-table adapters. All corresponding // TOML will be passed to the ParseConfig method or ParseDefaultConfig // for parsing. The ParseConfig file parses options in test case files. // The ParseDefaultConfig is parsed by `defaults.toml` files and can // be used to establish default configuration that will be inherited // by all units in a common directory (collection) type AdapterConfig struct { Name string ParseConfig TomlParser ParseDefaultConfig TomlParser } // Program-wide configuration which is recognized // in defaults.toml type Defaults struct { Points *float32 /* The TOML library only parses exported fields. The Adapters field is an intermediate mapping individual adapters to their locally defined configuration. After they individually process the configuration, it is mapped to the adapters_ field. See: decodeAdapters() */ Adapters *map[string] toml.Primitive adapters_ map[string] InheritableConfig /* The configs_ field is necessary to property implement the Inherit method using a common interface. */ configs_ *[]AdapterConfig } // Program-wide testcase config type TestCaseConfig struct { Defaults Title *string Description *string } // The default configuration must be able in inherit from // other defaults further up the tree // // This provides multiple levels of configurability func (child *Defaults) Inherit(p interface{}) { parent := p.(Defaults) // Inherit properties which haven't been configured if child.Points == nil { child.Points = parent.Points; } // Call the inherit method as defined by the adapters // If an adapter is undefined, inherit the parent configuration // // _configs represents all adapters (registered to a runner) for _, adapter := range *child.configs_ { parent_adapter, parent_exists := parent.adapters_[adapter.Name] child_adapter, child_exists := child.adapters_[adapter.Name] if parent_exists { if child_exists { child_adapter.Inherit(parent_adapter) } else { child.adapters_[adapter.Name] = parent_adapter } } } } // Parses the intermediate adapters Adapters containing TOML primitives // according to methods registered with the runner // Once parsed, they are stored alongside the registered name to determine // which adapter will receive the configuration func (defaults *Defaults) decodeAdapters(adapters []AdapterConfig, asDefault bool) { defaults.configs_ = &adapters defaults.adapters_ = make(map[string]InheritableConfig) if defaults.Adapters != nil { for _, config := range adapters { primitive, exists := (*defaults.Adapters)[config.Name] if exists { var parsed InheritableConfig if asDefault { parsed = config.ParseDefaultConfig(primitive) } else { parsed = config.ParseConfig(primitive) } defaults.adapters_[config.Name] = parsed } } } } // Decode defaults.toml func DecodeDefaults(path string, adapterCfg []AdapterConfig) Defaults { defaults := Defaults { } if _, err := toml.DecodeFile(path, &defaults); err != nil { log.Fatal(err) } defaults.decodeAdapters(adapterCfg, true) return defaults } // Decode an individual unit func DecodeConfig(path string, adapterCfg []AdapterConfig) TestCaseConfig { config := TestCaseConfig { } if _, err := toml.DecodeFile(path, &config); err != nil { log.Fatal(err) } config.decodeAdapters(adapterCfg, false) return config }