Esempio n. 1
0
// Dependencies returns the nodes that (( join ... )) requires to be resolved
// before its evaluation. Returns no dependencies on error, because who cares
// about eval order if Run is going to bomb out anyway.
func (JoinOperator) Dependencies(ev *Evaluator, args []*Expr, _ []*tree.Cursor) []*tree.Cursor {
	DEBUG("Calculating dependencies for (( join ... ))")
	deps := []*tree.Cursor{}
	if len(args) < 2 {
		DEBUG("Not enough arguments to (( join ... ))")
		return []*tree.Cursor{}
	}

	//skip the separator arg
	for _, arg := range args[1:] {
		if arg.Type == Literal {
			continue
		}
		if arg.Type != Reference {
			DEBUG("(( join ... )) argument not Literal or Reference type")
			return []*tree.Cursor{}
		}
		//get the real cursor
		finalCursor, err := arg.Resolve(ev.Tree)
		if err != nil {
			DEBUG("Could not resolve to a canonical path '%s'", arg.String())
			return []*tree.Cursor{}
		}
		//get the list at this location
		list, err := finalCursor.Reference.Resolve(ev.Tree)
		if err != nil {
			DEBUG("Could not retrieve object at path '%s'", arg.String())
			return []*tree.Cursor{}
		}
		//must be a list or a string
		switch list.(type) {
		case []interface{}:
			//add .* to the end of the cursor so we can glob all the elements
			globCursor, err := tree.ParseCursor(fmt.Sprintf("%s.*", finalCursor.Reference.String()))
			if err != nil {
				DEBUG("Could not parse cursor with '.*' appended. This is a BUG")
				return []*tree.Cursor{}
			}
			//have the cursor library get all the subelements for us
			subElements, err := globCursor.Glob(ev.Tree)
			if err != nil {
				DEBUG("Could not retrieve subelements at path '%s'. This may be a BUG.", arg.String())
				return []*tree.Cursor{}
			}
			deps = append(deps, subElements...)
		case string:
			deps = append(deps, finalCursor.Reference)
		default:
			DEBUG("Unsupported type at object location")
			return []*tree.Cursor{}
		}
	}
	DEBUG("Dependencies for (( join ... )):")
	for i, dep := range deps {
		DEBUG("\t#%d %s", i, dep.String())
	}
	return deps
}
Esempio n. 2
0
// Prune ...
func (ev *Evaluator) Prune(paths []string) error {
	DEBUG("pruning %d paths from the final YAML structure", len(paths))
	for _, path := range paths {
		c, err := tree.ParseCursor(path)
		if err != nil {
			return err
		}

		key := c.Component(-1)
		parent := c.Copy()
		parent.Pop()
		o, err := parent.Resolve(ev.Tree)
		if err != nil {
			continue
		}

		switch o.(type) {
		case map[interface{}]interface{}:
			if _, ok := o.(map[interface{}]interface{}); ok {
				DEBUG("  pruning %s", path)
				delete(o.(map[interface{}]interface{}), key)
			}

		case []interface{}:
			if list, ok := o.([]interface{}); ok {
				if idx, err := strconv.Atoi(key); err == nil {
					parent.Pop()
					if s, err := parent.Resolve(ev.Tree); err == nil {
						if reflect.TypeOf(s).Kind() == reflect.Map {
							parentName := fmt.Sprintf("%s", c.Component(-2))
							DEBUG("  pruning index %d of array '%s'", idx, parentName)

							length := len(list) - 1
							replacement := make([]interface{}, length)
							copy(replacement, append(list[:idx], list[idx+1:]...))

							delete(s.(map[interface{}]interface{}), parentName)
							s.(map[interface{}]interface{})[parentName] = replacement
						}
					}
				}
			}

		default:
			DEBUG("  I don't know how to prune %s\n    value=%v\n", path, o)
		}
	}
	DEBUG("")
	return nil
}
Esempio n. 3
0
	return true, nil
}

func (m *cursorMatcher) FailureMessage(actual interface{}) string {
	return fmt.Sprintf("Expected\n\t%s\nto equal\n\t%s", actual.(tree.Cursor), strings.Join(m.nodes, "."))
}

func (m *cursorMatcher) NegatedFailureMessage(actual interface{}) string {
	return fmt.Sprintf("Expected\n\t%s\nto not equal\n\t%s", actual.(tree.Cursor), strings.Join(m.nodes, "."))
}

