mcmp: initial implementation, intention is to use Component instead of Context for all component instantiation
parent
e9b7f24dba
commit
a30edfb5f9
@ -0,0 +1,155 @@ |
||||
package mcmp |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
"strings" |
||||
|
||||
"github.com/mediocregopher/mediocre-go-lib/mctx" |
||||
) |
||||
|
||||
type child struct { |
||||
*Component |
||||
name string |
||||
} |
||||
|
||||
// Component describes a single component of a program, and holds onto
|
||||
// key/values for that component for use in generic libraries which instantiate
|
||||
// those components.
|
||||
//
|
||||
// When instantiating a component it's generally necessary to know where in the
|
||||
// component hierarchy it lies, for purposes of creating configuration
|
||||
// parameters and so-forth. To support this, Components are able to spawn of
|
||||
// child Components, each with a blank key/value namespace. Each child is
|
||||
// differentiated from the other by a name, and a Component is able to use its
|
||||
// Path (the sequence of names of its ancestors) to differentiate itself from
|
||||
// any other component in the hierarchy.
|
||||
//
|
||||
// A new Component, i.e. the root Component in the hierarchy, can be initialized
|
||||
// by doing:
|
||||
// new(Component).
|
||||
type Component struct { |
||||
path []string |
||||
parent *Component |
||||
children []child |
||||
|
||||
kv map[interface{}]interface{} |
||||
} |
||||
|
||||
// SetValue sets the given key to the given value on the Component, overwriting
|
||||
// any previous value for that key.
|
||||
func (c *Component) SetValue(key, value interface{}) { |
||||
if c.kv == nil { |
||||
c.kv = make(map[interface{}]interface{}, 1) |
||||
} |
||||
c.kv[key] = value |
||||
} |
||||
|
||||
// Value returns the value which has been set for the given key.
|
||||
func (c *Component) Value(key interface{}) interface{} { |
||||
return c.kv[key] |
||||
} |
||||
|
||||
// Values returns all key/value pairs which have been set via SetValue. The
|
||||
// returned map should _not_ be modified.
|
||||
func (c *Component) Values() map[interface{}]interface{} { |
||||
return c.kv |
||||
} |
||||
|
||||
// HasValue returns true if the given key has had a value set on it with
|
||||
// SetValue.
|
||||
func (c *Component) HasValue(key interface{}) bool { |
||||
_, ok := c.kv[key] |
||||
return ok |
||||
} |
||||
|
||||
// Child returns a new child component of the method receiver. The child will
|
||||
// have the given name, and its Path will be the receiver's path with the name
|
||||
// appended. The child will not inherit any of the receiver's key/value pairs.
|
||||
//
|
||||
// If a child of the given name has already been created this method will panic.
|
||||
func (c *Component) Child(name string) *Component { |
||||
for _, child := range c.children { |
||||
if child.name == name { |
||||
panic(fmt.Sprintf("child with name %q already exists", name)) |
||||
} |
||||
} |
||||
|
||||
childComp := &Component{ |
||||
path: append(c.path, name), |
||||
parent: c, |
||||
} |
||||
c.children = append(c.children, child{name: name, Component: childComp}) |
||||
return childComp |
||||
} |
||||
|
||||
// Children returns all Components created via the Child method on this
|
||||
// Component, in the order they were created.
|
||||
func (c *Component) Children() []*Component { |
||||
children := make([]*Component, len(c.children)) |
||||
for i := range c.children { |
||||
children[i] = c.children[i].Component |
||||
} |
||||
return children |
||||
} |
||||
|
||||
// Name returns the name this Component was created with (via the Child method),
|
||||
// or false if this Component was not created via Child (and is therefore the
|
||||
// root Component).
|
||||
func (c *Component) Name() (string, bool) { |
||||
if len(c.path) == 0 { |
||||
return "", false |
||||
} |
||||
return c.path[len(c.path)-1], true |
||||
} |
||||
|
||||
// Path returns the sequence of names which were passed into Child calls in
|
||||
// order to create this Component. If the Component was not created via Child
|
||||
// (and is therefore the root Component) this will return an empty slice.
|
||||
//
|
||||
// root := new(Component)
|
||||
// child := root.Child("child")
|
||||
// grandChild := child.Child("grandchild")
|
||||
// fmt.Printf("%#v\n", root.Path()) // "[]string(nil)"
|
||||
// fmt.Printf("%#v\n", child.Path()) // []string{"child"}
|
||||
// fmt.Printf("%#v\n", grandChild.Path()) // []string{"child", "grandchild"}
|
||||
//
|
||||
func (c *Component) Path() []string { |
||||
return c.path |
||||
} |
||||
|
||||
func (c *Component) pathStr() string { |
||||
path := c.Path() |
||||
for i := range path { |
||||
path[i] = strings.ReplaceAll(path[i], "/", `\/`) |
||||
} |
||||
return "/" + strings.Join(path, "/") |
||||
} |
||||
|
||||
type annotateKey string |
||||
|
||||
// Annotate annotates the given Context with information about the Component.
|
||||
func (c *Component) Annotate(ctx context.Context) context.Context { |
||||
return mctx.Annotate(ctx, annotateKey("componentPath"), c.pathStr()) |
||||
} |
||||
|
||||
// Annotated is a shortcut for `c.Annotate(context.Background())`.
|
||||
func (c *Component) Annotated() context.Context { |
||||
return c.Annotate(context.Background()) |
||||
} |
||||
|
||||
// BreadthFirstVisit visits this Component and all of its children, and their
|
||||
// children, etc... in a breadth-first order. If the callback returns false then
|
||||
// the function returns without visiting any more Components.
|
||||
func BreadthFirstVisit(c *Component, callback func(*Component) bool) { |
||||
queue := []*Component{c} |
||||
for len(queue) > 0 { |
||||
if !callback(queue[0]) { |
||||
return |
||||
} |
||||
for _, child := range queue[0].Children() { |
||||
queue = append(queue, child) |
||||
} |
||||
queue = queue[1:] |
||||
} |
||||
} |
@ -0,0 +1,87 @@ |
||||
package mcmp |
||||
|
||||
import ( |
||||
. "testing" |
||||
|
||||
"github.com/mediocregopher/mediocre-go-lib/mtest/massert" |
||||
) |
||||
|
||||
func TestComponent(t *T) { |
||||
assertValue := func(c *Component, key, expectedValue interface{}) massert.Assertion { |
||||
val := c.Value(key) |
||||
ok := c.HasValue(key) |
||||
return massert.All( |
||||
massert.Equal(expectedValue, val), |
||||
massert.Equal(expectedValue != nil, ok), |
||||
) |
||||
} |
||||
|
||||
assertName := func(c *Component, expectedName string) massert.Assertion { |
||||
name, ok := c.Name() |
||||
return massert.All( |
||||
massert.Equal(expectedName, name), |
||||
massert.Equal(expectedName != "", ok), |
||||
) |
||||
} |
||||
|
||||
// test that a Component is initialized correctly
|
||||
c := new(Component) |
||||
massert.Require(t, |
||||
assertName(c, ""), |
||||
massert.Length(c.Path(), 0), |
||||
massert.Length(c.Children(), 0), |
||||
assertValue(c, "foo", nil), |
||||
assertValue(c, "bar", nil), |
||||
) |
||||
|
||||
// test that setting values work, and that values aren't inherited
|
||||
c.SetValue("foo", 1) |
||||
child := c.Child("child") |
||||
massert.Require(t, |
||||
assertName(child, "child"), |
||||
massert.Equal([]string{"child"}, child.Path()), |
||||
massert.Length(child.Children(), 0), |
||||
massert.Equal([]*Component{child}, c.Children()), |
||||
assertValue(c, "foo", 1), |
||||
assertValue(child, "foo", nil), |
||||
) |
||||
|
||||
// test that a child setting a value does not affect the parent
|
||||
child.SetValue("bar", 2) |
||||
massert.Require(t, |
||||
assertValue(c, "bar", nil), |
||||
assertValue(child, "bar", 2), |
||||
) |
||||
} |
||||
func TestBreadFirstVisit(t *T) { |
||||
cmp := new(Component) |
||||
cmp1 := cmp.Child("1") |
||||
cmp1a := cmp1.Child("a") |
||||
cmp1b := cmp1.Child("b") |
||||
cmp2 := cmp.Child("2") |
||||
|
||||
{ |
||||
got := make([]*Component, 0, 5) |
||||
BreadthFirstVisit(cmp, func(cmp *Component) bool { |
||||
got = append(got, cmp) |
||||
return true |
||||
}) |
||||
massert.Require(t, |
||||
massert.Equal([]*Component{cmp, cmp1, cmp2, cmp1a, cmp1b}, got), |
||||
) |
||||
} |
||||
|
||||
{ |
||||
got := make([]*Component, 0, 3) |
||||
BreadthFirstVisit(cmp, func(cmp *Component) bool { |
||||
if len(cmp.Path()) > 1 { |
||||
return false |
||||
} |
||||
got = append(got, cmp) |
||||
return true |
||||
}) |
||||
massert.Require(t, |
||||
massert.Equal([]*Component{cmp, cmp1, cmp2}, got), |
||||
) |
||||
} |
||||
} |
@ -0,0 +1,84 @@ |
||||
package mcmp |
||||
|
||||
const ( |
||||
seriesEls int = iota |
||||
seriesNumValueEls |
||||
) |
||||
|
||||
type seriesKey struct { |
||||
userKey interface{} |
||||
mod int |
||||
} |
||||
|
||||
// SeriesElement is used to describe a single element in a series, as
|
||||
// implemented by AddSeriesValue. A SeriesElement can either be a Child which
|
||||
// was spawned from the Component, or a Value which was added via
|
||||
// AddSeriesValue.
|
||||
type SeriesElement struct { |
||||
Child *Component |
||||
Value interface{} |
||||
} |
||||
|
||||
func seriesKeys(key interface{}) (seriesKey, seriesKey) { |
||||
return seriesKey{userKey: key, mod: seriesEls}, |
||||
seriesKey{userKey: key, mod: seriesNumValueEls} |
||||
} |
||||
|
||||
func getSeriesElements(c *Component, key interface{}) ([]SeriesElement, int) { |
||||
elsKey, numValueElsKey := seriesKeys(key) |
||||
lastEls, _ := c.Value(elsKey).([]SeriesElement) |
||||
lastNumValueEls, _ := c.Value(numValueElsKey).(int) |
||||
|
||||
children := c.Children() |
||||
lastNumChildrenEls := len(lastEls) - lastNumValueEls |
||||
|
||||
els := lastEls |
||||
for _, child := range children[lastNumChildrenEls:] { |
||||
els = append(els, SeriesElement{Child: child}) |
||||
} |
||||
return els, lastNumValueEls |
||||
} |
||||
|
||||
// AddSeriesValue is a helper which adds a value to a series which is being
|
||||
// stored under the given key on the given Component. The series of values added
|
||||
// under any key can be retrieved with GetSeriesValues.
|
||||
//
|
||||
// Additionally, AddSeriesValue keeps track of the order of calls to itself and
|
||||
// children spawned from the Component. By using GetSeriesElements you can
|
||||
// retrieve the sequence of values and children in the order they were added to
|
||||
// the Component.
|
||||
func AddSeriesValue(c *Component, key, value interface{}) { |
||||
lastEls, lastNumValueEls := getSeriesElements(c, key) |
||||
els := append(lastEls, SeriesElement{Value: value}) |
||||
|
||||
elsKey, numValueElsKey := seriesKeys(key) |
||||
c.SetValue(elsKey, els) |
||||
c.SetValue(numValueElsKey, lastNumValueEls+1) |
||||
} |
||||
|
||||
// GetSeriesElements returns the sequence of values that have been added to the
|
||||
// Component under the given key via AddSeriesValue, interlaced with children
|
||||
// which have been spawned from the Component, in the same respective order the
|
||||
// events originally happened.
|
||||
func GetSeriesElements(c *Component, key interface{}) []SeriesElement { |
||||
els, _ := getSeriesElements(c, key) |
||||
return els |
||||
} |
||||
|
||||
// GetSeriesValues returns the sequence of values that have been added to the
|
||||
// Component under the given key via AddSeriesValue, in the same order the
|
||||
// values were added.
|
||||
func GetSeriesValues(c *Component, key interface{}) []interface{} { |
||||
elsKey, numValueElsKey := seriesKeys(key) |
||||
els, _ := c.Value(elsKey).([]SeriesElement) |
||||
numValueEls, _ := c.Value(numValueElsKey).(int) |
||||
|
||||
values := make([]interface{}, 0, numValueEls) |
||||
for _, el := range els { |
||||
if el.Child != nil { |
||||
continue |
||||
} |
||||
values = append(values, el.Value) |
||||
} |
||||
return values |
||||
} |
@ -0,0 +1,45 @@ |
||||
package mcmp |
||||
|
||||
import ( |
||||
. "testing" |
||||
|
||||
"github.com/mediocregopher/mediocre-go-lib/mtest/massert" |
||||
) |
||||
|
||||
func TestSeries(t *T) { |
||||
key := "foo" |
||||
|
||||
// test empty state
|
||||
c := new(Component) |
||||
massert.Require(t, |
||||
massert.Length(GetSeriesElements(c, key), 0), |
||||
massert.Length(GetSeriesValues(c, key), 0), |
||||
) |
||||
|
||||
// test after a single value has been added
|
||||
AddSeriesValue(c, key, 1) |
||||
massert.Require(t, |
||||
massert.Equal([]SeriesElement{{Value: 1}}, GetSeriesElements(c, key)), |
||||
massert.Equal([]interface{}{1}, GetSeriesValues(c, key)), |
||||
) |
||||
|
||||
// test after a child has been added
|
||||
childA := c.Child("a") |
||||
massert.Require(t, |
||||
massert.Equal( |
||||
[]SeriesElement{{Value: 1}, {Child: childA}}, |
||||
GetSeriesElements(c, key), |
||||
), |
||||
massert.Equal([]interface{}{1}, GetSeriesValues(c, key)), |
||||
) |
||||
|
||||
// test after another value has been added
|
||||
AddSeriesValue(c, key, 2) |
||||
massert.Require(t, |
||||
massert.Equal( |
||||
[]SeriesElement{{Value: 1}, {Child: childA}, {Value: 2}}, |
||||
GetSeriesElements(c, key), |
||||
), |
||||
massert.Equal([]interface{}{1, 2}, GetSeriesValues(c, key)), |
||||
) |
||||
} |
Loading…
Reference in new issue