コード例 #1
0
ファイル: main.go プロジェクト: geofffranks/spruce
func mergeAllDocs(root map[interface{}]interface{}, paths []string) error {
	m := &Merger{}

	for _, path := range paths {
		DEBUG("Processing file '%s'", path)
		data, err := ioutil.ReadFile(path)
		if err != nil {
			return ansi.Errorf("@R{Error reading file} @m{%s}: %s\n", path, err.Error())
		}

		if handleConcourseQuoting {
			data = quoteConcourse(data)
		}

		doc, err := parseYAML(data)
		if err != nil {
			return ansi.Errorf("@m{%s}: @R{%s}\n", path, err.Error())
		}

		m.Merge(root, doc)

		tmpYaml, _ := yaml.Marshal(root) // we don't care about errors for debugging
		TRACE("Current data after processing '%s':\n%s", path, tmpYaml)
	}

	return m.Error()
}
コード例 #2
0
ファイル: operator.go プロジェクト: geofffranks/spruce
// Resolve ...
func (e *Expr) Resolve(tree map[interface{}]interface{}) (*Expr, error) {
	switch e.Type {
	case Literal:
		return e, nil

	case EnvVar:
		v := os.Getenv(e.Name)
		if v == "" {
			return nil, ansi.Errorf("@R{Environment variable} @c{$%s} @R{is not set}", e.Name)
		}
		return &Expr{Type: Literal, Literal: v}, nil

	case Reference:
		if _, err := e.Reference.Resolve(tree); err != nil {
			return nil, ansi.Errorf("@R{Unable to resolve `}@c{%s}@R{`: %s}", e.Reference, err)
		}
		return e, nil

	case LogicalOr:
		if o, err := e.Left.Resolve(tree); err == nil {
			return o, nil
		}
		return e.Right.Resolve(tree)
	}
	return nil, ansi.Errorf("@R{unknown expression operand type (}@c{%d}@R{)}", e.Type)
}
コード例 #3
0
ファイル: operator.go プロジェクト: geofffranks/spruce
// Reduce ...
func (e *Expr) Reduce() (*Expr, error) {

	var reduce func(*Expr) (*Expr, *Expr, bool)
	reduce = func(e *Expr) (*Expr, *Expr, bool) {
		switch e.Type {
		case Literal:
			return e, e, false
		case EnvVar:
			return e, nil, false
		case Reference:
			return e, nil, false

		case LogicalOr:
			l, short, _ := reduce(e.Left)
			if short != nil {
				return l, short, true
			}

			r, short, more := reduce(e.Right)
			return &Expr{
				Type:  LogicalOr,
				Left:  l,
				Right: r,
			}, short, more
		}
		return nil, nil, false
	}

	reduced, short, more := reduce(e)
	if more && short != nil {
		return reduced, ansi.Errorf("@R{literal} @c{%v} @R{short-circuits expression (}@c{%s}@R{)}", short, e)
	}
	return reduced, nil
}
コード例 #4
0
ファイル: merge.go プロジェクト: geofffranks/spruce
func canKeyMergeArray(disp string, array []interface{}, node string, key string) error {
	// ensure that all elements of `array` are maps,
	// and that they contain the key `key`

	for i, o := range array {
		if reflect.TypeOf(o).Kind() != reflect.Map {
			return ansi.Errorf("@m{%s.%d}: @R{%s object is a} @c{%s}@R{, not a} @c{map} @R{- cannot merge using keys}", node, i, disp, reflect.TypeOf(o).Kind().String())
		}

		obj := o.(map[interface{}]interface{})
		if _, ok := obj[key]; !ok {
			return ansi.Errorf("@m{%s.%d}: @R{%s object does not contain the key} @c{'%s'}@R{ - cannot merge}", node, i, disp, key)
		}
	}
	return nil
}
コード例 #5
0
ファイル: json.go プロジェクト: geofffranks/spruce
func JSONifyIO(in io.Reader) (string, error) {
	data, err := ioutil.ReadAll(in)
	if err != nil {
		return "", ansi.Errorf("@R{Error reading input}: %s", err)
	}
	return jsonifyData(data)
}
コード例 #6
0
ファイル: json.go プロジェクト: geofffranks/spruce
func JSONifyFiles(paths []string) ([]string, error) {
	l := make([]string, len(paths))

	for i, path := range paths {
		DEBUG("Processing file '%s'", path)
		data, err := ioutil.ReadFile(path)
		if err != nil {
			return nil, ansi.Errorf("@R{Error reading file} @m{%s}: %s", path, err)
		}

		if l[i], err = jsonifyData(data); err != nil {
			return nil, ansi.Errorf("%s: %s", path, err)
		}
	}

	return l, nil
}
コード例 #7
0
ファイル: operator.go プロジェクト: geofffranks/spruce
// Run ...
func (op *Opcall) Run(ev *Evaluator) (*Response, error) {
	was := ev.Here
	ev.Here = op.where
	r, err := op.op.Run(ev, op.args)
	ev.Here = was

	if err != nil {
		return nil, ansi.Errorf("@m{$.%s}: @R{%s}", op.where, err)
	}
	return r, nil
}
コード例 #8
0
ファイル: main.go プロジェクト: geofffranks/spruce
func parseYAML(data []byte) (map[interface{}]interface{}, error) {
	y, err := simpleyaml.NewYaml(data)
	if err != nil {
		return nil, err
	}

	doc, err := y.Map()
	if err != nil {
		return nil, ansi.Errorf("@R{Root of YAML document is not a hash/map}: %s\n", err.Error())
	}

	return doc, nil
}
コード例 #9
0
ファイル: merge.go プロジェクト: geofffranks/spruce
func (m *Merger) mergeMap(orig map[interface{}]interface{}, n map[interface{}]interface{}, node string) {
	mergeRx := regexp.MustCompile(`^\s*\Q((\E\s*merge\s*.*\Q))\E`)
	for k, val := range n {
		path := fmt.Sprintf("%s.%v", node, k)
		if s, ok := val.(string); ok && mergeRx.MatchString(s) {
			m.Errors.Append(ansi.Errorf("@m{%s}: @R{inappropriate use of} @c{(( merge ))} @R{operator outside of a list} (this is @G{spruce}, after all)", path))
		}

		if _, exists := orig[k]; exists {
			DEBUG("%s: found upstream, merging it", path)
			orig[k] = m.mergeObj(orig[k], val, path)
		} else {
			DEBUG("%s: not found upstream, adding it", path)
			orig[k] = m.mergeObj(nil, deepCopy(val), path)
		}
	}
}
コード例 #10
0
ファイル: json.go プロジェクト: geofffranks/spruce
func jsonifyData(data []byte) (string, error) {
	y, err := simpleyaml.NewYaml(data)
	if err != nil {
		return "", err
	}

	doc, err := y.Map()
	if err != nil {
		return "", ansi.Errorf("@R{Root of YAML document is not a hash/map}: %s\n", err.Error())
	}

	b, err := json.Marshal(deinterface(doc))
	if err != nil {
		return "", err
	}

	return string(b), nil
}
コード例 #11
0
ファイル: evaluator.go プロジェクト: geofffranks/spruce
// CheckForCycles ...
func (ev *Evaluator) CheckForCycles(maxDepth int) error {
	DEBUG("checking for cycles in final YAML structure")

	var check func(o interface{}, depth int) error
	check = func(o interface{}, depth int) error {
		if depth == 0 {
			return ansi.Errorf("@*{Hit max recursion depth. You seem to have a self-referencing dataset}")
		}

		switch o.(type) {
		case []interface{}:
			for _, v := range o.([]interface{}) {
				if err := check(v, depth-1); err != nil {
					return err
				}
			}

		case map[interface{}]interface{}:
			for _, v := range o.(map[interface{}]interface{}) {
				if err := check(v, depth-1); err != nil {
					return err
				}
			}
		}

		return nil
	}

	err := check(ev.Tree, maxDepth)
	if err != nil {
		DEBUG("error: %s\n", err)
		return err
	}

	DEBUG("no cycles detected.\n")
	return nil
}
コード例 #12
0
// Run ...
func (CartesianProductOperator) Run(ev *Evaluator, args []*Expr) (*Response, error) {
	DEBUG("running (( cartesian-product ... )) operation at $.%s", ev.Here)
	defer DEBUG("done with (( cartesian-product ... )) operation at $%s\n", ev.Here)

	var vals [][]string

	for i, arg := range args {
		v, err := arg.Resolve(ev.Tree)
		if err != nil {
			DEBUG("     [%d]: resolution failed\n    error: %s", i, err)
			return nil, err
		}

		switch v.Type {
		case Literal:
			DEBUG("  arg[%d]: found string literal '%s'", i, v.Literal)
			vals = append(vals, []string{v.Literal.(string)})

		case Reference:
			DEBUG("  arg[%d]: trying to resolve reference $.%s", i, v.Reference)
			s, err := v.Reference.Resolve(ev.Tree)
			if err != nil {
				DEBUG("     [%d]: resolution failed\n    error: %s", i, err)
				return nil, ansi.Errorf("Unable to resolve `@m{%s}`: %s", v.Reference, err)
			}
			switch s.(type) {
			case []interface{}:
				var strs []string

				DEBUG("     [%d]: resolved to a list; verifying", i)
				for j, sub := range s.([]interface{}) {
					if _, ok := sub.([]interface{}); ok {
						DEBUG("       list[%d]: list item is itself a list; error!", j)
						return nil, fmt.Errorf("cartesian-product operator can only operate on lists of scalar values")

					} else if _, ok := sub.(map[interface{}]interface{}); ok {
						DEBUG("       list[%d]: list item is a map; error!", j)
						return nil, fmt.Errorf("cartesian-product operator can only operate on lists of scalar values")

					}
					DEBUG("       list[%d]: list item is a scalar: %v", j, sub)
					strs = append(strs, fmt.Sprintf("%v", sub))
				}
				vals = append(vals, strs)

			case map[interface{}]interface{}:
				DEBUG("     [%d]: resolved to a map; error!", i)
				return nil, fmt.Errorf("cartesian-product operator only accepts arrays and string values")

			default:
				DEBUG("     [%d]: resolved to a scalar; appending", i)
				vals = append(vals, []string{fmt.Sprintf("%v", s)})
			}

		default:
			DEBUG("  arg[%d]: I don't know what to do with '%v'", i, arg)
			return nil, fmt.Errorf("cartesian-product operator only accepts key reference arguments")
		}
		DEBUG("")
	}

	switch len(args) {
	case 0:
		DEBUG("  no arguments supplied to (( cartesian-product ... )) operation.  oops.")
		return nil, ansi.Errorf("no arguments specified to @c{(( cartesian-product ... ))}")

	case 1:
		DEBUG("  called with only one argument; returning value as-is")
		return &Response{
			Type:  Replace,
			Value: vals[0],
		}, nil

	default:
		DEBUG("  called with more than one arguments; combining into a single list of strings")

		lst := vals[0]
		for _, l := range vals[1:] {
			lst = cartesian(lst, l)
		}

		return &Response{
			Type:  Replace,
			Value: lst,
		}, nil
	}
}
コード例 #13
0
ファイル: merge.go プロジェクト: geofffranks/spruce
func (m *Merger) mergeArray(orig []interface{}, n []interface{}, node string) []interface{} {

	if shouldInlineMergeArray(n) {
		DEBUG("%s: performing explicit inline array merge", node)
		return m.mergeArrayInline(orig, n[1:], node)

	} else if shouldReplaceArray(n) {
		DEBUG("%s: replacing with new data", node)
		return n[1:]

	} else if should, key := shouldKeyMergeArray(n); should {
		DEBUG("%s: performing key-based array merge, using key '%s'", node, key)

		if err := canKeyMergeArray("new", n[1:], node, key); err != nil {
			m.Errors.Append(err)
			return nil
		}
		if err := canKeyMergeArray("original", orig, node, key); err != nil {
			m.Errors.Append(err)
			return nil
		}

		return m.mergeArrayByKey(orig, n[1:], node, key)

	}

	modificationDefinitions := getArrayModifications(n)
	DEBUG("%s: performing %d modification operations against list", node, len(modificationDefinitions))
	DEBUG("%+v", modificationDefinitions)

	// Create a copy of orig for the (multiple) modifications that are about to happen
	result := make([]interface{}, len(orig))
	copy(result, orig)

	var idx int

	// Process the modifications definitions that were found in the new list
	for i := range modificationDefinitions {
		//Special tag for default behavior. Cannot be invoked explicitly by users
		if modificationDefinitions[i].defaultMerge {
			result = m.mergeArrayDefault(orig, modificationDefinitions[0].list, node)
			continue
		}
		if modificationDefinitions[i].key == "" && modificationDefinitions[i].name == "" { // Index comes directly from operation definition
			idx = modificationDefinitions[i].index

			// Replace the -1 marker with the actual 'end' index of the array
			if idx == -1 {
				idx = len(result)
			}

		} else { // Index look-up based on key and name
			key := modificationDefinitions[i].key
			name := modificationDefinitions[i].name
			delete := modificationDefinitions[i].delete

			// Sanity check original list, list must contain key/id based entries
			if err := canKeyMergeArray("original", result, node, key); err != nil {
				m.Errors.Append(err)
				return nil
			}

			// Sanity check new list, depending on the operation type (delete or insert)
			if delete == false {

				// Sanity check new list, list must contain key/id based entries
				if err := canKeyMergeArray("new", modificationDefinitions[i].list, node, key); err != nil {
					m.Errors.Append(err)
					return nil
				}

				// Since we have a way to identify indiviual entries based on their key/id, we can sanity check for possible duplicates
				for _, entry := range modificationDefinitions[i].list {
					obj := entry.(map[interface{}]interface{})
					entryName := obj[key].(string)
					if getIndexOfEntry(result, key, entryName) > 0 {
						m.Errors.Append(ansi.Errorf("@m{%s}: @R{unable to insert, because new list entry} @c{'%s: %s'} @R{is detected multiple times}", node, key, entryName))
						return nil
					}
				}
			} else {
				// Sanity check for delete operation, ensure no orphan entries follow the operator definition
				if len(modificationDefinitions[i].list) > 0 {
					m.Errors.Append(ansi.Errorf("@m{%s}: @R{item in array directly after} @c{(( delete %s: \"%s\" ))} @r{must be one of the array operators 'append', 'prepend', 'delete', or 'insert'}", node, key, name))
					return nil
				}
			}

			// Look up the index of the specified insertion point (based on its key/name)
			idx = getIndexOfEntry(result, key, name)
			if idx < 0 {
				m.Errors.Append(ansi.Errorf("@m{%s}: @R{unable to find specified modification point with} @c{'%s: %s'}", node, key, name))
				return nil
			}
		}

		// If after is specified, add one to the index to actually put the entry where it is expected
		if modificationDefinitions[i].relative == "after" {
			idx++
		}

		// Back out if idx is smaller than 0, or greater than the length (for inserts), or greater/equal than the length (for deletes)
		if (idx < 0) || (modificationDefinitions[i].delete == false && idx > len(result)) || (modificationDefinitions[i].delete == true && idx >= len(result)) {
			m.Errors.Append(ansi.Errorf("@m{%s}: @R{unable to modify the list, because specified index} @c{%d} @R{is out of bounds}", node, idx))
			return nil
		}

		if modificationDefinitions[i].delete == false {
			DEBUG("%s: inserting %d new elements to existing array at index %d", node, len(modificationDefinitions[i].list), idx)
			result = insertIntoList(result, idx, modificationDefinitions[i].list)
		} else {
			DEBUG("%s: deleting element at array index %d", node, idx)
			result = deleteIndexFromList(result, idx)
		}
	}

	return result

}
コード例 #14
0
ファイル: op_join.go プロジェクト: geofffranks/spruce
// Run ...
func (JoinOperator) Run(ev *Evaluator, args []*Expr) (*Response, error) {
	DEBUG("running (( join ... )) operation at $.%s", ev.Here)
	defer DEBUG("done with (( join ... )) operation at $%s\n", ev.Here)

	if len(args) == 0 {
		DEBUG("  no arguments supplied to (( join ... )) operation.")
		return nil, ansi.Errorf("no arguments specified to @c{(( join ... ))}")
	}

	if len(args) == 1 {
		DEBUG("  too few arguments supplied to (( join ... )) operation.")
		return nil, ansi.Errorf("too few arguments supplied to @c{(( join ... ))}")
	}

	var seperator string
	var list []string

	for i, arg := range args {
		if i == 0 { // argument #0: seperator
			sep, err := arg.Resolve(ev.Tree)
			if err != nil {
				DEBUG("     [%d]: resolution failed\n    error: %s", i, err)
				return nil, err
			}

			if sep.Type != Literal {
				DEBUG("     [%d]: unsupported type for join operator seperator argument: '%v'", i, sep)
				return nil, fmt.Errorf("join operator only accepts literal argument for the seperator")
			}

			DEBUG("     [%d]: list seperator will be: %s", i, sep)
			seperator = sep.Literal.(string)

		} else { // argument #1..n: list, or literal
			ref, err := arg.Resolve(ev.Tree)
			if err != nil {
				DEBUG("     [%d]: resolution failed\n    error: %s", i, err)
				return nil, err
			}

			switch ref.Type {
			case Literal:
				DEBUG("     [%d]: adding literal %s to the list", i, ref)
				list = append(list, fmt.Sprintf("%v", ref.Literal))

			case Reference:
				DEBUG("     [%d]: trying to resolve reference $.%s", i, ref.Reference)
				s, err := ref.Reference.Resolve(ev.Tree)
				if err != nil {
					DEBUG("     [%d]: resolution failed with error: %s", i, err)
					return nil, fmt.Errorf("Unable to resolve `%s`: %s", ref.Reference, err)
				}

				switch s.(type) {
				case []interface{}:
					DEBUG("     [%d]: $.%s is a list", i, ref.Reference)
					for idx, entry := range s.([]interface{}) {
						switch entry.(type) {
						case []interface{}:
							DEBUG("     [%d]: entry #%d in list is a list (not a literal)", i, idx)
							return nil, ansi.Errorf("entry #%d in list is not compatible for @c{(( join ... ))}", idx)

						case map[interface{}]interface{}:
							DEBUG("     [%d]: entry #%d in list is a map (not a literal)", i, idx)
							return nil, ansi.Errorf("entry #%d in list is not compatible for @c{(( join ... ))}", idx)

						default:
							list = append(list, fmt.Sprintf("%v", entry))
						}
					}

				case map[interface{}]interface{}:
					DEBUG("     [%d]: $.%s is a map (not a list or a literal)", i, ref.Reference)
					return nil, ansi.Errorf("referenced entry is not a list or string for @c{(( join ... ))}")

				default:
					DEBUG("     [%d]: $.%s is a literal", i, ref.Reference)
					list = append(list, fmt.Sprintf("%v", s))
				}

			default:
				DEBUG("     [%d]: unsupported type for join operator: '%v'", i, ref)
				return nil, fmt.Errorf("join operator only lists with string entries, and literals as data arguments")
			}
		}
	}

	// finally, join and return
	DEBUG("  joined list: %s", strings.Join(list, seperator))
	return &Response{
		Type:  Replace,
		Value: strings.Join(list, seperator),
	}, nil
}
コード例 #15
0
ファイル: evaluator.go プロジェクト: geofffranks/spruce
// DataFlow ...
func (ev *Evaluator) DataFlow(phase OperatorPhase) ([]*Opcall, error) {
	ev.Here = &tree.Cursor{}

	all := map[string]*Opcall{}
	locs := []*tree.Cursor{}
	errors := MultiError{Errors: []error{}}

	// forward decls of co-recursive function
	var check func(interface{})
	var scan func(interface{})

	check = func(v interface{}) {
		if s, ok := v.(string); ok {
			op, err := ParseOpcall(phase, s)
			if err != nil {
				errors.Append(err)
			} else if op != nil {
				op.where = ev.Here.Copy()
				if canon, err := op.where.Canonical(ev.Tree); err == nil {
					op.canonical = canon
				} else {
					op.canonical = op.where
				}
				all[op.canonical.String()] = op
				TRACE("found an operation at %s: %s", op.where.String(), op.src)
				TRACE("        (canonical at %s)", op.canonical.String())
				locs = append(locs, op.canonical)
			}
		} else {
			scan(v)
		}
	}

	scan = func(o interface{}) {
		switch o.(type) {
		case map[interface{}]interface{}:
			for k, v := range o.(map[interface{}]interface{}) {
				ev.Here.Push(fmt.Sprintf("%v", k))
				check(v)
				ev.Here.Pop()
			}

		case []interface{}:
			for i, v := range o.([]interface{}) {
				name := nameOfObj(v, fmt.Sprintf("%d", i))
				op, _ := ParseOpcall(phase, name)
				if op == nil {
					ev.Here.Push(name)
				} else {
					ev.Here.Push(fmt.Sprintf("%d", i))
				}
				check(v)
				ev.Here.Pop()
			}
		}
	}

	scan(ev.Tree)

	// construct the data flow graph, where a -> b = b calls/requires a
	// represent the graph as list of adjancies, that is [a,b] = a -> b
	var g [][2]*Opcall
	for _, a := range all {
		for _, path := range a.Dependencies(ev, locs) {
			if b, found := all[path.String()]; found {
				g = append(g, [2]*Opcall{b, a})
			}
		}
	}

	// construct a sorted list of keys in $all, so that we
	// can reliably generate the same DFA every time
	var sortedKeys []string
	for k := range all {
		sortedKeys = append(sortedKeys, k)
	}
	sort.Strings(sortedKeys)

	// find all nodes in g that are free (no futher dependencies)
	freeNodes := func(g [][2]*Opcall) []*Opcall {
		l := []*Opcall{}
		for _, k := range sortedKeys {
			node, ok := all[k]
			if !ok {
				continue
			}

			called := false
			for _, pair := range g {
				if pair[1] == node {
					called = true
					break
				}
			}

			if !called {
				delete(all, k)
				l = append(l, node)
			}
		}

		return l
	}

	// removes (nullifies) all dependencies on n in g
	remove := func(old [][2]*Opcall, n *Opcall) [][2]*Opcall {
		l := [][2]*Opcall{}
		for _, pair := range old {
			if pair[0] != n {
				l = append(l, pair)
			}
		}
		return l
	}

	// Kahn topological sort
	ops := []*Opcall{} // order in which to call the ops
	wave := 0
	for len(all) > 0 {
		wave++
		free := freeNodes(g)
		if len(free) == 0 {
			return nil, ansi.Errorf("@*{cycle detected in operator data-flow graph}")
		}

		for _, node := range free {
			TRACE("data flow: [%d] wave %d, op %s: %s", len(ops), wave, node.where, node.src)
			ops = append(ops, node)
			g = remove(g, node)
		}
	}

	if len(errors.Errors) > 0 {
		return nil, errors
	}
	return ops, nil
}
コード例 #16
0
ファイル: op_inject.go プロジェクト: geofffranks/spruce
// Run ...
func (InjectOperator) Run(ev *Evaluator, args []*Expr) (*Response, error) {
	DEBUG("running (( inject ... )) operation at $.%s", ev.Here)
	defer DEBUG("done with (( inject ... )) operation at $%s\n", ev.Here)

	var vals []map[interface{}]interface{}

	for i, arg := range args {
		v, err := arg.Resolve(ev.Tree)
		if err != nil {
			DEBUG("  arg[%d]: failed to resolve expression to a concrete value", i)
			DEBUG("     [%d]: error was: %s", i, err)
			return nil, err
		}
		switch v.Type {
		case Literal:
			DEBUG("  arg[%d]: found string literal '%s'", i, v.Literal)
			DEBUG("           (inject operator only handles references to other parts of the YAML tree)")
			return nil, fmt.Errorf("inject operator only accepts key reference arguments")

		case Reference:
			DEBUG("  arg[%d]: trying to resolve reference $.%s", i, v.Reference)
			s, err := v.Reference.Resolve(ev.Tree)
			if err != nil {
				DEBUG("     [%d]: resolution failed\n    error: %s", i, err)
				return nil, err
			}

			m, ok := s.(map[interface{}]interface{})
			if !ok {
				DEBUG("     [%d]: resolved to something that is not a map.  that is unacceptable.", i)
				return nil, ansi.Errorf("@c{%s} @R{is not a map}", v.Reference)
			}

			DEBUG("     [%d]: resolved to a map; appending to the list of maps to merge/inject", i)
			vals = append(vals, m)

		default:
			DEBUG("  arg[%d]: I don't know what to do with '%v'", i, arg)
			return nil, fmt.Errorf("inject operator only accepts key reference arguments")
		}
		DEBUG("")
	}

	switch len(vals) {
	case 0:
		DEBUG("  no arguments supplied to (( inject ... )) operation.  oops.")
		return nil, ansi.Errorf("no arguments specified to @c{(( inject ... ))}")

	default:
		DEBUG("  merging found maps into a single map to be injected")
		merged, err := Merge(vals...)
		if err != nil {
			DEBUG("  failed: %s\n", err)
			return nil, err
		}
		return &Response{
			Type:  Inject,
			Value: merged,
		}, nil
	}
}
コード例 #17
0
ファイル: evaluator.go プロジェクト: geofffranks/spruce
// 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
}