var _ = Describe("Cursors", func() {
	Context("Basic Cursor Parsing", func() {
		It("handles dotted-notation", func() {
			c, err := tree.ParseCursor("x.y.z")
			Ω(err).ShouldNot(HaveOccurred())
			Ω(c).Should(BeCursor("x", "y", "z"))
		})

		It("ignores the '$' sigil in the initial position", func() {
			c, err := tree.ParseCursor("$.x.y.z")
			Ω(err).ShouldNot(HaveOccurred())
			Ω(c).Should(BeCursor("x", "y", "z"))
		})

		It("handles the '$' sigil in any other position", func() {
			c, err := tree.ParseCursor("x.$.y.z")
			Ω(err).ShouldNot(HaveOccurred())
			Ω(c).Should(BeCursor("x", "$", "y", "z"))
		})
Esempio n. 4
0
// ParseOpcall ...
func ParseOpcall(phase OperatorPhase, src string) (*Opcall, error) {
	split := func(src string) []string {
		list := make([]string, 0, 0)

		buf := ""
		escaped := false
		quoted := false

		for _, c := range src {
			if escaped {
				switch c {
				case 'n':
					buf += "\n"
				case 'r':
					buf += "\r"
				case 't':
					buf += "\t"
				default:
					buf += string(c)
				}
				escaped = false
				continue
			}

			if c == '\\' {
				escaped = true
				continue
			}

			if c == ' ' || c == '\t' || c == ',' {
				if quoted {
					buf += string(c)
					continue
				} else {
					if buf != "" {
						list = append(list, buf)
						buf = ""
					}
					if c == ',' {
						list = append(list, ",")
					}
				}
				continue
			}

			if c == '"' {
				buf += string(c)
				quoted = !quoted
				continue
			}

			buf += string(c)
		}

		if buf != "" {
			list = append(list, buf)
		}

		return list
	}

	argify := func(src string) (args []*Expr, err error) {
		qstring := regexp.MustCompile(`(?s)^"(.*)"$`)
		integer := regexp.MustCompile(`^[+-]?\d+(\.\d+)?$`)
		float := regexp.MustCompile(`^[+-]?\d*\.\d+$`)
		envvar := regexp.MustCompile(`^\$[a-zA-Z_][a-zA-Z0-9_]*$`)

		var final []*Expr
		var left, op *Expr

		pop := func() {
			if left != nil {
				final = append(final, left)
				left = nil
			}
		}

		push := func(e *Expr) {
			TRACE("expr: pushing data expression `%s' onto stack", e)
			TRACE("expr:   start: left=`%s', op=`%s'", left, op)
			defer func() { TRACE("expr:     end: left=`%s', op=`%s'\n", left, op) }()

			if left == nil {
				left = e
				return
			}
			if op == nil {
				pop()
				left = e
				return
			}
			op.Left = left
			op.Right = e
			left = op
			op = nil
		}

		TRACE("expr: parsing `%s'", src)
		for i, arg := range split(src) {
			switch {
			case arg == ",":
				DEBUG("  #%d: literal comma found; treating what we've seen so far as a complete expression")
				pop()

			case envvar.MatchString(arg):
				DEBUG("  #%d: parsed as unquoted environment variable reference '%s'", i, arg)
				push(&Expr{Type: EnvVar, Name: arg[1:]})

			case qstring.MatchString(arg):
				m := qstring.FindStringSubmatch(arg)
				DEBUG("  #%d: parsed as quoted string literal '%s'", i, m[1])
				push(&Expr{Type: Literal, Literal: m[1]})

			case float.MatchString(arg):
				DEBUG("  #%d: parsed as unquoted floating point literal '%s'", i, arg)
				v, err := strconv.ParseFloat(arg, 64)
				if err != nil {
					DEBUG("  #%d: %s is not parsable as a floating point number: %s", i, arg, err)
					return args, err
				}
				push(&Expr{Type: Literal, Literal: v})

			case integer.MatchString(arg):
				DEBUG("  #%d: parsed as unquoted integer literal '%s'", i, arg)
				v, err := strconv.ParseInt(arg, 10, 64)
				if err != nil {
					DEBUG("  #%d: %s is not parsable as an integer: %s", i, arg, err)
					return args, err
				}
				push(&Expr{Type: Literal, Literal: v})

			case arg == "||":
				DEBUG("  #%d: parsed logical-or operator, '||'", i)

				if left == nil || op != nil {
					return args, fmt.Errorf(`syntax error near: %s`, src)
				}
				TRACE("expr: pushing || expr-op onto the stack")
				op = &Expr{Type: LogicalOr}

			case arg == "nil" || arg == "null" || arg == "~" || arg == "Nil" || arg == "Null" || arg == "NIL" || arg == "NULL":
				DEBUG("  #%d: parsed the nil value token '%s'", i, arg)
				push(&Expr{Type: Literal, Literal: nil})

			case arg == "false" || arg == "False" || arg == "FALSE":
				DEBUG("  #%d: parsed the false value token '%s'", i, arg)
				push(&Expr{Type: Literal, Literal: false})

			case arg == "true" || arg == "True" || arg == "TRUE":
				DEBUG("  #%d: parsed the true value token '%s'", i, arg)
				push(&Expr{Type: Literal, Literal: true})

			default:
				c, err := tree.ParseCursor(arg)
				if err != nil {
					DEBUG("  #%d: %s is a malformed reference: %s", i, arg, err)
					return args, err
				}
				DEBUG("  #%d: parsed as a reference to $.%s", i, c)
				push(&Expr{Type: Reference, Reference: c})
			}
		}
		pop()
		if left != nil || op != nil {
			return nil, fmt.Errorf(`syntax error near: %s`, src)
		}
		DEBUG("")

		for _, e := range final {
			TRACE("expr: pushing expression `%v' onto the operand list", e)
			reduced, err := e.Reduce()
			if err != nil {
				fmt.Fprintf(os.Stdout, "warning: %s\n", err)
			}
			args = append(args, reduced)
		}

		return args, nil
	}

	op := &Opcall{src: src}

	for _, pattern := range []string{
		`^\Q((\E\s*([a-zA-Z][a-zA-Z0-9_-]*)(?:\s*\((.*)\))?\s*\Q))\E$`, // (( op(x,y,z) ))
		`^\Q((\E\s*([a-zA-Z][a-zA-Z0-9_-]*)(?:\s+(.*))?\s*\Q))\E$`,     // (( op x y z ))
	} {
		re := regexp.MustCompile(pattern)
		if !re.MatchString(src) {
			continue
		}

		m := re.FindStringSubmatch(src)
		DEBUG("parsing `%s': looks like a (( %s ... )) operator\n arguments:", src, m[1])

		op.op = OperatorFor(m[1])
		if op.op.Phase() != phase {
			DEBUG("  - skipping (( %s ... )) operation; it belongs to a different phase", m[1])
			return nil, nil
		}

		args, err := argify(m[2])
		if err != nil {
			return nil, err
		}
		if len(args) == 0 {
			DEBUG("  (none)")
		}
		op.args = args
		return op, nil
	}

	return nil, nil
}
Esempio n. 5
0
func TestOperators(t *testing.T) {
	cursor := func(s string) *tree.Cursor {
		c, err := tree.ParseCursor(s)
		So(err, ShouldBeNil)
		return c
	}

	YAML := func(s string) map[interface{}]interface{} {
		y, err := simpleyaml.NewYaml([]byte(s))
		So(err, ShouldBeNil)

		data, err := y.Map()
		So(err, ShouldBeNil)

		return data
	}

	ref := func(s string) *Expr {
		return &Expr{Type: Reference, Reference: cursor(s)}
	}
	str := func(s string) *Expr {
		return &Expr{Type: Literal, Literal: s}
	}
	num := func(v int64) *Expr {
		return &Expr{Type: Literal, Literal: v}
	}
	null := func() *Expr {
		return &Expr{Type: Literal, Literal: nil}
	}
	env := func(s string) *Expr {
		return &Expr{Type: EnvVar, Name: s}
	}
	or := func(l *Expr, r *Expr) *Expr {
		return &Expr{Type: LogicalOr, Left: l, Right: r}
	}

	var exprOk func(*Expr, *Expr)
	exprOk = func(got *Expr, want *Expr) {
		So(got, ShouldNotBeNil)
		So(want, ShouldNotBeNil)

		So(got.Type, ShouldEqual, want.Type)
		switch want.Type {
		case Literal:
			So(got.Literal, ShouldEqual, want.Literal)

		case Reference:
			So(got.Reference.String(), ShouldEqual, want.Reference.String())

		case LogicalOr:
			exprOk(got.Left, want.Left)
			exprOk(got.Right, want.Right)
		}
	}

	Convey("Parser", t, func() {
		Convey("parses op calls in their entirety", func() {
			phase := EvalPhase

			opOk := func(code string, name string, args ...*Expr) {
				op, err := ParseOpcall(phase, code)
				So(err, ShouldBeNil)
				So(op, ShouldNotBeNil)

				_, ok := op.op.(NullOperator)
				So(ok, ShouldBeTrue)
				So(op.op.(NullOperator).Missing, ShouldEqual, name)

				So(len(op.args), ShouldEqual, len(args))
				for i, expect := range args {
					exprOk(op.args[i], expect)
				}
			}

			opErr := func(code string, msg string) {
				_, err := ParseOpcall(phase, code)
				So(err, ShouldNotBeNil)
				So(err.Error(), ShouldContainSubstring, msg)
			}

			Convey("handles opcodes with and without arguments", func() {
				opOk(`(( null ))`, "null")
				opOk(`(( null 42 ))`, "null", num(42))
				opOk(`(( null 1 2 3 4 ))`, "null", num(1), num(2), num(3), num(4))
			})

			Convey("ignores optional whitespace", func() {
				opOk(`((null))`, "null")
				opOk(`((	null	))`, "null")
				opOk(`((  	null  	))`, "null")

				args := []*Expr{num(1), num(2), num(3)}
				opOk(`((null 1 2 3))`, "null", args...)
				opOk(`((null 1	2	3))`, "null", args...)
				opOk(`((null 1	2	3	))`, "null", args...)
				opOk(`((null 1 	 2 	 3 	 ))`, "null", args...)
			})

			Convey("allows use of commas to separate arguments", func() {
				args := []*Expr{num(1), num(2), num(3)}
				opOk(`((null 1, 2, 3))`, "null", args...)
				opOk(`((null 1,	2,	3))`, "null", args...)
				opOk(`((null 1,	2,	3,	))`, "null", args...)
				opOk(`((null 1 ,	 2 ,	 3 ,	 ))`, "null", args...)
			})

			Convey("allows use of parentheses around arguments", func() {
				args := []*Expr{num(1), num(2), num(3)}
				opOk(`((null(1,2,3)))`, "null", args...)
				opOk(`((null(1, 2, 3) ))`, "null", args...)
				opOk(`((null( 1,	2,	3)))`, "null", args...)
				opOk(`((null (1,	2,	3)	))`, "null", args...)
				opOk(`((null (1 ,	 2 ,	 3)	 ))`, "null", args...)
			})

			Convey("handles string literal arguments", func() {
				opOk(`(( null "string" ))`, "null", str("string"))
				opOk(`(( null "string with whitespace" ))`, "null", str("string with whitespace"))
				opOk(`(( null "a \"quoted\" string" ))`, "null", str(`a "quoted" string`))
				opOk(`(( null "\\escaped" ))`, "null", str(`\escaped`))
			})

			Convey("handles reference (cursor) arguments", func() {
				opOk(`(( null x.y.z ))`, "null", ref("x.y.z"))
				opOk(`(( null x.[0].z ))`, "null", ref("x.0.z"))
				opOk(`(( null x[0].z ))`, "null", ref("x.0.z"))
				opOk(`(( null x[0]z ))`, "null", ref("x.0.z"))
			})

			Convey("handles mixed collections of argument types", func() {
				opOk(`(( xyzzy "string" x.y.z 42  ))`, "xyzzy", str("string"), ref("x.y.z"), num(42))
				opOk(`(( xyzzy("string" x.y.z 42) ))`, "xyzzy", str("string"), ref("x.y.z"), num(42))
			})

			Convey("handles expression-based operands", func() {
				opOk(`(( null meta.key || "default" ))`, "null",
					or(ref("meta.key"), str("default")))

				opOk(`(( null meta.key || "default" "second" ))`, "null",
					or(ref("meta.key"), str("default")),
					str("second"))

				opOk(`(( null meta.key || "default", "second" ))`, "null",
					or(ref("meta.key"), str("default")),
					str("second"))

				opOk(`(( null meta.key || "default", meta.other || nil ))`, "null",
					or(ref("meta.key"), str("default")),
					or(ref("meta.other"), null()))

				opOk(`(( null meta.key || "default"     meta.other || nil ))`, "null",
					or(ref("meta.key"), str("default")),
					or(ref("meta.other"), null()))
			})

			Convey("handles environment variables as operands", func() {
				os.Setenv("SPRUCE_FOO", "first test")
				os.Setenv("_SPRUCE", "_sprucify!")
				os.Setenv("ENOENT", "")
				os.Setenv("http_proxy", "no://thank/you")

				opOk(`(( null $SPRUCE_FOO ))`, "null", env("SPRUCE"))
				opOk(`(( null $_SPRUCE ))`, "null", env("_SPRUCE"))
				opOk(`(( null $ENOENT || $SPRUCE_FOO ))`, "null",
					or(env("ENOENT"), env("SPRUCE_FOO")))
				opOk(`(( null $http_proxy))`, "null", env("http_proxy"))
			})

			Convey("throws errors for malformed expression", func() {
				opErr(`(( null meta.key ||, nil ))`,
					`syntax error near: meta.key ||, nil`)

				opErr(`(( null || ))`,
					`syntax error near: ||`)

				opErr(`(( null || meta.key ))`,
					`syntax error near: || meta.key`)

				opErr(`(( null meta.key || || ))`,
					`syntax error near: meta.key || ||`)
			})
		})
	})

	Convey("Expression Engine", t, func() {
		var e *Expr
		var tree map[interface{}]interface{}

		evaluate := func(e *Expr, tree map[interface{}]interface{}) interface{} {
			v, err := e.Evaluate(tree)
			So(err, ShouldBeNil)
			return v
		}

		Convey("Literals evaluate to themselves", func() {
			e = &Expr{Type: Literal, Literal: "value"}
			So(evaluate(e, tree), ShouldEqual, "value")

			e = &Expr{Type: Literal, Literal: ""}
			So(evaluate(e, tree), ShouldEqual, "")

			e = &Expr{Type: Literal, Literal: nil}
			So(evaluate(e, tree), ShouldEqual, nil)
		})

		Convey("References evaluate to the referenced part of the YAML tree", func() {
			tree = YAML(`---
meta:
  foo: FOO
  bar: BAR
`)

			e = &Expr{Type: Reference, Reference: cursor("meta.foo")}
			So(evaluate(e, tree), ShouldEqual, "FOO")

			e = &Expr{Type: Reference, Reference: cursor("meta.bar")}
			So(evaluate(e, tree), ShouldEqual, "BAR")
		})

		Convey("|| operator evaluates to the first found value", func() {
			tree = YAML(`---
meta:
  foo: FOO
  bar: BAR
`)

			So(evaluate(or(str("first"), str("second")), tree), ShouldEqual, "first")
			So(evaluate(or(ref("meta.foo"), str("second")), tree), ShouldEqual, "FOO")
			So(evaluate(or(ref("meta.ENOENT"), ref("meta.foo")), tree), ShouldEqual, "FOO")
		})

		Convey("|| operator treats nil as a found value", func() {
			tree = YAML(`---
meta:
  foo: FOO
  bar: BAR
`)

			So(evaluate(or(null(), str("second")), tree), ShouldBeNil)
			So(evaluate(or(ref("meta.ENOENT"), null()), tree), ShouldBeNil)
		})
	})

	Convey("Expression Reduction Algorithm", t, func() {
		var orig, final *Expr
		var err error

		Convey("ignores singleton expression", func() {
			orig = str("string")
			final, err = orig.Reduce()
			So(err, ShouldBeNil)
			exprOk(final, orig)

			orig = null()
			final, err = orig.Reduce()
			So(err, ShouldBeNil)
			exprOk(final, orig)

			orig = ref("meta.key")
			final, err = orig.Reduce()
			So(err, ShouldBeNil)
			exprOk(final, orig)
		})

		Convey("handles normal alternates that terminated in a literal", func() {
			orig = or(ref("a.b.c"), str("default"))
			final, err = orig.Reduce()
			So(err, ShouldBeNil)
			exprOk(final, orig)
		})

		Convey("throws errors (warnings) for unreachable alternates", func() {
			orig = or(null(), str("ignored"))
			final, err = orig.Reduce()
			So(err, ShouldNotBeNil)
			So(err.Error(), ShouldContainSubstring, `literal nil short-circuits expression (nil || "ignored")`)
			exprOk(final, null())

			orig = or(ref("some.key"), or(str("default"), ref("ignored.key")))
			final, err = orig.Reduce()
			So(err, ShouldNotBeNil)
			So(err.Error(), ShouldContainSubstring, `literal "default" short-circuits expression (some.key || "default" || ignored.key)`)
			exprOk(final, or(ref("some.key"), str("default")))

			orig = or(or(ref("some.key"), str("default")), ref("ignored.key"))
			final, err = orig.Reduce()
			So(err, ShouldNotBeNil)
			So(err.Error(), ShouldContainSubstring, `literal "default" short-circuits expression (some.key || "default" || ignored.key)`)
			exprOk(final, or(ref("some.key"), str("default")))
		})
	})

	Convey("File Operator", t, func() {
		op := FileOperator{}
		ev := &Evaluator{
			Tree: YAML(
				`meta:
  sample_file: assets/file_operator/sample.txt
`),
		}
		basedir, _ := os.Getwd()

		Convey("can read a direct file", func() {
			r, err := op.Run(ev, []*Expr{
				str("assets/file_operator/test.txt"),
			})
			So(err, ShouldBeNil)
			So(r, ShouldNotBeNil)

			So(r.Type, ShouldEqual, Replace)
			So(r.Value.(string), ShouldEqual, "This is a test\n")
		})

		Convey("can read a file from a reference", func() {
			r, err := op.Run(ev, []*Expr{
				ref("meta.sample_file"),
			})

			So(err, ShouldBeNil)
			So(r, ShouldNotBeNil)

			So(r.Type, ShouldEqual, Replace)

			content, err := ioutil.ReadFile("assets/file_operator/sample.txt")
			So(r.Value.(string), ShouldEqual, string(content))
		})

		Convey("can read a file relative to a specified base path", func() {
			os.Setenv("SPRUCE_FILE_BASE_PATH", filepath.Join(basedir, "assets/file_operator"))
			r, err := op.Run(ev, []*Expr{
				str("test.txt"),
			})
			So(err, ShouldBeNil)
			So(r, ShouldNotBeNil)

			So(r.Type, ShouldEqual, Replace)
			So(r.Value.(string), ShouldEqual, "This is a test\n")
		})

		if _, err := os.Stat("/etc/hosts"); err == nil {
			Convey("can read an absolute path", func() {
				os.Setenv("SPRUCE_FILE_BASE_PATH", filepath.Join(basedir, "assets/file_operator"))
				r, err := op.Run(ev, []*Expr{
					str("/etc/hosts"),
				})
				So(err, ShouldBeNil)
				So(r, ShouldNotBeNil)

				So(r.Type, ShouldEqual, Replace)

				content, err := ioutil.ReadFile("/etc/hosts")
				So(r.Value.(string), ShouldEqual, string(content))
			})
		}

		Convey("can handle a missing file", func() {
			r, err := op.Run(ev, []*Expr{
				str("no_one_should_ever_name_a_file_that_doesnt_exist_this_name"),
			})
			So(err, ShouldNotBeNil)
			So(r, ShouldBeNil)
		})

	})

	Convey("Grab Operator", t, func() {
		op := GrabOperator{}
		ev := &Evaluator{
			Tree: YAML(
				`key:
  subkey:
    value: found it
    other: value 2
  list1:
    - first
    - second
  list2:
    - third
    - fourth
  lonely:
    - one
`),
		}

		Convey("can grab a single value", func() {
			r, err := op.Run(ev, []*Expr{
				ref("key.subkey.value"),
			})
			So(err, ShouldBeNil)
			So(r, ShouldNotBeNil)

			So(r.Type, ShouldEqual, Replace)
			So(r.Value.(string), ShouldEqual, "found it")
		})

		Convey("can grab a single list value", func() {
			r, err := op.Run(ev, []*Expr{
				ref("key.lonely"),
			})
			So(err, ShouldBeNil)
			So(r, ShouldNotBeNil)

			So(r.Type, ShouldEqual, Replace)

			l, ok := r.Value.([]interface{})
			So(ok, ShouldBeTrue)

			So(len(l), ShouldEqual, 1)
			So(l[0], ShouldEqual, "one")
		})

		Convey("can grab a multiple lists and flatten them", func() {
			r, err := op.Run(ev, []*Expr{
				ref("key.list1"),
				ref("key.lonely"),
				ref("key.list2"),
				ref("key.lonely.0"),
			})
			So(err, ShouldBeNil)
			So(r, ShouldNotBeNil)

			So(r.Type, ShouldEqual, Replace)

			l, ok := r.Value.([]interface{})
			So(ok, ShouldBeTrue)

			So(len(l), ShouldEqual, 6)
			So(l[0], ShouldEqual, "first")
			So(l[1], ShouldEqual, "second")
			So(l[2], ShouldEqual, "one")
			So(l[3], ShouldEqual, "third")
			So(l[4], ShouldEqual, "fourth")
			So(l[5], ShouldEqual, "one")
		})

		Convey("can grab multiple values", func() {
			r, err := op.Run(ev, []*Expr{
				ref("key.subkey.value"),
				ref("key.subkey.other"),
			})
			So(err, ShouldBeNil)
			So(r, ShouldNotBeNil)

			So(r.Type, ShouldEqual, Replace)
			v, ok := r.Value.([]interface{})
			So(ok, ShouldBeTrue)
			So(len(v), ShouldEqual, 2)
			So(v[0], ShouldEqual, "found it")
			So(v[1], ShouldEqual, "value 2")
		})

		Convey("flattens constituent arrays", func() {
			r, err := op.Run(ev, []*Expr{
				ref("key.list2"),
				ref("key.list1"),
			})
			So(err, ShouldBeNil)
			So(r, ShouldNotBeNil)

			So(r.Type, ShouldEqual, Replace)
			v, ok := r.Value.([]interface{})
			So(ok, ShouldBeTrue)
			So(len(v), ShouldEqual, 4)
			So(v[0], ShouldEqual, "third")
			So(v[1], ShouldEqual, "fourth")
			So(v[2], ShouldEqual, "first")
			So(v[3], ShouldEqual, "second")
		})

		Convey("throws errors for missing arguments", func() {
			_, err := op.Run(ev, []*Expr{})
			So(err, ShouldNotBeNil)
		})

		Convey("throws errors for dangling references", func() {
			_, err := op.Run(ev, []*Expr{
				ref("key.that.does.not.exist"),
			})
			So(err, ShouldNotBeNil)
		})
	})

	Convey("Environment Variable Resolution (via grab)", t, func() {
		op := GrabOperator{}
		ev := &Evaluator{}
		os.Setenv("GRAB_ONE", "one")
		os.Setenv("GRAB_TWO", "two")
		os.Setenv("GRAB_NOT", "")

		Convey("can grab a single environment value", func() {
			r, err := op.Run(ev, []*Expr{
				env("GRAB_ONE"),
			})
			So(err, ShouldBeNil)
			So(r, ShouldNotBeNil)

			So(r.Type, ShouldEqual, Replace)
			So(r.Value.(string), ShouldEqual, "one")
		})

		Convey("tries alternates until it finds a set environment variable", func() {
			r, err := op.Run(ev, []*Expr{
				or(env("GRAB_THREE"), or(env("GRAB_TWO"), env("GRAB_ONE"))),
			})
			So(err, ShouldBeNil)
			So(r, ShouldNotBeNil)

			So(r.Type, ShouldEqual, Replace)
			So(r.Value.(string), ShouldEqual, "two")
		})

		Convey("throws errors for unset environment variables", func() {
			_, err := op.Run(ev, []*Expr{
				env("GRAB_NOT"),
			})
			So(err, ShouldNotBeNil)
		})
	})

	Convey("Concat Operator", t, func() {
		op := ConcatOperator{}
		ev := &Evaluator{
			Tree: YAML(
				`key:
  subkey:
    value: found it
    other: value 2
  list1:
    - first
    - second
  list2:
    - third
    - fourth
douglas:
  adams: 42
math:
  PI: 3.14159
`),
		}

		Convey("can concat a single value", func() {
			r, err := op.Run(ev, []*Expr{
				ref("key.subkey.value"),
				ref("key.list1.0"),
			})
			So(err, ShouldBeNil)
			So(r, ShouldNotBeNil)

			So(r.Type, ShouldEqual, Replace)
			So(r.Value.(string), ShouldEqual, "found itfirst")
		})

		Convey("can concat a literal values", func() {
			r, err := op.Run(ev, []*Expr{
				str("a literal "),
				str("value"),
			})
			So(err, ShouldBeNil)
			So(r, ShouldNotBeNil)

			So(r.Type, ShouldEqual, Replace)
			So(r.Value.(string), ShouldEqual, "a literal value")
		})

		Convey("can concat multiple values", func() {
			r, err := op.Run(ev, []*Expr{
				str("I "),
				ref("key.subkey.value"),
				str("!"),
			})
			So(err, ShouldBeNil)
			So(r, ShouldNotBeNil)

			So(r.Type, ShouldEqual, Replace)
			So(r.Value.(string), ShouldEqual, "I found it!")
		})

		Convey("can concat integer literals", func() {
			r, err := op.Run(ev, []*Expr{
				str("the answer = "),
				ref("douglas.adams"),
			})
			So(err, ShouldBeNil)
			So(r, ShouldNotBeNil)

			So(r.Type, ShouldEqual, Replace)
			So(r.Value.(string), ShouldEqual, "the answer = 42")
		})

		Convey("can concat float literals", func() {
			r, err := op.Run(ev, []*Expr{
				ref("math.PI"),
				str(" is PI"),
			})
			So(err, ShouldBeNil)
			So(r, ShouldNotBeNil)

			So(r.Type, ShouldEqual, Replace)
			So(r.Value.(string), ShouldEqual, "3.14159 is PI")
		})

		Convey("throws errors for missing arguments", func() {
			_, err := op.Run(ev, []*Expr{})
			So(err, ShouldNotBeNil)

			_, err = op.Run(ev, []*Expr{str("one")})
			So(err, ShouldNotBeNil)
		})

		Convey("throws errors for dangling references", func() {
			_, err := op.Run(ev, []*Expr{
				ref("key.that.does.not.exist"),
				str("string"),
			})
			So(err, ShouldNotBeNil)
		})
	})

	Convey("static_ips Operator", t, func() {
		op := StaticIPOperator{}
		Reset(func() {
			UsedIPs = map[string]string{}
		})

		Convey("can resolve valid networks inside of job contexts", func() {
			ev := &Evaluator{
				Here: cursor("jobs.job1.networks.0.static_ips"),
				Tree: YAML(
					`networks:
  - name: test-network
    subnets:
      - static: [ 10.0.0.5 - 10.0.0.10 ]
jobs:
  - name: job1
    instances: 3
    networks:
      - name: test-network
        static_ips: <---------- HERE -----------------
`),
			}

			r, err := op.Run(ev, []*Expr{num(0), num(1), num(2)})
			So(err, ShouldBeNil)
			So(r, ShouldNotBeNil)

			So(r.Type, ShouldEqual, Replace)
			v, ok := r.Value.([]interface{})
			So(ok, ShouldBeTrue)
			So(len(v), ShouldEqual, 3)
			So(v[0], ShouldEqual, "10.0.0.5")
			So(v[1], ShouldEqual, "10.0.0.6")
			So(v[2], ShouldEqual, "10.0.0.7")
		})
		Convey("works with new style bosh manifests", func() {
			ev := &Evaluator{
				Here: cursor("instance_groups.job1.networks.0.static_ips"),
				Tree: YAML(
					`networks:
- name: test-net
  subnets:
  - static: [ 10.0.0.5 - 10.0.0.10 ]
instance_groups:
- name: job1
  instances: 2
  networks:
  - name: test-net
    static_ips: <------------- HERE ------------
`),
			}

			r, err := op.Run(ev, []*Expr{num(0), num(1), num(2)})
			So(err, ShouldBeNil)
			So(r, ShouldNotBeNil)
			So(r.Type, ShouldEqual, Replace)
			v, ok := r.Value.([]interface{})
			So(ok, ShouldBeTrue)
			So(len(v), ShouldEqual, 2)
			So(v[0], ShouldEqual, "10.0.0.5")
			So(v[1], ShouldEqual, "10.0.0.6")
		})

		Convey("works with multiple subnets", func() {
			ev := &Evaluator{
				Here: cursor("instance_groups.job1.networks.0.static_ips"),
				Tree: YAML(
					`networks:
- name: test-net
  subnets:
  - static: [ 10.0.0.2 - 10.0.0.3 ]
  - static: [ 10.0.1.5 - 10.0.1.10 ]
instance_groups:
- name: job1
  instances: 4
  networks:
  - name: test-net
    static_ips: <------------- HERE ------------
`),
			}

			r, err := op.Run(ev, []*Expr{num(0), num(1), num(2), num(3)})
			So(err, ShouldBeNil)
			So(r, ShouldNotBeNil)
			So(r.Type, ShouldEqual, Replace)
			v, ok := r.Value.([]interface{})
			So(ok, ShouldBeTrue)
			So(len(v), ShouldEqual, 4)
			So(v[0], ShouldEqual, "10.0.0.2")
			So(v[1], ShouldEqual, "10.0.0.3")
			So(v[2], ShouldEqual, "10.0.1.5")
			So(v[3], ShouldEqual, "10.0.1.6")
		})

		Convey("works with multiple subnets with an availability zone", func() {
			ev := &Evaluator{
				Here: cursor("instance_groups.job1.networks.0.static_ips"),
				Tree: YAML(
					`networks:
- name: test-net
  subnets:
  - static: [ 10.0.0.2 - 10.0.0.3 ]
    az: z2
  - static: [ 10.0.1.5 - 10.0.1.10 ]
    az: z1
instance_groups:
- name: job1
  instances: 4
  networks:
  - name: test-net
    static_ips: <------------- HERE ------------
`),
			}

			r, err := op.Run(ev, []*Expr{num(0), num(1), num(2), num(3)})
			So(err, ShouldBeNil)
			So(r, ShouldNotBeNil)
			So(r.Type, ShouldEqual, Replace)
			v, ok := r.Value.([]interface{})
			So(ok, ShouldBeTrue)
			So(len(v), ShouldEqual, 4)
			So(v[0], ShouldEqual, "10.0.0.2")
			So(v[1], ShouldEqual, "10.0.0.3")
			So(v[2], ShouldEqual, "10.0.1.5")
			So(v[3], ShouldEqual, "10.0.1.6")
		})

		Convey("works with instance_group availability zones", func() {
			ev := &Evaluator{
				Here: cursor("instance_groups.job1.networks.0.static_ips"),
				Tree: YAML(
					`networks:
- name: test-net
  subnets:
  - static: [ 10.0.0.2 - 10.0.0.3 ]
    az: z1
  - static: [ 10.0.1.5 - 10.0.1.10 ]
    az: z2
instance_groups:
- name: job1
  instances: 3
  azs: [z2]
  networks:
  - name: test-net
    static_ips: <------------- HERE ------------
`),
			}

			r, err := op.Run(ev, []*Expr{num(0), num(1), num(2)})
			So(err, ShouldBeNil)
			So(r, ShouldNotBeNil)
			So(r.Type, ShouldEqual, Replace)
			v, ok := r.Value.([]interface{})
			So(ok, ShouldBeTrue)
			So(len(v), ShouldEqual, 3)
			So(v[0], ShouldEqual, "10.0.1.5")
			So(v[1], ShouldEqual, "10.0.1.6")
			So(v[2], ShouldEqual, "10.0.1.7")
		})

		Convey("works with directly specified availability zones", func() {
			ev := &Evaluator{
				Here: cursor("instance_groups.job1.networks.0.static_ips"),
				Tree: YAML(
					`networks:
- name: test-net
  subnets:
  - static: [ 10.0.0.2 - 10.0.0.4 ]
    az: z1
  - static: [ 10.0.2.6 - 10.0.2.10 ]
    az: z2
instance_groups:
- name: job1
  instances: 6
  azs: [z1,z2]
  networks:
  - name: test-net
    static_ips: <------------- HERE ------------
`),
			}

			r, err := op.Run(ev, []*Expr{
				str("z2:1"),
				num(0),
				str("z1:2"),
				str("z2:2"),
				num(1),
				str("z2:4"),
			})
			So(err, ShouldBeNil)
			So(r, ShouldNotBeNil)
			So(r.Type, ShouldEqual, Replace)
			v, ok := r.Value.([]interface{})
			So(ok, ShouldBeTrue)
			So(len(v), ShouldEqual, 6)
			So(v[0], ShouldEqual, "10.0.2.7")
			So(v[1], ShouldEqual, "10.0.0.2")
			So(v[2], ShouldEqual, "10.0.0.4")
			So(v[3], ShouldEqual, "10.0.2.8")
			So(v[4], ShouldEqual, "10.0.0.3")
			So(v[5], ShouldEqual, "10.0.2.10")
		})

		Convey("throws an error if an unknown availability zone is used in operator", func() {
			ev := &Evaluator{
				Here: cursor("instance_groups.job1.networks.0.static_ips"),
				Tree: YAML(
					`networks:
- name: test-net
  subnets:
  - static: [ 10.0.0.2 - 10.0.0.4 ]
    az: z1
  - static: [ 10.0.2.6 - 10.0.2.10 ]
    az: z2
instance_groups:
- name: job1
  instances: 2
  azs: [z1,z2]
  networks:
  - name: test-net
    static_ips: <------------- HERE ------------
`),
			}

			r, err := op.Run(ev, []*Expr{
				str("z2:0"),
				str("z3:1"),
			})
			So(err, ShouldNotBeNil)
			So(r, ShouldBeNil)
		})

		Convey("throws an error if offset for an availability zone is out of bounds", func() {
			ev := &Evaluator{
				Here: cursor("instance_groups.job1.networks.0.static_ips"),
				Tree: YAML(
					`networks:
- name: test-net
  subnets:
  - static: [ 10.0.0.1 - 10.0.0.5 ]
    az: z1
  - static: [ 10.0.2.1 - 10.0.2.5 ]
    az: z2
instance_groups:
- name: job1
  instances: 2
  azs: [z1,z2]
  networks:
  - name: test-net
    static_ips: <------------- HERE ------------
`),
			}

			r, err := op.Run(ev, []*Expr{
				str("z1:4"),
				str("z1:5"),
			})
			So(err, ShouldNotBeNil)
			So(r, ShouldBeNil)
		})

		Convey("throws an error if an instance_group availability zone is not found in subnets", func() {
			ev := &Evaluator{
				Here: cursor("instance_groups.job1.networks.0.static_ips"),
				Tree: YAML(
					`networks:
- name: test-net
  subnets:
  - static: [ 10.0.0.2 - 10.0.0.4 ]
    az: z1
  - static: [ 10.0.2.6 - 10.0.2.10 ]
    az: z2
instance_groups:
- name: job1
  instances: 2
  azs: [z1,z2,z3]
  networks:
  - name: test-net
    static_ips: <------------- HERE ------------
`),
			}

			r, err := op.Run(ev, []*Expr{
				str("z1:0"),
				str("z2:1"),
			})
			So(err, ShouldNotBeNil)
			So(r, ShouldBeNil)
		})

		Convey("can resolve valid large networks inside of job contexts", func() {
			ev := &Evaluator{
				Here: cursor("jobs.job1.networks.0.static_ips"),
				Tree: YAML(
					`networks:
  - name: test-network
    subnets:
      - static: [ 10.0.0.0 - 10.1.0.1 ]
jobs:
  - name: job1
    instances: 7
    networks:
      - name: test-network
        static_ips: <---------- HERE -----------------
`),
			}

			r, err := op.Run(ev, []*Expr{
				num(0),
				num(255),   // 2^8 - 1
				num(256),   // 2^8
				num(257),   // 2^8 + 1
				num(65535), // 2^16 - 1
				num(65536), // 2^16
				num(65537), // 2^16 + 1

				// 1st octet rollover testing disabled due to improve speed.
				// but verified working on 11/30/2015 - gfranks
				//				num(16777215), // 2^24 - 1
				//				num(16777216), // 2^24
				//				num(16777217), // 2^24 + 1
			})
			So(err, ShouldBeNil)
			So(r, ShouldNotBeNil)

			So(r.Type, ShouldEqual, Replace)
			v, ok := r.Value.([]interface{})
			So(ok, ShouldBeTrue)
			So(len(v), ShouldEqual, 7)
			So(v[0], ShouldEqual, "10.0.0.0")
			So(v[1], ShouldEqual, "10.0.0.255")
			So(v[2], ShouldEqual, "10.0.1.0") //  3rd octet rollover
			So(v[3], ShouldEqual, "10.0.1.1")

			So(v[4], ShouldEqual, "10.0.255.255")
			So(v[5], ShouldEqual, "10.1.0.0") //  2nd octet rollover
			So(v[6], ShouldEqual, "10.1.0.1")

			// 1st octet rollover testing disabled due to improve speed.
			// but verified working on 11/30/2015 - gfranks
			//			So(v[7], ShouldEqual, "10.255.255.255")
			//			So(v[8], ShouldEqual, "11.0.0.0") //  1st octet rollover
			//			So(v[9], ShouldEqual, "11.0.0.1")
		})

		Convey("throws an error if no job name is specified", func() {
			ev := &Evaluator{
				Here: cursor("jobs.0.networks.0.static_ips"),
				Tree: YAML(
					`networks:
  - name: test-network
    subnets:
      - static: [ 10.0.0.5 - 10.0.0.10 ]
jobs:
  - instances: 3
    networks:
      - name: test-network
        static_ips: <---------- HERE -----------------
`),
			}

			r, err := op.Run(ev, []*Expr{num(0), num(1), num(2)})
			So(err, ShouldNotBeNil)
			So(r, ShouldBeNil)
		})

		Convey("throws an error if no job instances specified", func() {
			ev := &Evaluator{
				Here: cursor("jobs.job1.networks.0.static_ips"),
				Tree: YAML(
					`networks:
  - name: test-network
    subnets:
      - static: [ 10.0.0.5 - 10.0.0.10 ]
jobs:
  - name: job1
    networks:
      - name: test-network
        static_ips: <---------- HERE -----------------
`),
			}

			r, err := op.Run(ev, []*Expr{num(0), num(1), num(2)})
			So(err, ShouldNotBeNil)
			So(r, ShouldBeNil)
		})

		Convey("throws an error if job instances is not a number", func() {
			ev := &Evaluator{
				Here: cursor("jobs.job1.networks.0.static_ips"),
				Tree: YAML(
					`networks:
  - name: test-network
    subnets:
      - static: [ 10.0.0.5 - 10.0.0.10 ]
jobs:
  - name: job1
    instances: PI
    networks:
      - name: test-network
        static_ips: <---------- HERE -----------------
`),
			}

			r, err := op.Run(ev, []*Expr{num(0), num(1), num(2)})
			So(err, ShouldNotBeNil)
			So(r, ShouldBeNil)
		})

		Convey("throws an error if job has no network name", func() {
			ev := &Evaluator{
				Here: cursor("jobs.job1.networks.0.static_ips"),
				Tree: YAML(
					`networks:
  - name: test-network
    subnets:
      - static: [ 10.0.0.5 - 10.0.0.10 ]
jobs:
  - name: job1
    instances: 3
    networks:
      - static_ips: <---------- HERE -----------------
`),
			}

			r, err := op.Run(ev, []*Expr{num(0), num(1), num(2)})
			So(err, ShouldNotBeNil)
			So(r, ShouldBeNil)
		})

		Convey("throws an error if network has no subnets key", func() {
			ev := &Evaluator{
				Here: cursor("jobs.job1.networks.0.static_ips"),
				Tree: YAML(
					`networks:
  - name: test-network
jobs:
  - name: job1
    instances: 3
    networks:
      - name: test-network
        static_ips: <---------- HERE -----------------
`),
			}

			r, err := op.Run(ev, []*Expr{num(0), num(1), num(2)})
			So(err, ShouldNotBeNil)
			So(r, ShouldBeNil)
		})

		Convey("throws an error if network has no subnets", func() {
			ev := &Evaluator{
				Here: cursor("jobs.job1.networks.0.static_ips"),
				Tree: YAML(
					`networks:
  - name: test-network
    subnets: []
jobs:
  - name: job1
    instances: 3
    networks:
      - name: test-network
        static_ips: <---------- HERE -----------------
`),
			}

			r, err := op.Run(ev, []*Expr{num(0), num(1), num(2)})
			So(err, ShouldNotBeNil)
			So(r, ShouldBeNil)
		})

		Convey("throws an error if network has no static ranges", func() {
			ev := &Evaluator{
				Here: cursor("jobs.job1.networks.0.static_ips"),
				Tree: YAML(
					`networks:
  - name: test-network
    subnets:
     - {}
jobs:
  - name: job1
    instances: 3
    networks:
      - name: test-network
        static_ips: <---------- HERE -----------------
`),
			}

			r, err := op.Run(ev, []*Expr{num(0), num(1), num(2)})
			So(err, ShouldNotBeNil)
			So(r, ShouldBeNil)
		})

		Convey("throws an error if network has malformed static range array(s)", func() {
			ev := &Evaluator{
				Here: cursor("jobs.job1.networks.0.static_ips"),
				Tree: YAML(
					`networks:
  - name: test-network
    subnets:
    - static: [ 10.0.0.1, 10.0.0.254 ]
jobs:
  - name: job1
    instances: 3
    networks:
      - name: test-network
        static_ips: <---------- HERE -----------------
`),
			}

			r, err := op.Run(ev, []*Expr{num(0), num(1), num(2)})
			So(err, ShouldNotBeNil)
			So(r, ShouldBeNil)
		})

		Convey("throws an error if network static range has malformed IP addresses", func() {
			ev := &Evaluator{
				Here: cursor("jobs.job1.networks.0.static_ips"),
				Tree: YAML(
					`networks:
  - name: test-network
    subnets:
    - static: 10.0.0.0.0.0.0.1 - geoff
jobs:
  - name: job1
    instances: 3
    networks:
      - name: test-network
        static_ips: <---------- HERE -----------------
`),
			}

			r, err := op.Run(ev, []*Expr{num(0), num(1), num(2)})
			So(err, ShouldNotBeNil)
			So(r, ShouldBeNil)
		})

		Convey("throws an error if the static address pool is too small", func() {
			ev := &Evaluator{
				Here: cursor("jobs.job1.networks.0.static_ips"),
				Tree: YAML(
					`networks:
  - name: test-network
    subnets:
    - static: 172.16.31.10 - 172.16.31.11
jobs:
  - name: job1
    instances: 3
    networks:
      - name: test-network
        static_ips: <---------- HERE -----------------
`),
			}

			r, err := op.Run(ev, []*Expr{num(0), num(1), num(2)})
			So(err, ShouldNotBeNil)
			So(r, ShouldBeNil)
		})

		Convey("throws an error if the address pool ends before it starts", func() {
			ev := &Evaluator{
				Here: cursor("jobs.job1.networks.0.static_ips"),
				Tree: YAML(
					`networks:
  - name: test-network
    subnets:
    - static: [ 10.8.0.1 - 10.0.0.255 ]
jobs:
  - name: job1
    instances: 3
    networks:
      - name: test-network
        static_ips: <---------- HERE -----------------
`),
			}

			r, err := op.Run(ev, []*Expr{num(0), num(1), num(2)})
			So(err, ShouldNotBeNil)
			So(err.Error(), ShouldContainSubstring, "ends before it starts")
			So(r, ShouldBeNil)
		})
	})

	Convey("inject Operator", t, func() {
		op := InjectOperator{}
		ev := &Evaluator{
			Tree: YAML(
				`key:
  subkey:
    value: found it
    other: value 2
  subkey2:
    value: overridden
    third: trois
  list1:
    - first
    - second
  list2:
    - third
    - fourth
`),
		}

		Convey("can inject a single sub-map", func() {
			r, err := op.Run(ev, []*Expr{
				ref("key.subkey"),
			})
			So(err, ShouldBeNil)
			So(r, ShouldNotBeNil)

			So(r.Type, ShouldEqual, Inject)
			v, ok := r.Value.(map[interface{}]interface{})
			So(ok, ShouldBeTrue)
			So(v["value"], ShouldEqual, "found it")
			So(v["other"], ShouldEqual, "value 2")
		})

		Convey("can inject multiple sub-maps", func() {
			r, err := op.Run(ev, []*Expr{
				ref("key.subkey"),
				ref("key.subkey2"),
			})
			So(err, ShouldBeNil)
			So(r, ShouldNotBeNil)

			So(r.Type, ShouldEqual, Inject)
			v, ok := r.Value.(map[interface{}]interface{})
			So(ok, ShouldBeTrue)
			So(len(v), ShouldEqual, 3)
			So(v["value"], ShouldEqual, "overridden")
			So(v["other"], ShouldEqual, "value 2")
			So(v["third"], ShouldEqual, "trois")
		})

		Convey("handles non-existent references", func() {
			_, err := op.Run(ev, []*Expr{
				ref("key.subkey"),
				ref("key.subkey2"),
				ref("key.subkey2.ENOENT"),
			})
			So(err, ShouldNotBeNil)
		})

		Convey("throws an error when trying to inject a scalar", func() {
			_, err := op.Run(ev, []*Expr{
				ref("key.subkey.value"),
			})
			So(err, ShouldNotBeNil)
		})

		Convey("throws an error when trying to inject a list", func() {
			_, err := op.Run(ev, []*Expr{
				ref("key.list1"),
			})
			So(err, ShouldNotBeNil)
		})
	})

	Convey("param Operator", t, func() {
		op := ParamOperator{}
		ev := &Evaluator{}

		Convey("always causes an error", func() {
			r, err := op.Run(ev, []*Expr{
				str("this is the error"),
			})
			So(err, ShouldNotBeNil)
			So(err.Error(), ShouldEqual, "this is the error")
			So(r, ShouldBeNil)
		})
	})

	Convey("Join Operator", t, func() {
		op := JoinOperator{}
		ev := &Evaluator{
			Tree: YAML(
				`---
meta:
  authorities:
  - password.write
  - clients.write
  - clients.read
  - scim.write
  - scim.read
  - uaa.admin
  - clients.secret

  secondlist:
  - admin.write
  - admin.read

  emptylist: []

  anotherkey:
  - entry1
  - somekey: value
  - entry2

  somestanza:
    foo: bar
    wom: bat
`),
		}

		Convey("can join a simple list", func() {
			r, err := op.Run(ev, []*Expr{
				str(","),
				ref("meta.authorities"),
			})
			So(err, ShouldBeNil)
			So(r, ShouldNotBeNil)

			So(r.Type, ShouldEqual, Replace)
			So(r.Value.(string), ShouldEqual, "password.write,clients.write,clients.read,scim.write,scim.read,uaa.admin,clients.secret")
		})

		Convey("can join multiple lists", func() {
			r, err := op.Run(ev, []*Expr{
				str(","),
				ref("meta.authorities"),
				ref("meta.secondlist"),
			})
			So(err, ShouldBeNil)
			So(r, ShouldNotBeNil)

			So(r.Type, ShouldEqual, Replace)
			So(r.Value.(string), ShouldEqual, "password.write,clients.write,clients.read,scim.write,scim.read,uaa.admin,clients.secret,admin.write,admin.read")
		})

		Convey("can join an empty list", func() {
			r, err := op.Run(ev, []*Expr{
				str(","),
				ref("meta.emptylist"),
			})
			So(err, ShouldBeNil)
			So(r, ShouldNotBeNil)

			So(r.Type, ShouldEqual, Replace)
			So(r.Value.(string), ShouldEqual, "")
		})

		Convey("can join string literals", func() {
			r, err := op.Run(ev, []*Expr{
				str(","),
				str("password.write"),
				str("clients.write"),
			})
			So(err, ShouldBeNil)
			So(r, ShouldNotBeNil)

			So(r.Type, ShouldEqual, Replace)
			So(r.Value.(string), ShouldEqual, "password.write,clients.write")
		})

		Convey("can join integer literals", func() {
			r, err := op.Run(ev, []*Expr{
				str(":"),
				num(4), num(8), num(15),
				num(16), num(23), num(42),
			})
			So(err, ShouldBeNil)
			So(r, ShouldNotBeNil)

			So(r.Type, ShouldEqual, Replace)
			So(r.Value.(string), ShouldEqual, "4:8:15:16:23:42")
		})

		Convey("can join referenced string entry", func() {
			r, err := op.Run(ev, []*Expr{
				str(","),
				ref("meta.somestanza.foo"),
			})
			So(err, ShouldBeNil)
			So(r, ShouldNotBeNil)

			So(r.Type, ShouldEqual, Replace)
			So(r.Value.(string), ShouldEqual, "bar")
		})

		Convey("can join referenced string entries", func() {
			r, err := op.Run(ev, []*Expr{
				str(","),
				ref("meta.somestanza.foo"),
				ref("meta.somestanza.wom"),
			})
			So(err, ShouldBeNil)
			So(r, ShouldNotBeNil)

			So(r.Type, ShouldEqual, Replace)
			So(r.Value.(string), ShouldEqual, "bar,bat")
		})

		Convey("can join multiple referenced entries", func() {
			r, err := op.Run(ev, []*Expr{
				str(","),
				ref("meta.authorities"),
				ref("meta.somestanza.foo"),
				ref("meta.somestanza.wom"),
				str("ending"),
			})
			So(err, ShouldBeNil)
			So(r, ShouldNotBeNil)

			So(r.Type, ShouldEqual, Replace)
			So(r.Value.(string), ShouldEqual, "password.write,clients.write,clients.read,scim.write,scim.read,uaa.admin,clients.secret,bar,bat,ending")
		})

		Convey("throws an error when there are no arguments", func() {
			r, err := op.Run(ev, []*Expr{})
			So(err, ShouldNotBeNil)
			So(err.Error(), ShouldContainSubstring, "no arguments specified")
			So(r, ShouldBeNil)
		})

		Convey("throws an error when there are too few arguments", func() {
			r, err := op.Run(ev, []*Expr{
				str(","),
			})
			So(err, ShouldNotBeNil)
			So(err.Error(), ShouldContainSubstring, "too few arguments supplied")
			So(r, ShouldBeNil)
		})

		Convey("throws an error when seperator argument is not a literal", func() {
			r, err := op.Run(ev, []*Expr{
				ref("meta.emptylist"),
				ref("meta.authorities"),
			})
			So(err, ShouldNotBeNil)
			So(err.Error(), ShouldContainSubstring, "join operator only accepts literal argument for the seperator")
			So(r, ShouldBeNil)
		})

		Convey("throws an error when referenced entry is not a list or literal", func() {
			r, err := op.Run(ev, []*Expr{
				str(","),
				ref("meta.somestanza"),
			})
			So(err, ShouldNotBeNil)
			So(err.Error(), ShouldContainSubstring, "referenced entry is not a list or string")
			So(r, ShouldBeNil)
		})

		Convey("throws an error when referenced list contains non-string entries", func() {
			r, err := op.Run(ev, []*Expr{
				str(","),
				ref("meta.anotherkey"),
			})
			So(err, ShouldNotBeNil)
			So(err.Error(), ShouldContainSubstring, "is not compatible for")
			So(r, ShouldBeNil)
		})

		Convey("throws an error when there are unresolvable references", func() {
			r, err := op.Run(ev, []*Expr{
				str(","),
				ref("meta.non-existent"),
			})
			So(err, ShouldNotBeNil)
			So(err.Error(), ShouldContainSubstring, "Unable to resolve")
			So(r, ShouldBeNil)
		})

		Convey("calculates dependencies correctly", func() {

			//TODO: Move this to a higher scope when more dependencies tests are added
			shouldHaveDeps := func(actual interface{}, expected ...interface{}) string {
				deps := actual.([]*tree.Cursor)
				paths := []string{}
				for _, path := range expected {
					normalizedPath, err := tree.ParseCursor(path.(string))
					if err != nil {
						panic(fmt.Sprintf("improper path %s passed to test", path.(string)))
					}
					paths = append(paths, normalizedPath.String())
				}
				actualPaths := []string{}
				//make an array so we can give some coherent output on error
				for _, dep := range deps {
					//Pass through tree so that tests can tolerate changes to the cursor lib
					actualPaths = append(actualPaths, dep.String())
				}
				//sort and compare
				sort.Strings(actualPaths)
				sort.Strings(paths)
				match := reflect.DeepEqual(actualPaths, paths)
				//give result
				if !match {
					return fmt.Sprintf("actual: %+v\n expected: %+v", actualPaths, paths)
				}
				return ""
			}

			Convey("with a single list", func() {
				deps := op.Dependencies(ev, []*Expr{
					str(" "),
					ref("meta.secondlist"),
				}, nil)
				So(deps, shouldHaveDeps, "meta.secondlist.[0]", "meta.secondlist.[1]")
			})

			Convey("with multiple lists", func() {
				deps := op.Dependencies(ev, []*Expr{
					str(" "),
					ref("meta.authorities"),
					ref("meta.secondlist"),
				}, nil)
				So(deps, shouldHaveDeps, "meta.authorities.[0]", "meta.authorities.[1]",
					"meta.authorities.[2]", "meta.authorities.[3]", "meta.authorities.[4]",
					"meta.authorities.[5]", "meta.authorities.[6]",
					"meta.secondlist.[0]", "meta.secondlist.[1]")
			})

			Convey("with a reference string", func() {
				deps := op.Dependencies(ev, []*Expr{
					str(" "),
					ref("meta.somestanza.foo"),
				}, nil)
				So(deps, shouldHaveDeps, "meta.somestanza.foo")
			})

			Convey("with multiple reference strings", func() {
				deps := op.Dependencies(ev, []*Expr{
					str(" "),
					ref("meta.somestanza.foo"),
					ref("meta.somestanza.wom"),
				}, nil)
				So(deps, shouldHaveDeps, "meta.somestanza.foo", "meta.somestanza.wom")
			})

			Convey("with a reference string and a list", func() {
				deps := op.Dependencies(ev, []*Expr{
					str(" "),
					ref("meta.somestanza.foo"),
					ref("meta.secondlist"),
				}, nil)
				So(deps, shouldHaveDeps, "meta.somestanza.foo", "meta.secondlist.[0]",
					"meta.secondlist.[1]")
			})

			Convey("with a literal string", func() {
				deps := op.Dependencies(ev, []*Expr{
					str(" "),
					str("literally literal"),
				}, nil)
				So(deps, shouldHaveDeps)
			})

			Convey("with a literal string and a reference string", func() {
				deps := op.Dependencies(ev, []*Expr{
					str(" "),
					str("beep"),
					ref("meta.somestanza.foo"),
				}, nil)
				So(deps, shouldHaveDeps, "meta.somestanza.foo")
			})
		})
	})

	Convey("empty operator", t, func() {
		op := EmptyOperator{}
		ev := &Evaluator{
			Tree: YAML(
				`---
meta:
  authorities: meep
`),
		}

		//These three are with unquoted arguments (references)
		Convey("can replace with a hash", func() {
			r, err := op.Run(ev, []*Expr{
				ref("hash"),
			})
			So(err, ShouldBeNil)
			So(r, ShouldNotBeNil)
			So(r.Type, ShouldEqual, Replace)
			val, isHash := r.Value.(map[string]interface{})
			So(isHash, ShouldBeTrue)
			So(val, ShouldResemble, map[string]interface{}{})
		})

		Convey("can replace with an array", func() {
			r, err := op.Run(ev, []*Expr{
				ref("array"),
			})
			So(err, ShouldBeNil)
			So(r, ShouldNotBeNil)
			So(r.Type, ShouldEqual, Replace)
			val, isArray := r.Value.([]interface{})
			So(isArray, ShouldBeTrue)
			So(val, ShouldResemble, []interface{}{})
		})

		Convey("can replace with an empty string", func() {
			r, err := op.Run(ev, []*Expr{
				ref("string"),
			})
			So(err, ShouldBeNil)
			So(r, ShouldNotBeNil)
			So(r.Type, ShouldEqual, Replace)
			val, isString := r.Value.(string)
			So(isString, ShouldBeTrue)
			So(val, ShouldEqual, "")
		})

		Convey("throws an error for unrecognized types", func() {
			r, err := op.Run(ev, []*Expr{
				ref("void"),
			})
			So(r, ShouldBeNil)
			So(err, ShouldNotBeNil)
		})

		Convey("works with string literals", func() {
			r, err := op.Run(ev, []*Expr{
				str("hash"),
			})
			So(err, ShouldBeNil)
			So(r, ShouldNotBeNil)
			So(r.Type, ShouldEqual, Replace)
			val, isHash := r.Value.(map[string]interface{})
			So(isHash, ShouldBeTrue)
			So(val, ShouldResemble, map[string]interface{}{})
		})

		Convey("throws an error with no args", func() {
			r, err := op.Run(ev, []*Expr{})
			So(r, ShouldBeNil)
			So(err, ShouldNotBeNil)
		})

		Convey("throws an error with too many args", func() {
			r, err := op.Run(ev, []*Expr{
				ref("hash"),
				ref("array"),
			})
			So(r, ShouldBeNil)
			So(err, ShouldNotBeNil)
		})

	})

}
Esempio n. 6
0
// Cherry-pick ...
func (ev *Evaluator) CherryPick(paths []string) error {
	DEBUG("cherry-picking %d paths from the final YAML structure", len(paths))

	if len(paths) > 0 {
		// This will serve as the replacement tree ...
		replacement := make(map[interface{}]interface{})

		for _, path := range paths {
			cursor, err := tree.ParseCursor(path)
			if err != nil {
				return err
			}

			// These variables will potentially be modified (depending on the structure)
			var cherryName string
			var cherryValue interface{}

			// Resolve the value that needs to be cherry picked
			cherryValue, err = cursor.Resolve(ev.Tree)
			if err != nil {
				return err
			}

			// Name of the parameter of the to-be-picked value
			cherryName = cursor.Nodes[len(cursor.Nodes)-1]

			// Since the cherry can be deep down the structure, we need to go down
			// (or up, depending how you read it) the structure to include the parent
			// names of the respective cherry. The pointer will be reassigned with
			// each level.
			pointer := cursor
			for pointer != nil {
				parent := pointer.Copy()
				parent.Pop()

				if parent.String() == "" {
					// Empty parent string means we reached the root, setting the pointer nil to stop processing ...
					pointer = nil

					// ... create the final cherry wrapped in its container ...
					tmp := make(map[interface{}]interface{})
					tmp[cherryName] = cherryValue

					// ... and add it to the replacement map
					DEBUG("Merging '%s' into the replacement tree", path)
					merger := &Merger{AppendByDefault: true}
					merged := merger.mergeObj(tmp, replacement, path)
					if err := merger.Error(); err != nil {
						return err
					}

					replacement = merged.(map[interface{}]interface{})

				} else {
					// Reassign the pointer to the parent and restructre the current cherry value to address the parent structure and name
					pointer = parent

					// Depending on the type of the parent, either a map or a list is created for the new parent of the cherry value
					if obj, err := parent.Resolve(ev.Tree); err == nil {
						switch obj.(type) {
						case map[interface{}]interface{}:
							tmp := make(map[interface{}]interface{})
							tmp[cherryName] = cherryValue

							cherryName = parent.Nodes[len(parent.Nodes)-1]
							cherryValue = tmp

						case []interface{}:
							tmp := make([]interface{}, 0, 0)
							tmp = append(tmp, cherryValue)

							cherryName = parent.Nodes[len(parent.Nodes)-1]
							cherryValue = tmp

						default:
							return ansi.Errorf("@*{Unsupported type detected, %s is neither a map nor a list}", parent.String())
						}

					} else {
						return err
					}
				}
			}
		}

		// replace the existing tree with a new one that contain the cherry-picks
		ev.Tree = replacement
	}

	DEBUG("")
	return nil
}